Merge pull request #1436 from MediaBrowser/dev

Merge from dev
This commit is contained in:
Luke 2016-02-08 13:27:08 -05:00
commit 063fd56832
27 changed files with 336 additions and 421 deletions

View File

@ -74,6 +74,34 @@ namespace MediaBrowser.Api.Library
public bool RememberCorrection { get; set; } 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")] [Authenticated(Roles = "Admin")]
public class FileOrganizationService : BaseApiService public class FileOrganizationService : BaseApiService
{ {
@ -130,5 +158,21 @@ namespace MediaBrowser.Api.Library
Task.WaitAll(task); 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);
}
} }
} }

View File

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

View File

@ -430,7 +430,7 @@ namespace MediaBrowser.Api.Playback.Dash
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; 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 // Add resolution params, if specified
if (!hasGraphicalSubs) if (!hasGraphicalSubs)

View File

@ -822,7 +822,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; 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"; //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";

View File

@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; 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 // Add resolution params, if specified
if (!hasGraphicalSubs) if (!hasGraphicalSubs)

View File

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

View File

@ -67,5 +67,19 @@ namespace MediaBrowser.Controller.FileOrganization
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken); 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);
} }
} }

View File

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

View File

@ -26,7 +26,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
var format = string.Empty; var format = string.Empty;
var keyFrame = 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 // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
format = " -f mp4 -movflags frag_keyframe+empty_moov"; format = " -f mp4 -movflags frag_keyframe+empty_moov";
@ -95,7 +96,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
args += GetOutputSizeParam(state, videoCodec); args += GetOutputSizeParam(state, videoCodec);
} }
var qualityParam = GetVideoQualityParam(state, videoCodec, false); var qualityParam = GetVideoQualityParam(state, videoCodec);
if (!string.IsNullOrEmpty(qualityParam)) if (!string.IsNullOrEmpty(qualityParam))
{ {

View File

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

View File

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

View File

@ -9,9 +9,16 @@ namespace MediaBrowser.Model.FileOrganization
/// <value>The tv options.</value> /// <value>The tv options.</value>
public TvFileOrganizationOptions TvOptions { get; set; } 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() public AutoOrganizeOptions()
{ {
TvOptions = new TvFileOrganizationOptions(); TvOptions = new TvFileOrganizationOptions();
SmartMatchInfos = new SmartMatchInfo[]{};
} }
} }
} }

View File

@ -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[] { };
}
}
}

View File

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

View File

@ -46,12 +46,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
public Task<FileOrganizationResult> OrganizeEpisodeFile(string path, CancellationToken cancellationToken) public Task<FileOrganizationResult> OrganizeEpisodeFile(string path, CancellationToken cancellationToken)
{ {
var options = _config.GetAutoOrganizeOptions().TvOptions; var options = _config.GetAutoOrganizeOptions();
return OrganizeEpisodeFile(path, options, false, cancellationToken); 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); _logger.Info("Sorting file {0}", path);
@ -110,6 +110,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
premiereDate, premiereDate,
options, options,
overwriteExisting, overwriteExisting,
false,
result, result,
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
} }
@ -145,7 +146,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
return result; 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); var result = _organizationService.GetResult(request.ResultId);
@ -159,6 +160,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
null, null,
options, options,
true, true,
request.RememberCorrection,
result, result,
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
@ -173,12 +175,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
int? episodeNumber, int? episodeNumber,
int? endingEpiosdeNumber, int? endingEpiosdeNumber,
DateTime? premiereDate, DateTime? premiereDate,
TvFileOrganizationOptions options, AutoOrganizeOptions options,
bool overwriteExisting, bool overwriteExisting,
bool rememberCorrection,
FileOrganizationResult result, FileOrganizationResult result,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var series = GetMatchingSeries(seriesName, result); var series = GetMatchingSeries(seriesName, result, options);
if (series == null) if (series == null)
{ {
@ -197,6 +200,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
premiereDate, premiereDate,
options, options,
overwriteExisting, overwriteExisting,
rememberCorrection,
result, result,
cancellationToken); cancellationToken);
} }
@ -207,15 +211,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
int? episodeNumber, int? episodeNumber,
int? endingEpiosdeNumber, int? endingEpiosdeNumber,
DateTime? premiereDate, DateTime? premiereDate,
TvFileOrganizationOptions options, AutoOrganizeOptions options,
bool overwriteExisting, bool overwriteExisting,
bool rememberCorrection,
FileOrganizationResult result, FileOrganizationResult result,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
_logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path);
var originalExtractedSeriesString = result.ExtractedName;
// Proceed to sort the file // 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)) if (string.IsNullOrEmpty(newPath))
{ {
@ -234,7 +241,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (!overwriteExisting) 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); _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath);
result.Status = FileSortingStatus.SkippedExisting; result.Status = FileSortingStatus.SkippedExisting;
@ -251,7 +258,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
} }
} }
PerformFileSorting(options, result); PerformFileSorting(options.TvOptions, result);
if (overwriteExisting) 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) 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); var parsedName = _libraryManager.ParseName(seriesName);
@ -445,13 +482,28 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
result.ExtractedName = nameWithoutYear; result.ExtractedName = nameWithoutYear;
result.ExtractedYear = yearInName; result.ExtractedYear = yearInName;
return _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series) var series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
.Cast<Series>() .Cast<Series>()
.Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i)) .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i))
.Where(i => i.Item2 > 0) .Where(i => i.Item2 > 0)
.OrderByDescending(i => i.Item2) .OrderByDescending(i => i.Item2)
.Select(i => i.Item1) .Select(i => i.Item1)
.FirstOrDefault(); .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> /// <summary>

