Browse Source

Merge pull request #1436 from MediaBrowser/dev

Merge from dev
Luke 9 years ago
parent
commit
063fd56832
27 changed files with 329 additions and 414 deletions
  1. 44 0
      MediaBrowser.Api/Library/FileOrganizationService.cs
  2. 6 12
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 1 1
      MediaBrowser.Api/Playback/Dash/MpegDashService.cs
  4. 1 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  5. 1 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  6. 1 1
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  7. 14 0
      MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs
  8. 6 12
      MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
  9. 3 2
      MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
  10. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  11. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  12. 7 0
      MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs
  13. 16 0
      MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs
  14. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  15. 63 11
      MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  16. 4 0
      MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs
  17. 58 4
      MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs
  18. 6 6
      MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs
  19. 6 6
      MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs
  20. 6 3
      MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
  21. 23 5
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  22. 42 38
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  23. 3 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  24. 1 1
      MediaBrowser.Server.Implementations/Sorting/IsFolderComparer.cs
  25. 1 0
      MediaBrowser.Server.Implementations/packages.config
  26. 0 1
      MediaBrowser.WebDashboard/Api/PackageCreator.cs
  27. 9 309
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 44 - 0
MediaBrowser.Api/Library/FileOrganizationService.cs

@@ -74,6 +74,34 @@ namespace MediaBrowser.Api.Library
         public bool RememberCorrection { get; set; }
     }
 
+    [Route("/Library/FileOrganizationSmartMatch", "GET", Summary = "Gets smart match entries")]
+    public class GetSmartMatchInfos : IReturn<QueryResult<SmartMatchInfo>>
+    {
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        /// <value>The start index.</value>
+        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? StartIndex { get; set; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? Limit { get; set; }
+    }
+
+    [Route("/Library/FileOrganizationSmartMatch/{Id}/Delete", "POST", Summary = "Deletes a smart match entry")]
+    public class DeleteSmartMatchEntry
+    {
+        [ApiMember(Name = "Id", Description = "Item ID", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "MatchString", Description = "SmartMatch String", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string MatchString { get; set; }
+    }
+
     [Authenticated(Roles = "Admin")]
     public class FileOrganizationService : BaseApiService
     {
@@ -130,5 +158,21 @@ namespace MediaBrowser.Api.Library
 
             Task.WaitAll(task);
         }
+
+        public object Get(GetSmartMatchInfos request)
+        {
+            var result = _iFileOrganizationService.GetSmartMatchInfos(new FileOrganizationResultQuery
+            {
+                Limit = request.Limit,
+                StartIndex = request.StartIndex
+            });
+
+            return ToOptimizedSerializedResultUsingCache(result);
+        }
+
+        public void Post(DeleteSmartMatchEntry request)
+        {
+            _iFileOrganizationService.DeleteSmartMatchEntry(request.Id, request.MatchString);
+        }
     }
 }

+ 6 - 12
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -305,9 +305,8 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="videoCodec">The video codec.</param>
-        /// <param name="isHls">if set to <c>true</c> [is HLS].</param>
         /// <returns>System.String.</returns>
-        protected string GetVideoQualityParam(StreamState state, string videoCodec, bool isHls)
+        protected string GetVideoQualityParam(StreamState state, string videoCodec)
         {
             var param = string.Empty;
 
@@ -385,7 +384,7 @@ namespace MediaBrowser.Api.Playback
                 param = "-mbd 2";
             }
 
-            param += GetVideoBitrateParam(state, videoCodec, isHls);
+            param += GetVideoBitrateParam(state, videoCodec);
 
             var framerate = GetFramerateParam(state);
             if (framerate.HasValue)
@@ -1190,7 +1189,7 @@ namespace MediaBrowser.Api.Playback
             return bitrate;
         }
 
-        protected string GetVideoBitrateParam(StreamState state, string videoCodec, bool isHls)
+        protected string GetVideoBitrateParam(StreamState state, string videoCodec)
         {
             var bitrate = state.OutputVideoBitrate;
 
@@ -1209,14 +1208,9 @@ namespace MediaBrowser.Api.Playback
                 }
 
                 // h264
