浏览代码

3.0.5346.38509

Luke Pulverenti 10 年之前
父节点
当前提交
ca66390e24

+ 20 - 13
MediaBrowser.Api/ConfigurationService.cs

@@ -141,22 +141,29 @@ namespace MediaBrowser.Api
 
         private string AutoDetectMetadataService()
         {
-            var paths = _libraryManager.GetDefaultVirtualFolders()
-                .SelectMany(i => i.Locations)
-                .Distinct(StringComparer.OrdinalIgnoreCase)
-                .Select(i => new DirectoryInfo(i))
-                .ToList();
-
-            if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories))
-                .Any())
+            try
             {
-                return XbmcMetadata;
+                var paths = _libraryManager.GetDefaultVirtualFolders()
+                   .SelectMany(i => i.Locations)
+                   .Distinct(StringComparer.OrdinalIgnoreCase)
+                   .Select(i => new DirectoryInfo(i))
+                   .ToList();
+
+                if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories))
+                    .Any())
+                {
+                    return XbmcMetadata;
+                }
+
+                if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories))
+                    .Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase)))
+                {
+                    return MediaBrowserMetadata;
+                }
             }
-
-            if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories))
-                .Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase)))
+            catch (Exception)
             {
-                return MediaBrowserMetadata;
+                
             }
             
             return XbmcMetadata;

+ 1 - 1
MediaBrowser.Api/Library/LibraryService.cs

@@ -286,7 +286,7 @@ namespace MediaBrowser.Api.Library
 
         public void Post(PostUpdatedSeries request)
         {
-            
+            Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
         }
 
         public object Get(GetFile request)

+ 11 - 4
MediaBrowser.Api/PlaylistService.cs

@@ -36,6 +36,13 @@ namespace MediaBrowser.Api
 
         [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
+        /// <value>The user id.</value>
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string UserId { get; set; }
     }
 
     [Route("/Playlists/{Id}/Items", "DELETE", Summary = "Removes items from a playlist")]
@@ -58,8 +65,8 @@ namespace MediaBrowser.Api
         /// Gets or sets the user id.
         /// </summary>
         /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public Guid? UserId { get; set; }
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string UserId { get; set; }
 
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
@@ -115,7 +122,7 @@ namespace MediaBrowser.Api
 
         public void Post(AddToPlaylist request)
         {
-            var task = _playlistManager.AddToPlaylist(request.Id, request.Ids.Split(','));
+            var task = _playlistManager.AddToPlaylist(request.Id, request.Ids.Split(','), request.UserId);
 
             Task.WaitAll(task);
         }
@@ -130,7 +137,7 @@ namespace MediaBrowser.Api
         public object Get(GetPlaylistItems request)
         {
             var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
-            var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(new Guid(request.UserId)) : null;
 
             var items = playlist.GetManageableItems().ToArray();
 

+ 2 - 1
MediaBrowser.Controller/Playlists/IPlaylistManager.cs

@@ -26,8 +26,9 @@ namespace MediaBrowser.Controller.Playlists
         /// </summary>
         /// <param name="playlistId">The playlist identifier.</param>
         /// <param name="itemIds">The item ids.</param>
+        /// <param name="userId">The user identifier.</param>
         /// <returns>Task.</returns>
-        Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds);
+        Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds, string userId);
 
         /// <summary>
         /// Removes from playlist.

+ 14 - 8
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Playlists
             }
 
             return inputItems.SelectMany(i => GetPlaylistItems(i, user))
-                .Where(m =>  string.Equals(m.MediaType, playlistMediaType, StringComparison.OrdinalIgnoreCase));
+                .Where(m => string.Equals(m.MediaType, playlistMediaType, StringComparison.OrdinalIgnoreCase));
         }
 
         private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem i, User user)
