update metadata editor

pull/702/head
Luke Pulverenti 9 years ago
parent c3d6c19cc3
commit 718545a79b

@ -839,7 +839,7 @@ namespace MediaBrowser.Api.Playback
}
// leave blank so ffmpeg will decide
return string.Empty;
return null;
}
/// <summary>
@ -849,7 +849,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state)
{
var arg = string.Format("{1}-i {0}", GetInputPathArgument(state), GetVideoDecoder(state));
var arg = string.Format("-i {0}", GetInputPathArgument(state));
if (state.SubtitleStream != null)
{
@ -1060,6 +1060,7 @@ namespace MediaBrowser.Api.Playback
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
@ -2191,6 +2192,12 @@ namespace MediaBrowser.Api.Playback
inputModifier += " -re";
}
var videoDecoder = GetVideoDecoder(state);
if (!string.IsNullOrWhiteSpace(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
return inputModifier;
}

@ -115,7 +115,11 @@ namespace MediaBrowser.Controller.Channels
var info = GetItemLookupInfo<ChannelItemLookupInfo>();
info.ContentType = ContentType;
info.ExtraType = ExtraType;
if (ExtraType.HasValue)
{
info.ExtraType = ExtraType.Value;
}
return info;
}

@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Channels
ChannelMediaContentType ContentType { get; set; }
ExtraType ExtraType { get; set; }
ExtraType? ExtraType { get; set; }
List<ChannelMediaInfo> ChannelMediaSources { get; set; }
}

@ -24,14 +24,20 @@ namespace MediaBrowser.Controller.Entities.Audio
IThemeMedia,
IArchivable
{
public string FormatName { get; set; }
public long? Size { get; set; }
public string Container { get; set; }
public int? TotalBitrate { get; set; }
public List<string> Tags { get; set; }
public ExtraType ExtraType { get; set; }
public ExtraType? ExtraType { get; set; }
public bool IsThemeMedia { get; set; }
[IgnoreDataMember]
public bool IsThemeMedia
{
get
{
return ExtraType.HasValue && ExtraType.Value == Model.Entities.ExtraType.ThemeSong;
}
}
public Audio()
{
@ -46,12 +52,6 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
}
/// <summary>
/// Gets or sets a value indicating whether this instance has embedded image.
/// </summary>
/// <value><c>true</c> if this instance has embedded image; otherwise, <c>false</c>.</value>
public bool HasEmbeddedImage { get; set; }
[IgnoreDataMember]
protected override bool SupportsOwnedItems
{
@ -212,8 +212,7 @@ namespace MediaBrowser.Controller.Entities.Audio
Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
RunTimeTicks = i.RunTimeTicks,
Container = i.Container,
Size = i.Size,
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
Size = i.Size
};
if (string.IsNullOrEmpty(info.Container))

@ -66,6 +66,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// Gets the tracks.
/// </summary>
/// <value>The tracks.</value>
[IgnoreDataMember]
public IEnumerable<Audio> Tracks
{
get

@ -880,7 +880,7 @@ namespace MediaBrowser.Controller.Entities
if (!i.IsThemeMedia)
{
i.IsThemeMedia = true;
i.ExtraType = ExtraType.ThemeVideo;
subOptions.ForceSave = true;
}
@ -910,7 +910,7 @@ namespace MediaBrowser.Controller.Entities
if (!i.IsThemeMedia)
{
i.IsThemeMedia = true;
i.ExtraType = ExtraType.ThemeSong;
subOptions.ForceSave = true;
}

@ -61,10 +61,6 @@ namespace MediaBrowser.Controller.Entities.TV
return base.CreateUserDataKey();
}
/// <summary>
/// The _series
/// </summary>
private Series _series;
/// <summary>
/// This Episode's Series Instance
/// </summary>
@ -72,7 +68,7 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public Series Series
{
get { return _series ?? (_series = FindParent<Series>()); }
get { return FindParent<Series>(); }
}
[IgnoreDataMember]

@ -33,14 +33,20 @@ namespace MediaBrowser.Controller.Entities
public List<string> LocalAlternateVersions { get; set; }
public List<LinkedChild> LinkedAlternateVersions { get; set; }
public bool IsThemeMedia { get; set; }
[IgnoreDataMember]
public bool IsThemeMedia
{
get
{
return ExtraType.HasValue && ExtraType.Value == Model.Entities.ExtraType.ThemeVideo;
}
}
public string FormatName { get; set; }
public long? Size { get; set; }
public string Container { get; set; }
public int? TotalBitrate { get; set; }
public string ShortOverview { get; set; }
public ExtraType ExtraType { get; set; }
public ExtraType? ExtraType { get; set; }
/// <summary>
/// Gets or sets the preferred metadata country code.
@ -498,7 +504,6 @@ namespace MediaBrowser.Controller.Entities
VideoType = i.VideoType,
Container = i.Container,
Size = i.Size,
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
Timestamp = i.Timestamp,
Type = type,
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),