-                if (isHls)
-                {
-                    return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
-                        bitrate.Value.ToString(UsCulture),
-                        (bitrate.Value * 2).ToString(UsCulture));
-                }
-
-                return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+                return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
+                    bitrate.Value.ToString(UsCulture),
+                    (bitrate.Value * 2).ToString(UsCulture));
             }
 
             return string.Empty;

+ 1 - 1
MediaBrowser.Api/Playback/Dash/MpegDashService.cs

@@ -430,7 +430,7 @@ namespace MediaBrowser.Api.Playback.Dash
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            args += " " + GetVideoQualityParam(state, GetH264Encoder(state), true) + keyFrameArg;
+            args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)

+ 1 - 1
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -822,7 +822,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-                args += " " + GetVideoQualityParam(state, GetH264Encoder(state), true) + keyFrameArg;
+                args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
 
                 //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
 

+ 1 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            args += " " + GetVideoQualityParam(state, GetH264Encoder(state), true) + keyFrameArg;
+            args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -166,7 +166,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 args += GetOutputSizeParam(state, videoCodec);
             }
 
-            var qualityParam = GetVideoQualityParam(state, videoCodec, false);
+            var qualityParam = GetVideoQualityParam(state, videoCodec);
 
             if (!string.IsNullOrEmpty(qualityParam))
             {

+ 14 - 0
MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs

@@ -67,5 +67,19 @@ namespace MediaBrowser.Controller.FileOrganization
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Returns a list of smart match entries
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>IEnumerable{SmartMatchInfo}.</returns>
+        QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query);
+
+        /// <summary>
+        /// Deletes a smart match entry.
+        /// </summary>
+        /// <param name="Id">Item Id.</param>
+        /// <param name="matchString">The match string to delete.</param>
+        void DeleteSmartMatchEntry(string Id, string matchString);
     }
 }

+ 6 - 12
MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs

@@ -568,9 +568,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="videoCodec">The video codec.</param>
-        /// <param name="isHls">if set to <c>true</c> [is HLS].</param>
         /// <returns>System.String.</returns>
-        protected string GetVideoQualityParam(EncodingJob state, string videoCodec, bool isHls)
+        protected string GetVideoQualityParam(EncodingJob state, string videoCodec)
         {
             var param = string.Empty;
 
@@ -648,7 +647,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 param = "-mbd 2";
             }
 
-            param += GetVideoBitrateParam(state, videoCodec, isHls);
+            param += GetVideoBitrateParam(state, videoCodec);
 
             var framerate = GetFramerateParam(state);
             if (framerate.HasValue)
@@ -718,7 +717,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return "-pix_fmt yuv420p " + param;
         }
 
-        protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)
+        protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
         {
             var bitrate = state.OutputVideoBitrate;
 
@@ -737,14 +736,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
 
                 // h264
-                if (isHls)
-                {
-                    return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
-                        bitrate.Value.ToString(UsCulture),
-                        (bitrate.Value * 2).ToString(UsCulture));
-                }
-
-                return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+                return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
+                    bitrate.Value.ToString(UsCulture),
+                    (bitrate.Value * 2).ToString(UsCulture));
             }
 
             return string.Empty;

+ 3 - 2
MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs

@@ -26,7 +26,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
             var format = string.Empty;
             var keyFrame = string.Empty;
 
-            if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
+                state.Options.Context == EncodingContext.Streaming)
             {
                 // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
                 format = " -f mp4 -movflags frag_keyframe+empty_moov";
@@ -95,7 +96,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 args += GetOutputSizeParam(state, videoCodec);
             }
 