@@ -57,25 +57,31 @@ namespace MediaBrowser.Controller.Playlists
             var musicGenre = i as MusicGenre;
             if (musicGenre != null)
             {
-                var songs = user.RootFolder
-                    .GetRecursiveChildren(user)
+                var items = user == null
+                    ? LibraryManager.RootFolder.GetRecursiveChildren()
+                    : user.RootFolder.GetRecursiveChildren(user, true);
+
+                var songs = items
                     .OfType<Audio>()
                     .Where(a => a.Genres.Contains(musicGenre.Name, StringComparer.OrdinalIgnoreCase));
 
-                return LibraryManager.Sort(songs, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
+                return LibraryManager.Sort(songs, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
             }
 
             var musicArtist = i as MusicArtist;
             if (musicArtist != null)
             {
-                var songs = user.RootFolder
-                    .GetRecursiveChildren(user)
+                var items = user == null
+                    ? LibraryManager.RootFolder.GetRecursiveChildren()
+                    : user.RootFolder.GetRecursiveChildren(user, true);
+
+                var songs = items
                     .OfType<Audio>()
                     .Where(a => a.HasArtist(musicArtist.Name));
 
-                return LibraryManager.Sort(songs, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
+                return LibraryManager.Sort(songs, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
             }
-            
+
             var folder = i as Folder;
 
             if (folder != null)

+ 0 - 16
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -69,22 +69,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             get { return FFMpegPath; }
         }
 
-        /// <summary>
-        /// The _semaphoreLocks
-        /// </summary>
-        private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
-            new ConcurrentDictionary<string, SemaphoreSlim>();
-
-        /// <summary>
-        /// Gets the lock.
-        /// </summary>
-        /// <param name="filename">The filename.</param>
-        /// <returns>System.Object.</returns>
-        private SemaphoreSlim GetLock(string filename)
-        {
-            return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
-        }
-
         /// <summary>
         /// Gets the media info.
         /// </summary>

+ 2 - 1
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -63,6 +63,7 @@
     <Compile Include="Subtitles\ISubtitleWriter.cs" />
     <Compile Include="Subtitles\SrtParser.cs" />
     <Compile Include="Subtitles\SrtWriter.cs" />
+    <Compile Include="Subtitles\AssParser.cs" />
     <Compile Include="Subtitles\SsaParser.cs" />
     <Compile Include="Subtitles\SubtitleEncoder.cs" />
     <Compile Include="Subtitles\SubtitleTrackInfo.cs" />
@@ -96,4 +97,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
+</Project>

+ 71 - 0
MediaBrowser.MediaEncoding/Subtitles/AssParser.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+    public class AssParser : ISubtitleParser
+    {
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+        public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+        {
+            var trackInfo = new SubtitleTrackInfo();
+            var eventIndex = 1;
+            using (var reader = new StreamReader(stream))
+            {
+                string line;
+                while (reader.ReadLine() != "[Events]")
+                {}
+                var headers = ParseFieldHeaders(reader.ReadLine());
+
+                while ((line = reader.ReadLine()) != null)
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
+                    
+                    if (string.IsNullOrWhiteSpace(line))
+                    {
+                        continue;
+                    }
+                    if(line.StartsWith("["))
+                        break;
+                    if(string.IsNullOrEmpty(line))
+                        continue;
+                    var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
+                    eventIndex++;
+                    var sections = line.Substring(10).Split(',');
+
+                    subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
+                    subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
+                    subEvent.Text = string.Join(",", sections.Skip(headers["Text"]));
+                    subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
+
+                    trackInfo.TrackEvents.Add(subEvent);
+                }
+            }
+            return trackInfo;
+        }
+
+        long GetTicks(string time)
+        {
+            TimeSpan span;
+            return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span)
+                ? span.Ticks: 0;
+        }
+
+        private Dictionary<string,int> ParseFieldHeaders(string line) {
+            var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList();
+
+            var result = new Dictionary<string, int> {
+                                                         {"Start", fields.IndexOf("Start")},
+                                                         {"End", fields.IndexOf("End")},
+                                                         {"Text", fields.IndexOf("Text")}
+                                                     };
+            return result;
+        }
+    }
+}

+ 360 - 40
MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs

@@ -1,71 +1,391 @@
 using System;
-using System.Collections.Generic;
-using System.Globalization;
 using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