View File

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

View File

@ -11,6 +11,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
@ -96,9 +97,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
return _repo.Delete(resultId); return _repo.Delete(resultId);
} }
private TvFileOrganizationOptions GetTvOptions() private AutoOrganizeOptions GetAutoOrganizeptions()
{ {
return _config.GetAutoOrganizeOptions().TvOptions; return _config.GetAutoOrganizeOptions();
} }
public async Task PerformOrganization(string resultId) public async Task PerformOrganization(string resultId)
@ -113,7 +114,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager); _libraryMonitor, _providerManager);
await organizer.OrganizeEpisodeFile(result.OriginalPath, GetTvOptions(), true, CancellationToken.None) await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeptions(), true, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -127,7 +128,60 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager); _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);
}
} }
} }
} }

View File

@ -50,17 +50,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
get { return "Library"; } 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) 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) 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 public bool IsHidden
{ {
get { return !GetTvOptions().IsEnabled; } get { return !GetAutoOrganizeOptions().TvOptions.IsEnabled; }
} }
public bool IsEnabled public bool IsEnabled
{ {
get { return GetTvOptions().IsEnabled; } get { return GetAutoOrganizeOptions().TvOptions.IsEnabled; }
} }
public bool IsActivityLogged public bool IsActivityLogged

View File

@ -52,13 +52,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
return false; 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) var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize)
.OrderBy(_fileSystem.GetCreationTimeUtc) .OrderBy(_fileSystem.GetCreationTimeUtc)
.Where(i => EnableOrganization(i, options)) .Where(i => EnableOrganization(i, options.TvOptions))
.ToList(); .ToList();
var processedFolders = new HashSet<string>(); var processedFolders = new HashSet<string>();
@ -76,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
try 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)) if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase))
{ {
processedFolders.Add(file.DirectoryName); processedFolders.Add(file.DirectoryName);
@ -100,7 +100,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
foreach (var path in processedFolders) foreach (var path in processedFolders)
{ {
var deleteExtensions = options.LeftOverFileExtensionsToDelete var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete
.Select(i => i.Trim().TrimStart('.')) .Select(i => i.Trim().TrimStart('.'))
.Where(i => !string.IsNullOrEmpty(i)) .Where(i => !string.IsNullOrEmpty(i))
.Select(i => "." + i) .Select(i => "." + i)
@ -111,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
DeleteLeftOverFiles(path, deleteExtensions); DeleteLeftOverFiles(path, deleteExtensions);
} }
if (options.DeleteEmptyFolders) if (options.TvOptions.DeleteEmptyFolders)
{ {
if (!IsWatchFolder(path, watchLocations)) if (!IsWatchFolder(path, watchLocations))
{ {

View File

@ -165,11 +165,14 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
var item = _libraryManager.GetItemById(id); 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); progress.Report(100);

View File

@ -171,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
epgData = GetEpgDataForChannel(timer.ChannelId); 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); var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
@ -664,12 +664,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException("timer"); throw new ArgumentNullException("timer");
} }
ProgramInfo info = null;
if (string.IsNullOrWhiteSpace(timer.ProgramId)) 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) if (info == null)
{ {
@ -775,14 +785,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
{ {
_logger.Info("Opened recording stream from tuner provider"); _logger.Info("Opened recording stream from tuner provider");
using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
{ {
result.Item2.Release(); result.Item2.Release();
isResourceOpen = false; isResourceOpen = false;
_logger.Info("Copying recording stream to file stream"); _logger.Info("Copying recording stream to file stream");
await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false); 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)); 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 private string RecordingPath
{ {
get get

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -101,6 +101,12 @@
<Content Include="dashboard-ui\components\chromecasthelpers.js"> <Content Include="dashboard-ui\components\chromecasthelpers.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </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"> <Content Include="dashboard-ui\components\recordingcreator\recordingcreator.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -281,6 +287,9 @@
<Content Include="dashboard-ui\scripts\mysyncsettings.js"> <Content Include="dashboard-ui\scripts\mysyncsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\autoorganizesmart.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\searchmenu.js"> <Content Include="dashboard-ui\scripts\searchmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -368,15 +377,9 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.css"> <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.controlgroup.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </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"> <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </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"> <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.panel.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -1039,309 +1042,6 @@
<Content Include="dashboard-ui\scripts\wizardsettings.js"> <Content Include="dashboard-ui\scripts\wizardsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </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"> <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\images\icons-svg\action-black.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>