-            var qualityParam = GetVideoQualityParam(state, videoCodec, false);
+            var qualityParam = GetVideoQualityParam(state, videoCodec);
 
             if (!string.IsNullOrEmpty(qualityParam))
             {

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -668,6 +668,9 @@
     <Compile Include="..\MediaBrowser.Model\FileOrganization\FileSortingStatus.cs">
       <Link>FileOrganization\FileSortingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\FileOrganization\SmartMatchInfo.cs">
+      <Link>FileOrganization\SmartMatchInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
       <Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -633,6 +633,9 @@
     <Compile Include="..\MediaBrowser.Model\FileOrganization\FileSortingStatus.cs">
       <Link>FileOrganization\FileSortingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\FileOrganization\SmartMatchInfo.cs">
+      <Link>FileOrganization\SmartMatchInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
       <Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
     </Compile>

+ 7 - 0
MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs

@@ -9,9 +9,16 @@ namespace MediaBrowser.Model.FileOrganization
         /// <value>The tv options.</value>
         public TvFileOrganizationOptions TvOptions { get; set; }
 
+        /// <summary>
+        /// Gets or sets a list of smart match entries.
+        /// </summary>
+        /// <value>The smart match entries.</value>
+        public SmartMatchInfo[] SmartMatchInfos { get; set; }
+
         public AutoOrganizeOptions()
         {
             TvOptions = new TvFileOrganizationOptions();
+            SmartMatchInfos = new SmartMatchInfo[]{};
         }
     }
 }

+ 16 - 0
MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs

@@ -0,0 +1,16 @@
+
+namespace MediaBrowser.Model.FileOrganization
+{
+    public class SmartMatchInfo
+    {
+        public string Id { get; set; }
+        public string Name { get; set; }
+        public FileOrganizerType OrganizerType { get; set; }
+        public string[] MatchStrings { get; set; }
+
+        public SmartMatchInfo()
+        {
+            MatchStrings = new string[] { };
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -137,6 +137,7 @@
     <Compile Include="Dto\MetadataEditorInfo.cs" />
     <Compile Include="Dto\NameIdPair.cs" />
     <Compile Include="Dto\NameValuePair.cs" />
+    <Compile Include="FileOrganization\SmartMatchInfo.cs" />
     <Compile Include="MediaInfo\LiveStreamRequest.cs" />
     <Compile Include="MediaInfo\LiveStreamResponse.cs" />
     <Compile Include="MediaInfo\PlaybackInfoRequest.cs" />

+ 63 - 11
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -46,12 +46,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         public Task<FileOrganizationResult> OrganizeEpisodeFile(string path, CancellationToken cancellationToken)
         {
-            var options = _config.GetAutoOrganizeOptions().TvOptions;
+            var options = _config.GetAutoOrganizeOptions();
 
             return OrganizeEpisodeFile(path, options, false, cancellationToken);
         }
 
-        public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting, CancellationToken cancellationToken)
+        public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken)
         {
             _logger.Info("Sorting file {0}", path);
 
@@ -110,6 +110,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                         premiereDate,
                         options,
                         overwriteExisting,
+						false,
                         result,
                         cancellationToken).ConfigureAwait(false);
                 }
@@ -145,7 +146,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return result;
         }
 
-        public async Task<FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, TvFileOrganizationOptions options, CancellationToken cancellationToken)
+        public async Task<FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken)
         {
             var result = _organizationService.GetResult(request.ResultId);
 
@@ -159,6 +160,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 null,
                 options,
                 true,
+				request.RememberCorrection,
                 result,
                 cancellationToken).ConfigureAwait(false);
 
@@ -173,12 +175,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             int? episodeNumber,
             int? endingEpiosdeNumber,
             DateTime? premiereDate,
-            TvFileOrganizationOptions options,
+            AutoOrganizeOptions options,
             bool overwriteExisting,
+			bool rememberCorrection,
             FileOrganizationResult result,
             CancellationToken cancellationToken)
         {
-            var series = GetMatchingSeries(seriesName, result);
+            var series = GetMatchingSeries(seriesName, result, options);
 
             if (series == null)
             {
@@ -197,6 +200,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 premiereDate,
                 options,
                 overwriteExisting,
+				rememberCorrection,
                 result,
                 cancellationToken);
         }
@@ -207,15 +211,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             int? episodeNumber,
             int? endingEpiosdeNumber,
             DateTime? premiereDate,
-            TvFileOrganizationOptions options,
+            AutoOrganizeOptions options,
             bool overwriteExisting,
+			bool rememberCorrection,
             FileOrganizationResult result,
             CancellationToken cancellationToken)
         {
             _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path);
 