+using System.Text;
 using System.Threading;
 
 namespace MediaBrowser.MediaEncoding.Subtitles
 {
+    /// <summary>
+    /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs
+    /// </summary>
     public class SsaParser : ISubtitleParser
     {
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
         public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
         {
             var trackInfo = new SubtitleTrackInfo();
-            var eventIndex = 1;
+
             using (var reader = new StreamReader(stream))
             {
+                bool eventsStarted = false;
+
+                string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(',');
+                int indexLayer = 0;
+                int indexStart = 1;
+                int indexEnd = 2;
+                int indexStyle = 3;
+                int indexName = 4;
+                int indexEffect = 8;
+                int indexText = 9;
+                int lineNumber = 0;
+
+                var header = new StringBuilder();
+
                 string line;
-                while (reader.ReadLine() != "[Events]")
-                {}
-                var headers = ParseFieldHeaders(reader.ReadLine());
 
                 while ((line = reader.ReadLine()) != null)
                 {
                     cancellationToken.ThrowIfCancellationRequested();
-                    
-                    if (string.IsNullOrWhiteSpace(line))
+
+                    lineNumber++;
+                    if (!eventsStarted)
+                        header.AppendLine(line);
+
+                    if (line.Trim().ToLower() == "[events]")
                     {
-                        continue;
+                        eventsStarted = true;
                     }
-                    if(line.StartsWith("["))
-                        break;
-                    if(string.IsNullOrEmpty(line))
-                        continue;
-                    var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
-                    eventIndex++;
-                    var sections = line.Substring(10).Split(',');
-
-                    subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
-                    subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
-                    subEvent.Text = string.Join(",", sections.Skip(headers["Text"]));
-                    subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
-
-                    trackInfo.TrackEvents.Add(subEvent);
-                }
+                    else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";"))
+                    {
+                        // skip comment lines
+                    }
+                    else if (eventsStarted && line.Trim().Length > 0)
+                    {
+                        string s = line.Trim().ToLower();
+                        if (s.StartsWith("format:"))
+                        {
+                            if (line.Length > 10)
+                            {
+                                format = line.ToLower().Substring(8).Split(',');
+                                for (int i = 0; i < format.Length; i++)
+                                {
+                                    if (format[i].Trim().ToLower() == "layer")
+                                        indexLayer = i;
+                                    else if (format[i].Trim().ToLower() == "start")
+                                        indexStart = i;
+                                    else if (format[i].Trim().ToLower() == "end")
+                                        indexEnd = i;
+                                    else if (format[i].Trim().ToLower() == "text")
+                                        indexText = i;
+                                    else if (format[i].Trim().ToLower() == "effect")
+                                        indexEffect = i;
+                                    else if (format[i].Trim().ToLower() == "style")
+                                        indexStyle = i;
+                                }
+                            }
+                        }
+                        else if (!string.IsNullOrEmpty(s))
+                        {
+                            string text = string.Empty;
+                            string start = string.Empty;
+                            string end = string.Empty;
+                            string style = string.Empty;
+                            string layer = string.Empty;
+                            string effect = string.Empty;
+                            string name = string.Empty;
+
+                            string[] splittedLine;
+
+                            if (s.StartsWith("dialogue:"))
+                                splittedLine = line.Substring(10).Split(',');
+                            else
+                                splittedLine = line.Split(',');
+
+                            for (int i = 0; i < splittedLine.Length; i++)
+                            {
+                                if (i == indexStart)
+                                    start = splittedLine[i].Trim();
+                                else if (i == indexEnd)
+                                    end = splittedLine[i].Trim();
+                                else if (i == indexLayer)
+                                    layer = splittedLine[i];
+                                else if (i == indexEffect)
+                                    effect = splittedLine[i];
+                                else if (i == indexText)
+                                    text = splittedLine[i];
+                                else if (i == indexStyle)
+                                    style = splittedLine[i];
+                                else if (i == indexName)
+                                    name = splittedLine[i];
+                                else if (i > indexText)
+                                    text += "," + splittedLine[i];
+                            }
+
+                            try
+                            {
+                                var p = new SubtitleTrackEvent();
+
+                                p.StartPositionTicks = GetTimeCodeFromString(start);
+                                p.EndPositionTicks = GetTimeCodeFromString(end);
+                                p.Text = GetFormattedText(text);
+
+                                trackInfo.TrackEvents.Add(p);
+                            }
+                            catch
+                            {
+                            }
+                        }
+                    }
+                } 
+                
+                //if (header.Length > 0)
+                    //subtitle.Header = header.ToString();
+
+                //subtitle.Renumber(1);
             }
             return trackInfo;
         }
 