@ -18,6 +18,7 @@ namespace MediaBrowser.Controller.LiveTv
public string ProviderImagePath { get; set; }
public string ProviderImageUrl { get; set; }
public string EpisodeTitle { get; set; }
[IgnoreDataMember]
public bool IsSeries { get; set; }
public string SeriesTimerId { get; set; }
[IgnoreDataMember]
@ -25,6 +26,7 @@ namespace MediaBrowser.Controller.LiveTv
public RecordingStatus Status { get; set; }
[IgnoreDataMember]
public bool IsSports { get; set; }
[IgnoreDataMember]
public bool IsNews { get; set; }
[IgnoreDataMember]
public bool IsKids { get; set; }
@ -32,7 +34,9 @@ namespace MediaBrowser.Controller.LiveTv
[IgnoreDataMember]
public bool IsMovie { get; set; }
public bool? IsHD { get; set; }
[IgnoreDataMember]
public bool IsLive { get; set; }
[IgnoreDataMember]
public bool IsPremiere { get; set; }
public ChannelType ChannelType { get; set; }
public string ProgramId { get; set; }

@ -125,18 +125,21 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets a value indicating whether this instance is series.
/// </summary>
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
public bool IsSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is live.
/// </summary>
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
public bool IsLive { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is news.
/// </summary>
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
public bool IsNews { get; set; }
/// <summary>
@ -150,6 +153,7 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets a value indicating whether this instance is premiere.
/// </summary>
/// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
public bool IsPremiere { get; set; }
/// <summary>
@ -248,6 +252,7 @@ namespace MediaBrowser.Controller.LiveTv
return info;
}
[IgnoreDataMember]
public override bool SupportsPeople
{
get

@ -18,6 +18,7 @@ namespace MediaBrowser.Controller.LiveTv
public string ProviderImagePath { get; set; }
public string ProviderImageUrl { get; set; }
public string EpisodeTitle { get; set; }
[IgnoreDataMember]
public bool IsSeries { get; set; }
public string SeriesTimerId { get; set; }
[IgnoreDataMember]
@ -25,6 +26,7 @@ namespace MediaBrowser.Controller.LiveTv
public RecordingStatus Status { get; set; }
[IgnoreDataMember]
public bool IsSports { get; set; }
[IgnoreDataMember]
public bool IsNews { get; set; }
[IgnoreDataMember]
public bool IsKids { get; set; }
@ -32,7 +34,9 @@ namespace MediaBrowser.Controller.LiveTv
[IgnoreDataMember]
public bool IsMovie { get; set; }
public bool? IsHD { get; set; }
[IgnoreDataMember]
public bool IsLive { get; set; }
[IgnoreDataMember]
public bool IsPremiere { get; set; }
public ChannelType ChannelType { get; set; }
public string ProgramId { get; set; }

@ -342,9 +342,55 @@ namespace MediaBrowser.MediaEncoding.Encoder
inputModifier += " -re";
}
var videoDecoder = GetVideoDecoder(job);
if (!string.IsNullOrWhiteSpace(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
return inputModifier;
}
/// <summary>
/// Gets the name of the output video codec
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected string GetVideoDecoder(EncodingJob state)
{
if (string.Equals(GetEncodingOptions().HardwareVideoDecoder, "qsv", StringComparison.OrdinalIgnoreCase))
{
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{
switch (state.MediaSource.VideoStream.Codec.ToLower())
{
case "avc":
case "h264":
if (MediaEncoder.SupportsDecoder("h264_qsv"))
{
return "-c:v h264_qsv ";
}
break;
case "mpeg2video":
if (MediaEncoder.SupportsDecoder("mpeg2_qsv"))
{
return "-c:v mpeg2_qsv ";
}
break;
case "vc1":
if (MediaEncoder.SupportsDecoder("vc1_qsv"))
{
return "-c:v vc1_qsv ";
}
break;
}
}
}
// leave blank so ffmpeg will decide
return null;
}
private string GetUserAgentParam(EncodingJob job)
{
string useragent = null;
@ -436,7 +482,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
}
if (state.MediaSource.RequiresOpening)
if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.LiveStreamId))
{
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
{

@ -56,7 +56,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public bool EnableMpegtsM2TsMode { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public long? EncodingDurationTicks { get; set; }
public string LiveTvStreamId { get; set; }
public string LiveStreamId { get; set; }
public long? RunTimeTicks;
public string ItemType { get; set; }

@ -326,26 +326,36 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.Nullable{System.Int32}.</returns>
private int? GetNumAudioChannelsParam(EncodingJobOptions request, MediaStream audioStream, string outputAudioCodec)
{
if (audioStream != null)
var inputChannels = audioStream == null
? null
: audioStream.Channels;
if (inputChannels <= 0)
{
var codec = outputAudioCodec ?? string.Empty;
inputChannels = null;
}
if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
{
// wmav2 currently only supports two channel output
return 2;
}
var codec = outputAudioCodec ?? string.Empty;
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
{
// wmav2 currently only supports two channel output
return Math.Min(2, inputChannels ?? 2);
}
if (request.MaxAudioChannels.HasValue)
{
if (audioStream != null && audioStream.Channels.HasValue)
if (inputChannels.HasValue)
{
return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
return Math.Min(request.MaxAudioChannels.Value, inputChannels.Value);
}
var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
? 2
: 6;
// If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
return Math.Min(request.MaxAudioChannels.Value, 5);
return Math.Min(request.MaxAudioChannels.Value, channelLimit);
}
return request.AudioChannels;
@ -519,6 +529,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
if (videoStream.IsAnamorphic ?? false)
{
return false;
}
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue)
{

@ -43,7 +43,7 @@ namespace MediaBrowser.Providers.MediaInfo
var audio = (Audio)item;
// Can't extract if we didn't find a video stream in the file
if (!audio.HasEmbeddedImage)
if (!audio.GetMediaSources(false).Take(1).SelectMany(i => i.MediaStreams).Any(i => i.Type == MediaStreamType.EmbeddedImage))
{
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}

@ -103,9 +103,8 @@ namespace MediaBrowser.Providers.MediaInfo
{
var mediaStreams = mediaInfo.MediaStreams;
audio.FormatName = mediaInfo.Container;
//audio.FormatName = mediaInfo.Container;
audio.TotalBitrate = mediaInfo.Bitrate;
audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage);
audio.RunTimeTicks = mediaInfo.RunTimeTicks;
audio.Size = mediaInfo.Size;

@ -190,8 +190,8 @@ namespace MediaBrowser.Providers.MediaInfo
var mediaStreams = mediaInfo.MediaStreams;
video.TotalBitrate = mediaInfo.Bitrate;
video.FormatName = (mediaInfo.Container ?? string.Empty)
.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
//video.FormatName = (mediaInfo.Container ?? string.Empty)
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;

@ -650,11 +650,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
}
else if (string.IsNullOrWhiteSpace(info.Etag))
{
await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
else
{
if (string.IsNullOrWhiteSpace(info.Etag) || !string.Equals(info.Etag, item.Etag, StringComparison.OrdinalIgnoreCase))
// Increment this whenver some internal change deems it necessary
var etag = info.Etag + "1";
if (!string.Equals(etag, item.Etag, StringComparison.OrdinalIgnoreCase))
{
item.Etag = info.Etag;
item.Etag = etag;
await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
}
@ -1162,15 +1169,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var program in channelPrograms)
{
if (program.StartDate.Kind != DateTimeKind.Utc)
{
_logger.Error("{0} returned StartDate.DateTimeKind.{1} instead of UTC for program {2}", service.Name, program.StartDate.Kind.ToString(), program.Name);
}
else if (program.EndDate.Kind != DateTimeKind.Utc)
{
_logger.Error("{0} returned EndDate.DateTimeKind.{1} instead of UTC for program {2}", service.Name, program.EndDate.Kind.ToString(), program.Name);
}
var programItem = await GetProgram(program, channelId, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
programs.Add(programItem.Id);

@ -232,6 +232,7 @@
<Compile Include="Localization\LocalizationManager.cs" />
<Compile Include="Logging\PatternsLogger.cs" />
<Compile Include="MediaEncoder\EncodingManager.cs" />
<Compile Include="Notifications\IConfigurableNotificationService.cs" />
<Compile Include="Persistence\BaseSqliteRepository.cs" />
<Compile Include="Persistence\CleanDatabaseScheduledTask.cs" />
<Compile Include="Social\SharingManager.cs" />

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Notifications
{
public interface IConfigurableNotificationService
{
bool IsHidden { get; }
bool IsEnabled(string notificationType);
}
}

@ -3,10 +3,11 @@ using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Notifications;
using System.Threading;
using System.Threading.Tasks;
using System;
namespace MediaBrowser.Server.Implementations.Notifications
{
public class InternalNotificationService : INotificationService
public class InternalNotificationService : INotificationService, IConfigurableNotificationService
{
private readonly INotificationsRepository _repo;
@ -36,6 +37,24 @@ namespace MediaBrowser.Server.Implementations.Notifications
public bool IsEnabledForUser(User user)
{
return user.Policy.IsAdministrator;
}
public bool IsHidden
{
get { return true; }
}
public bool IsEnabled(string notificationType)
{
if (notificationType.IndexOf("playback", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
if (notificationType.IndexOf("newlibrarycontent", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
return true;
}
}

@ -230,8 +230,19 @@ namespace MediaBrowser.Server.Implementations.Notifications
private bool IsEnabled(INotificationService service, string notificationType)
{
return string.IsNullOrEmpty(notificationType) ||
GetConfiguration().IsServiceEnabled(service.Name, notificationType);
if (string.IsNullOrEmpty(notificationType))
{
return true;
}
var configurable = service as IConfigurableNotificationService;
if (configurable != null)
{
return configurable.IsEnabled(notificationType);
}
return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
}
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
@ -268,7 +279,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
public IEnumerable<NotificationServiceInfo> GetNotificationServices()
{
return _services.Select(i => new NotificationServiceInfo
return _services.Where(i =>
{
var configurable = i as IConfigurableNotificationService;
return configurable == null || !configurable.IsHidden;
}).Select(i => new NotificationServiceInfo
{
Name = i.Name,
Id = i.Name.GetMD5().ToString("N")

@ -72,7 +72,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _deletePeopleCommand;
private IDbCommand _savePersonCommand;
private const int LatestSchemaVersion = 7;
private const int LatestSchemaVersion = 9;
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@ -177,6 +177,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(_logger, "TypedBaseItems", "IsOffline", "BIT");
_connection.AddColumn(_logger, "TypedBaseItems", "LocationType", "Text");
_connection.AddColumn(_logger, "TypedBaseItems", "IsSeries", "BIT");
_connection.AddColumn(_logger, "TypedBaseItems", "IsLive", "BIT");
_connection.AddColumn(_logger, "TypedBaseItems", "IsNews", "BIT");
_connection.AddColumn(_logger, "TypedBaseItems", "IsPremiere", "BIT");
PrepareStatements();
_mediaStreamsRepository.Initialize();
@ -199,6 +204,10 @@ namespace MediaBrowser.Server.Implementations.Persistence
"IsMovie",
"IsSports",
"IsKids",
"IsSeries",
"IsLive",
"IsNews",
"IsPremiere",
"CommunityRating",
"CustomRating",
"IndexNumber",
@ -222,6 +231,10 @@ namespace MediaBrowser.Server.Implementations.Persistence
"IsKids",
"IsMovie",
"IsSports",
"IsSeries",
"IsLive",
"IsNews",
"IsPremiere",
"CommunityRating",
"CustomRating",
"IndexNumber",
@ -369,12 +382,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsKids;
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsMovie;
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsSports;
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsSeries;
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsLive;
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsNews;
_saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsPremiere;
}
else
{
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
}
_saveItemCommand.GetParameter(index++).Value = item.CommunityRating;
@ -563,26 +584,46 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
hasProgramAttributes.IsKids = reader.GetBoolean(8);
}
if (!reader.IsDBNull(9))
{
hasProgramAttributes.IsSeries = reader.GetBoolean(9);
}
if (!reader.IsDBNull(10))
{
hasProgramAttributes.IsLive = reader.GetBoolean(10);
}
if (!reader.IsDBNull(11))
{
hasProgramAttributes.IsNews = reader.GetBoolean(11);
}
if (!reader.IsDBNull(12))
{
hasProgramAttributes.IsPremiere = reader.GetBoolean(12);
}
}
if (!reader.IsDBNull(9))
if (!reader.IsDBNull(13))
{
item.CommunityRating = reader.GetFloat(9);
item.CommunityRating = reader.GetFloat(13);
}
if (!reader.IsDBNull(10))
if (!reader.IsDBNull(14))
{
item.CustomRating = reader.GetString(10);
item.CustomRating = reader.GetString(14);
}
if (!reader.IsDBNull(11))
if (!reader.IsDBNull(15))
{
item.IndexNumber = reader.GetInt32(11);
item.IndexNumber = reader.GetInt32(15);
}
if (!reader.IsDBNull(12))
if (!reader.IsDBNull(16))
{
item.IsLocked = reader.GetBoolean(12);
item.IsLocked = reader.GetBoolean(16);
}
return item;

@ -173,6 +173,12 @@
<Content Include="dashboard-ui\components\imageuploader\imageuploader.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\paperdialoghelper.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Loading…
Cancel
Save