+            var originalExtractedSeriesString = result.ExtractedName;
+
             // Proceed to sort the file
-            var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, cancellationToken).ConfigureAwait(false);
+            var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false);
 
             if (string.IsNullOrEmpty(newPath))
             {
@@ -234,7 +241,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
             if (!overwriteExisting)
             {
-                if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
+                if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
                 {
                     _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath);
                     result.Status = FileSortingStatus.SkippedExisting;
@@ -251,7 +258,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 }
             }
 
-            PerformFileSorting(options, result);
+            PerformFileSorting(options.TvOptions, result);
 
             if (overwriteExisting)
             {
@@ -285,6 +292,36 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                     }
                 }
             }
+
+            if (rememberCorrection)
+            {
+                SaveSmartMatchString(originalExtractedSeriesString, series, options);
+            }
+        }
+
+        private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options)
+        {
+            var seriesIdString = series.Id.ToString("N");
+            SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.Id, seriesIdString));
+
+            if (info == null)
+            {
+                info = new SmartMatchInfo();
+                info.Id = series.Id.ToString("N");
+                info.OrganizerType = FileOrganizerType.Episode;
+                info.Name = series.Name;
+                var list = options.SmartMatchInfos.ToList();
+                list.Add(info);
+                options.SmartMatchInfos = list.ToArray();
+            }
+
+            if (!info.MatchStrings.Contains(matchString, StringComparer.OrdinalIgnoreCase))
+            {
+                var list = info.MatchStrings.ToList();
+                list.Add(matchString);
+                info.MatchStrings = list.ToArray();
+                _config.SaveAutoOrganizeOptions(options);
+            }
         }
 
         private void DeleteLibraryFile(string path, bool renameRelatedFiles, string targetPath)
@@ -435,7 +472,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }
         }
 
-        private Series GetMatchingSeries(string seriesName, FileOrganizationResult result)
+        private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options)
         {
             var parsedName = _libraryManager.ParseName(seriesName);
 
@@ -445,13 +482,28 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             result.ExtractedName = nameWithoutYear;
             result.ExtractedYear = yearInName;
 
-            return _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
+            var series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
                 .Cast<Series>()
                 .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i))
                 .Where(i => i.Item2 > 0)
                 .OrderByDescending(i => i.Item2)
                 .Select(i => i.Item1)
                 .FirstOrDefault();
+
+            if (series == null)
+            {
+                SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(e => e.MatchStrings.Contains(seriesName, StringComparer.OrdinalIgnoreCase));
+
+                if (info != null)
+                {
+                    series = _libraryManager.RootFolder
+                        .GetRecursiveChildren(i => i is Series)
+                        .Cast<Series>()
+                        .FirstOrDefault(i => string.Equals(i.Id.ToString("N"), info.Id));
+                }
+            }
+
+            return series ?? new Series();
         }
 
         /// <summary>

+ 4 - 0
MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs

@@ -10,6 +10,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         {
             return manager.GetConfiguration<AutoOrganizeOptions>("autoorganize");
         }
+        public static void SaveAutoOrganizeOptions(this IConfigurationManager manager, AutoOrganizeOptions options)
+        {
+            manager.SaveConfiguration("autoorganize", options);
+        }
     }
 
     public class AutoOrganizeOptionsFactory : IConfigurationFactory

+ 58 - 4
MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using System;
 using System.IO;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
@@ -96,9 +97,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return _repo.Delete(resultId);
         }
 
-        private TvFileOrganizationOptions GetTvOptions()
+        private AutoOrganizeOptions GetAutoOrganizeptions()
         {
-            return _config.GetAutoOrganizeOptions().TvOptions;
+            return _config.GetAutoOrganizeOptions();
         }
 
         public async Task PerformOrganization(string resultId)
@@ -113,7 +114,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
                 _libraryMonitor, _providerManager);
 
-            await organizer.OrganizeEpisodeFile(result.OriginalPath, GetTvOptions(), true, CancellationToken.None)
+            await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeptions(), true, CancellationToken.None)
                     .ConfigureAwait(false);
         }
 