-        long GetTicks(string time)
+        private static long GetTimeCodeFromString(string time)
+        {
+            // h:mm:ss.cc
+            string[] timeCode = time.Split(':', '.');
+            return new TimeSpan(0, int.Parse(timeCode[0]),
+                                int.Parse(timeCode[1]),
+                                int.Parse(timeCode[2]),
+                                int.Parse(timeCode[3]) * 10).Ticks;
+        }
+
+        public static string GetFormattedText(string text)
+        {
+            text = text.Replace("\\N", Environment.NewLine).Replace("\\n", Environment.NewLine);
+            bool italic = false;
+
+            for (int i = 0; i < 10; i++) // just look ten times...
+            {
+                if (text.Contains(@"{\fn"))
+                {
+                    int start = text.IndexOf(@"{\fn");
+                    int end = text.IndexOf('}', start);
+                    if (end > 0 && !text.Substring(start).StartsWith("{\\fn}"))
+                    {
+                        string fontName = text.Substring(start + 4, end - (start + 4));
+                        string extraTags = string.Empty;
+                        CheckAndAddSubTags(ref fontName, ref extraTags, out italic);
+                        text = text.Remove(start, end - start + 1);
+                        if (italic)
+                            text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
+                        else
+                            text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
+
+                        int indexOfEndTag = text.IndexOf("{\\fn}", start);
+                        if (indexOfEndTag > 0)
+                            text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
+                        else
+                            text += "</font>";
+                    }
+                }
+
+                if (text.Contains(@"{\fs"))
+                {
+                    int start = text.IndexOf(@"{\fs");
+                    int end = text.IndexOf('}', start);
+                    if (end > 0 && !text.Substring(start).StartsWith("{\\fs}"))
+                    {
+                        string fontSize = text.Substring(start + 4, end - (start + 4));
+                        string extraTags = string.Empty;
+                        CheckAndAddSubTags(ref fontSize, ref extraTags, out italic);
+                        if (IsInteger(fontSize))
+                        {
+                            text = text.Remove(start, end - start + 1);
+                            if (italic)
+                                text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
+                            else
+                                text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
+
+                            int indexOfEndTag = text.IndexOf("{\\fs}", start);
+                            if (indexOfEndTag > 0)
+                                text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
+                            else
+                                text += "</font>";
+                        }
+                    }
+                }
+
+                if (text.Contains(@"{\c"))
+                {
+                    int start = text.IndexOf(@"{\c");
+                    int end = text.IndexOf('}', start);
+                    if (end > 0 && !text.Substring(start).StartsWith("{\\c}"))
+                    {
+                        string color = text.Substring(start + 4, end - (start + 4));
+                        string extraTags = string.Empty;
+                        CheckAndAddSubTags(ref color, ref extraTags, out italic);
+
+                        color = color.Replace("&", string.Empty).TrimStart('H');
+                        color = color.PadLeft(6, '0');
+
+                        // switch to rrggbb from bbggrr
+                        color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
+                        color = color.ToLower();
+
+                        text = text.Remove(start, end - start + 1);
+                        if (italic)
+                            text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+                        else
+                            text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
+                        int indexOfEndTag = text.IndexOf("{\\c}", start);
+                        if (indexOfEndTag > 0)
+                            text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
+                        else
+                            text += "</font>";
+                    }
+                }
+
+                if (text.Contains(@"{\1c")) // "1" specifices primary color
+                {
+                    int start = text.IndexOf(@"{\1c");
+                    int end = text.IndexOf('}', start);
+                    if (end > 0 && !text.Substring(start).StartsWith("{\\1c}"))
+                    {
+                        string color = text.Substring(start + 5, end - (start + 5));
+                        string extraTags = string.Empty;
+                        CheckAndAddSubTags(ref color, ref extraTags, out italic);
+
+                        color = color.Replace("&", string.Empty).TrimStart('H');
+                        color = color.PadLeft(6, '0');
+
+                        // switch to rrggbb from bbggrr
+                        color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
+                        color = color.ToLower();
+
+                        text = text.Remove(start, end - start + 1);
+                        if (italic)
+                            text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+                        else
+                            text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
+                        text += "</font>";
+                    }
+                }
+
+            }
+
+            text = text.Replace(@"{\i1}", "<i>");
+            text = text.Replace(@"{\i0}", "</i>");
+            text = text.Replace(@"{\i}", "</i>");
+            if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
+                text += "</i>";
+
+            text = text.Replace(@"{\u1}", "<u>");
+            text = text.Replace(@"{\u0}", "</u>");
+            text = text.Replace(@"{\u}", "</u>");
+            if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
+                text += "</u>";
+
+            text = text.Replace(@"{\b1}", "<b>");
+            text = text.Replace(@"{\b0}", "</b>");
+            text = text.Replace(@"{\b}", "</b>");
+            if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
+                text += "</b>";
+
+            return text;
+        }
+
+        private static bool IsInteger(string s)
         {
-            TimeSpan span;
-            return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span)
-                ? span.Ticks: 0;
+            int i;
+            if (int.TryParse(s, out i))
+                return true;
+            return false;
         }
 
