From 52ce5d05c1c91235ef045e0f6ff766baba6bebb8 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Mon, 3 Jun 2024 22:34:14 -0400 Subject: [PATCH 01/21] Create destination file immediately --- TwitchDownloaderCore/Chat/ChatHtml.cs | 13 +---- TwitchDownloaderCore/Chat/ChatJson.cs | 15 +---- TwitchDownloaderCore/Chat/ChatText.cs | 12 +--- TwitchDownloaderCore/ChatDownloader.cs | 16 +++++- TwitchDownloaderCore/ChatRenderer.cs | 56 ++++++++++--------- TwitchDownloaderCore/ChatUpdater.cs | 16 +++++- TwitchDownloaderCore/ClipDownloader.cs | 48 +++++++++------- .../Options/ChatDownloadOptions.cs | 5 +- .../Options/ChatRenderOptions.cs | 1 + .../Options/ChatUpdateOptions.cs | 5 +- .../Options/ClipDownloadOptions.cs | 6 +- .../Options/TsMergeOptions.cs | 6 +- .../Options/VideoDownloadOptions.cs | 3 +- TwitchDownloaderCore/TsMerger.cs | 15 ++++- TwitchDownloaderCore/TwitchHelper.cs | 27 ++++++++- TwitchDownloaderCore/VideoDownloader.cs | 28 ++++++---- 16 files changed, 165 insertions(+), 107 deletions(-) diff --git a/TwitchDownloaderCore/Chat/ChatHtml.cs b/TwitchDownloaderCore/Chat/ChatHtml.cs index f00d51d0..96272477 100644 --- a/TwitchDownloaderCore/Chat/ChatHtml.cs +++ b/TwitchDownloaderCore/Chat/ChatHtml.cs @@ -18,10 +18,8 @@ public static class ChatHtml /// /// Serializes a chat Html file. /// - public static async Task SerializeAsync(string filePath, ChatRoot chatRoot, ITaskLogger logger, bool embedData = true, CancellationToken cancellationToken = default) + public static async Task SerializeAsync(FileStream fileStream, string filePath, ChatRoot chatRoot, ITaskLogger logger, bool embedData = true, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(filePath, nameof(filePath)); - Dictionary thirdEmoteData = new(); await BuildThirdPartyDictionary(chatRoot, embedData, thirdEmoteData, logger, cancellationToken); @@ -35,14 +33,7 @@ public static async Task SerializeAsync(string filePath, ChatRoot chatRoot, ITas using var templateStream = new MemoryStream(Properties.Resources.chat_template); using var templateReader = new StreamReader(templateStream); - var outputDirectory = Directory.GetParent(Path.GetFullPath(filePath))!; - if (!outputDirectory.Exists) - { - TwitchHelper.CreateDirectory(outputDirectory.FullName); - } - - await using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.Read); - await using var sw = new StreamWriter(fs); + await using var sw = new StreamWriter(fileStream); while (!templateReader.EndOfStream) { diff --git a/TwitchDownloaderCore/Chat/ChatJson.cs b/TwitchDownloaderCore/Chat/ChatJson.cs index af80a634..137f67b4 100644 --- a/TwitchDownloaderCore/Chat/ChatJson.cs +++ b/TwitchDownloaderCore/Chat/ChatJson.cs @@ -244,25 +244,16 @@ private static async Task UpgradeChatJson(ChatRoot chatRoot) /// /// Asynchronously serializes a chat json file. /// - public static async Task SerializeAsync(string filePath, ChatRoot chatRoot, ChatCompression compression, CancellationToken cancellationToken) + public static async Task SerializeAsync(FileStream fileStream, ChatRoot chatRoot, ChatCompression compression, CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(chatRoot, nameof(chatRoot)); - - var outputDirectory = Directory.GetParent(Path.GetFullPath(filePath))!; - if (!outputDirectory.Exists) - { - TwitchHelper.CreateDirectory(outputDirectory.FullName); - } - - await using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.Read); switch (compression) { case ChatCompression.None: - await JsonSerializer.SerializeAsync(fs, chatRoot, _jsonSerializerOptions, cancellationToken); + await JsonSerializer.SerializeAsync(fileStream, chatRoot, _jsonSerializerOptions, cancellationToken); break; case ChatCompression.Gzip: { - await using var gs = new GZipStream(fs, CompressionLevel.SmallestSize); + await using var gs = new GZipStream(fileStream, CompressionLevel.SmallestSize); await JsonSerializer.SerializeAsync(gs, chatRoot, _jsonSerializerOptions, cancellationToken); break; } diff --git a/TwitchDownloaderCore/Chat/ChatText.cs b/TwitchDownloaderCore/Chat/ChatText.cs index 31930e81..9b6f66ad 100644 --- a/TwitchDownloaderCore/Chat/ChatText.cs +++ b/TwitchDownloaderCore/Chat/ChatText.cs @@ -11,17 +11,9 @@ public static class ChatText /// /// Serializes a chat plain text file. /// - public static async Task SerializeAsync(string filePath, ChatRoot chatRoot, TimestampFormat timeFormat) + public static async Task SerializeAsync(FileStream fileStream, ChatRoot chatRoot, TimestampFormat timeFormat) { - ArgumentNullException.ThrowIfNull(filePath, nameof(filePath)); - - var outputDirectory = Directory.GetParent(Path.GetFullPath(filePath))!; - if (!outputDirectory.Exists) - { - TwitchHelper.CreateDirectory(outputDirectory.FullName); - } - - await using var sw = new StreamWriter(filePath); + await using var sw = new StreamWriter(fileStream); foreach (var comment in chatRoot.comments) { var username = comment.commenter.display_name; diff --git a/TwitchDownloaderCore/ChatDownloader.cs b/TwitchDownloaderCore/ChatDownloader.cs index d2cffeb4..c9f916bc 100644 --- a/TwitchDownloaderCore/ChatDownloader.cs +++ b/TwitchDownloaderCore/ChatDownloader.cs @@ -251,6 +251,16 @@ public async Task DownloadAsync(CancellationToken cancellationToken) throw new NullReferenceException("Null or empty video/clip ID"); } + var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + if (outputFileInfo is null) + { + _progress.LogWarning("No destination file was provided, aborting."); + return; + } + + // Open the destination file so that it exists in the filesystem. + await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + DownloadType downloadType = downloadOptions.Id.All(char.IsDigit) ? DownloadType.Video : DownloadType.Clip; ChatRoot chatRoot = new() @@ -519,13 +529,13 @@ public async Task DownloadAsync(CancellationToken cancellationToken) switch (downloadOptions.DownloadFormat) { case ChatFormat.Json: - await ChatJson.SerializeAsync(downloadOptions.Filename, chatRoot, downloadOptions.Compression, cancellationToken); + await ChatJson.SerializeAsync(outputFs, chatRoot, downloadOptions.Compression, cancellationToken); break; case ChatFormat.Html: - await ChatHtml.SerializeAsync(downloadOptions.Filename, chatRoot, _progress, downloadOptions.EmbedData, cancellationToken); + await ChatHtml.SerializeAsync(outputFs, outputFileInfo.FullName, chatRoot, _progress, downloadOptions.EmbedData, cancellationToken); break; case ChatFormat.Text: - await ChatText.SerializeAsync(downloadOptions.Filename, chatRoot, downloadOptions.TimeFormat); + await ChatText.SerializeAsync(outputFs, chatRoot, downloadOptions.TimeFormat); break; default: throw new NotSupportedException($"{downloadOptions.DownloadFormat} is not a supported output format."); diff --git a/TwitchDownloaderCore/ChatRenderer.cs b/TwitchDownloaderCore/ChatRenderer.cs index 99850a26..a2def73b 100644 --- a/TwitchDownloaderCore/ChatRenderer.cs +++ b/TwitchDownloaderCore/ChatRenderer.cs @@ -71,6 +71,24 @@ public ChatRenderer(ChatRenderOptions chatRenderOptions, ITaskProgress progress) public async Task RenderVideoAsync(CancellationToken cancellationToken) { + var outputFileInfo = TwitchHelper.ClaimFile(renderOptions.OutputFile, renderOptions.FileOverwriteCallback, _progress); + var maskFileInfo = renderOptions.GenerateMask ? TwitchHelper.ClaimFile(renderOptions.MaskFile, renderOptions.FileOverwriteCallback, _progress) : null; + if (outputFileInfo is null) + { + _progress.LogWarning("No destination file was provided, aborting."); + return; + } + + if (renderOptions.GenerateMask && maskFileInfo is null) + { + _progress.LogWarning("No mask file was provided, aborting."); + return; + } + + // Open the destination files so that they exist in the filesystem. + await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + await using var maskFs = maskFileInfo?.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + _progress.SetStatus("Fetching Images [1/2]"); await Task.Run(() => FetchScaledImages(cancellationToken), cancellationToken); @@ -107,20 +125,17 @@ public async Task RenderVideoAsync(CancellationToken cancellationToken) (int startTick, int totalTicks) = GetVideoTicks(); - var renderFileDirectory = Directory.GetParent(Path.GetFullPath(renderOptions.OutputFile))!; - if (!renderFileDirectory.Exists) - { - TwitchHelper.CreateDirectory(renderFileDirectory.FullName); - } - - if (File.Exists(renderOptions.OutputFile)) - File.Delete(renderOptions.OutputFile); + // Delete the files as it is not guaranteed that the overwrite flag is passed in the FFmpeg args. + outputFs.Close(); + if (outputFileInfo.Exists) + outputFileInfo.Delete(); - if (renderOptions.GenerateMask && File.Exists(renderOptions.MaskFile)) - File.Delete(renderOptions.MaskFile); + maskFs?.Close(); + if (renderOptions.GenerateMask && maskFileInfo!.Exists) + maskFileInfo.Delete(); - FfmpegProcess ffmpegProcess = GetFfmpegProcess(0, false); - FfmpegProcess maskProcess = renderOptions.GenerateMask ? GetFfmpegProcess(0, true) : null; + FfmpegProcess ffmpegProcess = GetFfmpegProcess(outputFileInfo); + FfmpegProcess maskProcess = renderOptions.GenerateMask ? GetFfmpegProcess(maskFileInfo) : null; _progress.SetTemplateStatus(@"Rendering Video {0}% ({1:h\hm\ms\s} Elapsed | {2:h\hm\ms\s} Remaining)", 0, TimeSpan.Zero, TimeSpan.Zero); try @@ -323,22 +338,9 @@ private static void SetFrameMask(SKBitmap frame) } } - private FfmpegProcess GetFfmpegProcess(int partNumber, bool isMask) + private FfmpegProcess GetFfmpegProcess(FileInfo fileInfo) { - string savePath; - if (partNumber == 0) - { - if (isMask) - savePath = renderOptions.MaskFile; - else - savePath = renderOptions.OutputFile; - } - else - { - savePath = Path.Combine(renderOptions.TempFolder, Path.GetRandomFileName() + (isMask ? "_mask" : "") + Path.GetExtension(renderOptions.OutputFile)); - } - - savePath = Path.GetFullPath(savePath); + string savePath = fileInfo.FullName; string inputArgs = new StringBuilder(renderOptions.InputArgs) .Replace("{fps}", renderOptions.Framerate.ToString()) diff --git a/TwitchDownloaderCore/ChatUpdater.cs b/TwitchDownloaderCore/ChatUpdater.cs index 8e99366b..3317b06c 100644 --- a/TwitchDownloaderCore/ChatUpdater.cs +++ b/TwitchDownloaderCore/ChatUpdater.cs @@ -32,6 +32,16 @@ public ChatUpdater(ChatUpdateOptions updateOptions, ITaskProgress progress) public async Task UpdateAsync(CancellationToken cancellationToken) { + var outputFileInfo = TwitchHelper.ClaimFile(_updateOptions.OutputFile, _updateOptions.FileOverwriteCallback, _progress); + if (outputFileInfo is null) + { + _progress.LogWarning("No destination file was provided, aborting."); + return; + } + + // Open the destination file so that it exists in the filesystem. + await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + chatRoot.FileInfo = new() { Version = ChatRootVersion.CurrentVersion, CreatedAt = chatRoot.FileInfo.CreatedAt, UpdatedAt = DateTime.Now }; if (!Path.GetExtension(_updateOptions.InputFile.Replace(".gz", ""))!.Equals(".json", StringComparison.OrdinalIgnoreCase)) { @@ -70,13 +80,13 @@ public async Task UpdateAsync(CancellationToken cancellationToken) switch (_updateOptions.OutputFormat) { case ChatFormat.Json: - await ChatJson.SerializeAsync(_updateOptions.OutputFile, chatRoot, _updateOptions.Compression, cancellationToken); + await ChatJson.SerializeAsync(outputFs, chatRoot, _updateOptions.Compression, cancellationToken); break; case ChatFormat.Html: - await ChatHtml.SerializeAsync(_updateOptions.OutputFile, chatRoot, _progress, chatRoot.embeddedData != null && (chatRoot.embeddedData.firstParty?.Count > 0 || chatRoot.embeddedData.twitchBadges?.Count > 0), cancellationToken); + await ChatHtml.SerializeAsync(outputFs, outputFileInfo.FullName, chatRoot, _progress, chatRoot.embeddedData != null && (chatRoot.embeddedData.firstParty?.Count > 0 || chatRoot.embeddedData.twitchBadges?.Count > 0), cancellationToken); break; // If there is embedded data, it's almost guaranteed to be first party emotes or badges. case ChatFormat.Text: - await ChatText.SerializeAsync(_updateOptions.OutputFile, chatRoot, _updateOptions.TextTimestampFormat); + await ChatText.SerializeAsync(outputFs, chatRoot, _updateOptions.TextTimestampFormat); break; default: throw new NotSupportedException($"{_updateOptions.OutputFormat} is not a supported output format."); diff --git a/TwitchDownloaderCore/ClipDownloader.cs b/TwitchDownloaderCore/ClipDownloader.cs index a6171eda..90db73dd 100644 --- a/TwitchDownloaderCore/ClipDownloader.cs +++ b/TwitchDownloaderCore/ClipDownloader.cs @@ -31,6 +31,17 @@ public ClipDownloader(ClipDownloadOptions clipDownloadOptions, ITaskProgress pro public async Task DownloadAsync(CancellationToken cancellationToken) { + + var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + if (outputFileInfo is null) + { + _progress.LogWarning("No destination file was provided, aborting."); + return; + } + + // Open the destination file so that it exists in the filesystem. + await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + _progress.SetStatus("Fetching Clip Info"); var downloadUrl = await GetDownloadUrl(); @@ -38,23 +49,11 @@ public async Task DownloadAsync(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); - var clipDirectory = Directory.GetParent(Path.GetFullPath(downloadOptions.Filename))!; - if (!clipDirectory.Exists) - { - TwitchHelper.CreateDirectory(clipDirectory.FullName); - } - _progress.SetTemplateStatus("Downloading Clip {0}%", 0); - void DownloadProgressHandler(StreamCopyProgress streamProgress) - { - var percent = (int)(streamProgress.BytesCopied / (double)streamProgress.SourceLength * 100); - _progress.ReportProgress(percent); - } - if (!downloadOptions.EncodeMetadata) { - await DownloadFileTaskAsync(downloadUrl, downloadOptions.Filename, downloadOptions.ThrottleKib, new Progress(DownloadProgressHandler), cancellationToken); + await DownloadFileTaskAsync(downloadUrl, outputFs, downloadOptions.ThrottleKib, new Progress(DownloadProgressHandler), cancellationToken); return; } @@ -66,16 +65,21 @@ void DownloadProgressHandler(StreamCopyProgress streamProgress) var tempFile = Path.Combine(downloadOptions.TempFolder, $"{downloadOptions.Id}_{DateTimeOffset.UtcNow.Ticks}.mp4"); try { - await DownloadFileTaskAsync(downloadUrl, tempFile, downloadOptions.ThrottleKib, new Progress(DownloadProgressHandler), cancellationToken); + await using (var tempFileStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + await DownloadFileTaskAsync(downloadUrl, tempFileStream, downloadOptions.ThrottleKib, new Progress(DownloadProgressHandler), cancellationToken); + } + + outputFs.Close(); _progress.SetTemplateStatus("Encoding Clip Metadata {0}%", 0); var clipChapter = TwitchHelper.GenerateClipChapter(clipInfo.data.clip); - await EncodeClipWithMetadata(tempFile, downloadOptions.Filename, clipInfo.data.clip, clipChapter, cancellationToken); + await EncodeClipWithMetadata(tempFile, outputFileInfo.FullName, clipInfo.data.clip, clipChapter, cancellationToken); - if (!File.Exists(downloadOptions.Filename)) + if (!outputFileInfo.Exists) { - File.Move(tempFile, downloadOptions.Filename); + File.Move(tempFile, outputFileInfo.FullName); _progress.LogError("Unable to serialize metadata. The download has been completed without custom metadata."); } @@ -85,6 +89,12 @@ void DownloadProgressHandler(StreamCopyProgress streamProgress) { File.Delete(tempFile); } + + void DownloadProgressHandler(StreamCopyProgress streamProgress) + { + var percent = (int)(streamProgress.BytesCopied / (double)streamProgress.SourceLength * 100); + _progress.ReportProgress(percent); + } } private async Task GetDownloadUrl() @@ -120,7 +130,7 @@ private async Task GetDownloadUrl() return downloadUrl + "?sig=" + clip.playbackAccessToken.signature + "&token=" + HttpUtility.UrlEncode(clip.playbackAccessToken.value); } - private static async Task DownloadFileTaskAsync(string url, string destinationFile, int throttleKib, IProgress progress, CancellationToken cancellationToken) + private static async Task DownloadFileTaskAsync(string url, FileStream fs, int throttleKib, IProgress progress, CancellationToken cancellationToken) { var request = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); @@ -130,13 +140,11 @@ private static async Task DownloadFileTaskAsync(string url, string destinationFi if (throttleKib == -1) { - await using var fs = new FileStream(destinationFile, FileMode.Create, FileAccess.Write, FileShare.Read); await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); await contentStream.ProgressCopyToAsync(fs, contentLength, progress, cancellationToken).ConfigureAwait(false); } else { - await using var fs = new FileStream(destinationFile, FileMode.Create, FileAccess.Write, FileShare.Read); await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); await using var throttledStream = new ThrottledStream(contentStream, throttleKib); await throttledStream.ProgressCopyToAsync(fs, contentLength, progress, cancellationToken).ConfigureAwait(false); diff --git a/TwitchDownloaderCore/Options/ChatDownloadOptions.cs b/TwitchDownloaderCore/Options/ChatDownloadOptions.cs index bf6ee5ca..3b5118dc 100644 --- a/TwitchDownloaderCore/Options/ChatDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/ChatDownloadOptions.cs @@ -1,4 +1,6 @@ -using TwitchDownloaderCore.Tools; +using System; +using System.IO; +using TwitchDownloaderCore.Tools; namespace TwitchDownloaderCore.Options { @@ -34,5 +36,6 @@ public string FileExtension } } public string TempFolder { get; set; } + public Func FileOverwriteCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/ChatRenderOptions.cs b/TwitchDownloaderCore/Options/ChatRenderOptions.cs index bdfc1883..6a049121 100644 --- a/TwitchDownloaderCore/Options/ChatRenderOptions.cs +++ b/TwitchDownloaderCore/Options/ChatRenderOptions.cs @@ -92,5 +92,6 @@ public string MaskFile public EmojiVendor EmojiVendor { get; set; } = EmojiVendor.GoogleNotoColor; public int[] TimestampWidths { get; set; } public bool AdjustUsernameVisibility { get; set; } + public Func FileOverwriteCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/ChatUpdateOptions.cs b/TwitchDownloaderCore/Options/ChatUpdateOptions.cs index 13333990..9e9b3332 100644 --- a/TwitchDownloaderCore/Options/ChatUpdateOptions.cs +++ b/TwitchDownloaderCore/Options/ChatUpdateOptions.cs @@ -1,4 +1,6 @@ -using TwitchDownloaderCore.Tools; +using System; +using System.IO; +using TwitchDownloaderCore.Tools; namespace TwitchDownloaderCore.Options { @@ -33,5 +35,6 @@ public string FileExtension } } public string TempFolder { get; set; } + public Func FileOverwriteCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/ClipDownloadOptions.cs b/TwitchDownloaderCore/Options/ClipDownloadOptions.cs index 9f8c6ce9..3a0a1620 100644 --- a/TwitchDownloaderCore/Options/ClipDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/ClipDownloadOptions.cs @@ -1,4 +1,7 @@ -namespace TwitchDownloaderCore.Options +using System; +using System.IO; + +namespace TwitchDownloaderCore.Options { public class ClipDownloadOptions { @@ -9,5 +12,6 @@ public class ClipDownloadOptions public string TempFolder { get; set; } public bool EncodeMetadata { get; set; } public string FfmpegPath { get; set; } + public Func FileOverwriteCallback { get; set; } = info => info; } } \ No newline at end of file diff --git a/TwitchDownloaderCore/Options/TsMergeOptions.cs b/TwitchDownloaderCore/Options/TsMergeOptions.cs index 8dd41e29..e44da205 100644 --- a/TwitchDownloaderCore/Options/TsMergeOptions.cs +++ b/TwitchDownloaderCore/Options/TsMergeOptions.cs @@ -1,8 +1,12 @@ -namespace TwitchDownloaderCore.Options +using System; +using System.IO; + +namespace TwitchDownloaderCore.Options { public class TsMergeOptions { public string OutputFile { get; set; } public string InputFile { get; set; } + public Func FileOverwriteCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/VideoDownloadOptions.cs b/TwitchDownloaderCore/Options/VideoDownloadOptions.cs index 19151a58..70df7f0b 100644 --- a/TwitchDownloaderCore/Options/VideoDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/VideoDownloadOptions.cs @@ -18,5 +18,6 @@ public class VideoDownloadOptions public string FfmpegPath { get; set; } public string TempFolder { get; set; } public Func CacheCleanerCallback { get; set; } + public Func FileOverwriteCallback { get; set; } = info => info; } -} +} \ No newline at end of file diff --git a/TwitchDownloaderCore/TsMerger.cs b/TwitchDownloaderCore/TsMerger.cs index 16d01f6d..eda1693a 100644 --- a/TwitchDownloaderCore/TsMerger.cs +++ b/TwitchDownloaderCore/TsMerger.cs @@ -26,6 +26,16 @@ public async Task MergeAsync(CancellationToken cancellationToken) throw new FileNotFoundException("Input file does not exist"); } + var outputFileInfo = TwitchHelper.ClaimFile(mergeOptions.OutputFile, mergeOptions.FileOverwriteCallback, _progress); + if (outputFileInfo is null) + { + _progress.LogWarning("No destination file was provided, aborting."); + return; + } + + // Open the destination file so that it exists in the filesystem. + await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + var isM3U8 = false; var fileList = new List(); await using (var fs = File.Open(mergeOptions.InputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) @@ -54,7 +64,7 @@ public async Task MergeAsync(CancellationToken cancellationToken) _progress.SetTemplateStatus("Combining Parts {0}% [2/2]", 0); - await CombineVideoParts(fileList, cancellationToken); + await CombineVideoParts(fileList, outputFs, cancellationToken); _progress.ReportProgress(100); } @@ -111,7 +121,7 @@ private static async Task VerifyVideoPart(string filePath) return true; } - private async Task CombineVideoParts(IReadOnlyCollection fileList, CancellationToken cancellationToken) + private async Task CombineVideoParts(IReadOnlyCollection fileList, FileStream outputStream, CancellationToken cancellationToken) { DriveInfo outputDrive = DriveHelper.GetOutputDrive(mergeOptions.OutputFile); string outputFile = mergeOptions.OutputFile; @@ -119,7 +129,6 @@ private async Task CombineVideoParts(IReadOnlyCollection fileList, Cance int partCount = fileList.Count; int doneCount = 0; - await using var outputStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.Read); foreach (var partFile in fileList) { await DriveHelper.WaitForDrive(outputDrive, _progress, cancellationToken); diff --git a/TwitchDownloaderCore/TwitchHelper.cs b/TwitchDownloaderCore/TwitchHelper.cs index 03dbbf81..303554d4 100644 --- a/TwitchDownloaderCore/TwitchHelper.cs +++ b/TwitchDownloaderCore/TwitchHelper.cs @@ -1,7 +1,7 @@ using SkiaSharp; using System; -using System.Buffers; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; @@ -854,6 +854,31 @@ where comments return returnList; } + [return: MaybeNull] + public static FileInfo ClaimFile(string path, Func fileAlreadyExistsCallback, ITaskLogger logger) + { + var fileInfo = new FileInfo(path); + if (fileInfo.Exists) + { + if (fileAlreadyExistsCallback is null) + { + logger.LogWarning($"{nameof(fileAlreadyExistsCallback)} was null."); + } + else + { + fileInfo = fileAlreadyExistsCallback(fileInfo); + } + } + + var directory = fileInfo.Directory; + if (directory is not null && !directory.Exists) + { + CreateDirectory(directory.FullName); + } + + return fileInfo; + } + public static DirectoryInfo CreateDirectory(string path) { DirectoryInfo directoryInfo = Directory.CreateDirectory(path); diff --git a/TwitchDownloaderCore/VideoDownloader.cs b/TwitchDownloaderCore/VideoDownloader.cs index 0262f5dc..0a8439b7 100644 --- a/TwitchDownloaderCore/VideoDownloader.cs +++ b/TwitchDownloaderCore/VideoDownloader.cs @@ -39,6 +39,16 @@ public VideoDownloader(VideoDownloadOptions videoDownloadOptions, ITaskProgress public async Task DownloadAsync(CancellationToken cancellationToken) { + var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + if (outputFileInfo is null) + { + _progress.LogWarning("No destination file was provided, aborting."); + return; + } + + // Open the destination file so that it exists in the filesystem. + await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); + await TwitchHelper.CleanupAbandonedVideoCaches(downloadOptions.TempFolder, downloadOptions.CacheCleanerCallback, _progress); string downloadFolder = Path.Combine( @@ -49,8 +59,6 @@ public async Task DownloadAsync(CancellationToken cancellationToken) try { - ServicePointManager.DefaultConnectionLimit = downloadOptions.DownloadThreads; - GqlVideoResponse videoInfoResponse = await TwitchHelper.GetVideoInfo(downloadOptions.Id); if (videoInfoResponse.data.video == null) { @@ -100,17 +108,13 @@ await FfmpegMetadata.SerializeAsync(metadataPath, videoInfo.owner.displayName, d videoInfo.description?.Replace(" \n", "\n").Replace("\n\n", "\n").TrimEnd(), downloadOptions.TrimBeginning ? downloadOptions.TrimBeginningTime : TimeSpan.Zero, videoChapterResponse.data.video.moments.edges, cancellationToken); - var finalizedFileDirectory = Directory.GetParent(Path.GetFullPath(downloadOptions.Filename))!; - if (!finalizedFileDirectory.Exists) - { - TwitchHelper.CreateDirectory(finalizedFileDirectory.FullName); - } + outputFs.Close(); int ffmpegExitCode; var ffmpegRetries = 0; do { - ffmpegExitCode = await Task.Run(() => RunFfmpegVideoCopy(downloadFolder, metadataPath, startOffset, seekDuration > TimeSpan.Zero ? seekDuration : videoLength), cancellationToken); + ffmpegExitCode = await Task.Run(() => RunFfmpegVideoCopy(downloadFolder, outputFileInfo, metadataPath, startOffset, seekDuration > TimeSpan.Zero ? seekDuration : videoLength), cancellationToken); if (ffmpegExitCode != 0) { _progress.LogError($"Failed to finalize video (code {ffmpegExitCode}), retrying in 10 seconds..."); @@ -118,7 +122,7 @@ await FfmpegMetadata.SerializeAsync(metadataPath, videoInfo.owner.displayName, d } } while (ffmpegExitCode != 0 && ffmpegRetries++ < 1); - if (ffmpegExitCode != 0 || !File.Exists(downloadOptions.Filename)) + if (ffmpegExitCode != 0 || !outputFileInfo.Exists) { _shouldClearCache = false; throw new Exception($"Failed to finalize video. The download cache has not been cleared and can be found at {downloadFolder} along with a log file."); @@ -329,7 +333,7 @@ private static bool VerifyVideoPart(string filePath) return true; } - private int RunFfmpegVideoCopy(string downloadFolder, string metadataPath, TimeSpan startOffset, TimeSpan seekDuration) + private int RunFfmpegVideoCopy(string tempFolder, FileInfo outputFile, string metadataPath, TimeSpan startOffset, TimeSpan seekDuration) { var process = new Process { @@ -338,7 +342,7 @@ private int RunFfmpegVideoCopy(string downloadFolder, string metadataPath, TimeS FileName = downloadOptions.FfmpegPath, Arguments = string.Format( "-hide_banner -stats -y -avoid_negative_ts make_zero " + (downloadOptions.TrimBeginning ? "-ss {2} " : "") + "-i \"{0}\" -i \"{1}\" -map_metadata 1 -analyzeduration {3} -probesize {3} " + (downloadOptions.TrimEnding ? "-t {4} " : "") + "-c:v copy \"{5}\"", - Path.Combine(downloadFolder, "output.ts"), metadataPath, startOffset.TotalSeconds.ToString(CultureInfo.InvariantCulture), int.MaxValue, seekDuration.TotalSeconds.ToString(CultureInfo.InvariantCulture), Path.GetFullPath(downloadOptions.Filename)), + Path.Combine(tempFolder, "output.ts"), metadataPath, startOffset.TotalSeconds.ToString(CultureInfo.InvariantCulture), int.MaxValue, seekDuration.TotalSeconds.ToString(CultureInfo.InvariantCulture), outputFile.FullName), UseShellExecute = false, CreateNoWindow = true, RedirectStandardInput = false, @@ -363,7 +367,7 @@ private int RunFfmpegVideoCopy(string downloadFolder, string metadataPath, TimeS process.Start(); process.BeginErrorReadLine(); - using var logWriter = File.AppendText(Path.Combine(downloadFolder, "ffmpegLog.txt")); + using var logWriter = File.AppendText(Path.Combine(tempFolder, "ffmpegLog.txt")); do // We cannot handle logging inside the ErrorDataReceived lambda because more than 1 can come in at once and cause a race condition. lay295#598 { Thread.Sleep(100); From c89531d4d2633119724daaef23b1e72544505fd0 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:47:47 -0400 Subject: [PATCH 02/21] Fix potential NRE --- TwitchDownloaderCore/TwitchHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchDownloaderCore/TwitchHelper.cs b/TwitchDownloaderCore/TwitchHelper.cs index 303554d4..5ee7ddec 100644 --- a/TwitchDownloaderCore/TwitchHelper.cs +++ b/TwitchDownloaderCore/TwitchHelper.cs @@ -867,10 +867,11 @@ public static FileInfo ClaimFile(string path, Func fileAlrea else { fileInfo = fileAlreadyExistsCallback(fileInfo); + logger.LogVerbose($"{path} will be renamed to {fileInfo?.FullName}."); } } - var directory = fileInfo.Directory; + var directory = fileInfo?.Directory; if (directory is not null && !directory.Exists) { CreateDirectory(directory.FullName); From fc6b4aab4d865b551e711f5bb592e7259f668a7b Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:51:12 -0400 Subject: [PATCH 03/21] Implement FileOverwriteHandler for CLI --- .../Models/{LogLevel.cs => Enums.cs} | 8 +++ .../Models/IFileOverwriteArgs.cs | 10 ++++ .../Modes/Arguments/ChatDownloadArgs.cs | 4 +- .../Modes/Arguments/ChatRenderArgs.cs | 4 +- .../Modes/Arguments/ChatUpdateArgs.cs | 4 +- .../Modes/Arguments/ClipDownloadArgs.cs | 5 +- .../Modes/Arguments/TsMergeArgs.cs | 5 +- .../Modes/Arguments/VideoDownloadArgs.cs | 4 +- TwitchDownloaderCLI/Modes/DownloadClip.cs | 8 +-- TwitchDownloaderCLI/Modes/DownloadVideo.cs | 8 +-- .../Tools/FileOverwriteHandler.cs | 52 +++++++++++++++++++ TwitchDownloaderCore/Tools/FilenameService.cs | 19 +++++++ 12 files changed, 119 insertions(+), 12 deletions(-) rename TwitchDownloaderCLI/Models/{LogLevel.cs => Enums.cs} (71%) create mode 100644 TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs create mode 100644 TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs diff --git a/TwitchDownloaderCLI/Models/LogLevel.cs b/TwitchDownloaderCLI/Models/Enums.cs similarity index 71% rename from TwitchDownloaderCLI/Models/LogLevel.cs rename to TwitchDownloaderCLI/Models/Enums.cs index e472e17a..6b6cd172 100644 --- a/TwitchDownloaderCLI/Models/LogLevel.cs +++ b/TwitchDownloaderCLI/Models/Enums.cs @@ -13,4 +13,12 @@ internal enum LogLevel Error = 1 << 5, Ffmpeg = 1 << 6, } + + public enum OverwriteBehavior + { + Overwrite, + Exit, + Rename, + Prompt, + } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs b/TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs new file mode 100644 index 00000000..d31f2d7f --- /dev/null +++ b/TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace TwitchDownloaderCLI.Models +{ + public interface IFileOverwriteArgs + { + [Option("overwrite", Default = OverwriteBehavior.Prompt, HelpText = ". Valid values are: Overwrite, Exit, Rename, Prompt.")] + public OverwriteBehavior OverwriteBehavior { get; set; } + } +} \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs index 2d4eaadb..5512a965 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs @@ -5,7 +5,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatdownload", HelpText = "Downloads the chat from a VOD or clip")] - internal sealed class ChatDownloadArgs : TwitchDownloaderArgs + internal sealed class ChatDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD or clip to download that chat of.")] public string Id { get; set; } @@ -45,5 +45,7 @@ internal sealed class ChatDownloadArgs : TwitchDownloaderArgs [Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")] public string TempFolder { get; set; } + + public OverwriteBehavior OverwriteBehavior { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs index f8afe990..c34317dd 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatrender", HelpText = "Renders a chat JSON as a video")] - internal sealed class ChatRenderArgs : TwitchDownloaderArgs + internal sealed class ChatRenderArgs : TwitchDownloaderArgs, IFileOverwriteArgs { [Option('i', "input", Required = true, HelpText = "Path to JSON chat file input.")] public string InputFile { get; set; } @@ -152,5 +152,7 @@ internal sealed class ChatRenderArgs : TwitchDownloaderArgs [Option("scale-highlight-indent", Default = 1.0, HelpText = "Number to scale highlight indent size (sub messages).")] public double ScaleAccentIndent { get; set; } + + public OverwriteBehavior OverwriteBehavior { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs index 7818dc6d..8aa8405f 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs @@ -5,7 +5,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatupdate", HelpText = "Updates the embedded emotes, badges, bits, and trims a chat JSON and/or converts a JSON chat to another format.")] - internal sealed class ChatUpdateArgs : TwitchDownloaderArgs + internal sealed class ChatUpdateArgs : TwitchDownloaderArgs, IFileOverwriteArgs { [Option('i', "input", Required = true, HelpText = "Path to input file. Valid extensions are: .json, .json.gz.")] public string InputFile { get; set; } @@ -42,5 +42,7 @@ internal sealed class ChatUpdateArgs : TwitchDownloaderArgs [Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")] public string TempFolder { get; set; } + + public OverwriteBehavior OverwriteBehavior { get; set; } } } diff --git a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs index ebbdf978..4502fd1b 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs @@ -1,9 +1,10 @@ using CommandLine; +using TwitchDownloaderCLI.Models; namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("clipdownload", HelpText = "Downloads a clip from Twitch")] - internal sealed class ClipDownloadArgs : TwitchDownloaderArgs + internal sealed class ClipDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the clip to download.")] public string Id { get; set; } @@ -25,5 +26,7 @@ internal sealed class ClipDownloadArgs : TwitchDownloaderArgs [Option("temp-path", Default = "", HelpText = "Path to temporary caching folder.")] public string TempFolder { get; set; } + + public OverwriteBehavior OverwriteBehavior { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs index a19bdf0b..24ad1b09 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs @@ -1,14 +1,17 @@ using CommandLine; +using TwitchDownloaderCLI.Models; namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("tsmerge", HelpText = "Concatenates multiple .ts/.tsv/.tsa/.m2t/.m2ts (MPEG Transport Stream) files into a single file")] - internal sealed class TsMergeArgs : TwitchDownloaderArgs + internal sealed class TsMergeArgs : TwitchDownloaderArgs, IFileOverwriteArgs { [Option('i', "input", Required = true, HelpText = "Path a text file containing the absolute paths of the files to concatenate, separated by newlines. M3U/M3U8 is also supported.")] public string InputList { get; set; } [Option('o', "output", Required = true, HelpText = "Path to output file.")] public string OutputFile { get; set; } + + public OverwriteBehavior OverwriteBehavior { get; set; } } } diff --git a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs index d0b93b6c..d150cc92 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("videodownload", HelpText = "Downloads a stream VOD from Twitch")] - internal sealed class VideoDownloadArgs : TwitchDownloaderArgs + internal sealed class VideoDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD to download.")] public string Id { get; set; } @@ -35,5 +35,7 @@ internal sealed class VideoDownloadArgs : TwitchDownloaderArgs [Option("temp-path", Default = "", HelpText = "Path to temporary caching folder.")] public string TempFolder { get; set; } + + public OverwriteBehavior OverwriteBehavior { get; set; } } } diff --git a/TwitchDownloaderCLI/Modes/DownloadClip.cs b/TwitchDownloaderCLI/Modes/DownloadClip.cs index 8f2f3a28..a1cc97bf 100644 --- a/TwitchDownloaderCLI/Modes/DownloadClip.cs +++ b/TwitchDownloaderCLI/Modes/DownloadClip.cs @@ -21,13 +21,14 @@ internal static void Download(ClipDownloadArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); } - var downloadOptions = GetDownloadOptions(inputOptions, progress); + var overwriteHandler = new FileOverwriteHandler(inputOptions); + var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); var clipDownloader = new ClipDownloader(downloadOptions, progress); clipDownloader.DownloadAsync(new CancellationToken()).Wait(); } - private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOptions, ITaskLogger logger) + private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) { if (inputOptions.Id is null) { @@ -50,7 +51,8 @@ private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOpti ThrottleKib = inputOptions.ThrottleKib, FfmpegPath = string.IsNullOrWhiteSpace(inputOptions.FfmpegPath) ? FfmpegHandler.FfmpegExecutableName : Path.GetFullPath(inputOptions.FfmpegPath), EncodeMetadata = inputOptions.EncodeMetadata!.Value, - TempFolder = inputOptions.TempFolder + TempFolder = inputOptions.TempFolder, + FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, }; return downloadOptions; diff --git a/TwitchDownloaderCLI/Modes/DownloadVideo.cs b/TwitchDownloaderCLI/Modes/DownloadVideo.cs index 9a9bc596..ef907f78 100644 --- a/TwitchDownloaderCLI/Modes/DownloadVideo.cs +++ b/TwitchDownloaderCLI/Modes/DownloadVideo.cs @@ -19,13 +19,14 @@ internal static void Download(VideoDownloadArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); - var downloadOptions = GetDownloadOptions(inputOptions, progress); + var overwriteHandler = new FileOverwriteHandler(inputOptions); + var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); var videoDownloader = new VideoDownloader(downloadOptions, progress); videoDownloader.DownloadAsync(new CancellationToken()).Wait(); } - private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOptions, ITaskLogger logger) + private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) { if (inputOptions.Id is null) { @@ -76,7 +77,8 @@ private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOp "Run 'TwitchDownloaderCLI cache help' for more information."); return Array.Empty(); - } + }, + FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, }; return downloadOptions; diff --git a/TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs b/TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs new file mode 100644 index 00000000..e3457430 --- /dev/null +++ b/TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using TwitchDownloaderCLI.Models; +using TwitchDownloaderCore.Tools; + +namespace TwitchDownloaderCLI.Tools +{ + internal class FileOverwriteHandler + { + private readonly IFileOverwriteArgs _overwriteArgs; + + public FileOverwriteHandler(IFileOverwriteArgs overwriteArgs) + { + _overwriteArgs = overwriteArgs; + } + + [return: MaybeNull] + public FileInfo HandleOverwriteCallback(FileInfo fileInfo) + { + return _overwriteArgs.OverwriteBehavior switch + { + OverwriteBehavior.Overwrite => fileInfo, + OverwriteBehavior.Exit => null, + OverwriteBehavior.Rename => FilenameService.GetNonCollidingName(fileInfo), + OverwriteBehavior.Prompt => PromptUser(fileInfo), + _ => throw new ArgumentOutOfRangeException(nameof(_overwriteArgs.OverwriteBehavior), _overwriteArgs.OverwriteBehavior, null) + }; + } + + [return: MaybeNull] + private static FileInfo PromptUser(FileInfo fileInfo) + { + Console.WriteLine($"{fileInfo.FullName} already exists."); + + while (true) + { + Console.Write("[O] Overwrite / [R] Rename / [E] Exit: "); + var userInput = Console.ReadLine()!.Trim().ToLower(); + switch (userInput) + { + case "o" or "overwrite": + return fileInfo; + case "e" or "exit": + return null; + case "r" or "rename": + return FilenameService.GetNonCollidingName(fileInfo); + } + } + } + } +} \ No newline at end of file diff --git a/TwitchDownloaderCore/Tools/FilenameService.cs b/TwitchDownloaderCore/Tools/FilenameService.cs index 46dd0245..7ce61e45 100644 --- a/TwitchDownloaderCore/Tools/FilenameService.cs +++ b/TwitchDownloaderCore/Tools/FilenameService.cs @@ -86,5 +86,24 @@ private static string[] GetTemplateSubfolders(ref string fullPath) private static readonly char[] FilenameInvalidChars = Path.GetInvalidFileNameChars(); private static string RemoveInvalidFilenameChars(string filename) => filename.ReplaceAny(FilenameInvalidChars, '_'); + + public static FileInfo GetNonCollidingName(FileInfo fileInfo) + { + var fi = fileInfo; + + var parentDir = Path.GetDirectoryName(fi.FullName)!; + var oldName = Path.GetFileNameWithoutExtension(fi.Name.AsSpan()); + var extension = Path.GetExtension(fi.Name.AsSpan()); + + var i = 1; + while (fi.Exists) + { + var newName = Path.Combine(parentDir, $"{oldName} ({i}){extension}"); + fi = new FileInfo(newName); + i++; + } + + return fi; + } } } \ No newline at end of file From e84de6d425e1002ca76616b697278255640aaf0d Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:51:43 -0400 Subject: [PATCH 04/21] Add GetNonCollidingName tests --- .../ToolTests/FilenameServiceTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/TwitchDownloaderCore.Tests/ToolTests/FilenameServiceTests.cs b/TwitchDownloaderCore.Tests/ToolTests/FilenameServiceTests.cs index 18084153..94626cfe 100644 --- a/TwitchDownloaderCore.Tests/ToolTests/FilenameServiceTests.cs +++ b/TwitchDownloaderCore.Tests/ToolTests/FilenameServiceTests.cs @@ -151,5 +151,45 @@ public void DoesNotInterpretBogusTemplateParameter() Assert.Equal(EXPECTED, result); } + + [Fact] + public void GetNonCollidingNameWorks_WhenNoCollisionExists() + { + var expected = Path.Combine(Path.GetTempPath(), "foo.txt"); + var path = Path.Combine(Path.GetTempPath(), "foo.txt"); + var fileInfo = new FileInfo(path); + + try + { + var actual = FilenameService.GetNonCollidingName(fileInfo); + + Assert.Equal(expected, actual.FullName); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void GetNonCollidingNameWorks_WhenCollisionExists() + { + var expected = Path.Combine(Path.GetTempPath(), "foo (1).txt"); + var path = Path.Combine(Path.GetTempPath(), "foo.txt"); + var fileInfo = new FileInfo(path); + + try + { + fileInfo.Create().Close(); + + var actual = FilenameService.GetNonCollidingName(fileInfo); + + Assert.Equal(expected, actual.FullName); + } + finally + { + File.Delete(path); + } + } } } \ No newline at end of file From 8432bdbddc952db4d457fa9f26ac614eb225d730 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 01:35:31 -0400 Subject: [PATCH 05/21] TwitchDownloaderArgs -> ITwitchDownloaderArgs & rearrange arg interfaces due to help text ordering --- TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs | 7 ++++++- TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs | 5 ++++- TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs | 5 ++++- TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs | 5 ++++- TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs | 5 ++++- TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs | 7 ++++++- .../{Models => Modes/Arguments}/IFileOverwriteArgs.cs | 5 +++-- .../{TwitchDownloaderArgs.cs => ITwitchDownloaderArgs.cs} | 2 +- TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs | 5 ++++- TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs | 5 ++++- TwitchDownloaderCLI/Program.cs | 4 ++-- TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs | 1 + 12 files changed, 43 insertions(+), 13 deletions(-) rename TwitchDownloaderCLI/{Models => Modes/Arguments}/IFileOverwriteArgs.cs (65%) rename TwitchDownloaderCLI/Modes/Arguments/{TwitchDownloaderArgs.cs => ITwitchDownloaderArgs.cs} (91%) diff --git a/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs index 1f9c89bc..92c66843 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/CacheArgs.cs @@ -1,14 +1,19 @@ using CommandLine; +using TwitchDownloaderCLI.Models; namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("cache", HelpText = "Manage the working cache")] - internal sealed class CacheArgs : TwitchDownloaderArgs + internal sealed class CacheArgs : ITwitchDownloaderArgs { [Option('c', "clear", Default = false, Required = false, HelpText = "Clears the default cache folder.")] public bool ClearCache { get; set; } [Option("force-clear", Default = false, Required = false, HelpText = "Clears the default cache folder, bypassing the confirmation prompt")] public bool ForceClearCache { get; set; } + + // Interface args + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs index 5512a965..1609c97b 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs @@ -5,7 +5,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatdownload", HelpText = "Downloads the chat from a VOD or clip")] - internal sealed class ChatDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArgs + internal sealed class ChatDownloadArgs : IFileOverwriteArgs, ITwitchDownloaderArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD or clip to download that chat of.")] public string Id { get; set; } @@ -46,6 +46,9 @@ internal sealed class ChatDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArg [Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")] public string TempFolder { get; set; } + // Interface args public OverwriteBehavior OverwriteBehavior { get; set; } + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs index c34317dd..48fbf0c2 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatrender", HelpText = "Renders a chat JSON as a video")] - internal sealed class ChatRenderArgs : TwitchDownloaderArgs, IFileOverwriteArgs + internal sealed class ChatRenderArgs : IFileOverwriteArgs, ITwitchDownloaderArgs { [Option('i', "input", Required = true, HelpText = "Path to JSON chat file input.")] public string InputFile { get; set; } @@ -153,6 +153,9 @@ internal sealed class ChatRenderArgs : TwitchDownloaderArgs, IFileOverwriteArgs [Option("scale-highlight-indent", Default = 1.0, HelpText = "Number to scale highlight indent size (sub messages).")] public double ScaleAccentIndent { get; set; } + // Interface args public OverwriteBehavior OverwriteBehavior { get; set; } + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs index 8aa8405f..3d39d9bc 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs @@ -5,7 +5,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatupdate", HelpText = "Updates the embedded emotes, badges, bits, and trims a chat JSON and/or converts a JSON chat to another format.")] - internal sealed class ChatUpdateArgs : TwitchDownloaderArgs, IFileOverwriteArgs + internal sealed class ChatUpdateArgs : IFileOverwriteArgs, ITwitchDownloaderArgs { [Option('i', "input", Required = true, HelpText = "Path to input file. Valid extensions are: .json, .json.gz.")] public string InputFile { get; set; } @@ -43,6 +43,9 @@ internal sealed class ChatUpdateArgs : TwitchDownloaderArgs, IFileOverwriteArgs [Option("temp-path", Default = "", HelpText = "Path to temporary folder to use for cache.")] public string TempFolder { get; set; } + // Interface args public OverwriteBehavior OverwriteBehavior { get; set; } + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } diff --git a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs index 4502fd1b..c1cc51fa 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("clipdownload", HelpText = "Downloads a clip from Twitch")] - internal sealed class ClipDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArgs + internal sealed class ClipDownloadArgs : IFileOverwriteArgs, ITwitchDownloaderArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the clip to download.")] public string Id { get; set; } @@ -27,6 +27,9 @@ internal sealed class ClipDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArg [Option("temp-path", Default = "", HelpText = "Path to temporary caching folder.")] public string TempFolder { get; set; } + // Interface args public OverwriteBehavior OverwriteBehavior { get; set; } + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs index c3988428..2d2ff505 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/FfmpegArgs.cs @@ -1,11 +1,16 @@ using CommandLine; +using TwitchDownloaderCLI.Models; namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("ffmpeg", HelpText = "Manage standalone ffmpeg")] - internal sealed class FfmpegArgs : TwitchDownloaderArgs + internal sealed class FfmpegArgs : ITwitchDownloaderArgs { [Option('d', "download", Default = false, Required = false, HelpText = "Downloads FFmpeg as a standalone file.")] public bool DownloadFfmpeg { get; set; } + + // Interface args + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } diff --git a/TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs similarity index 65% rename from TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs rename to TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs index d31f2d7f..68a8bed6 100644 --- a/TwitchDownloaderCLI/Models/IFileOverwriteArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs @@ -1,8 +1,9 @@ using CommandLine; +using TwitchDownloaderCLI.Models; -namespace TwitchDownloaderCLI.Models +namespace TwitchDownloaderCLI.Modes.Arguments { - public interface IFileOverwriteArgs + internal interface IFileOverwriteArgs { [Option("overwrite", Default = OverwriteBehavior.Prompt, HelpText = ". Valid values are: Overwrite, Exit, Rename, Prompt.")] public OverwriteBehavior OverwriteBehavior { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/TwitchDownloaderArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ITwitchDownloaderArgs.cs similarity index 91% rename from TwitchDownloaderCLI/Modes/Arguments/TwitchDownloaderArgs.cs rename to TwitchDownloaderCLI/Modes/Arguments/ITwitchDownloaderArgs.cs index 07945f8e..21b09a3d 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/TwitchDownloaderArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ITwitchDownloaderArgs.cs @@ -3,7 +3,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { - internal abstract class TwitchDownloaderArgs + internal interface ITwitchDownloaderArgs { [Option("banner", Default = true, HelpText = "Displays a banner containing version and copyright information.")] public bool? ShowBanner { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs index 24ad1b09..51c74496 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("tsmerge", HelpText = "Concatenates multiple .ts/.tsv/.tsa/.m2t/.m2ts (MPEG Transport Stream) files into a single file")] - internal sealed class TsMergeArgs : TwitchDownloaderArgs, IFileOverwriteArgs + internal sealed class TsMergeArgs : IFileOverwriteArgs, ITwitchDownloaderArgs { [Option('i', "input", Required = true, HelpText = "Path a text file containing the absolute paths of the files to concatenate, separated by newlines. M3U/M3U8 is also supported.")] public string InputList { get; set; } @@ -12,6 +12,9 @@ internal sealed class TsMergeArgs : TwitchDownloaderArgs, IFileOverwriteArgs [Option('o', "output", Required = true, HelpText = "Path to output file.")] public string OutputFile { get; set; } + // Interface args public OverwriteBehavior OverwriteBehavior { get; set; } + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } diff --git a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs index d150cc92..00a473b7 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("videodownload", HelpText = "Downloads a stream VOD from Twitch")] - internal sealed class VideoDownloadArgs : TwitchDownloaderArgs, IFileOverwriteArgs + internal sealed class VideoDownloadArgs : IFileOverwriteArgs, ITwitchDownloaderArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD to download.")] public string Id { get; set; } @@ -36,6 +36,9 @@ internal sealed class VideoDownloadArgs : TwitchDownloaderArgs, IFileOverwriteAr [Option("temp-path", Default = "", HelpText = "Path to temporary caching folder.")] public string TempFolder { get; set; } + // Interface args public OverwriteBehavior OverwriteBehavior { get; set; } + public bool? ShowBanner { get; set; } + public LogLevel LogLevel { get; set; } } } diff --git a/TwitchDownloaderCLI/Program.cs b/TwitchDownloaderCLI/Program.cs index 3149b62d..070da004 100644 --- a/TwitchDownloaderCLI/Program.cs +++ b/TwitchDownloaderCLI/Program.cs @@ -29,7 +29,7 @@ private static void Main(string[] args) parserResult.WithNotParsed(errors => WriteHelpText(errors, parserResult, parser.Settings)); CoreLicensor.EnsureFilesExist(AppContext.BaseDirectory); - WriteApplicationBanner((TwitchDownloaderArgs)parserResult.Value); + WriteApplicationBanner((ITwitchDownloaderArgs)parserResult.Value); parserResult .WithParsed(DownloadVideo.Download) @@ -74,7 +74,7 @@ private static void WriteHelpText(IEnumerable errors, ParserResult Date: Wed, 5 Jun 2024 01:37:53 -0400 Subject: [PATCH 06/21] Update help text --- TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs index 68a8bed6..67dda8dc 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs @@ -5,7 +5,8 @@ namespace TwitchDownloaderCLI.Modes.Arguments { internal interface IFileOverwriteArgs { - [Option("overwrite", Default = OverwriteBehavior.Prompt, HelpText = ". Valid values are: Overwrite, Exit, Rename, Prompt.")] + // TODO: This is probably a bad arg name + [Option("overwrite", Default = OverwriteBehavior.Prompt, HelpText = "Sets the handling of output file name collisions. Valid values are: Overwrite, Exit, Rename, Prompt.")] public OverwriteBehavior OverwriteBehavior { get; set; } } } \ No newline at end of file From 9e72248c4884a65e9b41385b7ee01e7648949133 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:30:48 -0400 Subject: [PATCH 07/21] Create WPF task file overwrite handler --- .../Services/FileOverwriteService.cs | 99 +++++++++++++++++++ .../WindowQueueOptions.xaml.cs | 29 +++++- 2 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 TwitchDownloaderWPF/Services/FileOverwriteService.cs diff --git a/TwitchDownloaderWPF/Services/FileOverwriteService.cs b/TwitchDownloaderWPF/Services/FileOverwriteService.cs new file mode 100644 index 00000000..169ed72f --- /dev/null +++ b/TwitchDownloaderWPF/Services/FileOverwriteService.cs @@ -0,0 +1,99 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Windows; +using Ookii.Dialogs.Wpf; +using TwitchDownloaderCore.Tools; + +namespace TwitchDownloaderWPF.Services +{ + public static class FileOverwriteService + { + private enum OverwriteCommand + { + Prompt, + Overwrite, + Rename, + Cancel + } + + private static OverwriteCommand _overwriteCommand = OverwriteCommand.Prompt; + + public static FileInfo HandleOverwriteCallback(FileInfo fileInfo, Window owner) + { + if (_overwriteCommand is not OverwriteCommand.Prompt) + { + return GetResult(fileInfo, _overwriteCommand); + } + + var result = ShowDialog(fileInfo, owner, out var rememberChoice); + + if (rememberChoice) + { + _overwriteCommand = result; + } + + return GetResult(fileInfo, result); + } + + private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out bool rememberChoice) + { + using var dialog = new TaskDialog(); + dialog.WindowTitle = "File already exists"; + dialog.MainInstruction = $"{fileInfo.Name} already exists."; + dialog.Content = $"The file {fileInfo.FullName} already exists. What would you like to do?"; + + dialog.EnableHyperlinks = true; + dialog.HyperlinkClicked += Hyperlink_OnClicked; + + dialog.ButtonStyle = TaskDialogButtonStyle.CommandLinks; + + var overwriteButton = new TaskDialogButton("Overwrite"); + overwriteButton.CommandLinkNote = "The existing file will be overwritten."; + dialog.Buttons.Add(overwriteButton); + + var renameButton = new TaskDialogButton("Rename"); + renameButton.CommandLinkNote = "The new file will be renamed."; + dialog.Buttons.Add(renameButton); + + var cancelButton = new TaskDialogButton("Cancel"); + cancelButton.CommandLinkNote = "The task will be canceled."; + dialog.Buttons.Add(cancelButton); + + dialog.VerificationText = "Remember my choice"; + dialog.IsVerificationChecked = false; + + var buttonResult = dialog.ShowDialog(owner); + + rememberChoice = dialog.IsVerificationChecked; + + if (buttonResult == overwriteButton) + return OverwriteCommand.Overwrite; + + if (buttonResult == renameButton) + return OverwriteCommand.Rename; + + if (buttonResult == cancelButton) + return OverwriteCommand.Cancel; + + // This should never happen + throw new ArgumentOutOfRangeException(); + } + + private static FileInfo GetResult(FileInfo fileInfo, OverwriteCommand command) + { + return command switch + { + OverwriteCommand.Overwrite => fileInfo, + OverwriteCommand.Rename => FilenameService.GetNonCollidingName(fileInfo), + OverwriteCommand.Cancel => null, + _ => throw new ArgumentOutOfRangeException(nameof(command), command, null) + }; + } + + private static void Hyperlink_OnClicked(object sender, HyperlinkClickedEventArgs e) + { + Process.Start(new ProcessStartInfo(e.Href) { UseShellExecute = true }); + } + } +} \ No newline at end of file diff --git a/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs b/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs index 19ed1a9b..8a7e1f57 100644 --- a/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs +++ b/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs @@ -100,6 +100,11 @@ public WindowQueueOptions(List dataList) textFolder.Text = queueFolder; } + private FileInfo HandleOverwriteCallback(FileInfo fileInfo) + { + return Dispatcher.Invoke(() => FileOverwriteService.HandleOverwriteCallback(fileInfo, Application.Current.MainWindow)); + } + private void btnQueue_Click(object sender, RoutedEventArgs e) { if (_parentPage != null) @@ -114,6 +119,8 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) } VideoDownloadOptions downloadOptions = vodDownloadPage.GetOptions(null, textFolder.Text); + downloadOptions.FileOverwriteCallback = HandleOverwriteCallback; + VodDownloadTask downloadTask = new VodDownloadTask { DownloadOptions = downloadOptions, @@ -142,6 +149,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.DownloadFormat = ChatFormat.Text; chatOptions.EmbedData = checkEmbed.IsChecked.GetValueOrDefault(); chatOptions.Filename = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(downloadOptions.Filename) + "." + chatOptions.DownloadFormat); + chatOptions.FileOverwriteCallback = HandleOverwriteCallback; if (downloadOptions.TrimBeginning) { @@ -180,6 +188,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) renderOptions.OutputFile = Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower()); } renderOptions.InputFile = chatOptions.Filename; + renderOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -224,7 +233,8 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) : -1, TempFolder = Settings.Default.TempPath, EncodeMetadata = clipDownloadPage.CheckMetadata.IsChecked!.Value, - FfmpegPath = "ffmpeg" + FfmpegPath = "ffmpeg", + FileOverwriteCallback = HandleOverwriteCallback, }; ClipDownloadTask downloadTask = new ClipDownloadTask @@ -258,6 +268,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.Filename = Path.Combine(folderPath, FilenameService.GetFilename(Settings.Default.TemplateChat, downloadTask.Info.Title, chatOptions.Id, clipDownloadPage.currentVideoTime, clipDownloadPage.textStreamer.Text, TimeSpan.Zero, clipDownloadPage.clipLength, clipDownloadPage.viewCount.ToString(), clipDownloadPage.game) + "." + chatOptions.FileExtension); + chatOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatDownloadTask chatTask = new ChatDownloadTask { @@ -284,6 +295,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) renderOptions.OutputFile = Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower()); } renderOptions.InputFile = chatOptions.Filename; + renderOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -322,6 +334,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.TrimBeginning ? TimeSpan.FromSeconds(chatOptions.TrimBeginningTime) : TimeSpan.Zero, chatOptions.TrimEnding ? TimeSpan.FromSeconds(chatOptions.TrimEndingTime) : chatDownloadPage.vodLength, chatDownloadPage.viewCount.ToString(), chatDownloadPage.game) + "." + chatOptions.FileExtension); + chatOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatDownloadTask chatTask = new ChatDownloadTask { @@ -343,6 +356,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) { ChatRenderOptions renderOptions = MainWindow.pageChatRender.GetOptions(Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), '.' + MainWindow.pageChatRender.comboFormat.Text.ToLower())); renderOptions.InputFile = chatOptions.Filename; + renderOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -380,6 +394,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.TrimBeginning ? TimeSpan.FromSeconds(chatOptions.TrimBeginningTime) : TimeSpan.Zero, chatOptions.TrimEnding ? TimeSpan.FromSeconds(chatOptions.TrimEndingTime) : chatUpdatePage.VideoLength, chatUpdatePage.ViewCount.ToString(), chatUpdatePage.Game) + "." + chatOptions.FileExtension); + chatOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatUpdateTask chatTask = new ChatUpdateTask { @@ -415,6 +430,8 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) string filePath = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(fileName) + "." + fileFormat.ToLower()); ChatRenderOptions renderOptions = MainWindow.pageChatRender.GetOptions(filePath); renderOptions.InputFile = fileName; + renderOptions.FileOverwriteCallback = HandleOverwriteCallback; + ChatRenderTask renderTask = new ChatRenderTask { DownloadOptions = renderOptions, @@ -472,7 +489,8 @@ private void EnqueueDataList() DownloadThreads = Settings.Default.VodDownloadThreads, ThrottleKib = Settings.Default.DownloadThrottleEnabled ? Settings.Default.MaximumBandwidthKib - : -1 + : -1, + FileOverwriteCallback = HandleOverwriteCallback, }; downloadOptions.Filename = Path.Combine(folderPath, FilenameService.GetFilename(Settings.Default.TemplateVod, taskData.Title, taskData.Id, taskData.Time, taskData.Streamer, downloadOptions.TrimBeginning ? downloadOptions.TrimBeginningTime : TimeSpan.Zero, @@ -508,7 +526,8 @@ private void EnqueueDataList() : -1, TempFolder = Settings.Default.TempPath, EncodeMetadata = Settings.Default.EncodeClipMetadata, - FfmpegPath = "ffmpeg" + FfmpegPath = "ffmpeg", + FileOverwriteCallback = HandleOverwriteCallback, }; ClipDownloadTask downloadTask = new ClipDownloadTask @@ -538,7 +557,8 @@ private void EnqueueDataList() TimeFormat = TimestampFormat.Relative, Id = taskData.Id, TrimBeginning = false, - TrimEnding = false + TrimEnding = false, + FileOverwriteCallback = HandleOverwriteCallback, }; if (radioJson.IsChecked == true) downloadOptions.DownloadFormat = ChatFormat.Json; @@ -577,6 +597,7 @@ private void EnqueueDataList() renderOptions.OutputFile = Path.ChangeExtension(downloadOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower()); } renderOptions.InputFile = downloadOptions.Filename; + renderOptions.FileOverwriteCallback = HandleOverwriteCallback; ChatRenderTask renderTask = new ChatRenderTask { From 1e6c44efa847e3b8e683de07084dbd12973dc03d Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:40:10 -0400 Subject: [PATCH 08/21] Throw when no output file is returned, refresh FileInfos before checking Exists property --- TwitchDownloaderCore/ChatDownloader.cs | 5 ----- TwitchDownloaderCore/ChatRenderer.cs | 13 ++----------- TwitchDownloaderCore/ChatUpdater.cs | 5 ----- TwitchDownloaderCore/ClipDownloader.cs | 7 +------ TwitchDownloaderCore/Tools/FilenameService.cs | 1 + TwitchDownloaderCore/TsMerger.cs | 5 ----- TwitchDownloaderCore/TwitchHelper.cs | 12 +++++++++--- TwitchDownloaderCore/VideoDownloader.cs | 6 +----- .../Services/FileOverwriteService.cs | 3 +++ 9 files changed, 17 insertions(+), 40 deletions(-) diff --git a/TwitchDownloaderCore/ChatDownloader.cs b/TwitchDownloaderCore/ChatDownloader.cs index c9f916bc..cfa51efb 100644 --- a/TwitchDownloaderCore/ChatDownloader.cs +++ b/TwitchDownloaderCore/ChatDownloader.cs @@ -252,11 +252,6 @@ public async Task DownloadAsync(CancellationToken cancellationToken) } var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); - if (outputFileInfo is null) - { - _progress.LogWarning("No destination file was provided, aborting."); - return; - } // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/ChatRenderer.cs b/TwitchDownloaderCore/ChatRenderer.cs index a2def73b..570fa172 100644 --- a/TwitchDownloaderCore/ChatRenderer.cs +++ b/TwitchDownloaderCore/ChatRenderer.cs @@ -73,17 +73,6 @@ public async Task RenderVideoAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(renderOptions.OutputFile, renderOptions.FileOverwriteCallback, _progress); var maskFileInfo = renderOptions.GenerateMask ? TwitchHelper.ClaimFile(renderOptions.MaskFile, renderOptions.FileOverwriteCallback, _progress) : null; - if (outputFileInfo is null) - { - _progress.LogWarning("No destination file was provided, aborting."); - return; - } - - if (renderOptions.GenerateMask && maskFileInfo is null) - { - _progress.LogWarning("No mask file was provided, aborting."); - return; - } // Open the destination files so that they exist in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); @@ -127,10 +116,12 @@ public async Task RenderVideoAsync(CancellationToken cancellationToken) // Delete the files as it is not guaranteed that the overwrite flag is passed in the FFmpeg args. outputFs.Close(); + outputFileInfo.Refresh(); if (outputFileInfo.Exists) outputFileInfo.Delete(); maskFs?.Close(); + maskFileInfo?.Refresh(); if (renderOptions.GenerateMask && maskFileInfo!.Exists) maskFileInfo.Delete(); diff --git a/TwitchDownloaderCore/ChatUpdater.cs b/TwitchDownloaderCore/ChatUpdater.cs index 3317b06c..63553653 100644 --- a/TwitchDownloaderCore/ChatUpdater.cs +++ b/TwitchDownloaderCore/ChatUpdater.cs @@ -33,11 +33,6 @@ public ChatUpdater(ChatUpdateOptions updateOptions, ITaskProgress progress) public async Task UpdateAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(_updateOptions.OutputFile, _updateOptions.FileOverwriteCallback, _progress); - if (outputFileInfo is null) - { - _progress.LogWarning("No destination file was provided, aborting."); - return; - } // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/ClipDownloader.cs b/TwitchDownloaderCore/ClipDownloader.cs index 90db73dd..3effb59e 100644 --- a/TwitchDownloaderCore/ClipDownloader.cs +++ b/TwitchDownloaderCore/ClipDownloader.cs @@ -31,13 +31,7 @@ public ClipDownloader(ClipDownloadOptions clipDownloadOptions, ITaskProgress pro public async Task DownloadAsync(CancellationToken cancellationToken) { - var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); - if (outputFileInfo is null) - { - _progress.LogWarning("No destination file was provided, aborting."); - return; - } // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); @@ -77,6 +71,7 @@ public async Task DownloadAsync(CancellationToken cancellationToken) var clipChapter = TwitchHelper.GenerateClipChapter(clipInfo.data.clip); await EncodeClipWithMetadata(tempFile, outputFileInfo.FullName, clipInfo.data.clip, clipChapter, cancellationToken); + outputFileInfo.Refresh(); if (!outputFileInfo.Exists) { File.Move(tempFile, outputFileInfo.FullName); diff --git a/TwitchDownloaderCore/Tools/FilenameService.cs b/TwitchDownloaderCore/Tools/FilenameService.cs index 7ce61e45..2f8a1c0e 100644 --- a/TwitchDownloaderCore/Tools/FilenameService.cs +++ b/TwitchDownloaderCore/Tools/FilenameService.cs @@ -89,6 +89,7 @@ private static string[] GetTemplateSubfolders(ref string fullPath) public static FileInfo GetNonCollidingName(FileInfo fileInfo) { + fileInfo.Refresh(); var fi = fileInfo; var parentDir = Path.GetDirectoryName(fi.FullName)!; diff --git a/TwitchDownloaderCore/TsMerger.cs b/TwitchDownloaderCore/TsMerger.cs index eda1693a..c5b76e35 100644 --- a/TwitchDownloaderCore/TsMerger.cs +++ b/TwitchDownloaderCore/TsMerger.cs @@ -27,11 +27,6 @@ public async Task MergeAsync(CancellationToken cancellationToken) } var outputFileInfo = TwitchHelper.ClaimFile(mergeOptions.OutputFile, mergeOptions.FileOverwriteCallback, _progress); - if (outputFileInfo is null) - { - _progress.LogWarning("No destination file was provided, aborting."); - return; - } // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/TwitchHelper.cs b/TwitchDownloaderCore/TwitchHelper.cs index 5ee7ddec..06b1d5e4 100644 --- a/TwitchDownloaderCore/TwitchHelper.cs +++ b/TwitchDownloaderCore/TwitchHelper.cs @@ -854,7 +854,6 @@ where comments return returnList; } - [return: MaybeNull] public static FileInfo ClaimFile(string path, Func fileAlreadyExistsCallback, ITaskLogger logger) { var fileInfo = new FileInfo(path); @@ -867,11 +866,18 @@ public static FileInfo ClaimFile(string path, Func fileAlrea else { fileInfo = fileAlreadyExistsCallback(fileInfo); - logger.LogVerbose($"{path} will be renamed to {fileInfo?.FullName}."); + + if (fileInfo is null) + { + // I would prefer to not throw here, but the alternative is refactoring the task queue :/ + throw new FileNotFoundException("No destination file was provided, aborting."); + } + + logger.LogVerbose($"{path} will be renamed to {fileInfo.FullName}."); } } - var directory = fileInfo?.Directory; + var directory = fileInfo.Directory; if (directory is not null && !directory.Exists) { CreateDirectory(directory.FullName); diff --git a/TwitchDownloaderCore/VideoDownloader.cs b/TwitchDownloaderCore/VideoDownloader.cs index 0a8439b7..d11eb33f 100644 --- a/TwitchDownloaderCore/VideoDownloader.cs +++ b/TwitchDownloaderCore/VideoDownloader.cs @@ -40,11 +40,6 @@ public VideoDownloader(VideoDownloadOptions videoDownloadOptions, ITaskProgress public async Task DownloadAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); - if (outputFileInfo is null) - { - _progress.LogWarning("No destination file was provided, aborting."); - return; - } // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); @@ -122,6 +117,7 @@ await FfmpegMetadata.SerializeAsync(metadataPath, videoInfo.owner.displayName, d } } while (ffmpegExitCode != 0 && ffmpegRetries++ < 1); + outputFileInfo.Refresh(); if (ffmpegExitCode != 0 || !outputFileInfo.Exists) { _shouldClearCache = false; diff --git a/TwitchDownloaderWPF/Services/FileOverwriteService.cs b/TwitchDownloaderWPF/Services/FileOverwriteService.cs index 169ed72f..a93e9033 100644 --- a/TwitchDownloaderWPF/Services/FileOverwriteService.cs +++ b/TwitchDownloaderWPF/Services/FileOverwriteService.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Windows; using Ookii.Dialogs.Wpf; @@ -19,6 +20,7 @@ private enum OverwriteCommand private static OverwriteCommand _overwriteCommand = OverwriteCommand.Prompt; + [return: MaybeNull] public static FileInfo HandleOverwriteCallback(FileInfo fileInfo, Window owner) { if (_overwriteCommand is not OverwriteCommand.Prompt) @@ -80,6 +82,7 @@ private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out throw new ArgumentOutOfRangeException(); } + [return: MaybeNull] private static FileInfo GetResult(FileInfo fileInfo, OverwriteCommand command) { return command switch From 166c4c853ecd01ac64ddcd4035f036a08e4b30e7 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:03:51 -0400 Subject: [PATCH 09/21] Genericize opening explorer for a given file --- TwitchDownloaderWPF/PageQueue.xaml.cs | 19 ++---------- .../Services/FileOverwriteService.cs | 2 +- TwitchDownloaderWPF/Services/FileService.cs | 31 +++++++++++++++++++ 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 TwitchDownloaderWPF/Services/FileService.cs diff --git a/TwitchDownloaderWPF/PageQueue.xaml.cs b/TwitchDownloaderWPF/PageQueue.xaml.cs index d44da4a0..5f1baf84 100644 --- a/TwitchDownloaderWPF/PageQueue.xaml.cs +++ b/TwitchDownloaderWPF/PageQueue.xaml.cs @@ -8,6 +8,7 @@ using TwitchDownloaderWPF.Properties; using System.Diagnostics; using System.IO; +using TwitchDownloaderWPF.Services; namespace TwitchDownloaderWPF { @@ -308,23 +309,7 @@ private void MenuItemOpenTaskFolder_Click(object sender, RoutedEventArgs e) return; } - var outputFolder = Path.GetDirectoryName(task.OutputFile); - if (!Directory.Exists(outputFolder)) - { - return; - } - - var args = File.Exists(task.OutputFile) - ? $"/select,\"{task.OutputFile}\"" - : $"\"{outputFolder}\""; - - Process.Start(new ProcessStartInfo - { - FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"), - Arguments = args, - UseShellExecute = true, - WorkingDirectory = outputFolder - }); + FileService.OpenExplorerForFile(new FileInfo(task.OutputFile)); } } } diff --git a/TwitchDownloaderWPF/Services/FileOverwriteService.cs b/TwitchDownloaderWPF/Services/FileOverwriteService.cs index a93e9033..33e85c0e 100644 --- a/TwitchDownloaderWPF/Services/FileOverwriteService.cs +++ b/TwitchDownloaderWPF/Services/FileOverwriteService.cs @@ -96,7 +96,7 @@ private static FileInfo GetResult(FileInfo fileInfo, OverwriteCommand command) private static void Hyperlink_OnClicked(object sender, HyperlinkClickedEventArgs e) { - Process.Start(new ProcessStartInfo(e.Href) { UseShellExecute = true }); + FileService.OpenExplorerForFile(new FileInfo(e.Href)); } } } \ No newline at end of file diff --git a/TwitchDownloaderWPF/Services/FileService.cs b/TwitchDownloaderWPF/Services/FileService.cs new file mode 100644 index 00000000..6cfe8927 --- /dev/null +++ b/TwitchDownloaderWPF/Services/FileService.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace TwitchDownloaderWPF.Services +{ + public static class FileService + { + public static void OpenExplorerForFile(FileInfo fileInfo) + { + var directoryInfo = fileInfo.Directory; + if (directoryInfo is null || !directoryInfo.Exists) + { + return; + } + + fileInfo.Refresh(); + var args = fileInfo.Exists + ? $"/select,\"{fileInfo.FullName}\"" + : $"\"{directoryInfo.FullName}\""; + + Process.Start(new ProcessStartInfo + { + FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"), + Arguments = args, + UseShellExecute = true, + WorkingDirectory = directoryInfo.FullName + }); + } + } +} \ No newline at end of file From ddd99244c2d2bfe3fdd74e1597d107bb9a5cbcc8 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:07:44 -0400 Subject: [PATCH 10/21] Update ___Options output file property to reflect possibly new filename --- TwitchDownloaderCore/ChatDownloader.cs | 1 + TwitchDownloaderCore/ChatRenderer.cs | 1 + TwitchDownloaderCore/ChatUpdater.cs | 1 + TwitchDownloaderCore/ClipDownloader.cs | 1 + TwitchDownloaderCore/TsMerger.cs | 1 + TwitchDownloaderCore/VideoDownloader.cs | 1 + 6 files changed, 6 insertions(+) diff --git a/TwitchDownloaderCore/ChatDownloader.cs b/TwitchDownloaderCore/ChatDownloader.cs index cfa51efb..b2f8f786 100644 --- a/TwitchDownloaderCore/ChatDownloader.cs +++ b/TwitchDownloaderCore/ChatDownloader.cs @@ -252,6 +252,7 @@ public async Task DownloadAsync(CancellationToken cancellationToken) } var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + downloadOptions.Filename = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/ChatRenderer.cs b/TwitchDownloaderCore/ChatRenderer.cs index 570fa172..9420c365 100644 --- a/TwitchDownloaderCore/ChatRenderer.cs +++ b/TwitchDownloaderCore/ChatRenderer.cs @@ -72,6 +72,7 @@ public ChatRenderer(ChatRenderOptions chatRenderOptions, ITaskProgress progress) public async Task RenderVideoAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(renderOptions.OutputFile, renderOptions.FileOverwriteCallback, _progress); + renderOptions.OutputFile = outputFileInfo.FullName; var maskFileInfo = renderOptions.GenerateMask ? TwitchHelper.ClaimFile(renderOptions.MaskFile, renderOptions.FileOverwriteCallback, _progress) : null; // Open the destination files so that they exist in the filesystem. diff --git a/TwitchDownloaderCore/ChatUpdater.cs b/TwitchDownloaderCore/ChatUpdater.cs index 63553653..5b50cc81 100644 --- a/TwitchDownloaderCore/ChatUpdater.cs +++ b/TwitchDownloaderCore/ChatUpdater.cs @@ -33,6 +33,7 @@ public ChatUpdater(ChatUpdateOptions updateOptions, ITaskProgress progress) public async Task UpdateAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(_updateOptions.OutputFile, _updateOptions.FileOverwriteCallback, _progress); + _updateOptions.OutputFile = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/ClipDownloader.cs b/TwitchDownloaderCore/ClipDownloader.cs index 3effb59e..0fc57000 100644 --- a/TwitchDownloaderCore/ClipDownloader.cs +++ b/TwitchDownloaderCore/ClipDownloader.cs @@ -32,6 +32,7 @@ public ClipDownloader(ClipDownloadOptions clipDownloadOptions, ITaskProgress pro public async Task DownloadAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + downloadOptions.Filename = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/TsMerger.cs b/TwitchDownloaderCore/TsMerger.cs index c5b76e35..6ed8b2ab 100644 --- a/TwitchDownloaderCore/TsMerger.cs +++ b/TwitchDownloaderCore/TsMerger.cs @@ -27,6 +27,7 @@ public async Task MergeAsync(CancellationToken cancellationToken) } var outputFileInfo = TwitchHelper.ClaimFile(mergeOptions.OutputFile, mergeOptions.FileOverwriteCallback, _progress); + mergeOptions.OutputFile = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/VideoDownloader.cs b/TwitchDownloaderCore/VideoDownloader.cs index d11eb33f..f9817d46 100644 --- a/TwitchDownloaderCore/VideoDownloader.cs +++ b/TwitchDownloaderCore/VideoDownloader.cs @@ -40,6 +40,7 @@ public VideoDownloader(VideoDownloadOptions videoDownloadOptions, ITaskProgress public async Task DownloadAsync(CancellationToken cancellationToken) { var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + downloadOptions.Filename = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); From 811319fcbfa7b26976496f0225b92a2425fddc3e Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:53:57 -0400 Subject: [PATCH 11/21] Add icon --- TwitchDownloaderWPF/Services/FileOverwriteService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TwitchDownloaderWPF/Services/FileOverwriteService.cs b/TwitchDownloaderWPF/Services/FileOverwriteService.cs index 33e85c0e..14480cc4 100644 --- a/TwitchDownloaderWPF/Services/FileOverwriteService.cs +++ b/TwitchDownloaderWPF/Services/FileOverwriteService.cs @@ -43,7 +43,8 @@ private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out using var dialog = new TaskDialog(); dialog.WindowTitle = "File already exists"; dialog.MainInstruction = $"{fileInfo.Name} already exists."; - dialog.Content = $"The file {fileInfo.FullName} already exists. What would you like to do?"; + dialog.Content = $"The file {fileInfo.FullName} already exists. Do you want to overwrite it?"; + dialog.MainIcon = TaskDialogIcon.Information; dialog.EnableHyperlinks = true; dialog.HyperlinkClicked += Hyperlink_OnClicked; From 20c92dd09c64f42feffecebe9be68a36f7fb27f7 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:04:34 -0400 Subject: [PATCH 12/21] Add translations --- .../Services/FileOverwriteService.cs | 21 +++-- .../Translations/Strings.Designer.cs | 90 +++++++++++++++++++ .../Translations/Strings.es.resx | 30 +++++++ .../Translations/Strings.fr.resx | 30 +++++++ .../Translations/Strings.it.resx | 30 +++++++ .../Translations/Strings.ja.resx | 30 +++++++ .../Translations/Strings.pl.resx | 30 +++++++ .../Translations/Strings.pt-br.resx | 30 +++++++ TwitchDownloaderWPF/Translations/Strings.resx | 30 +++++++ .../Translations/Strings.ru.resx | 30 +++++++ .../Translations/Strings.tr.resx | 30 +++++++ .../Translations/Strings.uk.resx | 30 +++++++ 12 files changed, 400 insertions(+), 11 deletions(-) diff --git a/TwitchDownloaderWPF/Services/FileOverwriteService.cs b/TwitchDownloaderWPF/Services/FileOverwriteService.cs index 14480cc4..6df0227a 100644 --- a/TwitchDownloaderWPF/Services/FileOverwriteService.cs +++ b/TwitchDownloaderWPF/Services/FileOverwriteService.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Windows; @@ -41,9 +40,9 @@ public static FileInfo HandleOverwriteCallback(FileInfo fileInfo, Window owner) private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out bool rememberChoice) { using var dialog = new TaskDialog(); - dialog.WindowTitle = "File already exists"; - dialog.MainInstruction = $"{fileInfo.Name} already exists."; - dialog.Content = $"The file {fileInfo.FullName} already exists. Do you want to overwrite it?"; + dialog.WindowTitle = Translations.Strings.TitleFileAlreadyExists; + dialog.MainInstruction = string.Format(Translations.Strings.FileAlreadyExistsHeader, fileInfo.Name); + dialog.Content = string.Format(Translations.Strings.FileAlreadyExistsBody, $"{fileInfo.FullName}"); dialog.MainIcon = TaskDialogIcon.Information; dialog.EnableHyperlinks = true; @@ -51,19 +50,19 @@ private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out dialog.ButtonStyle = TaskDialogButtonStyle.CommandLinks; - var overwriteButton = new TaskDialogButton("Overwrite"); - overwriteButton.CommandLinkNote = "The existing file will be overwritten."; + var overwriteButton = new TaskDialogButton(Translations.Strings.FileAlreadyExistsOverwrite); + overwriteButton.CommandLinkNote = Translations.Strings.FileAlreadyExistsOverwriteDescription; dialog.Buttons.Add(overwriteButton); - var renameButton = new TaskDialogButton("Rename"); - renameButton.CommandLinkNote = "The new file will be renamed."; + var renameButton = new TaskDialogButton(Translations.Strings.FileAlreadyExistsRename); + renameButton.CommandLinkNote = Translations.Strings.FileAlreadyExistsRenameDescription; dialog.Buttons.Add(renameButton); - var cancelButton = new TaskDialogButton("Cancel"); - cancelButton.CommandLinkNote = "The task will be canceled."; + var cancelButton = new TaskDialogButton(Translations.Strings.FileAlreadyExistsCancel); + cancelButton.CommandLinkNote = Translations.Strings.FileAlreadyExistsCancelDescription; dialog.Buttons.Add(cancelButton); - dialog.VerificationText = "Remember my choice"; + dialog.VerificationText = Translations.Strings.FileAlreadyExistsRememberMyChoice; dialog.IsVerificationChecked = false; var buttonResult = dialog.ShowDialog(owner); diff --git a/TwitchDownloaderWPF/Translations/Strings.Designer.cs b/TwitchDownloaderWPF/Translations/Strings.Designer.cs index a61b3835..f0822eef 100644 --- a/TwitchDownloaderWPF/Translations/Strings.Designer.cs +++ b/TwitchDownloaderWPF/Translations/Strings.Designer.cs @@ -896,6 +896,87 @@ public static string FileAgeInDays { } } + /// + /// Looks up a localized string similar to The file {0} already exists. Do you want to overwrite it?. + /// + public static string FileAlreadyExistsBody { + get { + return ResourceManager.GetString("FileAlreadyExistsBody", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string FileAlreadyExistsCancel { + get { + return ResourceManager.GetString("FileAlreadyExistsCancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The task will be canceled.. + /// + public static string FileAlreadyExistsCancelDescription { + get { + return ResourceManager.GetString("FileAlreadyExistsCancelDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} already exists.. + /// + public static string FileAlreadyExistsHeader { + get { + return ResourceManager.GetString("FileAlreadyExistsHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Overwrite. + /// + public static string FileAlreadyExistsOverwrite { + get { + return ResourceManager.GetString("FileAlreadyExistsOverwrite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The existing file will be overwritten.. + /// + public static string FileAlreadyExistsOverwriteDescription { + get { + return ResourceManager.GetString("FileAlreadyExistsOverwriteDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remember my choice. + /// + public static string FileAlreadyExistsRememberMyChoice { + get { + return ResourceManager.GetString("FileAlreadyExistsRememberMyChoice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rename. + /// + public static string FileAlreadyExistsRename { + get { + return ResourceManager.GetString("FileAlreadyExistsRename", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The new file will be renamed.. + /// + public static string FileAlreadyExistsRenameDescription { + get { + return ResourceManager.GetString("FileAlreadyExistsRenameDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to {title} {id} {date} {channel} {date_custom=""} {random_string} {trim_start} {trim_end} {trim_start_custom=""} {trim_end_custom=""} {length} {length_custom=""} {views} {game}. /// @@ -1940,6 +2021,15 @@ public static string TitleEnqueueOptions { } } + /// + /// Looks up a localized string similar to File already exists. + /// + public static string TitleFileAlreadyExists { + get { + return ResourceManager.GetString("TitleFileAlreadyExists", resourceCulture); + } + } + /// /// Looks up a localized string similar to Global Settings. /// diff --git a/TwitchDownloaderWPF/Translations/Strings.es.resx b/TwitchDownloaderWPF/Translations/Strings.es.resx index 61564b5f..8b99b3ec 100644 --- a/TwitchDownloaderWPF/Translations/Strings.es.resx +++ b/TwitchDownloaderWPF/Translations/Strings.es.resx @@ -898,4 +898,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + diff --git a/TwitchDownloaderWPF/Translations/Strings.fr.resx b/TwitchDownloaderWPF/Translations/Strings.fr.resx index 8477fd56..0a01bc10 100644 --- a/TwitchDownloaderWPF/Translations/Strings.fr.resx +++ b/TwitchDownloaderWPF/Translations/Strings.fr.resx @@ -897,4 +897,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.it.resx b/TwitchDownloaderWPF/Translations/Strings.it.resx index 6a8981aa..5786b9b4 100644 --- a/TwitchDownloaderWPF/Translations/Strings.it.resx +++ b/TwitchDownloaderWPF/Translations/Strings.it.resx @@ -898,4 +898,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + diff --git a/TwitchDownloaderWPF/Translations/Strings.ja.resx b/TwitchDownloaderWPF/Translations/Strings.ja.resx index 94d29be0..f255c0ae 100644 --- a/TwitchDownloaderWPF/Translations/Strings.ja.resx +++ b/TwitchDownloaderWPF/Translations/Strings.ja.resx @@ -896,4 +896,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.pl.resx b/TwitchDownloaderWPF/Translations/Strings.pl.resx index 819bde7a..58111474 100644 --- a/TwitchDownloaderWPF/Translations/Strings.pl.resx +++ b/TwitchDownloaderWPF/Translations/Strings.pl.resx @@ -897,4 +897,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.pt-br.resx b/TwitchDownloaderWPF/Translations/Strings.pt-br.resx index 33577e25..89769a46 100644 --- a/TwitchDownloaderWPF/Translations/Strings.pt-br.resx +++ b/TwitchDownloaderWPF/Translations/Strings.pt-br.resx @@ -896,4 +896,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.resx b/TwitchDownloaderWPF/Translations/Strings.resx index 0db53f84..6435cc62 100644 --- a/TwitchDownloaderWPF/Translations/Strings.resx +++ b/TwitchDownloaderWPF/Translations/Strings.resx @@ -896,4 +896,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.ru.resx b/TwitchDownloaderWPF/Translations/Strings.ru.resx index 5b299529..e5ffa584 100644 --- a/TwitchDownloaderWPF/Translations/Strings.ru.resx +++ b/TwitchDownloaderWPF/Translations/Strings.ru.resx @@ -897,4 +897,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.tr.resx b/TwitchDownloaderWPF/Translations/Strings.tr.resx index 9476b7c7..a076387a 100644 --- a/TwitchDownloaderWPF/Translations/Strings.tr.resx +++ b/TwitchDownloaderWPF/Translations/Strings.tr.resx @@ -898,4 +898,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.uk.resx b/TwitchDownloaderWPF/Translations/Strings.uk.resx index 9b16c853..5621f47d 100644 --- a/TwitchDownloaderWPF/Translations/Strings.uk.resx +++ b/TwitchDownloaderWPF/Translations/Strings.uk.resx @@ -897,4 +897,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice + From b22fb021c42734d7cbc5647bbcbb3dc01b120e38 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:11:47 -0400 Subject: [PATCH 13/21] Annotate as windows only --- TwitchDownloaderWPF/Services/FileService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TwitchDownloaderWPF/Services/FileService.cs b/TwitchDownloaderWPF/Services/FileService.cs index 6cfe8927..829b661c 100644 --- a/TwitchDownloaderWPF/Services/FileService.cs +++ b/TwitchDownloaderWPF/Services/FileService.cs @@ -1,11 +1,13 @@ using System; using System.Diagnostics; using System.IO; +using System.Runtime.Versioning; namespace TwitchDownloaderWPF.Services { public static class FileService { + [SupportedOSPlatform("windows")] public static void OpenExplorerForFile(FileInfo fileInfo) { var directoryInfo = fileInfo.Directory; From 3fda631e213d468a29929be7a685ce94bc507d9e Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:31:28 -0400 Subject: [PATCH 14/21] Update README --- TwitchDownloaderCLI/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/TwitchDownloaderCLI/README.md b/TwitchDownloaderCLI/README.md index 71b6d7e2..77beed4f 100644 --- a/TwitchDownloaderCLI/README.md +++ b/TwitchDownloaderCLI/README.md @@ -66,6 +66,9 @@ Path to FFmpeg executable. **--temp-path** Path to temporary folder for cache. +**--overwrite** +(Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. + ## Arguments for mode clipdownload #### Downloads a clip from Twitch @@ -90,6 +93,9 @@ Path to FFmpeg executable. **--temp-path** Path to temporary folder for cache. +**--overwrite** +(Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. + ## Arguments for mode chatdownload #### Downloads the chat of a VOD, highlight, or clip @@ -129,6 +135,9 @@ Time to trim ending. See [Time durations](#time-durations) for a more detailed e **--temp-path** Path to temporary folder for cache. +**--overwrite** +(Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. + ## Arguments for mode chatupdate #### Updates the embedded emotes, badges, bits, and trims a chat JSON and/or converts a JSON chat to another format @@ -168,6 +177,9 @@ Path to output file. File extension will be used to determine new chat type. Val **--temp-path** Path to temporary folder for cache. +**--overwrite** +(Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. + ## Arguments for mode chatrender #### Renders a chat JSON as a video @@ -320,6 +332,9 @@ Other = `1`, Broadcaster = `2`, Moderator = `4`, VIP = `8`, Subscriber = `16`, P **--scale-highlight-indent** (Default: `1.0`) Number to scale highlight indent size (sub messages). +**--overwrite** +(Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. + ## Arguments for mode ffmpeg #### Manage standalone FFmpeg @@ -344,6 +359,9 @@ Path a text file containing the absolute paths of the files to concatenate, sepa **-o / --output (REQUIRED)** File the program will output to. +**--overwrite** +(Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. + --- ## Example Commands From 69dd6872c9245899e9a042ad9829864a3e0ceb4a Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:42:58 -0400 Subject: [PATCH 15/21] Forgot to add it to the whole CLI --- TwitchDownloaderCLI/Modes/DownloadChat.cs | 8 +++++--- TwitchDownloaderCLI/Modes/MergeTs.cs | 8 +++++--- TwitchDownloaderCLI/Modes/RenderChat.cs | 7 +++++-- TwitchDownloaderCLI/Modes/UpdateChat.cs | 8 +++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/TwitchDownloaderCLI/Modes/DownloadChat.cs b/TwitchDownloaderCLI/Modes/DownloadChat.cs index f0eb9fb8..c69cfbb1 100644 --- a/TwitchDownloaderCLI/Modes/DownloadChat.cs +++ b/TwitchDownloaderCLI/Modes/DownloadChat.cs @@ -16,13 +16,14 @@ internal static void Download(ChatDownloadArgs inputOptions) { var progress = new CliTaskProgress(inputOptions.LogLevel); - var downloadOptions = GetDownloadOptions(inputOptions, progress); + var overwriteHandler = new FileOverwriteHandler(inputOptions); + var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); var chatDownloader = new ChatDownloader(downloadOptions, progress); chatDownloader.DownloadAsync(CancellationToken.None).Wait(); } - private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOptions, ITaskLogger logger) + private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) { if (inputOptions.Id is null) { @@ -64,7 +65,8 @@ private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOpti BttvEmotes = (bool)inputOptions.BttvEmotes!, FfzEmotes = (bool)inputOptions.FfzEmotes!, StvEmotes = (bool)inputOptions.StvEmotes!, - TempFolder = inputOptions.TempFolder + TempFolder = inputOptions.TempFolder, + FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, }; return downloadOptions; diff --git a/TwitchDownloaderCLI/Modes/MergeTs.cs b/TwitchDownloaderCLI/Modes/MergeTs.cs index 2f717304..db0755c9 100644 --- a/TwitchDownloaderCLI/Modes/MergeTs.cs +++ b/TwitchDownloaderCLI/Modes/MergeTs.cs @@ -14,18 +14,20 @@ internal static void Merge(TsMergeArgs inputOptions) progress.LogInfo("The TS merger is experimental and is subject to change without notice in future releases."); - var mergeOptions = GetMergeOptions(inputOptions); + var overwriteHandler = new FileOverwriteHandler(inputOptions); + var mergeOptions = GetMergeOptions(inputOptions, overwriteHandler); var tsMerger = new TsMerger(mergeOptions, progress); tsMerger.MergeAsync(new CancellationToken()).Wait(); } - private static TsMergeOptions GetMergeOptions(TsMergeArgs inputOptions) + private static TsMergeOptions GetMergeOptions(TsMergeArgs inputOptions, FileOverwriteHandler overwriteHandler) { TsMergeOptions mergeOptions = new() { OutputFile = inputOptions.OutputFile, - InputFile = inputOptions.InputList + InputFile = inputOptions.InputList, + FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, }; return mergeOptions; diff --git a/TwitchDownloaderCLI/Modes/RenderChat.cs b/TwitchDownloaderCLI/Modes/RenderChat.cs index 75e8ba03..98776ff7 100644 --- a/TwitchDownloaderCLI/Modes/RenderChat.cs +++ b/TwitchDownloaderCLI/Modes/RenderChat.cs @@ -19,13 +19,15 @@ internal static void Render(ChatRenderArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); - var renderOptions = GetRenderOptions(inputOptions, progress); + var overwriteHandler = new FileOverwriteHandler(inputOptions); + var renderOptions = GetRenderOptions(inputOptions, overwriteHandler, progress); + using var chatRenderer = new ChatRenderer(renderOptions, progress); chatRenderer.ParseJsonAsync().Wait(); chatRenderer.RenderVideoAsync(new CancellationToken()).Wait(); } - private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, ITaskLogger logger) + private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) { ChatRenderOptions renderOptions = new() { @@ -93,6 +95,7 @@ private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, I DisperseCommentOffsets = inputOptions.DisperseCommentOffsets, AlternateMessageBackgrounds = inputOptions.AlternateMessageBackgrounds, AdjustUsernameVisibility = inputOptions.AdjustUsernameVisibility, + FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, }; if (renderOptions.GenerateMask && renderOptions.BackgroundColor.Alpha == 255 && !(renderOptions.AlternateMessageBackgrounds! && renderOptions.AlternateBackgroundColor.Alpha != 255)) diff --git a/TwitchDownloaderCLI/Modes/UpdateChat.cs b/TwitchDownloaderCLI/Modes/UpdateChat.cs index 2dd7b0a0..64fc6bb8 100644 --- a/TwitchDownloaderCLI/Modes/UpdateChat.cs +++ b/TwitchDownloaderCLI/Modes/UpdateChat.cs @@ -16,14 +16,15 @@ internal static void Update(ChatUpdateArgs inputOptions) { var progress = new CliTaskProgress(inputOptions.LogLevel); - var updateOptions = GetUpdateOptions(inputOptions, progress); + var overwriteHandler = new FileOverwriteHandler(inputOptions); + var updateOptions = GetUpdateOptions(inputOptions, overwriteHandler, progress); var chatUpdater = new ChatUpdater(updateOptions, progress); chatUpdater.ParseJsonAsync().Wait(); chatUpdater.UpdateAsync(new CancellationToken()).Wait(); } - private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, ITaskLogger logger) + private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) { if (!File.Exists(inputOptions.InputFile)) { @@ -76,7 +77,8 @@ private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, I FfzEmotes = (bool)inputOptions.FfzEmotes!, StvEmotes = (bool)inputOptions.StvEmotes!, TextTimestampFormat = inputOptions.TimeFormat, - TempFolder = inputOptions.TempFolder + TempFolder = inputOptions.TempFolder, + FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, }; return updateOptions; From 189244383ade8db3a90501d3e1d6e14f83ef8558 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:45:52 -0400 Subject: [PATCH 16/21] Overwrite -> Collision --- .../Modes/Arguments/ChatDownloadArgs.cs | 2 +- .../Modes/Arguments/ChatRenderArgs.cs | 2 +- .../Modes/Arguments/ChatUpdateArgs.cs | 2 +- .../Modes/Arguments/ClipDownloadArgs.cs | 2 +- ...OverwriteArgs.cs => IFileCollisionArgs.cs} | 5 ++- .../Modes/Arguments/TsMergeArgs.cs | 2 +- .../Modes/Arguments/VideoDownloadArgs.cs | 2 +- TwitchDownloaderCLI/Modes/DownloadChat.cs | 6 ++-- TwitchDownloaderCLI/Modes/DownloadClip.cs | 6 ++-- TwitchDownloaderCLI/Modes/DownloadVideo.cs | 6 ++-- TwitchDownloaderCLI/Modes/MergeTs.cs | 6 ++-- TwitchDownloaderCLI/Modes/RenderChat.cs | 6 ++-- TwitchDownloaderCLI/Modes/UpdateChat.cs | 6 ++-- TwitchDownloaderCLI/README.md | 12 +++---- ...riteHandler.cs => FileCollisionHandler.cs} | 14 ++++---- TwitchDownloaderCore/ChatDownloader.cs | 2 +- TwitchDownloaderCore/ChatRenderer.cs | 4 +-- TwitchDownloaderCore/ChatUpdater.cs | 2 +- TwitchDownloaderCore/ClipDownloader.cs | 2 +- .../Options/ChatDownloadOptions.cs | 2 +- .../Options/ChatRenderOptions.cs | 2 +- .../Options/ChatUpdateOptions.cs | 2 +- .../Options/ClipDownloadOptions.cs | 2 +- .../Options/TsMergeOptions.cs | 2 +- .../Options/VideoDownloadOptions.cs | 2 +- TwitchDownloaderCore/TsMerger.cs | 2 +- TwitchDownloaderCore/VideoDownloader.cs | 2 +- ...riteService.cs => FileCollisionService.cs} | 30 ++++++++--------- .../WindowQueueOptions.xaml.cs | 32 +++++++++---------- 29 files changed, 83 insertions(+), 84 deletions(-) rename TwitchDownloaderCLI/Modes/Arguments/{IFileOverwriteArgs.cs => IFileCollisionArgs.cs} (65%) rename TwitchDownloaderCLI/Tools/{FileOverwriteHandler.cs => FileCollisionHandler.cs} (78%) rename TwitchDownloaderWPF/Services/{FileOverwriteService.cs => FileCollisionService.cs} (77%) diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs index 1609c97b..f062be49 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatDownloadArgs.cs @@ -5,7 +5,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatdownload", HelpText = "Downloads the chat from a VOD or clip")] - internal sealed class ChatDownloadArgs : IFileOverwriteArgs, ITwitchDownloaderArgs + internal sealed class ChatDownloadArgs : IFileCollisionArgs, ITwitchDownloaderArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD or clip to download that chat of.")] public string Id { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs index 48fbf0c2..188554b8 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatRenderArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatrender", HelpText = "Renders a chat JSON as a video")] - internal sealed class ChatRenderArgs : IFileOverwriteArgs, ITwitchDownloaderArgs + internal sealed class ChatRenderArgs : IFileCollisionArgs, ITwitchDownloaderArgs { [Option('i', "input", Required = true, HelpText = "Path to JSON chat file input.")] public string InputFile { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs index 3d39d9bc..c6cb1492 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ChatUpdateArgs.cs @@ -5,7 +5,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("chatupdate", HelpText = "Updates the embedded emotes, badges, bits, and trims a chat JSON and/or converts a JSON chat to another format.")] - internal sealed class ChatUpdateArgs : IFileOverwriteArgs, ITwitchDownloaderArgs + internal sealed class ChatUpdateArgs : IFileCollisionArgs, ITwitchDownloaderArgs { [Option('i', "input", Required = true, HelpText = "Path to input file. Valid extensions are: .json, .json.gz.")] public string InputFile { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs index c1cc51fa..1b901f60 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/ClipDownloadArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("clipdownload", HelpText = "Downloads a clip from Twitch")] - internal sealed class ClipDownloadArgs : IFileOverwriteArgs, ITwitchDownloaderArgs + internal sealed class ClipDownloadArgs : IFileCollisionArgs, ITwitchDownloaderArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the clip to download.")] public string Id { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/IFileCollisionArgs.cs similarity index 65% rename from TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs rename to TwitchDownloaderCLI/Modes/Arguments/IFileCollisionArgs.cs index 67dda8dc..9fa70774 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/IFileOverwriteArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/IFileCollisionArgs.cs @@ -3,10 +3,9 @@ namespace TwitchDownloaderCLI.Modes.Arguments { - internal interface IFileOverwriteArgs + internal interface IFileCollisionArgs { - // TODO: This is probably a bad arg name - [Option("overwrite", Default = OverwriteBehavior.Prompt, HelpText = "Sets the handling of output file name collisions. Valid values are: Overwrite, Exit, Rename, Prompt.")] + [Option("collision", Default = OverwriteBehavior.Prompt, HelpText = "Sets the handling of output file name collisions. Valid values are: Overwrite, Exit, Rename, Prompt.")] public OverwriteBehavior OverwriteBehavior { get; set; } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs index 51c74496..96270d75 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/TsMergeArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("tsmerge", HelpText = "Concatenates multiple .ts/.tsv/.tsa/.m2t/.m2ts (MPEG Transport Stream) files into a single file")] - internal sealed class TsMergeArgs : IFileOverwriteArgs, ITwitchDownloaderArgs + internal sealed class TsMergeArgs : IFileCollisionArgs, ITwitchDownloaderArgs { [Option('i', "input", Required = true, HelpText = "Path a text file containing the absolute paths of the files to concatenate, separated by newlines. M3U/M3U8 is also supported.")] public string InputList { get; set; } diff --git a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs index 00a473b7..2e2e6f0c 100644 --- a/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs +++ b/TwitchDownloaderCLI/Modes/Arguments/VideoDownloadArgs.cs @@ -4,7 +4,7 @@ namespace TwitchDownloaderCLI.Modes.Arguments { [Verb("videodownload", HelpText = "Downloads a stream VOD from Twitch")] - internal sealed class VideoDownloadArgs : IFileOverwriteArgs, ITwitchDownloaderArgs + internal sealed class VideoDownloadArgs : IFileCollisionArgs, ITwitchDownloaderArgs { [Option('u', "id", Required = true, HelpText = "The ID or URL of the VOD to download.")] public string Id { get; set; } diff --git a/TwitchDownloaderCLI/Modes/DownloadChat.cs b/TwitchDownloaderCLI/Modes/DownloadChat.cs index c69cfbb1..d886d908 100644 --- a/TwitchDownloaderCLI/Modes/DownloadChat.cs +++ b/TwitchDownloaderCLI/Modes/DownloadChat.cs @@ -16,14 +16,14 @@ internal static void Download(ChatDownloadArgs inputOptions) { var progress = new CliTaskProgress(inputOptions.LogLevel); - var overwriteHandler = new FileOverwriteHandler(inputOptions); + var overwriteHandler = new FileCollisionHandler(inputOptions); var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); var chatDownloader = new ChatDownloader(downloadOptions, progress); chatDownloader.DownloadAsync(CancellationToken.None).Wait(); } - private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) + private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger) { if (inputOptions.Id is null) { @@ -66,7 +66,7 @@ private static ChatDownloadOptions GetDownloadOptions(ChatDownloadArgs inputOpti FfzEmotes = (bool)inputOptions.FfzEmotes!, StvEmotes = (bool)inputOptions.StvEmotes!, TempFolder = inputOptions.TempFolder, - FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, + FileCollisionCallback = collisionHandler.HandleCollisionCallback, }; return downloadOptions; diff --git a/TwitchDownloaderCLI/Modes/DownloadClip.cs b/TwitchDownloaderCLI/Modes/DownloadClip.cs index a1cc97bf..860b350b 100644 --- a/TwitchDownloaderCLI/Modes/DownloadClip.cs +++ b/TwitchDownloaderCLI/Modes/DownloadClip.cs @@ -21,14 +21,14 @@ internal static void Download(ClipDownloadArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); } - var overwriteHandler = new FileOverwriteHandler(inputOptions); + var overwriteHandler = new FileCollisionHandler(inputOptions); var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); var clipDownloader = new ClipDownloader(downloadOptions, progress); clipDownloader.DownloadAsync(new CancellationToken()).Wait(); } - private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) + private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger) { if (inputOptions.Id is null) { @@ -52,7 +52,7 @@ private static ClipDownloadOptions GetDownloadOptions(ClipDownloadArgs inputOpti FfmpegPath = string.IsNullOrWhiteSpace(inputOptions.FfmpegPath) ? FfmpegHandler.FfmpegExecutableName : Path.GetFullPath(inputOptions.FfmpegPath), EncodeMetadata = inputOptions.EncodeMetadata!.Value, TempFolder = inputOptions.TempFolder, - FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, + FileCollisionCallback = collisionHandler.HandleCollisionCallback, }; return downloadOptions; diff --git a/TwitchDownloaderCLI/Modes/DownloadVideo.cs b/TwitchDownloaderCLI/Modes/DownloadVideo.cs index ef907f78..af7a697e 100644 --- a/TwitchDownloaderCLI/Modes/DownloadVideo.cs +++ b/TwitchDownloaderCLI/Modes/DownloadVideo.cs @@ -19,14 +19,14 @@ internal static void Download(VideoDownloadArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); - var overwriteHandler = new FileOverwriteHandler(inputOptions); + var overwriteHandler = new FileCollisionHandler(inputOptions); var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); var videoDownloader = new VideoDownloader(downloadOptions, progress); videoDownloader.DownloadAsync(new CancellationToken()).Wait(); } - private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) + private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger) { if (inputOptions.Id is null) { @@ -78,7 +78,7 @@ private static VideoDownloadOptions GetDownloadOptions(VideoDownloadArgs inputOp return Array.Empty(); }, - FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, + FileCollisionCallback = collisionHandler.HandleCollisionCallback, }; return downloadOptions; diff --git a/TwitchDownloaderCLI/Modes/MergeTs.cs b/TwitchDownloaderCLI/Modes/MergeTs.cs index db0755c9..32dbd0ea 100644 --- a/TwitchDownloaderCLI/Modes/MergeTs.cs +++ b/TwitchDownloaderCLI/Modes/MergeTs.cs @@ -14,20 +14,20 @@ internal static void Merge(TsMergeArgs inputOptions) progress.LogInfo("The TS merger is experimental and is subject to change without notice in future releases."); - var overwriteHandler = new FileOverwriteHandler(inputOptions); + var overwriteHandler = new FileCollisionHandler(inputOptions); var mergeOptions = GetMergeOptions(inputOptions, overwriteHandler); var tsMerger = new TsMerger(mergeOptions, progress); tsMerger.MergeAsync(new CancellationToken()).Wait(); } - private static TsMergeOptions GetMergeOptions(TsMergeArgs inputOptions, FileOverwriteHandler overwriteHandler) + private static TsMergeOptions GetMergeOptions(TsMergeArgs inputOptions, FileCollisionHandler collisionHandler) { TsMergeOptions mergeOptions = new() { OutputFile = inputOptions.OutputFile, InputFile = inputOptions.InputList, - FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, + FileCollisionCallback = collisionHandler.HandleCollisionCallback, }; return mergeOptions; diff --git a/TwitchDownloaderCLI/Modes/RenderChat.cs b/TwitchDownloaderCLI/Modes/RenderChat.cs index 98776ff7..377b1439 100644 --- a/TwitchDownloaderCLI/Modes/RenderChat.cs +++ b/TwitchDownloaderCLI/Modes/RenderChat.cs @@ -19,7 +19,7 @@ internal static void Render(ChatRenderArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); - var overwriteHandler = new FileOverwriteHandler(inputOptions); + var overwriteHandler = new FileCollisionHandler(inputOptions); var renderOptions = GetRenderOptions(inputOptions, overwriteHandler, progress); using var chatRenderer = new ChatRenderer(renderOptions, progress); @@ -27,7 +27,7 @@ internal static void Render(ChatRenderArgs inputOptions) chatRenderer.RenderVideoAsync(new CancellationToken()).Wait(); } - private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) + private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger) { ChatRenderOptions renderOptions = new() { @@ -95,7 +95,7 @@ private static ChatRenderOptions GetRenderOptions(ChatRenderArgs inputOptions, F DisperseCommentOffsets = inputOptions.DisperseCommentOffsets, AlternateMessageBackgrounds = inputOptions.AlternateMessageBackgrounds, AdjustUsernameVisibility = inputOptions.AdjustUsernameVisibility, - FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, + FileCollisionCallback = collisionHandler.HandleCollisionCallback, }; if (renderOptions.GenerateMask && renderOptions.BackgroundColor.Alpha == 255 && !(renderOptions.AlternateMessageBackgrounds! && renderOptions.AlternateBackgroundColor.Alpha != 255)) diff --git a/TwitchDownloaderCLI/Modes/UpdateChat.cs b/TwitchDownloaderCLI/Modes/UpdateChat.cs index 64fc6bb8..da54b4f0 100644 --- a/TwitchDownloaderCLI/Modes/UpdateChat.cs +++ b/TwitchDownloaderCLI/Modes/UpdateChat.cs @@ -16,7 +16,7 @@ internal static void Update(ChatUpdateArgs inputOptions) { var progress = new CliTaskProgress(inputOptions.LogLevel); - var overwriteHandler = new FileOverwriteHandler(inputOptions); + var overwriteHandler = new FileCollisionHandler(inputOptions); var updateOptions = GetUpdateOptions(inputOptions, overwriteHandler, progress); var chatUpdater = new ChatUpdater(updateOptions, progress); @@ -24,7 +24,7 @@ internal static void Update(ChatUpdateArgs inputOptions) chatUpdater.UpdateAsync(new CancellationToken()).Wait(); } - private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, FileOverwriteHandler overwriteHandler, ITaskLogger logger) + private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, FileCollisionHandler collisionHandler, ITaskLogger logger) { if (!File.Exists(inputOptions.InputFile)) { @@ -78,7 +78,7 @@ private static ChatUpdateOptions GetUpdateOptions(ChatUpdateArgs inputOptions, F StvEmotes = (bool)inputOptions.StvEmotes!, TextTimestampFormat = inputOptions.TimeFormat, TempFolder = inputOptions.TempFolder, - FileOverwriteCallback = overwriteHandler.HandleOverwriteCallback, + FileCollisionCallback = collisionHandler.HandleCollisionCallback, }; return updateOptions; diff --git a/TwitchDownloaderCLI/README.md b/TwitchDownloaderCLI/README.md index 77beed4f..0363464b 100644 --- a/TwitchDownloaderCLI/README.md +++ b/TwitchDownloaderCLI/README.md @@ -66,7 +66,7 @@ Path to FFmpeg executable. **--temp-path** Path to temporary folder for cache. -**--overwrite** +**--collision** (Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. ## Arguments for mode clipdownload @@ -93,7 +93,7 @@ Path to FFmpeg executable. **--temp-path** Path to temporary folder for cache. -**--overwrite** +**--collision** (Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. ## Arguments for mode chatdownload @@ -135,7 +135,7 @@ Time to trim ending. See [Time durations](#time-durations) for a more detailed e **--temp-path** Path to temporary folder for cache. -**--overwrite** +**--collision** (Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. ## Arguments for mode chatupdate @@ -177,7 +177,7 @@ Path to output file. File extension will be used to determine new chat type. Val **--temp-path** Path to temporary folder for cache. -**--overwrite** +**--collision** (Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. ## Arguments for mode chatrender @@ -332,7 +332,7 @@ Other = `1`, Broadcaster = `2`, Moderator = `4`, VIP = `8`, Subscriber = `16`, P **--scale-highlight-indent** (Default: `1.0`) Number to scale highlight indent size (sub messages). -**--overwrite** +**--collision** (Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. ## Arguments for mode ffmpeg @@ -359,7 +359,7 @@ Path a text file containing the absolute paths of the files to concatenate, sepa **-o / --output (REQUIRED)** File the program will output to. -**--overwrite** +**--collision** (Default: `Prompt`) Sets the handling of output file name collisions. Valid values are: `Overwrite`, `Exit`, `Rename`, `Prompt`. --- diff --git a/TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs b/TwitchDownloaderCLI/Tools/FileCollisionHandler.cs similarity index 78% rename from TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs rename to TwitchDownloaderCLI/Tools/FileCollisionHandler.cs index 8fe1d7a5..c9f9a784 100644 --- a/TwitchDownloaderCLI/Tools/FileOverwriteHandler.cs +++ b/TwitchDownloaderCLI/Tools/FileCollisionHandler.cs @@ -7,25 +7,25 @@ namespace TwitchDownloaderCLI.Tools { - internal class FileOverwriteHandler + internal class FileCollisionHandler { - private readonly IFileOverwriteArgs _overwriteArgs; + private readonly IFileCollisionArgs _collisionArgs; - public FileOverwriteHandler(IFileOverwriteArgs overwriteArgs) + public FileCollisionHandler(IFileCollisionArgs collisionArgs) { - _overwriteArgs = overwriteArgs; + _collisionArgs = collisionArgs; } [return: MaybeNull] - public FileInfo HandleOverwriteCallback(FileInfo fileInfo) + public FileInfo HandleCollisionCallback(FileInfo fileInfo) { - return _overwriteArgs.OverwriteBehavior switch + return _collisionArgs.OverwriteBehavior switch { OverwriteBehavior.Overwrite => fileInfo, OverwriteBehavior.Exit => null, OverwriteBehavior.Rename => FilenameService.GetNonCollidingName(fileInfo), OverwriteBehavior.Prompt => PromptUser(fileInfo), - _ => throw new ArgumentOutOfRangeException(nameof(_overwriteArgs.OverwriteBehavior), _overwriteArgs.OverwriteBehavior, null) + _ => throw new ArgumentOutOfRangeException(nameof(_collisionArgs.OverwriteBehavior), _collisionArgs.OverwriteBehavior, null) }; } diff --git a/TwitchDownloaderCore/ChatDownloader.cs b/TwitchDownloaderCore/ChatDownloader.cs index b2f8f786..ef94a768 100644 --- a/TwitchDownloaderCore/ChatDownloader.cs +++ b/TwitchDownloaderCore/ChatDownloader.cs @@ -251,7 +251,7 @@ public async Task DownloadAsync(CancellationToken cancellationToken) throw new NullReferenceException("Null or empty video/clip ID"); } - var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileCollisionCallback, _progress); downloadOptions.Filename = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. diff --git a/TwitchDownloaderCore/ChatRenderer.cs b/TwitchDownloaderCore/ChatRenderer.cs index 9420c365..62bb68f6 100644 --- a/TwitchDownloaderCore/ChatRenderer.cs +++ b/TwitchDownloaderCore/ChatRenderer.cs @@ -71,9 +71,9 @@ public ChatRenderer(ChatRenderOptions chatRenderOptions, ITaskProgress progress) public async Task RenderVideoAsync(CancellationToken cancellationToken) { - var outputFileInfo = TwitchHelper.ClaimFile(renderOptions.OutputFile, renderOptions.FileOverwriteCallback, _progress); + var outputFileInfo = TwitchHelper.ClaimFile(renderOptions.OutputFile, renderOptions.FileCollisionCallback, _progress); renderOptions.OutputFile = outputFileInfo.FullName; - var maskFileInfo = renderOptions.GenerateMask ? TwitchHelper.ClaimFile(renderOptions.MaskFile, renderOptions.FileOverwriteCallback, _progress) : null; + var maskFileInfo = renderOptions.GenerateMask ? TwitchHelper.ClaimFile(renderOptions.MaskFile, renderOptions.FileCollisionCallback, _progress) : null; // Open the destination files so that they exist in the filesystem. await using var outputFs = outputFileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.Read); diff --git a/TwitchDownloaderCore/ChatUpdater.cs b/TwitchDownloaderCore/ChatUpdater.cs index 5b50cc81..d2587d47 100644 --- a/TwitchDownloaderCore/ChatUpdater.cs +++ b/TwitchDownloaderCore/ChatUpdater.cs @@ -32,7 +32,7 @@ public ChatUpdater(ChatUpdateOptions updateOptions, ITaskProgress progress) public async Task UpdateAsync(CancellationToken cancellationToken) { - var outputFileInfo = TwitchHelper.ClaimFile(_updateOptions.OutputFile, _updateOptions.FileOverwriteCallback, _progress); + var outputFileInfo = TwitchHelper.ClaimFile(_updateOptions.OutputFile, _updateOptions.FileCollisionCallback, _progress); _updateOptions.OutputFile = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. diff --git a/TwitchDownloaderCore/ClipDownloader.cs b/TwitchDownloaderCore/ClipDownloader.cs index 0fc57000..72755acc 100644 --- a/TwitchDownloaderCore/ClipDownloader.cs +++ b/TwitchDownloaderCore/ClipDownloader.cs @@ -31,7 +31,7 @@ public ClipDownloader(ClipDownloadOptions clipDownloadOptions, ITaskProgress pro public async Task DownloadAsync(CancellationToken cancellationToken) { - var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileCollisionCallback, _progress); downloadOptions.Filename = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. diff --git a/TwitchDownloaderCore/Options/ChatDownloadOptions.cs b/TwitchDownloaderCore/Options/ChatDownloadOptions.cs index 3b5118dc..208db106 100644 --- a/TwitchDownloaderCore/Options/ChatDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/ChatDownloadOptions.cs @@ -36,6 +36,6 @@ public string FileExtension } } public string TempFolder { get; set; } - public Func FileOverwriteCallback { get; set; } = info => info; + public Func FileCollisionCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/ChatRenderOptions.cs b/TwitchDownloaderCore/Options/ChatRenderOptions.cs index 6a049121..eb1456c7 100644 --- a/TwitchDownloaderCore/Options/ChatRenderOptions.cs +++ b/TwitchDownloaderCore/Options/ChatRenderOptions.cs @@ -92,6 +92,6 @@ public string MaskFile public EmojiVendor EmojiVendor { get; set; } = EmojiVendor.GoogleNotoColor; public int[] TimestampWidths { get; set; } public bool AdjustUsernameVisibility { get; set; } - public Func FileOverwriteCallback { get; set; } = info => info; + public Func FileCollisionCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/ChatUpdateOptions.cs b/TwitchDownloaderCore/Options/ChatUpdateOptions.cs index 9e9b3332..c96932f9 100644 --- a/TwitchDownloaderCore/Options/ChatUpdateOptions.cs +++ b/TwitchDownloaderCore/Options/ChatUpdateOptions.cs @@ -35,6 +35,6 @@ public string FileExtension } } public string TempFolder { get; set; } - public Func FileOverwriteCallback { get; set; } = info => info; + public Func FileCollisionCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/ClipDownloadOptions.cs b/TwitchDownloaderCore/Options/ClipDownloadOptions.cs index 3a0a1620..f91542b3 100644 --- a/TwitchDownloaderCore/Options/ClipDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/ClipDownloadOptions.cs @@ -12,6 +12,6 @@ public class ClipDownloadOptions public string TempFolder { get; set; } public bool EncodeMetadata { get; set; } public string FfmpegPath { get; set; } - public Func FileOverwriteCallback { get; set; } = info => info; + public Func FileCollisionCallback { get; set; } = info => info; } } \ No newline at end of file diff --git a/TwitchDownloaderCore/Options/TsMergeOptions.cs b/TwitchDownloaderCore/Options/TsMergeOptions.cs index e44da205..f73ba626 100644 --- a/TwitchDownloaderCore/Options/TsMergeOptions.cs +++ b/TwitchDownloaderCore/Options/TsMergeOptions.cs @@ -7,6 +7,6 @@ public class TsMergeOptions { public string OutputFile { get; set; } public string InputFile { get; set; } - public Func FileOverwriteCallback { get; set; } = info => info; + public Func FileCollisionCallback { get; set; } = info => info; } } diff --git a/TwitchDownloaderCore/Options/VideoDownloadOptions.cs b/TwitchDownloaderCore/Options/VideoDownloadOptions.cs index 70df7f0b..6f76881f 100644 --- a/TwitchDownloaderCore/Options/VideoDownloadOptions.cs +++ b/TwitchDownloaderCore/Options/VideoDownloadOptions.cs @@ -18,6 +18,6 @@ public class VideoDownloadOptions public string FfmpegPath { get; set; } public string TempFolder { get; set; } public Func CacheCleanerCallback { get; set; } - public Func FileOverwriteCallback { get; set; } = info => info; + public Func FileCollisionCallback { get; set; } = info => info; } } \ No newline at end of file diff --git a/TwitchDownloaderCore/TsMerger.cs b/TwitchDownloaderCore/TsMerger.cs index 6ed8b2ab..4c8fed93 100644 --- a/TwitchDownloaderCore/TsMerger.cs +++ b/TwitchDownloaderCore/TsMerger.cs @@ -26,7 +26,7 @@ public async Task MergeAsync(CancellationToken cancellationToken) throw new FileNotFoundException("Input file does not exist"); } - var outputFileInfo = TwitchHelper.ClaimFile(mergeOptions.OutputFile, mergeOptions.FileOverwriteCallback, _progress); + var outputFileInfo = TwitchHelper.ClaimFile(mergeOptions.OutputFile, mergeOptions.FileCollisionCallback, _progress); mergeOptions.OutputFile = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. diff --git a/TwitchDownloaderCore/VideoDownloader.cs b/TwitchDownloaderCore/VideoDownloader.cs index f9817d46..5a109a3a 100644 --- a/TwitchDownloaderCore/VideoDownloader.cs +++ b/TwitchDownloaderCore/VideoDownloader.cs @@ -39,7 +39,7 @@ public VideoDownloader(VideoDownloadOptions videoDownloadOptions, ITaskProgress public async Task DownloadAsync(CancellationToken cancellationToken) { - var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileOverwriteCallback, _progress); + var outputFileInfo = TwitchHelper.ClaimFile(downloadOptions.Filename, downloadOptions.FileCollisionCallback, _progress); downloadOptions.Filename = outputFileInfo.FullName; // Open the destination file so that it exists in the filesystem. diff --git a/TwitchDownloaderWPF/Services/FileOverwriteService.cs b/TwitchDownloaderWPF/Services/FileCollisionService.cs similarity index 77% rename from TwitchDownloaderWPF/Services/FileOverwriteService.cs rename to TwitchDownloaderWPF/Services/FileCollisionService.cs index 6df0227a..703bbd97 100644 --- a/TwitchDownloaderWPF/Services/FileOverwriteService.cs +++ b/TwitchDownloaderWPF/Services/FileCollisionService.cs @@ -7,9 +7,9 @@ namespace TwitchDownloaderWPF.Services { - public static class FileOverwriteService + public static class FileCollisionService { - private enum OverwriteCommand + private enum CollisionCommand { Prompt, Overwrite, @@ -17,27 +17,27 @@ private enum OverwriteCommand Cancel } - private static OverwriteCommand _overwriteCommand = OverwriteCommand.Prompt; + private static CollisionCommand _collisionCommand = CollisionCommand.Prompt; [return: MaybeNull] - public static FileInfo HandleOverwriteCallback(FileInfo fileInfo, Window owner) + public static FileInfo HandleCollisionCallback(FileInfo fileInfo, Window owner) { - if (_overwriteCommand is not OverwriteCommand.Prompt) + if (_collisionCommand is not CollisionCommand.Prompt) { - return GetResult(fileInfo, _overwriteCommand); + return GetResult(fileInfo, _collisionCommand); } var result = ShowDialog(fileInfo, owner, out var rememberChoice); if (rememberChoice) { - _overwriteCommand = result; + _collisionCommand = result; } return GetResult(fileInfo, result); } - private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out bool rememberChoice) + private static CollisionCommand ShowDialog(FileInfo fileInfo, Window owner, out bool rememberChoice) { using var dialog = new TaskDialog(); dialog.WindowTitle = Translations.Strings.TitleFileAlreadyExists; @@ -70,26 +70,26 @@ private static OverwriteCommand ShowDialog(FileInfo fileInfo, Window owner, out rememberChoice = dialog.IsVerificationChecked; if (buttonResult == overwriteButton) - return OverwriteCommand.Overwrite; + return CollisionCommand.Overwrite; if (buttonResult == renameButton) - return OverwriteCommand.Rename; + return CollisionCommand.Rename; if (buttonResult == cancelButton) - return OverwriteCommand.Cancel; + return CollisionCommand.Cancel; // This should never happen throw new ArgumentOutOfRangeException(); } [return: MaybeNull] - private static FileInfo GetResult(FileInfo fileInfo, OverwriteCommand command) + private static FileInfo GetResult(FileInfo fileInfo, CollisionCommand command) { return command switch { - OverwriteCommand.Overwrite => fileInfo, - OverwriteCommand.Rename => FilenameService.GetNonCollidingName(fileInfo), - OverwriteCommand.Cancel => null, + CollisionCommand.Overwrite => fileInfo, + CollisionCommand.Rename => FilenameService.GetNonCollidingName(fileInfo), + CollisionCommand.Cancel => null, _ => throw new ArgumentOutOfRangeException(nameof(command), command, null) }; } diff --git a/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs b/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs index 8a7e1f57..366265e7 100644 --- a/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs +++ b/TwitchDownloaderWPF/WindowQueueOptions.xaml.cs @@ -100,9 +100,9 @@ public WindowQueueOptions(List dataList) textFolder.Text = queueFolder; } - private FileInfo HandleOverwriteCallback(FileInfo fileInfo) + private FileInfo HandleFileCollisionCallback(FileInfo fileInfo) { - return Dispatcher.Invoke(() => FileOverwriteService.HandleOverwriteCallback(fileInfo, Application.Current.MainWindow)); + return Dispatcher.Invoke(() => FileCollisionService.HandleCollisionCallback(fileInfo, Application.Current.MainWindow)); } private void btnQueue_Click(object sender, RoutedEventArgs e) @@ -119,7 +119,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) } VideoDownloadOptions downloadOptions = vodDownloadPage.GetOptions(null, textFolder.Text); - downloadOptions.FileOverwriteCallback = HandleOverwriteCallback; + downloadOptions.FileCollisionCallback = HandleFileCollisionCallback; VodDownloadTask downloadTask = new VodDownloadTask { @@ -149,7 +149,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.DownloadFormat = ChatFormat.Text; chatOptions.EmbedData = checkEmbed.IsChecked.GetValueOrDefault(); chatOptions.Filename = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(downloadOptions.Filename) + "." + chatOptions.DownloadFormat); - chatOptions.FileOverwriteCallback = HandleOverwriteCallback; + chatOptions.FileCollisionCallback = HandleFileCollisionCallback; if (downloadOptions.TrimBeginning) { @@ -188,7 +188,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) renderOptions.OutputFile = Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower()); } renderOptions.InputFile = chatOptions.Filename; - renderOptions.FileOverwriteCallback = HandleOverwriteCallback; + renderOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -234,7 +234,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) TempFolder = Settings.Default.TempPath, EncodeMetadata = clipDownloadPage.CheckMetadata.IsChecked!.Value, FfmpegPath = "ffmpeg", - FileOverwriteCallback = HandleOverwriteCallback, + FileCollisionCallback = HandleFileCollisionCallback, }; ClipDownloadTask downloadTask = new ClipDownloadTask @@ -268,7 +268,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.Filename = Path.Combine(folderPath, FilenameService.GetFilename(Settings.Default.TemplateChat, downloadTask.Info.Title, chatOptions.Id, clipDownloadPage.currentVideoTime, clipDownloadPage.textStreamer.Text, TimeSpan.Zero, clipDownloadPage.clipLength, clipDownloadPage.viewCount.ToString(), clipDownloadPage.game) + "." + chatOptions.FileExtension); - chatOptions.FileOverwriteCallback = HandleOverwriteCallback; + chatOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatDownloadTask chatTask = new ChatDownloadTask { @@ -295,7 +295,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) renderOptions.OutputFile = Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower()); } renderOptions.InputFile = chatOptions.Filename; - renderOptions.FileOverwriteCallback = HandleOverwriteCallback; + renderOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -334,7 +334,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.TrimBeginning ? TimeSpan.FromSeconds(chatOptions.TrimBeginningTime) : TimeSpan.Zero, chatOptions.TrimEnding ? TimeSpan.FromSeconds(chatOptions.TrimEndingTime) : chatDownloadPage.vodLength, chatDownloadPage.viewCount.ToString(), chatDownloadPage.game) + "." + chatOptions.FileExtension); - chatOptions.FileOverwriteCallback = HandleOverwriteCallback; + chatOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatDownloadTask chatTask = new ChatDownloadTask { @@ -356,7 +356,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) { ChatRenderOptions renderOptions = MainWindow.pageChatRender.GetOptions(Path.ChangeExtension(chatOptions.Filename.Replace(".gz", ""), '.' + MainWindow.pageChatRender.comboFormat.Text.ToLower())); renderOptions.InputFile = chatOptions.Filename; - renderOptions.FileOverwriteCallback = HandleOverwriteCallback; + renderOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -394,7 +394,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) chatOptions.TrimBeginning ? TimeSpan.FromSeconds(chatOptions.TrimBeginningTime) : TimeSpan.Zero, chatOptions.TrimEnding ? TimeSpan.FromSeconds(chatOptions.TrimEndingTime) : chatUpdatePage.VideoLength, chatUpdatePage.ViewCount.ToString(), chatUpdatePage.Game) + "." + chatOptions.FileExtension); - chatOptions.FileOverwriteCallback = HandleOverwriteCallback; + chatOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatUpdateTask chatTask = new ChatUpdateTask { @@ -430,7 +430,7 @@ private void btnQueue_Click(object sender, RoutedEventArgs e) string filePath = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(fileName) + "." + fileFormat.ToLower()); ChatRenderOptions renderOptions = MainWindow.pageChatRender.GetOptions(filePath); renderOptions.InputFile = fileName; - renderOptions.FileOverwriteCallback = HandleOverwriteCallback; + renderOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatRenderTask renderTask = new ChatRenderTask { @@ -490,7 +490,7 @@ private void EnqueueDataList() ThrottleKib = Settings.Default.DownloadThrottleEnabled ? Settings.Default.MaximumBandwidthKib : -1, - FileOverwriteCallback = HandleOverwriteCallback, + FileCollisionCallback = HandleFileCollisionCallback, }; downloadOptions.Filename = Path.Combine(folderPath, FilenameService.GetFilename(Settings.Default.TemplateVod, taskData.Title, taskData.Id, taskData.Time, taskData.Streamer, downloadOptions.TrimBeginning ? downloadOptions.TrimBeginningTime : TimeSpan.Zero, @@ -527,7 +527,7 @@ private void EnqueueDataList() TempFolder = Settings.Default.TempPath, EncodeMetadata = Settings.Default.EncodeClipMetadata, FfmpegPath = "ffmpeg", - FileOverwriteCallback = HandleOverwriteCallback, + FileCollisionCallback = HandleFileCollisionCallback, }; ClipDownloadTask downloadTask = new ClipDownloadTask @@ -558,7 +558,7 @@ private void EnqueueDataList() Id = taskData.Id, TrimBeginning = false, TrimEnding = false, - FileOverwriteCallback = HandleOverwriteCallback, + FileCollisionCallback = HandleFileCollisionCallback, }; if (radioJson.IsChecked == true) downloadOptions.DownloadFormat = ChatFormat.Json; @@ -597,7 +597,7 @@ private void EnqueueDataList() renderOptions.OutputFile = Path.ChangeExtension(downloadOptions.Filename.Replace(".gz", ""), " - CHAT." + MainWindow.pageChatRender.comboFormat.Text.ToLower()); } renderOptions.InputFile = downloadOptions.Filename; - renderOptions.FileOverwriteCallback = HandleOverwriteCallback; + renderOptions.FileCollisionCallback = HandleFileCollisionCallback; ChatRenderTask renderTask = new ChatRenderTask { From 78963803e9fe56563448837a2de9ed04613b77b6 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:46:43 -0400 Subject: [PATCH 17/21] Missed a few spots --- TwitchDownloaderCLI/Modes/DownloadChat.cs | 4 ++-- TwitchDownloaderCLI/Modes/DownloadClip.cs | 4 ++-- TwitchDownloaderCLI/Modes/DownloadVideo.cs | 4 ++-- TwitchDownloaderCLI/Modes/MergeTs.cs | 4 ++-- TwitchDownloaderCLI/Modes/RenderChat.cs | 4 ++-- TwitchDownloaderCLI/Modes/UpdateChat.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/TwitchDownloaderCLI/Modes/DownloadChat.cs b/TwitchDownloaderCLI/Modes/DownloadChat.cs index d886d908..c799ee01 100644 --- a/TwitchDownloaderCLI/Modes/DownloadChat.cs +++ b/TwitchDownloaderCLI/Modes/DownloadChat.cs @@ -16,8 +16,8 @@ internal static void Download(ChatDownloadArgs inputOptions) { var progress = new CliTaskProgress(inputOptions.LogLevel); - var overwriteHandler = new FileCollisionHandler(inputOptions); - var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); + var collisionHandler = new FileCollisionHandler(inputOptions); + var downloadOptions = GetDownloadOptions(inputOptions, collisionHandler, progress); var chatDownloader = new ChatDownloader(downloadOptions, progress); chatDownloader.DownloadAsync(CancellationToken.None).Wait(); diff --git a/TwitchDownloaderCLI/Modes/DownloadClip.cs b/TwitchDownloaderCLI/Modes/DownloadClip.cs index 860b350b..04da80e7 100644 --- a/TwitchDownloaderCLI/Modes/DownloadClip.cs +++ b/TwitchDownloaderCLI/Modes/DownloadClip.cs @@ -21,8 +21,8 @@ internal static void Download(ClipDownloadArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); } - var overwriteHandler = new FileCollisionHandler(inputOptions); - var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); + var collisionHandler = new FileCollisionHandler(inputOptions); + var downloadOptions = GetDownloadOptions(inputOptions, collisionHandler, progress); var clipDownloader = new ClipDownloader(downloadOptions, progress); clipDownloader.DownloadAsync(new CancellationToken()).Wait(); diff --git a/TwitchDownloaderCLI/Modes/DownloadVideo.cs b/TwitchDownloaderCLI/Modes/DownloadVideo.cs index af7a697e..0e3bc722 100644 --- a/TwitchDownloaderCLI/Modes/DownloadVideo.cs +++ b/TwitchDownloaderCLI/Modes/DownloadVideo.cs @@ -19,8 +19,8 @@ internal static void Download(VideoDownloadArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); - var overwriteHandler = new FileCollisionHandler(inputOptions); - var downloadOptions = GetDownloadOptions(inputOptions, overwriteHandler, progress); + var collisionHandler = new FileCollisionHandler(inputOptions); + var downloadOptions = GetDownloadOptions(inputOptions, collisionHandler, progress); var videoDownloader = new VideoDownloader(downloadOptions, progress); videoDownloader.DownloadAsync(new CancellationToken()).Wait(); diff --git a/TwitchDownloaderCLI/Modes/MergeTs.cs b/TwitchDownloaderCLI/Modes/MergeTs.cs index 32dbd0ea..24e1f502 100644 --- a/TwitchDownloaderCLI/Modes/MergeTs.cs +++ b/TwitchDownloaderCLI/Modes/MergeTs.cs @@ -14,8 +14,8 @@ internal static void Merge(TsMergeArgs inputOptions) progress.LogInfo("The TS merger is experimental and is subject to change without notice in future releases."); - var overwriteHandler = new FileCollisionHandler(inputOptions); - var mergeOptions = GetMergeOptions(inputOptions, overwriteHandler); + var collisionHandler = new FileCollisionHandler(inputOptions); + var mergeOptions = GetMergeOptions(inputOptions, collisionHandler); var tsMerger = new TsMerger(mergeOptions, progress); tsMerger.MergeAsync(new CancellationToken()).Wait(); diff --git a/TwitchDownloaderCLI/Modes/RenderChat.cs b/TwitchDownloaderCLI/Modes/RenderChat.cs index 377b1439..b107f903 100644 --- a/TwitchDownloaderCLI/Modes/RenderChat.cs +++ b/TwitchDownloaderCLI/Modes/RenderChat.cs @@ -19,8 +19,8 @@ internal static void Render(ChatRenderArgs inputOptions) FfmpegHandler.DetectFfmpeg(inputOptions.FfmpegPath, progress); - var overwriteHandler = new FileCollisionHandler(inputOptions); - var renderOptions = GetRenderOptions(inputOptions, overwriteHandler, progress); + var collisionHandler = new FileCollisionHandler(inputOptions); + var renderOptions = GetRenderOptions(inputOptions, collisionHandler, progress); using var chatRenderer = new ChatRenderer(renderOptions, progress); chatRenderer.ParseJsonAsync().Wait(); diff --git a/TwitchDownloaderCLI/Modes/UpdateChat.cs b/TwitchDownloaderCLI/Modes/UpdateChat.cs index da54b4f0..dd17007d 100644 --- a/TwitchDownloaderCLI/Modes/UpdateChat.cs +++ b/TwitchDownloaderCLI/Modes/UpdateChat.cs @@ -16,8 +16,8 @@ internal static void Update(ChatUpdateArgs inputOptions) { var progress = new CliTaskProgress(inputOptions.LogLevel); - var overwriteHandler = new FileCollisionHandler(inputOptions); - var updateOptions = GetUpdateOptions(inputOptions, overwriteHandler, progress); + var collisionHandler = new FileCollisionHandler(inputOptions); + var updateOptions = GetUpdateOptions(inputOptions, collisionHandler, progress); var chatUpdater = new ChatUpdater(updateOptions, progress); chatUpdater.ParseJsonAsync().Wait(); From 58da1b39e07f989536ddcbb6c824293127a362de Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:49:02 -0400 Subject: [PATCH 18/21] Update wording --- TwitchDownloaderCLI/Tools/FileCollisionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchDownloaderCLI/Tools/FileCollisionHandler.cs b/TwitchDownloaderCLI/Tools/FileCollisionHandler.cs index c9f9a784..770da3ab 100644 --- a/TwitchDownloaderCLI/Tools/FileCollisionHandler.cs +++ b/TwitchDownloaderCLI/Tools/FileCollisionHandler.cs @@ -32,7 +32,7 @@ public FileInfo HandleCollisionCallback(FileInfo fileInfo) [return: MaybeNull] private static FileInfo PromptUser(FileInfo fileInfo) { - Console.WriteLine($"{fileInfo.FullName} already exists."); + Console.WriteLine($"The file '{fileInfo.FullName}' already exists."); while (true) { From 7ccb1ef6cb46ee47f3baacbb11bc141259f649ae Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:11:35 -0400 Subject: [PATCH 19/21] Use warning icon to match file explorer overwrite prompt --- TwitchDownloaderWPF/Services/FileCollisionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchDownloaderWPF/Services/FileCollisionService.cs b/TwitchDownloaderWPF/Services/FileCollisionService.cs index 703bbd97..f74924b3 100644 --- a/TwitchDownloaderWPF/Services/FileCollisionService.cs +++ b/TwitchDownloaderWPF/Services/FileCollisionService.cs @@ -43,7 +43,7 @@ private static CollisionCommand ShowDialog(FileInfo fileInfo, Window owner, out dialog.WindowTitle = Translations.Strings.TitleFileAlreadyExists; dialog.MainInstruction = string.Format(Translations.Strings.FileAlreadyExistsHeader, fileInfo.Name); dialog.Content = string.Format(Translations.Strings.FileAlreadyExistsBody, $"{fileInfo.FullName}"); - dialog.MainIcon = TaskDialogIcon.Information; + dialog.MainIcon = TaskDialogIcon.Warning; dialog.EnableHyperlinks = true; dialog.HyperlinkClicked += Hyperlink_OnClicked; From 5c23ca795fdca3b05bd552472f4f478dba69ea72 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:49:06 -0400 Subject: [PATCH 20/21] Make "remember my choice" wording more clear --- TwitchDownloaderWPF/Translations/Strings.Designer.cs | 2 +- TwitchDownloaderWPF/Translations/Strings.es.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.fr.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.it.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.ja.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.pl.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.pt-br.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.ru.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.tr.resx | 2 +- TwitchDownloaderWPF/Translations/Strings.uk.resx | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/TwitchDownloaderWPF/Translations/Strings.Designer.cs b/TwitchDownloaderWPF/Translations/Strings.Designer.cs index f0822eef..e777c05e 100644 --- a/TwitchDownloaderWPF/Translations/Strings.Designer.cs +++ b/TwitchDownloaderWPF/Translations/Strings.Designer.cs @@ -951,7 +951,7 @@ public static string FileAlreadyExistsOverwriteDescription { } /// - /// Looks up a localized string similar to Remember my choice. + /// Looks up a localized string similar to Remember my choice for this session. /// public static string FileAlreadyExistsRememberMyChoice { get { diff --git a/TwitchDownloaderWPF/Translations/Strings.es.resx b/TwitchDownloaderWPF/Translations/Strings.es.resx index 8b99b3ec..20651baa 100644 --- a/TwitchDownloaderWPF/Translations/Strings.es.resx +++ b/TwitchDownloaderWPF/Translations/Strings.es.resx @@ -926,6 +926,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session diff --git a/TwitchDownloaderWPF/Translations/Strings.fr.resx b/TwitchDownloaderWPF/Translations/Strings.fr.resx index 0a01bc10..4387c5cf 100644 --- a/TwitchDownloaderWPF/Translations/Strings.fr.resx +++ b/TwitchDownloaderWPF/Translations/Strings.fr.resx @@ -925,6 +925,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.it.resx b/TwitchDownloaderWPF/Translations/Strings.it.resx index 5786b9b4..4eefecd3 100644 --- a/TwitchDownloaderWPF/Translations/Strings.it.resx +++ b/TwitchDownloaderWPF/Translations/Strings.it.resx @@ -926,6 +926,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session diff --git a/TwitchDownloaderWPF/Translations/Strings.ja.resx b/TwitchDownloaderWPF/Translations/Strings.ja.resx index f255c0ae..98320801 100644 --- a/TwitchDownloaderWPF/Translations/Strings.ja.resx +++ b/TwitchDownloaderWPF/Translations/Strings.ja.resx @@ -924,6 +924,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.pl.resx b/TwitchDownloaderWPF/Translations/Strings.pl.resx index 58111474..06119d9a 100644 --- a/TwitchDownloaderWPF/Translations/Strings.pl.resx +++ b/TwitchDownloaderWPF/Translations/Strings.pl.resx @@ -925,6 +925,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.pt-br.resx b/TwitchDownloaderWPF/Translations/Strings.pt-br.resx index 89769a46..1ee7a5f5 100644 --- a/TwitchDownloaderWPF/Translations/Strings.pt-br.resx +++ b/TwitchDownloaderWPF/Translations/Strings.pt-br.resx @@ -924,6 +924,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.resx b/TwitchDownloaderWPF/Translations/Strings.resx index 6435cc62..10c1726f 100644 --- a/TwitchDownloaderWPF/Translations/Strings.resx +++ b/TwitchDownloaderWPF/Translations/Strings.resx @@ -924,6 +924,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.ru.resx b/TwitchDownloaderWPF/Translations/Strings.ru.resx index e5ffa584..1445a5e2 100644 --- a/TwitchDownloaderWPF/Translations/Strings.ru.resx +++ b/TwitchDownloaderWPF/Translations/Strings.ru.resx @@ -925,6 +925,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.tr.resx b/TwitchDownloaderWPF/Translations/Strings.tr.resx index a076387a..694d1a60 100644 --- a/TwitchDownloaderWPF/Translations/Strings.tr.resx +++ b/TwitchDownloaderWPF/Translations/Strings.tr.resx @@ -926,6 +926,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session \ No newline at end of file diff --git a/TwitchDownloaderWPF/Translations/Strings.uk.resx b/TwitchDownloaderWPF/Translations/Strings.uk.resx index 5621f47d..7608071c 100644 --- a/TwitchDownloaderWPF/Translations/Strings.uk.resx +++ b/TwitchDownloaderWPF/Translations/Strings.uk.resx @@ -925,6 +925,6 @@ The task will be canceled. - Remember my choice + Remember my choice for this session From a6a81cb533d98aaa03ffc38608e186c7067dc53a Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:49:19 -0400 Subject: [PATCH 21/21] Fix missing zh-cn translations --- .../Translations/Strings.zh-cn.resx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/TwitchDownloaderWPF/Translations/Strings.zh-cn.resx b/TwitchDownloaderWPF/Translations/Strings.zh-cn.resx index 54313e1a..05889029 100644 --- a/TwitchDownloaderWPF/Translations/Strings.zh-cn.resx +++ b/TwitchDownloaderWPF/Translations/Strings.zh-cn.resx @@ -899,4 +899,34 @@ Others + + File already exists + + + {0} already exists. + + + The file {0} already exists. Do you want to overwrite it? + + + Overwrite + + + The existing file will be overwritten. + + + Rename + + + The new file will be renamed. + + + Cancel + + + The task will be canceled. + + + Remember my choice for this session +