@@ -127,7 +128,60 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
                 _libraryMonitor, _providerManager);
 
-            await organizer.OrganizeWithCorrection(request, GetTvOptions(), CancellationToken.None).ConfigureAwait(false);
+            await organizer.OrganizeWithCorrection(request, GetAutoOrganizeptions(), CancellationToken.None).ConfigureAwait(false);
+        }
+
+        public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query)
+        {
+            if (query == null)
+            {
+                throw new ArgumentNullException("query");
+            }
+
+            var options = GetAutoOrganizeptions();
+
+            var items = options.SmartMatchInfos.Skip(query.StartIndex ?? 0).Take(query.Limit ?? Int32.MaxValue).ToArray();
+
+            return new QueryResult<SmartMatchInfo>()
+            {
+                Items = items,
+                TotalRecordCount = options.SmartMatchInfos.Length
+            };
+        }
+
+        public void DeleteSmartMatchEntry(string IdString, string matchString)
+        {
+            Guid Id;
+
+            if (!Guid.TryParse(IdString, out Id))
+            {
+                throw new ArgumentNullException("Id");
+            }
+
+            if (string.IsNullOrEmpty(matchString))
+            {
+                throw new ArgumentNullException("matchString");
+            }
+
+            var options = GetAutoOrganizeptions();
+
+            SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.Id, IdString));
+
+            if (info != null && info.MatchStrings.Contains(matchString))
+            {
+                var list = info.MatchStrings.ToList();
+                list.Remove(matchString);
+                info.MatchStrings = list.ToArray();
+
+                if (info.MatchStrings.Length == 0)
+                {
+                    var infos = options.SmartMatchInfos.ToList();
+                    infos.Remove(info);
+                    options.SmartMatchInfos = infos.ToArray();
+                }
+
+                _config.SaveAutoOrganizeOptions(options);
+            }
         }
     }
 }

+ 6 - 6
MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs

@@ -50,17 +50,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             get { return "Library"; }
         }
 
-        private TvFileOrganizationOptions GetTvOptions()
+        private AutoOrganizeOptions GetAutoOrganizeOptions()
         {
-            return _config.GetAutoOrganizeOptions().TvOptions;
+            return _config.GetAutoOrganizeOptions();
         }
 
         public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            if (GetTvOptions().IsEnabled)
+            if (GetAutoOrganizeOptions().TvOptions.IsEnabled)
             {
                 await new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config, _providerManager)
-                    .Organize(GetTvOptions(), cancellationToken, progress).ConfigureAwait(false);
+                    .Organize(GetAutoOrganizeOptions(), cancellationToken, progress).ConfigureAwait(false);
             }
         }
 
@@ -74,12 +74,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         public bool IsHidden
         {
-            get { return !GetTvOptions().IsEnabled; }
+            get { return !GetAutoOrganizeOptions().TvOptions.IsEnabled; }
         }
 
         public bool IsEnabled
         {
-            get { return GetTvOptions().IsEnabled; }
+            get { return GetAutoOrganizeOptions().TvOptions.IsEnabled; }
         }
 
         public bool IsActivityLogged

+ 6 - 6
MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs

@@ -52,13 +52,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return false;
         }
 
-        public async Task Organize(TvFileOrganizationOptions options, CancellationToken cancellationToken, IProgress<double> progress)
+        public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress<double> progress)
         {
-            var watchLocations = options.WatchLocations.ToList();
+            var watchLocations = options.TvOptions.WatchLocations.ToList();
 
             var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize)
                 .OrderBy(_fileSystem.GetCreationTimeUtc)
-                .Where(i => EnableOrganization(i, options))
+                .Where(i => EnableOrganization(i, options.TvOptions))
                 .ToList();
 
             var processedFolders = new HashSet<string>();