-        private Dictionary<string,int> ParseFieldHeaders(string line) {
-            var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList();
+        private static int CountTagInText(string text, string tag)
+        {
+            int count = 0;
+            int index = text.IndexOf(tag);
+            while (index >= 0)
+            {
+                count++;
+                if (index == text.Length)
+                    return count;
+                index = text.IndexOf(tag, index + 1);
+            }
+            return count;
+        }
+
+        private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic)
+        {
+            italic = false;
+            int indexOfSPlit = tagName.IndexOf(@"\");
+            if (indexOfSPlit > 0)
+            {
+                string rest = tagName.Substring(indexOfSPlit).TrimStart('\\');
+                tagName = tagName.Remove(indexOfSPlit);
+
+                for (int i = 0; i < 10; i++)
+                {
+                    if (rest.StartsWith("fs") && rest.Length > 2)
+                    {
+                        indexOfSPlit = rest.IndexOf(@"\");
+                        string fontSize = rest;
+                        if (indexOfSPlit > 0)
+                        {
+                            fontSize = rest.Substring(0, indexOfSPlit);
+                            rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+                        }
+                        else
+                        {
+                            rest = string.Empty;
+                        }
+                        extraTags += " size=\"" + fontSize.Substring(2) + "\"";
+                    }
+                    else if (rest.StartsWith("fn") && rest.Length > 2)
+                    {
+                        indexOfSPlit = rest.IndexOf(@"\");
+                        string fontName = rest;
+                        if (indexOfSPlit > 0)
+                        {
+                            fontName = rest.Substring(0, indexOfSPlit);
+                            rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+                        }
+                        else
+                        {
+                            rest = string.Empty;
+                        }
+                        extraTags += " face=\"" + fontName.Substring(2) + "\"";
+                    }
+                    else if (rest.StartsWith("c") && rest.Length > 2)
+                    {
+                        indexOfSPlit = rest.IndexOf(@"\");
+                        string fontColor = rest;
+                        if (indexOfSPlit > 0)
+                        {
+                            fontColor = rest.Substring(0, indexOfSPlit);
+                            rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+                        }
+                        else
+                        {
+                            rest = string.Empty;
+                        }
+
+                        string color = fontColor.Substring(2);
+                        color = color.Replace("&", string.Empty).TrimStart('H');
+                        color = color.PadLeft(6, '0');
+                        // switch to rrggbb from bbggrr
+                        color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
+                        color = color.ToLower();
 
-            var result = new Dictionary<string, int> {
-                                                         {"Start", fields.IndexOf("Start")},
-                                                         {"End", fields.IndexOf("End")},
-                                                         {"Text", fields.IndexOf("Text")}
-                                                     };
-            return result;
+                        extraTags += " color=\"" + color + "\"";
+                    }
+                    else if (rest.StartsWith("i1") && rest.Length > 1)
+                    {
+                        indexOfSPlit = rest.IndexOf(@"\");
+                        italic = true;
+                        if (indexOfSPlit > 0)
+                        {
+                            rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+                        }
+                        else
+                        {
+                            rest = string.Empty;
+                        }
+                    }
+                    else if (rest.Length > 0 && rest.Contains("\\"))
+                    {
+                        indexOfSPlit = rest.IndexOf(@"\");
+                        rest = rest.Substring(indexOfSPlit).TrimStart('\\');
+                    }
+                }
+            }
         }
     }
 }

+ 5 - 2
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -239,11 +239,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             {
                 return new SrtParser();
             }
-            if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase) ||
-                string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
             {
                 return new SsaParser();
             }
+            if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
+            {
+                return new AssParser();
+            }
 
             if (throwIfMissing)
             {

+ 2 - 1
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -1271,8 +1271,9 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// <param name="playlistId">The playlist identifier.</param>
         /// <param name="itemIds">The item ids.</param>
+        /// <param name="userId">The user identifier.</param>
         /// <returns>Task.</returns>
-        Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds);
+        Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds, string userId);
 
         /// <summary>
         /// Removes from playlist.

+ 7 - 1
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -1,12 +1,12 @@
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Library;
+using MediaBrowser.Model.Providers;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Runtime.Serialization;
-using MediaBrowser.Model.Providers;
 
 namespace MediaBrowser.Model.Dto
 {
@@ -227,6 +227,12 @@ namespace MediaBrowser.Model.Dto
         /// <value>The production year.</value>
         public int? ProductionYear { get; set; }
 
+        /// <summary>
+        /// Gets or sets the recursive unplayed item count.
+        /// </summary>
+        /// <value>The recursive unplayed item count.</value>
+        public int? RecursiveUnplayedItemCount { get; set; }
+        
         /// <summary>
         /// Gets or sets the season count.
         /// </summary>

+ 1 - 0
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1366,6 +1366,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             dto.RecursiveItemCount = recursiveItemCount;
             dto.UserData.UnplayedItemCount = unplayed;
+            dto.RecursiveUnplayedItemCount = unplayed;
 
             if (recursiveItemCount > 0)
             {

+ 17 - 7
MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs

@@ -82,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
                         if (folder != null)
                         {
                             options.MediaType = folder.GetRecursiveChildren()
-                                .Where(i => !i.IsFolder)
+                                .Where(i => !i.IsFolder && i.SupportsAddingToPlaylist)
                                 .Select(i => i.MediaType)
                                 .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
                         }
@@ -100,6 +100,8 @@ namespace MediaBrowser.Server.Implementations.Playlists
                 throw new ArgumentException("A playlist media type is required.");
             }
 
+            var user = _userManager.GetUserById(new Guid(options.UserId));
+
             var path = Path.Combine(parentFolder.Path, folderName);
             path = GetTargetPath(path);
 
@@ -126,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
 
                 if (options.ItemIdList.Count > 0)
                 {
-                    await AddToPlaylist(playlist.Id.ToString("N"), options.ItemIdList);
+                    await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user);
                 }
 
                 return new PlaylistCreationResult
@@ -151,14 +153,21 @@ namespace MediaBrowser.Server.Implementations.Playlists
             return path;
         }
 
-        private IEnumerable<BaseItem> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType)
+        private IEnumerable<BaseItem> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user)
         {
             var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null);
 
-            return Playlist.GetPlaylistItems(playlistMediaType, items, null);
+            return Playlist.GetPlaylistItems(playlistMediaType, items, user);
         }
 
-        public async Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds)
+        public Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds, string userId)
+        {
+            var user = string.IsNullOrWhiteSpace(userId) ? null : _userManager.GetUserById(new Guid(userId));
+
+            return AddToPlaylistInternal(playlistId, itemIds, user);
+        }
+
+        private async Task AddToPlaylistInternal(string playlistId, IEnumerable<string> itemIds, User user)
         {
             var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
 
@@ -170,7 +179,9 @@ namespace MediaBrowser.Server.Implementations.Playlists
             var list = new List<LinkedChild>();
             var itemList = new List<BaseItem>();
 
-            var items = GetPlaylistItems(itemIds, playlist.MediaType).ToList();
+            var items = GetPlaylistItems(itemIds, playlist.MediaType, user)
+                .Where(i => i.SupportsAddingToPlaylist)
+                .ToList();
 
             foreach (var item in items)
             {
@@ -183,7 +194,6 @@ namespace MediaBrowser.Server.Implementations.Playlists
             await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             await playlist.RefreshMetadata(new MetadataRefreshOptions
             {
-
                 ForceSave = true
 
             }, CancellationToken.None).ConfigureAwait(false);

+ 1 - 1
MediaBrowser.Tests/MediaEncoding/Subtitles/SsaParserTests.cs

@@ -39,7 +39,7 @@ namespace MediaBrowser.Tests.MediaEncoding.Subtitles {
                                                                                      }
                                       };
 
-            var sut = new SsaParser();
+            var sut = new AssParser();
 
             var stream = File.OpenRead(@"MediaEncoding\Subtitles\TestSubtitles\data.ssa");