@@ -76,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
                     try
                     {
-                        var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false);
+                        var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.TvOptions.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false);
                         if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase))
                         {
                             processedFolders.Add(file.DirectoryName);
@@ -100,7 +100,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
             foreach (var path in processedFolders)
             {
-                var deleteExtensions = options.LeftOverFileExtensionsToDelete
+                var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete
                     .Select(i => i.Trim().TrimStart('.'))
                     .Where(i => !string.IsNullOrEmpty(i))
                     .Select(i => "." + i)
@@ -111,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                     DeleteLeftOverFiles(path, deleteExtensions);
                 }
 
-                if (options.DeleteEmptyFolders)
+                if (options.TvOptions.DeleteEmptyFolders)
                 {
                     if (!IsWatchFolder(path, watchLocations))
                     {

+ 6 - 3
MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs

@@ -165,11 +165,14 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
 
                 var item = _libraryManager.GetItemById(id);
 
-                await _libraryManager.DeleteItem(item, new DeleteOptions
+                if (item != null)
                 {
-                    DeleteFileLocation = false
+                    await _libraryManager.DeleteItem(item, new DeleteOptions
+                    {
+                        DeleteFileLocation = false
 
-                }).ConfigureAwait(false);
+                    }).ConfigureAwait(false);
+                }
             }
 
             progress.Report(100);

+ 23 - 5
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -171,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 {
                     epgData = GetEpgDataForChannel(timer.ChannelId);
                 }
-                await UpdateTimersForSeriesTimer(epgData, timer, false).ConfigureAwait(false);
+                await UpdateTimersForSeriesTimer(epgData, timer, true).ConfigureAwait(false);
             }
 
             var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
@@ -664,12 +664,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 throw new ArgumentNullException("timer");
             }
 
+            ProgramInfo info = null;
+
             if (string.IsNullOrWhiteSpace(timer.ProgramId))
             {
-                throw new InvalidOperationException("timer.ProgramId is null. Cannot record.");
+                _logger.Info("Timer {0} has null programId", timer.Id);
+            }
+            else
+            {
+                info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
             }
 
-            var info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
+            if (info == null)
+            {
+                _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
+                info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+            }
 
             if (info == null)
             {
@@ -775,14 +785,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
                     {
                         _logger.Info("Opened recording stream from tuner provider");
-                        
+
                         using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
                         {
                             result.Item2.Release();
                             isResourceOpen = false;
 
                             _logger.Info("Copying recording stream to file stream");
-                            
+
                             await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false);
                         }
                     }
@@ -867,6 +877,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return epgData.FirstOrDefault(p => string.Equals(p.Id, programId, StringComparison.OrdinalIgnoreCase));
         }
 
+        private ProgramInfo GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
+        {
+            var epgData = GetEpgDataForChannel(channelId);
+            var startDateTicks = startDateUtc.Ticks;
+            // Find the first program that starts within 3 minutes
+            return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks);
+        }
+
         private string RecordingPath
         {
             get

+ 42 - 38
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -46,53 +46,59 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
 
         protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
         {
-            var url = info.Url;
-            var urlHash = url.GetMD5().ToString("N");
+            var urlHash = info.Url.GetMD5().ToString("N");
 
-            string line;
             // Read the file and display it line by line.
-            using (var file = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
+            using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
             {
-                var channels = new List<M3UChannel>();
+                return GetChannels(reader, urlHash);
+            }
+        }
+
+        private List<M3UChannel> GetChannels(StreamReader reader, string urlHash)
+        {
+            var channels = new List<M3UChannel>();
 
-                string channnelName = null;
-                string channelNumber = null;
+            string channnelName = null;
+            string channelNumber = null;
+            string line;
 
-                while ((line = file.ReadLine()) != null)
+            while ((line = reader.ReadLine()) != null)
+            {
+                line = line.Trim();
+                if (string.IsNullOrWhiteSpace(line))
                 {
-                    line = line.Trim();
-                    if (string.IsNullOrWhiteSpace(line))
-                    {
-                        continue;
-                    }
+                    continue;
+                }
 
-                    if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
-                    {
-                        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))
+                if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
+                {
+                    line = line.Substring(8);
+                    Logger.Info("Found m3u channel: {0}", line);
+                    var parts = line.Split(new[] { ',' }, 2);
+                    channelNumber = parts[0];
+                    channnelName = parts[1];
+                }
+                else if (!string.IsNullOrWhiteSpace(channelNumber))
+                {
+                    channels.Add(new M3UChannel
                     {
-                        channels.Add(new M3UChannel
-                        {
-                            Name = channnelName,
-                            Number = channelNumber,
-                            Id = ChannelIdPrefix + urlHash + channelNumber,
-                            Path = line
-                        });
-
-                        channelNumber = null;
-                        channnelName = null;
-                    }
+                        Name = channnelName,
+                        Number = channelNumber,
+                        Id = ChannelIdPrefix + urlHash + line.GetMD5().ToString("N"),
+                        Path = line
+                    });
+
+                    channelNumber = null;
+                    channnelName = null;
                 }
-                return channels;
             }
+            return channels;
         }
 
         public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
@@ -159,8 +165,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
                 return null;
             }
 
-            //channelId = channelId.Substring(prefix.Length);
-
             var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
             var m3uchannels = channels.Cast<M3UChannel>();
             var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));

+ 3 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -45,6 +45,9 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\CommonIO.1.0.0.7\lib\net45\CommonIO.dll</HintPath>
     </Reference>
+    <Reference Include="Emby.XmlTv">
+      <HintPath>..\packages\Emby.XmlTv.1.0.0.46\lib\net45\Emby.XmlTv.dll</HintPath>
+    </Reference>
     <Reference Include="Interfaces.IO">
       <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
     </Reference>

+ 1 - 1
MediaBrowser.Server.Implementations/Sorting/IsFolderComparer.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
         /// <value>The name.</value>
         public string Name
         {
-            get { return ItemSortBy.Album; }
+            get { return ItemSortBy.IsFolder; }
         }
     }
 }

+ 1 - 0
MediaBrowser.Server.Implementations/packages.config

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
+  <package id="Emby.XmlTv" version="1.0.0.46" targetFramework="net45" />
   <package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
   <package id="MediaBrowser.Naming" version="1.0.0.46" targetFramework="net45" />
   <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />

+ 0 - 1
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -477,7 +477,6 @@ namespace MediaBrowser.WebDashboard.Api
 
             var files = new[]
                                   {
-                                      "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.css",
                                       "css/site.css",
                                       "css/librarymenu.css",
                                       "css/librarybrowser.css",

+ 9 - 309
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -101,6 +101,12 @@
     <Content Include="dashboard-ui\components\chromecasthelpers.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\autoorganizesmart.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="dashboard-ui\bower_components\fastclick\lib\fastclick.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\components\recordingcreator\recordingcreator.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -281,6 +287,9 @@
     <Content Include="dashboard-ui\scripts\mysyncsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\autoorganizesmart.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\searchmenu.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -368,15 +377,9 @@
     <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.panel.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -1039,309 +1042,6 @@
     <Content Include="dashboard-ui\scripts\wizardsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\ajax-loader.gif">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\action-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\action-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\alert-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\alert-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-d-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-d-l-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-d-l-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-d-r-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-d-r-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-d-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-l-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-l-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-r-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-r-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-u-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-u-l-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-u-l-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-u-r-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-u-r-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\arrow-u-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\audio-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\audio-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\back-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\back-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\bars-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\bars-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\bullets-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\bullets-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\calendar-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\calendar-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\camera-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\camera-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-d-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-d-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-l-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-l-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-r-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-r-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-u-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\carat-u-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\check-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\check-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\clock-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\clock-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\cloud-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\cloud-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\comment-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\comment-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\delete-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\delete-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\edit-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\edit-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\eye-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\eye-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\forbidden-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\forbidden-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\forward-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\forward-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\gear-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\gear-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\grid-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\grid-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\heart-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\heart-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\home-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\home-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\info-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\info-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\location-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\location-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\lock-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\lock-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\mail-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\mail-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\minus-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\minus-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\navigation-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\navigation-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\phone-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\phone-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\plus-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\plus-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\power-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\power-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\recycle-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\recycle-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\refresh-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\refresh-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\search-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\search-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\shop-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\shop-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\star-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\star-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\tag-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\tag-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\user-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\user-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\video-black.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-png\video-white.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-svg\action-black.svg">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>