From 803dc96700dd07d539917813af122ed62149d0d5 Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 15:03:19 +0200 Subject: [PATCH 1/8] Upgrade NewtonSoft.Json package to 13.0.3 --- BananaSplit/BananaSplit.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BananaSplit/BananaSplit.csproj b/BananaSplit/BananaSplit.csproj index 401bc63..6d1e585 100644 --- a/BananaSplit/BananaSplit.csproj +++ b/BananaSplit/BananaSplit.csproj @@ -14,7 +14,7 @@ - + From 844d6ec918b657ac684bdcd3e7c63aca450ea254 Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 15:04:25 +0200 Subject: [PATCH 2/8] Use en-US as CurrentCulture for ease of paramter formatting. --- BananaSplit/FFMPEG.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BananaSplit/FFMPEG.cs b/BananaSplit/FFMPEG.cs index 21d9a97..db81784 100644 --- a/BananaSplit/FFMPEG.cs +++ b/BananaSplit/FFMPEG.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; namespace BananaSplit { @@ -139,7 +141,7 @@ public void EncodeSegments(string source, string destination, string arguments, public ICollection DetectBlackFrameIntervals(string filePath, double blackFrameDuration, double blackFrameThreshold, double blackFramePixelThreshold, DataReceivedEventHandler outputHandler) { var frames = new List(); - + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); Process.StartInfo.FileName = "ffmpeg.exe"; Process.StartInfo.RedirectStandardError = true; Process.StartInfo.Arguments = $"-i \"{filePath}\" -vf blackdetect=d={blackFrameDuration}:pic_th={blackFrameThreshold}:pix_th={blackFramePixelThreshold} -an -f null -y /NUL"; From 9df4f920b3184377d746440fbf66168c69b3dba7 Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 15:05:34 +0200 Subject: [PATCH 3/8] Match episodes with multiepisode formatting (e.g. S02E05-E08) when renumbering --- BananaSplit/MainForm.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BananaSplit/MainForm.cs b/BananaSplit/MainForm.cs index 016591a..7c5ec6d 100644 --- a/BananaSplit/MainForm.cs +++ b/BananaSplit/MainForm.cs @@ -1,5 +1,6 @@ using BananaSplit.Extensions; using Microsoft.WindowsAPICodePack.Taskbar; + using System; using System.Collections.Generic; using System.Data; @@ -757,7 +758,7 @@ private string GetNewName(string fileName, int index) name = name.Replace(oldText, newText); break; case RenameType.Increment: - string numPattern = @"(S\d{2}E)(?'num'\d{2})"; + string numPattern = @"(S\d{2}E)(?'num'\d{2})(-E\d{2})?"; name = Regex.Replace( name, numPattern, From 61120d889715e1f4e1279beda2ab6e63da5db6a9 Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 15:06:09 +0200 Subject: [PATCH 4/8] Allow increment multiplier to be set to 0, for regular incremental renumbering. --- BananaSplit/SettingsForm.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BananaSplit/SettingsForm.Designer.cs b/BananaSplit/SettingsForm.Designer.cs index 430beb5..ad92566 100644 --- a/BananaSplit/SettingsForm.Designer.cs +++ b/BananaSplit/SettingsForm.Designer.cs @@ -308,7 +308,7 @@ private void InitializeComponent() // MultiplierInput.Location = new System.Drawing.Point(285, 249); MultiplierInput.Maximum = new decimal(new int[] { 1000, 0, 0, 0 }); - MultiplierInput.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + MultiplierInput.Minimum = new decimal(new int[] { 0, 0, 0, 0 }); MultiplierInput.Name = "MultiplierInput"; MultiplierInput.Size = new System.Drawing.Size(131, 39); MultiplierInput.TabIndex = 25; From 816699e13186a57de0fbcdc42006f5c2ad2e01db Mon Sep 17 00:00:00 2001 From: David Robinson Date: Wed, 4 Jun 2025 22:00:47 +0200 Subject: [PATCH 5/8] Upgrade to .net 8 --- BananaSplit/BananaSplit.csproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/BananaSplit/BananaSplit.csproj b/BananaSplit/BananaSplit.csproj index 6d1e585..8f7ba3a 100644 --- a/BananaSplit/BananaSplit.csproj +++ b/BananaSplit/BananaSplit.csproj @@ -1,7 +1,7 @@ - + WinExe - netcoreapp3.1 + net8.0-windows true 3.2.0 @@ -12,8 +12,6 @@ - - From 31065a7aba02fcec8ebc41cba52fcce3c689b69e Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 11:58:03 +0200 Subject: [PATCH 6/8] Remove WindowsApiCodepack dependency. --- BananaSplit/BananaSplit.csproj | 1 - BananaSplit/MainForm.cs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/BananaSplit/BananaSplit.csproj b/BananaSplit/BananaSplit.csproj index 8f7ba3a..9a79cf6 100644 --- a/BananaSplit/BananaSplit.csproj +++ b/BananaSplit/BananaSplit.csproj @@ -13,7 +13,6 @@ - diff --git a/BananaSplit/MainForm.cs b/BananaSplit/MainForm.cs index 7c5ec6d..be92c07 100644 --- a/BananaSplit/MainForm.cs +++ b/BananaSplit/MainForm.cs @@ -1,5 +1,4 @@ using BananaSplit.Extensions; -using Microsoft.WindowsAPICodePack.Taskbar; using System; using System.Collections.Generic; @@ -377,9 +376,6 @@ private void SetStatusBarProgressBarValue(int value, int maximum) value = maximum - 1; StatusBarProgressBar.Value = value; - - TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal); - TaskbarManager.Instance.SetProgressValue(value, maximum); } ) ); @@ -395,8 +391,6 @@ private void ClearStatusBarProgressBarValue() StatusBarProgressBar.Maximum = 1; StatusBarProgressBar.Value = 0; - TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress); - TaskbarManager.Instance.SetProgressValue(0, 1); } ) ); From 573ff0c0ebed6ce00fc1a5e108fb69d9f029b63c Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 11:58:51 +0200 Subject: [PATCH 7/8] Match episodes numbers for seasons with more than 99 episodes. --- BananaSplit/MainForm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BananaSplit/MainForm.cs b/BananaSplit/MainForm.cs index be92c07..e59cca9 100644 --- a/BananaSplit/MainForm.cs +++ b/BananaSplit/MainForm.cs @@ -752,7 +752,7 @@ private string GetNewName(string fileName, int index) name = name.Replace(oldText, newText); break; case RenameType.Increment: - string numPattern = @"(S\d{2}E)(?'num'\d{2})(-E\d{2})?"; + string numPattern = @"(S\d{2,}E)(?'num'\d{2,})(-E\d{2,})?"; name = Regex.Replace( name, numPattern, From 969527098a6b78498cc5379ee697c23d6cbceea3 Mon Sep 17 00:00:00 2001 From: David Robinson Date: Thu, 5 Jun 2025 23:58:03 +0200 Subject: [PATCH 8/8] WIP: Cleanup --- .gitattributes | 0 BananaSplit.sln | 66 +- BananaSplit/AutoMapperProfile.cs | 15 + BananaSplit/BananaSplit.csproj | 5 + BananaSplit/BlackFrame.cs | 7 - BananaSplit/FFMPEG.cs | 318 ++++---- BananaSplit/Iconicon-Veggies-Bananas.512.ico | Bin 0 -> 28060 bytes BananaSplit/LogForm.cs | 13 + BananaSplit/MKVToolNix.cs | 151 ++-- BananaSplit/MainForm.Designer.cs | 108 +-- BananaSplit/MainForm.cs | 802 ++----------------- BananaSplit/MainForm.resx | 535 ++++++++++++- BananaSplit/Processor.cs | 179 +++++ BananaSplit/Program.cs | 30 +- BananaSplit/QueueItem.cs | 102 +-- BananaSplit/QueueManager.cs | 172 ++++ BananaSplit/ReferenceFrame.cs | 3 +- BananaSplit/Renamer.cs | 139 ++++ BananaSplit/Scanner.cs | 154 ++++ BananaSplit/Settings.cs | 108 +-- BananaSplit/SettingsForm.Designer.cs | 224 +++--- BananaSplit/SettingsForm.cs | 146 ++-- BananaSplit/SettingsForm.resx | 62 +- BananaSplit/StatusBarManager.cs | 57 ++ BananaSplit/Utilities.cs | 11 +- Iconicon-Veggies-Bananas.512.ico | Bin 0 -> 28060 bytes Test/Test.csproj | 23 + Test/UnitTest1.cs | 10 + 28 files changed, 2019 insertions(+), 1421 deletions(-) create mode 100644 .gitattributes create mode 100644 BananaSplit/AutoMapperProfile.cs create mode 100644 BananaSplit/Iconicon-Veggies-Bananas.512.ico create mode 100644 BananaSplit/Processor.cs create mode 100644 BananaSplit/QueueManager.cs create mode 100644 BananaSplit/Renamer.cs create mode 100644 BananaSplit/Scanner.cs create mode 100644 BananaSplit/StatusBarManager.cs create mode 100644 Iconicon-Veggies-Bananas.512.ico create mode 100644 Test/Test.csproj create mode 100644 Test/UnitTest1.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e69de29 diff --git a/BananaSplit.sln b/BananaSplit.sln index 8bc7b0f..3801330 100644 --- a/BananaSplit.sln +++ b/BananaSplit.sln @@ -1,25 +1,41 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29728.190 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BananaSplit", "BananaSplit\BananaSplit.csproj", "{EC918B5D-EFE5-48EA-8F51-C05574198135}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EC918B5D-EFE5-48EA-8F51-C05574198135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EC918B5D-EFE5-48EA-8F51-C05574198135}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EC918B5D-EFE5-48EA-8F51-C05574198135}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EC918B5D-EFE5-48EA-8F51-C05574198135}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CA107A36-4967-4BBF-81C0-682AEBB81A55} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36109.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BananaSplit", "BananaSplit\BananaSplit.csproj", "{EC918B5D-EFE5-48EA-8F51-C05574198135}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{BAD3E14B-AA4E-4853-9557-AC2C83A226E0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Debug|x64.ActiveCfg = Debug|x64 + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Debug|x64.Build.0 = Debug|x64 + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Release|Any CPU.Build.0 = Release|Any CPU + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Release|x64.ActiveCfg = Release|x64 + {EC918B5D-EFE5-48EA-8F51-C05574198135}.Release|x64.Build.0 = Release|x64 + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Debug|x64.ActiveCfg = Debug|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Debug|x64.Build.0 = Debug|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Release|Any CPU.Build.0 = Release|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Release|x64.ActiveCfg = Release|Any CPU + {BAD3E14B-AA4E-4853-9557-AC2C83A226E0}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CA107A36-4967-4BBF-81C0-682AEBB81A55} + EndGlobalSection +EndGlobal diff --git a/BananaSplit/AutoMapperProfile.cs b/BananaSplit/AutoMapperProfile.cs new file mode 100644 index 0000000..fb37bb8 --- /dev/null +++ b/BananaSplit/AutoMapperProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BananaSplit; +public class AutoMapperProfile: Profile +{ + public AutoMapperProfile() + { + CreateMap(); + } +} diff --git a/BananaSplit/BananaSplit.csproj b/BananaSplit/BananaSplit.csproj index 9a79cf6..3eec380 100644 --- a/BananaSplit/BananaSplit.csproj +++ b/BananaSplit/BananaSplit.csproj @@ -4,6 +4,8 @@ net8.0-windows true 3.2.0 + Iconicon-Veggies-Bananas.512.ico + AnyCPU;x64 @@ -12,6 +14,9 @@ + + + diff --git a/BananaSplit/BlackFrame.cs b/BananaSplit/BlackFrame.cs index 6633744..53636f7 100644 --- a/BananaSplit/BlackFrame.cs +++ b/BananaSplit/BlackFrame.cs @@ -18,12 +18,5 @@ public BlackFrame() { Id = Guid.NewGuid(); } - - public TimeSpan GetMiddle() - { - var halfDuration = new TimeSpan(Duration.Ticks / 2); - - return End.Subtract(halfDuration); - } } } diff --git a/BananaSplit/FFMPEG.cs b/BananaSplit/FFMPEG.cs index db81784..443ddaf 100644 --- a/BananaSplit/FFMPEG.cs +++ b/BananaSplit/FFMPEG.cs @@ -9,108 +9,105 @@ namespace BananaSplit { - public class FFMPEG - { - private Process Process { get; set; } - - public FFMPEG() + public partial class Ffmpeg + { + private const string FfMpeg = "ffmpeg.exe"; + private const string FfProbe = "ffprobe.exe"; + + private Process FfProcess { get; set; } + + public Ffmpeg() { - Process = new Process(); + FfProcess = new Process(); - Process.StartInfo.UseShellExecute = false; - Process.StartInfo.RedirectStandardError = true; - Process.StartInfo.FileName = "ffmpeg.exe"; - Process.StartInfo.CreateNoWindow = true; + FfProcess.StartInfo.UseShellExecute = false; + FfProcess.StartInfo.RedirectStandardError = true; + FfProcess.StartInfo.FileName = FfMpeg; + FfProcess.StartInfo.CreateNoWindow = true; } public bool IsMatroska(string filePath, DataReceivedEventHandler outputHandler) { - Process.StartInfo.FileName = "ffprobe.exe"; - Process.StartInfo.Arguments = $"\"{filePath}\""; + FfProcess.StartInfo.FileName = FfProbe; + FfProcess.StartInfo.Arguments = $"\"{filePath}\""; var output = ""; - DataReceivedEventHandler handler = (s, evt) => + + void errorDataHandler(object s, DataReceivedEventArgs evt) { output += evt.Data + "\n"; outputHandler(s, evt); - }; - Process.ErrorDataReceived += handler; + } - Process.Start(); - Process.BeginErrorReadLine(); + FfProcess.ErrorDataReceived += errorDataHandler; - Process.WaitForExit(); - Process.ErrorDataReceived -= handler; - Process.CancelErrorRead(); + FfProcess.Start(); + FfProcess.BeginErrorReadLine(); - string pattern = @"^Input #\d+, matroska"; + FfProcess.WaitForExit(); + FfProcess.ErrorDataReceived -= errorDataHandler; + FfProcess.CancelErrorRead(); - Regex regex = new Regex(pattern, RegexOptions.Multiline); - - return regex.IsMatch(output); + return IsMatroskaRegex().IsMatch(output); } public TimeSpan GetDuration(string filePath, DataReceivedEventHandler outputHandler) { - TimeSpan duration = new TimeSpan(); + TimeSpan duration = TimeSpan.FromSeconds(0); - Process.StartInfo.FileName = "ffprobe.exe"; - Process.StartInfo.Arguments = $"\"{filePath}\""; - Process.StartInfo.RedirectStandardOutput = false; - Process.StartInfo.RedirectStandardError = true; + FfProcess.StartInfo.FileName = FfProbe; + FfProcess.StartInfo.Arguments = $"\"{filePath}\""; + FfProcess.StartInfo.RedirectStandardOutput = false; + FfProcess.StartInfo.RedirectStandardError = true; var output = ""; - DataReceivedEventHandler handler = (s, evt) => + void errorDataHandler(object s, DataReceivedEventArgs evt) { output += evt.Data + "\n"; outputHandler(s, evt); - }; - Process.ErrorDataReceived += handler; + } + FfProcess.ErrorDataReceived += errorDataHandler; - Process.Start(); - Process.BeginErrorReadLine(); + FfProcess.Start(); + FfProcess.BeginErrorReadLine(); - Process.WaitForExit(); - Process.ErrorDataReceived -= handler; - Process.CancelErrorRead(); + FfProcess.WaitForExit(); + FfProcess.ErrorDataReceived -= errorDataHandler; + FfProcess.CancelErrorRead(); string pattern = @"^\s+Duration: (?\d+(?:.\d+)*)"; - Regex regex = new Regex(pattern, RegexOptions.Multiline); - - if (regex.IsMatch(output)) + var match = Regex.Match(output, pattern, RegexOptions.Multiline); + if (match.Success) { - var durationString = regex.Match(output).Groups["duration"].Value; - TimeSpan.TryParse(durationString, out duration); + var durationString = match.Groups["duration"].Value; + TimeSpan.TryParse(durationString, new CultureInfo("en-US"), out duration); } - return duration; } public byte[] ExtractFrame(string filePath, TimeSpan time, DataReceivedEventHandler outputHandler) { - var timespan = String.Format("{0:D2}:{1:D2}:{2:D2}.{3}", time.Hours, time.Minutes, time.Seconds, time.Milliseconds); + var timespan = $"{time.Hours:D2}:{time.Minutes:D2}:{time.Seconds:D2}.{time.Milliseconds}"; - Process.StartInfo.FileName = "ffmpeg.exe"; - Process.StartInfo.Arguments = $"-ss {timespan} -i \"{filePath}\" -vframes 1 -c:v png -f image2pipe -"; - Process.StartInfo.RedirectStandardOutput = true; - Process.StartInfo.RedirectStandardError = true; + FfProcess.StartInfo.FileName = FfMpeg; + FfProcess.StartInfo.Arguments = $"-ss {timespan} -i \"{filePath}\" -vframes 1 -c:v png -f image2pipe -"; + FfProcess.StartInfo.RedirectStandardOutput = true; + FfProcess.StartInfo.RedirectStandardError = true; // ffmpeg uses stderr for some reason - DataReceivedEventHandler handler = (s, evt) => outputHandler(s, evt); - Process.ErrorDataReceived += handler; - - Process.Start(); - Process.BeginErrorReadLine(); - - using (MemoryStream ms = new MemoryStream()) - { - Process.StandardOutput.BaseStream.CopyTo(ms); - - Process.WaitForExit(); - Process.CancelErrorRead(); - Process.ErrorDataReceived -= handler; - - return ms.ToArray(); - } + void errorDataHandler(object s, DataReceivedEventArgs evt) => outputHandler(s, evt); + FfProcess.ErrorDataReceived += errorDataHandler; + + FfProcess.Start(); + FfProcess.BeginErrorReadLine(); + + using MemoryStream ms = new MemoryStream(); + FfProcess.StandardOutput.BaseStream.CopyTo(ms); + + FfProcess.WaitForExit(); + FfProcess.CancelErrorRead(); + FfProcess.ErrorDataReceived -= errorDataHandler; + + return ms.ToArray(); } @@ -118,140 +115,105 @@ public void EncodeSegments(string source, string destination, string arguments, { arguments = arguments.Replace("{source}", source); arguments = arguments.Replace("{destination}", destination); - arguments = arguments.Replace("{start}", String.Format("{0:D2}:{1:D2}:{2:D2}.{3}", segment.Start.Hours, segment.Start.Minutes, segment.Start.Seconds, segment.Start.Milliseconds)); - arguments = arguments.Replace("{end}", String.Format("{0:D2}:{1:D2}:{2:D2}.{3}", segment.End.Hours, segment.End.Minutes, segment.End.Seconds, segment.End.Milliseconds)); - arguments = arguments.Replace("{duration}", String.Format("{0:D2}:{1:D2}:{2:D2}.{3}", segment.Duration.Hours, segment.Duration.Minutes, segment.Duration.Seconds, segment.Duration.Milliseconds)); + arguments = arguments.Replace("{start}", segment.Start.ToString("c")); + arguments = arguments.Replace("{end}", segment.End.ToString("c")); + arguments = arguments.Replace("{duration}", segment.Duration.ToString("c")); - Process.StartInfo.FileName = "ffmpeg.exe"; - Process.StartInfo.Arguments = arguments; - Process.StartInfo.RedirectStandardOutput = false; - Process.StartInfo.RedirectStandardError = true; + FfProcess.StartInfo.FileName = FfMpeg; + FfProcess.StartInfo.Arguments = arguments; + FfProcess.StartInfo.RedirectStandardOutput = false; + FfProcess.StartInfo.RedirectStandardError = true; // ffmpeg uses stderr for some reason - DataReceivedEventHandler handler = (s, evt) => outputHandler(s, evt); - Process.ErrorDataReceived += handler; - Process.Start(); - Process.BeginErrorReadLine(); - - Process.WaitForExit(); - Process.ErrorDataReceived -= handler; - Process.CancelErrorRead(); + void errorDataHandler(object s, DataReceivedEventArgs evt) => outputHandler(s, evt); + FfProcess.ErrorDataReceived += errorDataHandler; + FfProcess.Start(); + FfProcess.BeginErrorReadLine(); + + FfProcess.WaitForExit(); + FfProcess.ErrorDataReceived -= errorDataHandler; + FfProcess.CancelErrorRead(); } public ICollection DetectBlackFrameIntervals(string filePath, double blackFrameDuration, double blackFrameThreshold, double blackFramePixelThreshold, DataReceivedEventHandler outputHandler) { - var frames = new List(); - Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); - Process.StartInfo.FileName = "ffmpeg.exe"; - Process.StartInfo.RedirectStandardError = true; - Process.StartInfo.Arguments = $"-i \"{filePath}\" -vf blackdetect=d={blackFrameDuration}:pic_th={blackFrameThreshold}:pix_th={blackFramePixelThreshold} -an -f null -y /NUL"; - - // ffmpeg uses stderr for some reason - var output = ""; - DataReceivedEventHandler handler = (s, evt) => - { - output += evt.Data + "\n"; outputHandler(s, evt); - }; - Process.ErrorDataReceived += handler; - - Process.Start(); - Process.BeginErrorReadLine(); - - Process.WaitForExit(); - Process.ErrorDataReceived -= handler; - Process.CancelErrorRead(); + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); + var arguments = $"-i \"{filePath}\" -vf blackdetect=d={blackFrameDuration}:pic_th={blackFrameThreshold}:pix_th={blackFramePixelThreshold} -an -f null -y /NUL"; + var output = RunBlackFrameDetection(arguments, outputHandler); string pattern = @"(?:blackdetect.+)(?:black_start:)(?\d+(?:.\d+)*) (?:black_end:)(?\d+(?:.\d+)*) (?:black_duration:)(?\d+(?:.\d+)*)"; - Regex regex = new Regex(pattern, RegexOptions.Multiline); - var matches = regex.Matches(output); - foreach (Match match in matches) - { - try - { - var frame = new BlackFrame(); - decimal start; - decimal end; - decimal duration; - - Decimal.TryParse(match.Groups["start"].Value, out start); - Decimal.TryParse(match.Groups["end"].Value, out end); - Decimal.TryParse(match.Groups["duration"].Value, out duration); - - frame.Start = TimeSpan.FromSeconds((double)start); - frame.End = TimeSpan.FromSeconds((double)end); - frame.Duration = TimeSpan.FromSeconds((double)duration); - frame.Marker = frame.GetMiddle(); - - frames.Add(frame); - } - catch { } + var frames = new List(); + foreach (var groups in matches.Select(match => match.Groups)) + { + BlackFrame frame = ParseBlackFrame(groups); + + if (frame == null) + continue; + + frames.Add(frame); } return frames; - } - - public ICollection DetectBlackFrames(string filePath, DataReceivedEventHandler outputHandler) - { - var frames = new List(); - - Process.StartInfo.FileName = "ffmpeg.exe"; - Process.StartInfo.RedirectStandardError = true; - Process.StartInfo.Arguments = $"-i \"{filePath}\" -vf blackframe -f null -y /NUL"; + } + + private static BlackFrame ParseBlackFrame(GroupCollection groups) + { + try + { + if (double.TryParse(groups["start"].Value, out var start) && + double.TryParse(groups["end"].Value, out var end) && + double.TryParse(groups["duration"].Value, out var duration)) + { + return new BlackFrame() + { + Start = TimeSpan.FromSeconds(start), + End = TimeSpan.FromSeconds(end), + Duration = TimeSpan.FromSeconds(duration), + Marker = GetMiddle(TimeSpan.FromSeconds(duration), TimeSpan.FromSeconds(end)), + }; + } + } + catch + { + return null; + } + return null; + } + + private static TimeSpan GetMiddle(TimeSpan Duration, TimeSpan End) + { + var halfDuration = new TimeSpan(Duration.Ticks / 2); + + return End.Subtract(halfDuration); + } + + private string RunBlackFrameDetection(string arguments, DataReceivedEventHandler outputHandler) + { + FfProcess.StartInfo.FileName = FfMpeg; + FfProcess.StartInfo.RedirectStandardError = true; + FfProcess.StartInfo.Arguments = arguments; // ffmpeg uses stderr for some reason var output = ""; - DataReceivedEventHandler handler = (s, evt) => + void errorDataHandler(object s, DataReceivedEventArgs evt) { output += evt.Data + "\n"; outputHandler(s, evt); - }; - Process.ErrorDataReceived += handler; - - Process.Start(); - Process.BeginErrorReadLine(); - - Process.WaitForExit(); - Process.ErrorDataReceived -= handler; - Process.CancelErrorRead(); - - string pattern = @"t:(?'time'\d+(?:\.\d+)*) type:\w last_keyframe:(?'group'\d+)"; - - Regex regex = new Regex(pattern, RegexOptions.Multiline); - - var matches = regex.Matches(output); - var blackFrames = new List(); - - var matchGroups = matches.GroupBy(m => m.Groups["group"].Value); - - - - foreach (var group in matchGroups) - { - foreach (var match in group) - { - decimal time; - - if (Decimal.TryParse(match.Groups["time"].Value, out time)) - { - blackFrames.Add(TimeSpan.FromSeconds((double)time)); - } - } - - var frame = new BlackFrame(); - - frame.Start = blackFrames.First(); - frame.End = blackFrames.Last(); - frame.Duration = blackFrames.Last().Subtract(blackFrames.First()); - frame.Marker = frame.GetMiddle(); - - frames.Add(frame); - - blackFrames = new List(); } - - return frames; - } + FfProcess.ErrorDataReceived += errorDataHandler; + + FfProcess.Start(); + FfProcess.BeginErrorReadLine(); + + FfProcess.WaitForExit(); + FfProcess.ErrorDataReceived -= errorDataHandler; + FfProcess.CancelErrorRead(); + return output; + } + + [GeneratedRegex(@"^Input #\d+, matroska", RegexOptions.Multiline)] + private static partial Regex IsMatroskaRegex(); } } diff --git a/BananaSplit/Iconicon-Veggies-Bananas.512.ico b/BananaSplit/Iconicon-Veggies-Bananas.512.ico new file mode 100644 index 0000000000000000000000000000000000000000..cce5bc1308625ce295183de3cd98936ffe6bb1e1 GIT binary patch literal 28060 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 literal 0 HcmV?d00001 diff --git a/BananaSplit/LogForm.cs b/BananaSplit/LogForm.cs index 763cec8..22b2957 100644 --- a/BananaSplit/LogForm.cs +++ b/BananaSplit/LogForm.cs @@ -5,6 +5,7 @@ namespace BananaSplit { public partial class LogForm : Form { + public MainForm MainForm { get; set; } public LogForm() { InitializeComponent(); @@ -28,5 +29,17 @@ public void Log(string text) { LogRichTextBox.AppendText(text + Environment.NewLine); } + + public void ShowLog() + { + MainForm?.Invoke(new MethodInvoker( + delegate () + { + if (!Visible) + { + Show(); + } + })); + } } } diff --git a/BananaSplit/MKVToolNix.cs b/BananaSplit/MKVToolNix.cs index f2ab6a7..9efd385 100644 --- a/BananaSplit/MKVToolNix.cs +++ b/BananaSplit/MKVToolNix.cs @@ -2,22 +2,23 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Xml.Serialization; namespace BananaSplit { - public class MKVToolNix + public class MkvToolNix { - private Process process { get; set; } + private Process MkvProcess { get; set; } - public MKVToolNix() + public MkvToolNix() { - process = new Process(); + MkvProcess = new Process(); - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardError = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.CreateNoWindow = true; + MkvProcess.StartInfo.UseShellExecute = false; + MkvProcess.StartInfo.RedirectStandardError = true; + MkvProcess.StartInfo.RedirectStandardOutput = true; + MkvProcess.StartInfo.CreateNoWindow = true; } public string RemuxToMatroska(string filepath) @@ -26,84 +27,82 @@ public string RemuxToMatroska(string filepath) var path = Path.GetDirectoryName(filepath); var extensionlessPath = Path.Combine(path, basename); - process.StartInfo.FileName = "mkvmerge.exe"; - process.StartInfo.Arguments = $"-o \"{extensionlessPath}.mkv\" \"{filepath}\""; + MkvProcess.StartInfo.FileName = "mkvmerge.exe"; + MkvProcess.StartInfo.Arguments = $"-o \"{extensionlessPath}.mkv\" \"{filepath}\""; - process.Start(); - process.WaitForExit(); + MkvProcess.Start(); + MkvProcess.WaitForExit(); return extensionlessPath + ".mkv"; } public void InjectChapters(string filepath, Chapters chapters) { - var temporaryXmlFile = Path.GetTempFileName(); - var temporaryOutputFile = Path.GetTempFileName(); - - process.StartInfo.FileName = "mkvmerge.exe"; - process.StartInfo.Arguments = $"--chapters \"{temporaryXmlFile}\" -o \"{temporaryOutputFile}\" \"{filepath}\""; + var temporaryXmlFile = Path.GetRandomFileName(); + var temporaryOutputFile = Path.GetRandomFileName(); XmlSerializer serializer = new XmlSerializer(typeof(Chapters)); XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces(); namespaces.Add("", ""); - TextWriter writer = new StreamWriter(temporaryXmlFile); - - serializer.Serialize(writer, chapters, namespaces); - - writer.Close(); - - process.Start(); - - var error = process.StandardError.ReadToEnd(); - var output = process.StandardOutput.ReadToEnd(); + using (StreamWriter writer = new(temporaryXmlFile)) + { + serializer.Serialize(writer, chapters, namespaces); + writer.Close(); + } + + MkvProcess.StartInfo.FileName = "mkvmerge.exe"; + MkvProcess.StartInfo.Arguments = $"--chapters \"{temporaryXmlFile}\" -o \"{temporaryOutputFile}\" \"{filepath}\""; + MkvProcess.Start(); - process.WaitForExit(); + MkvProcess.WaitForExit(); File.Delete(filepath); File.Move(temporaryOutputFile, filepath); File.Delete(temporaryXmlFile); } - public Chapters GenerateChapters(IEnumerable segments) + public static Chapters GenerateChapters(IEnumerable segments) { - var chapters = new Chapters(); - var editionEntry = new EditionEntry() + Chapters chapters = new() { - EditionFlagHidden = 0, - EditionFlagDefault = 0, - EditionUID = 1, - ChapterAtom = new List() + EditionEntry = new() + { + EditionFlagHidden = 0, + EditionFlagDefault = 0, + EditionUID = 1, + ChapterAtom = [] + } }; - + int i = 1; - foreach (var segment in segments) - { - var chapterAtom = new ChapterAtom(); - var timestamp = String.Format("{0:D2}:{1:D2}:{2:D2}.{3}", segment.Hours, segment.Minutes, segment.Seconds, segment.Milliseconds); - - chapterAtom.ChapterUID = i; - chapterAtom.ChapterFlagHidden = 0; - chapterAtom.ChapterFlagEnabled = 1; - chapterAtom.ChapterTimeStart = timestamp; - chapterAtom.ChapterDisplay = new ChapterDisplay() - { - ChapterLanguage = "eng", - ChapterString = timestamp - }; - - editionEntry.ChapterAtom.Add(chapterAtom); - - i++; + { + chapters.EditionEntry.ChapterAtom.Add(GenerateChapter(segment, i++)); } - chapters.EditionEntry = editionEntry; return chapters; + } + + private static ChapterAtom GenerateChapter(TimeSpan segment, int index) + { + var chapterAtom = new ChapterAtom(); + var timestamp = $"{segment.Hours:D2}:{segment.Minutes:D2}:{segment.Seconds:D2}.{segment.Milliseconds}"; + + chapterAtom.ChapterUID = index; + chapterAtom.ChapterFlagHidden = 0; + chapterAtom.ChapterFlagEnabled = 1; + chapterAtom.ChapterTimeStart = timestamp; + chapterAtom.ChapterDisplay = new ChapterDisplay() + { + ChapterLanguage = "eng", + ChapterString = timestamp + }; + + return chapterAtom; } - - public void SplitSegments(string source, string destination, string arguments, List segments, DataReceivedEventHandler outputHandler) + public void SplitSegments(string source, string destination, List segments, DataReceivedEventHandler outputHandler) { if (segments.Count <= 1) { @@ -114,38 +113,20 @@ public void SplitSegments(string source, string destination, string arguments, L // make sure the segments are in order segments.Sort((a, b) => a.End.CompareTo(b.End)); - segments.RemoveAt(segments.Count - 1); // last segment's end would be the end of the file - - string cuts = ""; - - bool first = true; - - foreach (var segment in segments) - { - if (!first) - { - cuts += ","; - } - first = false; - - cuts += String.Format("{0:D2}:{1:D2}:{2:D2}.{3}", segment.End.Hours, segment.End.Minutes, segment.End.Seconds, segment.End.Milliseconds); - - } - - process.StartInfo.FileName = "mkvmerge.exe"; - process.StartInfo.Arguments = $"-o \"{destination}\" --split timecodes:{cuts} \"{source}\""; - process.StartInfo.RedirectStandardOutput = false; - process.StartInfo.RedirectStandardError = true; + // last segment's end would be the end of the file + segments.RemoveAt(segments.Count - 1); + var cuts = string.Join(",", segments.Select(segment => $"{segment.End.Hours:D2}:{segment.End.Minutes:D2}:{segment.End.Seconds:D2}.{segment.End.Milliseconds}")); - DataReceivedEventHandler handler = (s, evt) => outputHandler(s, evt); - process.ErrorDataReceived += handler; - process.Start(); + MkvProcess.StartInfo.FileName = "mkvmerge.exe"; + MkvProcess.StartInfo.Arguments = $"-o \"{destination}\" --split timecodes:{cuts} \"{source}\""; + MkvProcess.StartInfo.RedirectStandardOutput = false; + MkvProcess.StartInfo.RedirectStandardError = true; - //var error = process.StandardError.ReadToEnd(); - //var output = process.StandardOutput.ReadToEnd(); + MkvProcess.ErrorDataReceived += (s, evt) => outputHandler(s, evt); + MkvProcess.Start(); - process.WaitForExit(); + MkvProcess.WaitForExit(); } diff --git a/BananaSplit/MainForm.Designer.cs b/BananaSplit/MainForm.Designer.cs index 6c706a9..9cf176f 100644 --- a/BananaSplit/MainForm.Designer.cs +++ b/BananaSplit/MainForm.Designer.cs @@ -29,6 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { components = new System.ComponentModel.Container(); + var resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); Menu = new System.Windows.Forms.MenuStrip(); FileMenuDropdown = new System.Windows.Forms.ToolStripMenuItem(); AddFilesToQueueMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -73,8 +74,8 @@ private void InitializeComponent() Menu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { FileMenuDropdown, OptionsMenuDropdown }); Menu.Location = new System.Drawing.Point(0, 0); Menu.Name = "Menu"; - Menu.Padding = new System.Windows.Forms.Padding(13, 4, 0, 4); - Menu.Size = new System.Drawing.Size(1733, 44); + Menu.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); + Menu.Size = new System.Drawing.Size(933, 24); Menu.TabIndex = 0; Menu.Text = "menuStrip1"; // @@ -82,46 +83,46 @@ private void InitializeComponent() // FileMenuDropdown.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { AddFilesToQueueMenuItem, AddFolderToQueueMenuItem }); FileMenuDropdown.Name = "FileMenuDropdown"; - FileMenuDropdown.Size = new System.Drawing.Size(71, 36); + FileMenuDropdown.Size = new System.Drawing.Size(37, 20); FileMenuDropdown.Text = "File"; // // AddFilesToQueueMenuItem // AddFilesToQueueMenuItem.Name = "AddFilesToQueueMenuItem"; - AddFilesToQueueMenuItem.Size = new System.Drawing.Size(372, 44); + AddFilesToQueueMenuItem.Size = new System.Drawing.Size(184, 22); AddFilesToQueueMenuItem.Text = "Add File(s) to Queue"; // // AddFolderToQueueMenuItem // AddFolderToQueueMenuItem.Name = "AddFolderToQueueMenuItem"; - AddFolderToQueueMenuItem.Size = new System.Drawing.Size(372, 44); + AddFolderToQueueMenuItem.Size = new System.Drawing.Size(184, 22); AddFolderToQueueMenuItem.Text = "Add Folder to Queue"; // // OptionsMenuDropdown // OptionsMenuDropdown.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { SettingsMenuItem, AboutMenuItem }); OptionsMenuDropdown.Name = "OptionsMenuDropdown"; - OptionsMenuDropdown.Size = new System.Drawing.Size(118, 36); + OptionsMenuDropdown.Size = new System.Drawing.Size(61, 20); OptionsMenuDropdown.Text = "Options"; // // SettingsMenuItem // SettingsMenuItem.Name = "SettingsMenuItem"; - SettingsMenuItem.Size = new System.Drawing.Size(359, 44); + SettingsMenuItem.Size = new System.Drawing.Size(116, 22); SettingsMenuItem.Text = "Settings"; // // AboutMenuItem // AboutMenuItem.Name = "AboutMenuItem"; - AboutMenuItem.Size = new System.Drawing.Size(359, 44); + AboutMenuItem.Size = new System.Drawing.Size(116, 22); AboutMenuItem.Text = "About"; // // FileBrowserSplitContainer // FileBrowserSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; FileBrowserSplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; - FileBrowserSplitContainer.Location = new System.Drawing.Point(7, 6); - FileBrowserSplitContainer.Margin = new System.Windows.Forms.Padding(7, 6, 7, 124); + FileBrowserSplitContainer.Location = new System.Drawing.Point(4, 3); + FileBrowserSplitContainer.Margin = new System.Windows.Forms.Padding(4, 3, 4, 58); FileBrowserSplitContainer.Name = "FileBrowserSplitContainer"; // // FileBrowserSplitContainer.Panel1 @@ -131,21 +132,20 @@ private void InitializeComponent() // FileBrowserSplitContainer.Panel2 // FileBrowserSplitContainer.Panel2.Controls.Add(ReferenceImageListView); - FileBrowserSplitContainer.Size = new System.Drawing.Size(1719, 724); - FileBrowserSplitContainer.SplitterDistance = 264; - FileBrowserSplitContainer.SplitterWidth = 9; + FileBrowserSplitContainer.Size = new System.Drawing.Size(925, 335); + FileBrowserSplitContainer.SplitterDistance = 767; + FileBrowserSplitContainer.SplitterWidth = 5; FileBrowserSplitContainer.TabIndex = 1; // // QueueList // QueueList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { FileName }); QueueList.Dock = System.Windows.Forms.DockStyle.Fill; - QueueList.HideSelection = false; QueueList.Location = new System.Drawing.Point(0, 0); - QueueList.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + QueueList.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); QueueList.Name = "QueueList"; QueueList.ShowItemToolTips = true; - QueueList.Size = new System.Drawing.Size(264, 724); + QueueList.Size = new System.Drawing.Size(767, 335); QueueList.TabIndex = 0; QueueList.UseCompatibleStateImageBehavior = false; QueueList.View = System.Windows.Forms.View.Details; @@ -159,12 +159,11 @@ private void InitializeComponent() // ReferenceImageListView.CheckBoxes = true; ReferenceImageListView.Dock = System.Windows.Forms.DockStyle.Fill; - ReferenceImageListView.HideSelection = false; ReferenceImageListView.LargeImageList = ReferenceImageList; ReferenceImageListView.Location = new System.Drawing.Point(0, 0); - ReferenceImageListView.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + ReferenceImageListView.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); ReferenceImageListView.Name = "ReferenceImageListView"; - ReferenceImageListView.Size = new System.Drawing.Size(1446, 724); + ReferenceImageListView.Size = new System.Drawing.Size(153, 335); ReferenceImageListView.SmallImageList = ReferenceImageList; ReferenceImageListView.TabIndex = 0; ReferenceImageListView.UseCompatibleStateImageBehavior = false; @@ -173,7 +172,7 @@ private void InitializeComponent() // // ReferenceImageList // - ReferenceImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; + ReferenceImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth16Bit; ReferenceImageList.ImageSize = new System.Drawing.Size(256, 256); ReferenceImageList.TransparentColor = System.Drawing.Color.Transparent; // @@ -181,22 +180,22 @@ private void InitializeComponent() // StatusBar.ImageScalingSize = new System.Drawing.Size(32, 32); StatusBar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { StatusBarProgressBar, StatusBarLabel }); - StatusBar.Location = new System.Drawing.Point(0, 852); + StatusBar.Location = new System.Drawing.Point(0, 395); StatusBar.Name = "StatusBar"; - StatusBar.Padding = new System.Windows.Forms.Padding(2, 0, 30, 0); - StatusBar.Size = new System.Drawing.Size(1733, 38); + StatusBar.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); + StatusBar.Size = new System.Drawing.Size(933, 22); StatusBar.TabIndex = 2; StatusBar.Text = "StatusBar"; // // StatusBarProgressBar // StatusBarProgressBar.Name = "StatusBarProgressBar"; - StatusBarProgressBar.Size = new System.Drawing.Size(217, 26); + StatusBarProgressBar.Size = new System.Drawing.Size(117, 16); // // StatusBarLabel // StatusBarLabel.Name = "StatusBarLabel"; - StatusBarLabel.Size = new System.Drawing.Size(0, 28); + StatusBarLabel.Size = new System.Drawing.Size(0, 17); // // ContainerPanel // @@ -204,41 +203,41 @@ private void InitializeComponent() ContainerPanel.Controls.Add(ActionBarPanel); ContainerPanel.Dock = System.Windows.Forms.DockStyle.Fill; ContainerPanel.Location = new System.Drawing.Point(0, 0); - ContainerPanel.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + ContainerPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); ContainerPanel.Name = "ContainerPanel"; - ContainerPanel.Padding = new System.Windows.Forms.Padding(0, 50, 0, 0); - ContainerPanel.Size = new System.Drawing.Size(1733, 852); + ContainerPanel.Padding = new System.Windows.Forms.Padding(0, 23, 0, 0); + ContainerPanel.Size = new System.Drawing.Size(933, 395); ContainerPanel.TabIndex = 4; // // MainPanel // MainPanel.Controls.Add(FileBrowserSplitContainer); MainPanel.Dock = System.Windows.Forms.DockStyle.Fill; - MainPanel.Location = new System.Drawing.Point(0, 50); - MainPanel.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + MainPanel.Location = new System.Drawing.Point(0, 23); + MainPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); MainPanel.Name = "MainPanel"; - MainPanel.Padding = new System.Windows.Forms.Padding(7, 6, 7, 6); - MainPanel.Size = new System.Drawing.Size(1733, 736); + MainPanel.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + MainPanel.Size = new System.Drawing.Size(933, 341); MainPanel.TabIndex = 1; // // ActionBarPanel // ActionBarPanel.Controls.Add(ProcessQueueButton); ActionBarPanel.Dock = System.Windows.Forms.DockStyle.Bottom; - ActionBarPanel.Location = new System.Drawing.Point(0, 786); - ActionBarPanel.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + ActionBarPanel.Location = new System.Drawing.Point(0, 364); + ActionBarPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); ActionBarPanel.Name = "ActionBarPanel"; - ActionBarPanel.Size = new System.Drawing.Size(1733, 66); + ActionBarPanel.Size = new System.Drawing.Size(933, 31); ActionBarPanel.TabIndex = 5; // // ProcessQueueButton // ProcessQueueButton.AutoSize = true; ProcessQueueButton.Dock = System.Windows.Forms.DockStyle.Right; - ProcessQueueButton.Location = new System.Drawing.Point(1395, 0); - ProcessQueueButton.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + ProcessQueueButton.Location = new System.Drawing.Point(751, 0); + ProcessQueueButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); ProcessQueueButton.Name = "ProcessQueueButton"; - ProcessQueueButton.Size = new System.Drawing.Size(338, 66); + ProcessQueueButton.Size = new System.Drawing.Size(182, 31); ProcessQueueButton.TabIndex = 0; ProcessQueueButton.Text = "Process Queue"; ProcessQueueButton.UseVisualStyleBackColor = true; @@ -248,18 +247,18 @@ private void InitializeComponent() QueueItemContextMenu.ImageScalingSize = new System.Drawing.Size(32, 32); QueueItemContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { QueueItemContextMenuProcess, QueueItemContextMenuRemove }); QueueItemContextMenu.Name = "QueueItemContextMenu"; - QueueItemContextMenu.Size = new System.Drawing.Size(175, 80); + QueueItemContextMenu.Size = new System.Drawing.Size(118, 48); // // QueueItemContextMenuProcess // QueueItemContextMenuProcess.Name = "QueueItemContextMenuProcess"; - QueueItemContextMenuProcess.Size = new System.Drawing.Size(174, 38); + QueueItemContextMenuProcess.Size = new System.Drawing.Size(117, 22); QueueItemContextMenuProcess.Text = "Process"; // // QueueItemContextMenuRemove // QueueItemContextMenuRemove.Name = "QueueItemContextMenuRemove"; - QueueItemContextMenuRemove.Size = new System.Drawing.Size(174, 38); + QueueItemContextMenuRemove.Size = new System.Drawing.Size(117, 22); QueueItemContextMenuRemove.Text = "Remove"; // // QueueListContextMenu @@ -267,30 +266,31 @@ private void InitializeComponent() QueueListContextMenu.ImageScalingSize = new System.Drawing.Size(32, 32); QueueListContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { QueueListContextMenuRemove, QueueListContextMenuProcess }); QueueListContextMenu.Name = "QueueListContextMenu"; - QueueListContextMenu.Size = new System.Drawing.Size(247, 80); + QueueListContextMenu.Size = new System.Drawing.Size(153, 48); // // QueueListContextMenuRemove // QueueListContextMenuRemove.Name = "QueueListContextMenuRemove"; - QueueListContextMenuRemove.Size = new System.Drawing.Size(246, 38); + QueueListContextMenuRemove.Size = new System.Drawing.Size(152, 22); QueueListContextMenuRemove.Text = "Remove All"; // // QueueListContextMenuProcess // QueueListContextMenuProcess.Name = "QueueListContextMenuProcess"; - QueueListContextMenuProcess.Size = new System.Drawing.Size(246, 38); + QueueListContextMenuProcess.Size = new System.Drawing.Size(152, 22); QueueListContextMenuProcess.Text = "Process Queue"; // // MainForm // - AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - ClientSize = new System.Drawing.Size(1733, 890); + ClientSize = new System.Drawing.Size(933, 417); Controls.Add(Menu); Controls.Add(ContainerPanel); Controls.Add(StatusBar); + Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon"); MainMenuStrip = Menu; - Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); Name = "MainForm"; Text = "BananaSplit"; Load += MainForm_Load; @@ -320,25 +320,25 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem AboutMenuItem; private System.Windows.Forms.SplitContainer FileBrowserSplitContainer; private System.Windows.Forms.ImageList ReferenceImageList; - private System.Windows.Forms.ListView QueueList; private System.Windows.Forms.ToolStripMenuItem FileMenuDropdown; private System.Windows.Forms.ToolStripMenuItem AddFilesToQueueMenuItem; private System.Windows.Forms.ToolStripMenuItem AddFolderToQueueMenuItem; private System.Windows.Forms.ColumnHeader FileName; private System.Windows.Forms.ListView ReferenceImageListView; - private System.Windows.Forms.StatusStrip StatusBar; - private System.Windows.Forms.ToolStripProgressBar StatusBarProgressBar; - private System.Windows.Forms.ToolStripStatusLabel StatusBarLabel; private System.Windows.Forms.Panel ContainerPanel; private System.Windows.Forms.Panel ActionBarPanel; private System.Windows.Forms.Panel MainPanel; private System.Windows.Forms.Button ProcessQueueButton; - private System.Windows.Forms.ContextMenuStrip QueueItemContextMenu; private System.Windows.Forms.ToolStripMenuItem QueueItemContextMenuRemove; private System.Windows.Forms.ToolStripMenuItem QueueItemContextMenuProcess; - private System.Windows.Forms.ContextMenuStrip QueueListContextMenu; private System.Windows.Forms.ToolStripMenuItem QueueListContextMenuRemove; - private System.Windows.Forms.ToolStripMenuItem QueueListContextMenuProcess; + private System.Windows.Forms.ToolStripMenuItem QueueListContextMenuProcess; + public System.Windows.Forms.ListView QueueList; + public System.Windows.Forms.ContextMenuStrip QueueItemContextMenu; + public System.Windows.Forms.ContextMenuStrip QueueListContextMenu; + public System.Windows.Forms.StatusStrip StatusBar; + public System.Windows.Forms.ToolStripProgressBar StatusBarProgressBar; + public System.Windows.Forms.ToolStripStatusLabel StatusBarLabel; } } diff --git a/BananaSplit/MainForm.cs b/BananaSplit/MainForm.cs index e59cca9..0c8daa1 100644 --- a/BananaSplit/MainForm.cs +++ b/BananaSplit/MainForm.cs @@ -14,489 +14,118 @@ namespace BananaSplit { public partial class MainForm : Form { - private SettingsForm SettingsForm; - private LogForm LogForm; - - private List QueueItems { get; set; } - private Thread ScanningThread; - private Thread ProcessingThread; - private FFMPEG FFMPEG; - private MKVToolNix MKVToolNix; - - private string[] SupportedExtensions = - { - ".avi", - ".flv", - ".m4p", - ".m4v", - ".mkv", - ".mov", - ".mp2", - ".mp4", - ".mpe", - ".mpeg", - ".mpg", - ".mpv", - ".ogg", - ".ts", - ".webm", - ".wmv" - }; - - public MainForm() - { - InitializeComponent(); - QueueItems = new List(); + private readonly SettingsForm SettingsForm; + private readonly QueueManager queueManager; + private readonly Processor processor; + private readonly List controlsToLock; + + public List QueueItems { get; set; } = []; + + public MainForm(SettingsForm settingsForm, QueueManager queueManager, StatusBarManager statusBarManager, LogForm logForm, Processor processor) + { + InitializeComponent(); + SettingsForm = settingsForm; + queueManager.MainForm = this; + statusBarManager.MainForm = this; + this.queueManager = queueManager; + logForm.MainForm = this; + this.processor = processor; + controlsToLock = [ + ProcessQueueButton, + QueueListContextMenuProcess, + QueueItemContextMenuProcess, + QueueListContextMenuRemove, + QueueItemContextMenuRemove, + AddFilesToQueueMenuItem, + AddFolderToQueueMenuItem + ]; } private void MainForm_Load(object sender, EventArgs e) { // Menu Items - AddFilesToQueueMenuItem.Click += AddFilesToQueueDialog; - AddFolderToQueueMenuItem.Click += AddFolderToQueueDialog; + AddFilesToQueueMenuItem.Click += queueManager.AddFilesToQueueDialog; + AddFolderToQueueMenuItem.Click += queueManager.AddFolderToQueueDialog; SettingsMenuItem.Click += OpenSettingsForm; // Queue List QueueList.SelectedIndexChanged += RenderReferenceImagesListView; - QueueList.MouseUp += OpenQueueItemContextMenu; + QueueList.MouseUp += queueManager.OpenQueueItemContextMenu; QueueList.KeyDown += QueueListKeyDownHandler; QueueItemContextMenuProcess.Click += ProcessQueueItem; - QueueItemContextMenuRemove.Click += RemoveQueueItem; + QueueItemContextMenuRemove.Click += queueManager.RemoveQueueItem; QueueListContextMenuProcess.Click += ProcessQueue; - QueueListContextMenuRemove.Click += RemoveQueueList; + QueueListContextMenuRemove.Click += queueManager.RemoveQueueList; // Other ProcessQueueButton.Click += ProcessQueue; - QueueList.Resize += AutoSizeQueueList; + QueueList.Resize += queueManager.AutoSizeQueueList; // Drag and Drop this.AllowDrop = true; - this.DragOver += AddDragOverItemToQueueDialog; - this.DragDrop += AddDragDropItemToQueueDialog; - - SettingsForm = new SettingsForm(); - LogForm = new LogForm(); - - FFMPEG = new FFMPEG(); - MKVToolNix = new MKVToolNix(); - } - - private void AutoSizeQueueList(object sender, EventArgs e) - { - QueueList.Columns[0].Width = QueueList.Width - 4; - } - - private void OpenQueueItemContextMenu(object sender, MouseEventArgs e) - { - if (e.Button == MouseButtons.Right && QueueList.FocusedItem != null && QueueList.FocusedItem.Bounds.Contains(e.Location)) - { - QueueItemContextMenu.Tag = QueueList.FocusedItem.Tag; - QueueItemContextMenu.Show(Cursor.Position); - } - else if (e.Button == MouseButtons.Right) - { - QueueListContextMenu.Show(Cursor.Position); - } - } - - private void AddFilesToQueueDialog(object sender, EventArgs e) - { - var fileContent = string.Empty; - var filePath = string.Empty; - - using (OpenFileDialog openFileDialog = new OpenFileDialog()) - { - openFileDialog.Filter = $"Video Files (*{String.Join(",*", SupportedExtensions)})|*{String.Join(";*", SupportedExtensions)}"; - openFileDialog.FilterIndex = 2; - openFileDialog.RestoreDirectory = true; - openFileDialog.Multiselect = true; - - if (openFileDialog.ShowDialog() == DialogResult.OK) - { - string[] files = openFileDialog.FileNames; - - bool addedAnything = false; - - foreach (var file in files) - { - addedAnything |= AddToQueue(file); - } - - if (addedAnything) - { - ScanningThread = new Thread(() => - { - ScanQueueItems(); - }); - - ScanningThread.Start(); - } - } - } - } - - private void AddFolderToQueueDialog(object sender, EventArgs e) - { - using (FolderBrowserDialog openFolderDialog = new FolderBrowserDialog()) - { - var result = openFolderDialog.ShowDialog(); - - if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(openFolderDialog.SelectedPath)) - { - string[] files = Directory.GetFiles(openFolderDialog.SelectedPath); - - bool addedAnything = false; - - foreach (var file in files) - { - addedAnything |= AddToQueue(file); - } - - if (addedAnything) - { - ScanningThread = new Thread(() => - { - ScanQueueItems(); - }); - - ScanningThread.Start(); - } - } - } + this.DragOver += QueueManager.SetDragOverEffect; + this.DragDrop += queueManager.AddDragDropItemsToQueue; } - - private bool AddToQueue(string path) + private void LockControls(bool enable) { - bool addedAnything = false; - if (File.Exists(path) && SupportedExtensions.Contains(Path.GetExtension(path).ToLower())) - { - QueueItems.Add(new QueueItem(path)); - addedAnything = true; - } - else if (Directory.Exists(path)) - { - foreach (var file in Directory.GetFiles(path)) - { - if (File.Exists(file) && SupportedExtensions.Contains(Path.GetExtension(file).ToLower())) + Invoke( + new MethodInvoker( + delegate () { - QueueItems.Add(new QueueItem(file)); - addedAnything = true; + ToggleControlsEnabled(enable); } - } - - foreach (var folder in Directory.GetDirectories(path)) - { - addedAnything |= AddToQueue(folder); - } - } - - return addedAnything; - } - - - private void AddDragOverItemToQueueDialog(object sender, DragEventArgs e) - { - if (e.Data.GetDataPresent(DataFormats.FileDrop)) - { - e.Effect = DragDropEffects.Link; - } - else - { - e.Effect = DragDropEffects.None; - } + ) + ); } - private void AddDragDropItemToQueueDialog(object sender, DragEventArgs e) + private void ToggleControlsEnabled(bool enable) { - bool addedAnything = false; - - var files = ((DataObject)e.Data).GetFileDropList().Cast().ToList(); - - if (files != null && files.Any()) + foreach (var controlToLock in controlsToLock) { - foreach (string file in files) + if (controlToLock is ToolStripMenuItem menuItem) { - addedAnything |= AddToQueue(file); + menuItem.Enabled = enable; + continue; } - - if (addedAnything) + if (controlToLock is Control control) { - ScanningThread = new Thread(() => - { - ScanQueueItems(); - }); - - ScanningThread.Start(); + control.Enabled = enable; } + AllowDrop = enable; } + Cursor.Current = enable ? Cursors.Default : Cursors.WaitCursor; + Cursor = enable ? Cursors.Default : Cursors.WaitCursor; } - private void ShowLog() - { - Invoke(new MethodInvoker( - delegate () - { - if (!LogForm.Visible) - { - LogForm.Show(); - } - }) - ); - } - - private void Log(string text) - { - Invoke(new MethodInvoker( - delegate () - { - if (LogForm.Visible) - { - LogForm.Log(text); - } - }) - ); - } - - private void ScanQueueItems() + private void RenderReferenceImagesListView(object sender, EventArgs e) { - if (SettingsForm.Settings.ShowLog) + if (QueueList.SelectedItems.Count <= 0) { - ShowLog(); + ReferenceImageListView.Clear(); + return; } - SetStatusBarProgressBarValue(0, QueueItems.Count); - - var i = 0; - int totalNumFrames = 0; - int countedFrames = 0; - - // Get all video durations and fps for a better progress bar - foreach (var item in QueueItems.Where(qi => !qi.Scanned)) - { - item.Duration = FFMPEG.GetDuration(item.FileName, (s, e) => - { - string logMsg = e.Data; - Log(logMsg); - if (logMsg == null) - { - return; - } - - if (item.Fps == 0) - { - string fpsPattern = @"(?'fps'[.\d]+) fps,"; - Regex regex = new Regex(fpsPattern, RegexOptions.Singleline); - - Match m = regex.Match(logMsg); - if (m.Success && float.TryParse(m.Groups["fps"].Value, out float fps)) - { - item.Fps = fps; - } - } - }); - - item.NumFrames = (int)Math.Ceiling(item.Duration.TotalSeconds * item.Fps); - totalNumFrames += item.NumFrames; - } + var selectedItem = (QueueItem)QueueList.SelectedItems[0].Tag; - // Parse items - foreach (var item in QueueItems.Where(qi => !qi.Scanned)) + foreach (var frame in selectedItem.BlackFrames) { - i++; - - SetStatusBarLabelValue($"Detecting frames for {Path.GetFileName(item.FileName)}"); - item.Scanned = true; - item.LastScanned = DateTime.Now; - item.BlackFrames = FFMPEG.DetectBlackFrameIntervals(item.FileName, SettingsForm.Settings.BlackFrameDuration, SettingsForm.Settings.BlackFrameThreshold, SettingsForm.Settings.BlackFramePixelThreshold, (s, e) => + if (frame.ReferenceFrame.Bitmap != null) { - string logMsg = e.Data; - Log(logMsg); - if (logMsg == null) - { - return; - } - - string framePattern = @"(\sframe:|frame=\s+)(?'frame'\d+)"; - Regex regex = new Regex(framePattern, RegexOptions.Singleline); - - Match m = regex.Match(logMsg); - if (m.Success && int.TryParse(m.Groups["frame"].Value, out int frame)) - { - SetStatusBarProgressBarValue(countedFrames + frame, totalNumFrames); - } - }); - countedFrames += item.NumFrames; - SetStatusBarProgressBarValue(countedFrames, totalNumFrames); - - var frameNum = 1; - foreach (var frame in item.BlackFrames) - { - long offset = (long)(SettingsForm.Settings.ReferenceFrameOffset * TimeSpan.TicksPerSecond); - TimeSpan referenceFramePosition = frame.End.Add(new TimeSpan(offset)); - - SetStatusBarLabelValue($"Generating frame {frameNum} of {item.BlackFrames.Count} at {referenceFramePosition}"); - frame.ReferenceFrame = new ReferenceFrame(); - frame.ReferenceFrame.Data = FFMPEG.ExtractFrame(item.FileName, referenceFramePosition, FFMPEGLog); - frameNum++; - } - - AddItemToQueue(item); - } - - SetStatusBarLabelValue("Done!"); - ClearStatusBarProgressBarValue(); - } - - private void FFMPEGLog(object sender, DataReceivedEventArgs e) - { - if (SettingsForm.Settings.ShowLog) - { - LogForm.Invoke( - new MethodInvoker( - delegate () - { - LogForm.Log(e.Data); - } - ) - ); - } - } - - private void SetStatusBarProgressBarValue(int value, int maximum) - { - StatusBar.Invoke( - new MethodInvoker( - delegate () - { - StatusBarProgressBar.Minimum = 0; - StatusBarProgressBar.Maximum = maximum; - - if (value >= maximum) - value = maximum - 1; - - StatusBarProgressBar.Value = value; - } - ) - ); - } - - private void ClearStatusBarProgressBarValue() - { - StatusBar.Invoke( - new MethodInvoker( - delegate () - { - StatusBarProgressBar.Minimum = 0; - StatusBarProgressBar.Maximum = 1; - - StatusBarProgressBar.Value = 0; - } - ) - ); - } - - private void SetStatusBarLabelValue(string value) - { - StatusBar.Invoke( - new MethodInvoker( - delegate () - { - StatusBarLabel.Text = value; - } - ) - ); - } - - private void LockControls() - { - Invoke( - new MethodInvoker( - delegate () - { - ProcessQueueButton.Enabled = false; - QueueListContextMenuProcess.Enabled = false; - QueueItemContextMenuProcess.Enabled = false; - QueueListContextMenuRemove.Enabled = false; - QueueItemContextMenuRemove.Enabled = false; - AddFilesToQueueMenuItem.Enabled = false; - AddFolderToQueueMenuItem.Enabled = false; - Cursor.Current = Cursors.WaitCursor; - Cursor = Cursors.WaitCursor; - AllowDrop = false; - } - ) - ); - } - - private void UnlockControls() - { - Invoke( - new MethodInvoker( - delegate () - { - ProcessQueueButton.Enabled = true; - QueueListContextMenuProcess.Enabled = true; - QueueItemContextMenuProcess.Enabled = true; - QueueListContextMenuRemove.Enabled = true; - QueueItemContextMenuRemove.Enabled = true; - AddFilesToQueueMenuItem.Enabled = true; - AddFolderToQueueMenuItem.Enabled = true; - Cursor.Current = Cursors.Default; - Cursor = Cursors.Default; - AllowDrop = true; - } - ) - ); - } - - private void AddItemToQueue(QueueItem item) - { - QueueList.Invoke( - new MethodInvoker( - delegate () - { - QueueList.Items.Add(new ListViewItem() - { - Text = Path.GetFileName(item.FileName), - ToolTipText = item.FileName, - Name = item.Id.ToString(), - Tag = item - }); - } - ) - ); - } + var bmp = frame.ReferenceFrame.Bitmap; - private void RenderReferenceImagesListView(object sender, EventArgs e) - { - if (QueueList.SelectedItems.Count > 0) - { - var selectedItem = (QueueItem)QueueList.SelectedItems[0].Tag; + ReferenceImageList.Add(bmp, frame.Id.ToString()); - foreach (var frame in selectedItem.BlackFrames) - { - if (frame.ReferenceFrame.Data.Length > 0) + ReferenceImageListView.Items.Add(new ListViewItem() { - var bmp = Utilities.BytesToImage(frame.ReferenceFrame.Data); - - ReferenceImageList.Add(bmp, frame.Id.ToString()); - - ReferenceImageListView.Items.Add(new ListViewItem() - { - ImageKey = frame.Id.ToString(), - Tag = frame, - Name = frame.Id.ToString(), - Text = frame.End.ToString(), - Checked = frame.Selected - }); - } + ImageKey = frame.Id.ToString(), + Tag = frame, + Name = frame.Id.ToString(), + Text = frame.End.ToString(), + Checked = frame.Selected + }); } } - else - { - ReferenceImageListView.Clear(); - } } private void OpenSettingsForm(object sender, EventArgs e) @@ -518,314 +147,25 @@ private void ReferenceImageListView_SelectedIndexChanged(object sender, EventArg } } - private void RemoveQueueItem(object sender, EventArgs e) - { - QueueItem queueItem = (QueueItem)QueueItemContextMenu.Tag; - - QueueList.Items.RemoveByKey(queueItem.Id.ToString()); - QueueItems.Remove(queueItem); - } - - private void RemoveQueueList(object sender, EventArgs e) - { - foreach (var queueItem in QueueItems) - { - QueueList.Items.RemoveByKey(queueItem.Id.ToString()); - } - - QueueItems.Clear(); - } - private void ProcessQueue(object sender, EventArgs e) { - if (SettingsForm.Settings.ShowLog) - { - ShowLog(); - } - - ProcessingThread = new Thread(() => - { - LockControls(); - - SetStatusBarProgressBarValue(0, QueueItems.Count); - - var i = 0; - - switch (SettingsForm.Settings.ProcessType) - { - case ProcessingType.MatroskaChapters: - foreach (var queueItem in QueueItems) - { - i++; - - SetStatusBarProgressBarValue(i, QueueItems.Count); - SetStatusBarLabelValue($"Adding chapters for {Path.GetFileName(queueItem.FileName)}"); - - ProcessMatroskaChapters(queueItem); - } - - SetStatusBarLabelValue("Done adding chapters!"); - break; - - case ProcessingType.SplitAndEncode: - foreach (var queueItem in QueueItems) - { - i++; - - SetStatusBarProgressBarValue(i, QueueItems.Count); - SetStatusBarLabelValue($"Encoding for {Path.GetFileName(queueItem.FileName)}"); - - ProcessSplitAndEncode(queueItem); - } - - SetStatusBarLabelValue("Done encoding!"); - break; - - case ProcessingType.MKVToolNixSplit: - foreach (var queueItem in QueueItems) - { - i++; - - SetStatusBarProgressBarValue(i, QueueItems.Count); - SetStatusBarLabelValue($"MKV Splitting for {Path.GetFileName(queueItem.FileName)}"); - - ProcessMKVSplit(queueItem); - } - - SetStatusBarLabelValue("Done splitting!"); - break; - } - - UnlockControls(); - - ClearStatusBarProgressBarValue(); - }); - - ProcessingThread.Start(); + processor.ProcessQueue(() => LockControls(false), () => LockControls(true), QueueItems); } private void ProcessQueueItem(object sender, EventArgs e) { - ProcessingThread = new Thread(() => - { - QueueItem queueItem = (QueueItem)QueueItemContextMenu.Tag; - - LockControls(); - - //SetStatusBarProgressBarValue(1, 1); - - switch (SettingsForm.Settings.ProcessType) - { - case ProcessingType.MatroskaChapters: - SetStatusBarLabelValue($"Adding chapters for {Path.GetFileName(queueItem.FileName)}"); - ProcessMatroskaChapters(queueItem); - SetStatusBarLabelValue("Done adding chapters!"); - break; - - case ProcessingType.SplitAndEncode: - SetStatusBarLabelValue($"Encoding for {Path.GetFileName(queueItem.FileName)}"); - ProcessSplitAndEncode(queueItem); - SetStatusBarLabelValue("Done encoding!"); - break; - - case ProcessingType.MKVToolNixSplit: - SetStatusBarLabelValue($"Splitting for {Path.GetFileName(queueItem.FileName)}"); - ProcessMKVSplit(queueItem); - SetStatusBarLabelValue("Done splitting!"); - break; - } - - UnlockControls(); - - //ClearStatusBarProgressBarValue(); - }); - - ProcessingThread.Start(); - } - - private void ProcessMatroskaChapters(QueueItem queueItem) - { - List chapterTimeSpans = new List(); - - // Always add the beginning as a chapter - chapterTimeSpans.Add(new TimeSpan(0, 0, 0)); - - foreach (var frame in queueItem.BlackFrames.Where(bf => bf.Selected)) - { - var halfDuration = new TimeSpan(frame.Duration.Ticks / 2); - - chapterTimeSpans.Add(frame.End.Subtract(halfDuration)); - } - - if (!FFMPEG.IsMatroska(queueItem.FileName, FFMPEGLog)) - { - var matroskaPath = MKVToolNix.RemuxToMatroska(queueItem.FileName); - - queueItem.FileName = matroskaPath; - } - - var chapters = MKVToolNix.GenerateChapters(chapterTimeSpans); - - MKVToolNix.InjectChapters(queueItem.FileName, chapters); - } - - - - - - - private void ProcessMKVSplit(QueueItem queueItem) - { - var segments = queueItem.GetSegments(); - - var newName = Path.Combine(Path.GetDirectoryName(queueItem.FileName), "output", Path.GetFileNameWithoutExtension(queueItem.FileName) + "-%03d.mkv"); - - MKVToolNix.SplitSegments(queueItem.FileName, newName, SettingsForm.Settings.FFMPEGArguments.Replace("\r\n", " "), segments.ToList(), FFMPEGLog); - - } - - - - - - - - - private void ProcessSplitAndEncode(QueueItem queueItem) - { - var segments = queueItem.GetSegments(); - var index = 1; - - // Rename original file if user wants it - var encodingFileName = queueItem.FileName; - if (SettingsForm.Settings.RenameOriginal) - { - var fi = new FileInfo(encodingFileName); - var path = Path.GetDirectoryName(encodingFileName); - var name = Path.GetFileNameWithoutExtension(encodingFileName); - var ext = Path.GetExtension(encodingFileName); - encodingFileName = Path.Combine(path, name + "_original" + ext); - try - { - fi.MoveTo(encodingFileName); - } - catch - { - MessageBox.Show("There was an error renaming the original file: " + encodingFileName + "/nMake sure it's not being used by another process!", "Error", MessageBoxButtons.OK); - return; - } - } - - foreach (var segment in segments) - { - //var newName = Path.Combine(Path.GetDirectoryName(queueItem.FileName), Path.GetFileNameWithoutExtension(queueItem.FileName) + "-" + index + ".mkv"); - var newName = GetNewName(queueItem.FileName, index); - - FFMPEG.EncodeSegments(encodingFileName, newName, SettingsForm.Settings.FFMPEGArguments.Replace("\r\n", " "), segment, FFMPEGLog); - - index++; - } - } - - private string GetNewName(string fileName, int index) - { - var path = Path.GetDirectoryName(fileName); - var name = Path.GetFileNameWithoutExtension(fileName); - var original = name; - - var oldText = SettingsForm.Settings.RenameFindText; - var newText = SettingsForm.Settings.RenameNewText; - - switch (SettingsForm.Settings.RenameType) - { - case RenameType.Prefix: - name = newText + name; - break; - case RenameType.Suffix: - name += newText; - break; - case RenameType.AppendAfter: - var ind = name.IndexOf(oldText); - name = name.Substring(0, ind + oldText.Length) + newText + name.Substring(ind + oldText.Length); - break; - case RenameType.Replace: - name = name.Replace(oldText, newText); - break; - case RenameType.Increment: - string numPattern = @"(S\d{2,}E)(?'num'\d{2,})(-E\d{2,})?"; - name = Regex.Replace( - name, - numPattern, - m => - { - if (SettingsForm.Settings.IncrementMultiplier == 0) - return m.Groups[1].Value + (int.Parse(m.Groups["num"].Value) + index - 1).ToString("D2"); - - return m.Groups[1].Value + - ( - ((int.Parse(m.Groups["num"].Value) - 1) * SettingsForm.Settings.IncrementMultiplier + - ((index - 1) % SettingsForm.Settings.IncrementMultiplier)) + - 1 - ).ToString("D2"); - } - ); - break; - } - - // Add the index if necessary - switch (SettingsForm.Settings.RenameType) - { - case RenameType.Prefix: - case RenameType.Suffix: - case RenameType.AppendAfter: - case RenameType.Replace: - case RenameType.Increment: - if (name.Contains("{i}")) - { - if (SettingsForm.Settings.StartIndex == 0) - { - name.Replace("{i}", "" + index.ToString().PadLeft(SettingsForm.Settings.Padding, '0')); - } - else - { - name.Replace("{i}", "" + (SettingsForm.Settings.StartIndex + index - 1).ToString().PadLeft(SettingsForm.Settings.Padding, '0')); - } - - } - //else - //{ - // name += "-" + index; - //} - break; - } - - // Make sure the name is different - if (name == original) - { - name += "-" + index; - } - - var newName = Path.Combine(path, name + ".mkv"); - - // Rename again if there's already a file with that name - if (File.Exists(newName)) - { - newName = Path.Combine(path, name + DateTimeOffset.Now.ToUnixTimeSeconds() + ".mkv"); - } - - return newName; + processor.ProcessQueueItem(() => LockControls(false), () => LockControls(true), (QueueItem)QueueItemContextMenu.Tag); } - private void QueueListKeyDownHandler(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Delete) { - foreach (ListViewItem eachItem in QueueList.SelectedItems) + foreach (ListViewItem selectedItem in QueueList.SelectedItems) { - QueueItem queueItem = eachItem.Tag as QueueItem; + QueueItem queueItem = selectedItem.Tag as QueueItem; - QueueList.Items.Remove(eachItem); + QueueList.Items.Remove(selectedItem); QueueItems.Remove(queueItem); } } diff --git a/BananaSplit/MainForm.resx b/BananaSplit/MainForm.resx index 7187ab7..5dc7a8f 100644 --- a/BananaSplit/MainForm.resx +++ b/BananaSplit/MainForm.resx @@ -1,4 +1,64 @@ - + + + @@ -75,4 +135,477 @@ 951, 17 + + + + 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 + + \ No newline at end of file diff --git a/BananaSplit/Processor.cs b/BananaSplit/Processor.cs new file mode 100644 index 0000000..07f63ec --- /dev/null +++ b/BananaSplit/Processor.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BananaSplit; +public class Processor(Settings settings, LogForm logForm, StatusBarManager statusBarManager, Renamer renamer) +{ + private List queueItems; + private Thread ProcessingThread; + private readonly Ffmpeg ffmpeg = new(); + private readonly MkvToolNix MkvTool = new(); + + + + public void ProcessQueue(Action PreAction, Action PostAction, List queueItems) { + if (settings.ShowLog) + { + logForm.ShowLog(); + } + this.queueItems = queueItems; + + ProcessingThread = new Thread(() => ProcessQueueItems(PreAction, PostAction)); + + ProcessingThread.Start(); + } + + private void ProcessQueueItems(Action PreAction, Action PostAction) + { + PreAction(); + + statusBarManager.SetStatusBarProgressBarValue(0, queueItems.Count); + + switch (settings.ProcessType) + { + case ProcessingType.MatroskaChapters: + ProcessMatroskaChaptersInQueue(); + break; + + case ProcessingType.SplitAndEncode: + ProcessSplitAndEncodeInQueue(); + break; + + case ProcessingType.MkvToolNixSplit: + ProcessMkvToolNixSplitInQueue(); + break; + } + + PostAction(); + + statusBarManager.ClearStatusBarProgressBarValue(); + } + + private void ProcessMkvToolNixSplitInQueue() + { + for (var i = 0; i < queueItems.Count; i++) + { + QueueItem queueItem = queueItems[i]; + statusBarManager.SetStatusBarProgressBarValue(i + 1, queueItems.Count); + statusBarManager.SetStatusBarLabelValue($"MKV Splitting for {Path.GetFileName(queueItem.FileName)}"); + + ProcessMKVSplit(queueItem); + } + + statusBarManager.SetStatusBarLabelValue("Done splitting!"); + } + + private void ProcessSplitAndEncodeInQueue() + { + for (var i = 0; i < queueItems.Count; i++) + { + QueueItem queueItem = queueItems[i]; + + statusBarManager.SetStatusBarProgressBarValue(i + 1, queueItems.Count); + statusBarManager.SetStatusBarLabelValue($"Encoding for {Path.GetFileName(queueItem.FileName)}"); + + ProcessSplitAndEncode(queueItem); + } + + statusBarManager.SetStatusBarLabelValue("Done encoding!"); + } + + private void ProcessMatroskaChaptersInQueue() + { + for (var i = 0; i < queueItems.Count; i++) + { + QueueItem queueItem = queueItems[i]; + + statusBarManager.SetStatusBarProgressBarValue(i + 1, queueItems.Count); + statusBarManager.SetStatusBarLabelValue($"Adding chapters for {Path.GetFileName(queueItem.FileName)}"); + + ProcessMatroskaChapters(queueItem); + } + + statusBarManager.SetStatusBarLabelValue("Done adding chapters!"); + } + + public void ProcessQueueItem(Action PreAction, Action PostAction, QueueItem queueItem) + { + queueItems = [queueItem]; + ProcessQueueItems(PreAction, PostAction); + } + + private void ProcessMatroskaChapters(QueueItem queueItem) + { + List chapterTimeSpans = + [ + // Always add the beginning as a chapter + new TimeSpan(0, 0, 0), + ]; + + chapterTimeSpans.AddRange( + queueItem.BlackFrames + .Where(bf => bf.Selected) + .Select(bf => bf.End.Subtract(bf.Duration / 2)) + ); + + + + if (!ffmpeg.IsMatroska(queueItem.FileName, FfmpegLog)) + { + var matroskaPath = MkvTool.RemuxToMatroska(queueItem.FileName); + queueItem.FileName = matroskaPath; + } + + var chapters = MkvToolNix.GenerateChapters(chapterTimeSpans); + + MkvTool.InjectChapters(queueItem.FileName, chapters); + } + + private void FfmpegLog(object sender, DataReceivedEventArgs e) + { + if (settings.ShowLog) + { + logForm.Invoke( + new MethodInvoker( + delegate () + { + logForm.Log(e.Data); + } + ) + ); + } + } + + private void ProcessMKVSplit(QueueItem queueItem) + { + var segments = queueItem.GetSegments(); + + var outputPath = Path.Combine(Path.GetDirectoryName(queueItem.FileName), "output"); + Directory.CreateDirectory(outputPath); + + var newName = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(queueItem.FileName) + "-%03d.mkv"); + + MkvTool.SplitSegments(queueItem.FileName, newName, segments.ToList(), FfmpegLog); + } + + private void ProcessSplitAndEncode(QueueItem queueItem) + { + var segments = queueItem.GetSegments(); + + var encodingFileName = queueItem.FileName; + + if (!renamer.RenameOriginalIfWanted(ref encodingFileName)) + return; + + for (int i = 0; i < segments.Count; i++) + { + var newName = renamer.GetNewName(queueItem.FileName, i + 1); + + ffmpeg.EncodeSegments(encodingFileName, newName, settings.FmpegArguments.Replace("\r\n", " "), segments[i], FfmpegLog); + } + } +} diff --git a/BananaSplit/Program.cs b/BananaSplit/Program.cs index 2bb5ee4..767f8ed 100644 --- a/BananaSplit/Program.cs +++ b/BananaSplit/Program.cs @@ -1,5 +1,8 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; using System.Windows.Forms; +using static System.Windows.Forms.DataFormats; namespace BananaSplit { @@ -12,8 +15,29 @@ internal static class Program private static void Main() { Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainForm()); + Application.SetCompatibleTextRenderingDefault(false); + + var host = CreateHostBuilder().Build(); + ServiceProvider = host.Services; + + Application.Run(ServiceProvider.GetRequiredService()); + } + + public static IServiceProvider ServiceProvider { get; private set; } + static IHostBuilder CreateHostBuilder() + { + return Host.CreateDefaultBuilder() + .ConfigureServices((context, services) => services + .AddSingleton() + .AddTransient() + .AddTransient() + .AddTransient() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddAutoMapper(typeof(MainForm).Assembly)); } } } diff --git a/BananaSplit/QueueItem.cs b/BananaSplit/QueueItem.cs index 9ba5a6a..af08983 100644 --- a/BananaSplit/QueueItem.cs +++ b/BananaSplit/QueueItem.cs @@ -4,63 +4,30 @@ namespace BananaSplit { - public class QueueItem + public class QueueItem(string fileName) { - public Guid Id { get; set; } - public string FileName { get; set; } - public bool Scanned { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); + public string FileName { get; set; } = fileName; + public bool Scanned { get; set; } = false; public DateTime LastScanned { get; set; } - public ICollection BlackFrames { get; set; } + public ICollection BlackFrames { get; set; } = []; public TimeSpan Duration { get; set; } - public float Fps { get; set; } - public int NumFrames { get; set; } + public float? Fps { get; set; } = null; + public int NumFrames { get; set; } = 0; - public QueueItem(string fileName) + public List GetSegments() { - Id = Guid.NewGuid(); - FileName = fileName; - Scanned = false; - BlackFrames = new List(); - Fps = 0; - NumFrames = 0; - } + List segments = []; - public ICollection GetSegments() - { - var segments = new List(); var selectedFrames = BlackFrames.Where(bf => bf.Selected).ToList(); if (selectedFrames.Count > 0) - { - // The first segment starts at the beginning of the video and ends at the start of the first black frame - var start = new Segment() - { - Start = new TimeSpan(0, 0, 0), - End = selectedFrames.First().Marker - }; - - // The last segment starts at the end of the last black frame to the end of the video - var end = new Segment() - { - Start = selectedFrames.Last().Marker, - End = Duration - }; - - var additionalSegments = new List(); - - int index = 0; - - // Loop through to get any additional segments. - while (additionalSegments.Count < selectedFrames.Count - 1) - { - additionalSegments.Add(new Segment() - { - Start = selectedFrames[index].Marker, - End = selectedFrames[index + 1].Marker - }); - - index++; - } + { + Segment start = GetStartSegment(selectedFrames); + + Segment end = GetEndingSegment(selectedFrames); + + List additionalSegments = GetAdditionalSegments(selectedFrames); segments.Add(start); segments.AddRange(additionalSegments); @@ -68,6 +35,43 @@ public ICollection GetSegments() } return segments; - } + } + + private static List GetAdditionalSegments(List selectedFrames) + { + var additionalSegments = new List(); + + // Loop through to get any additional segments. + for (var i = 0; i < selectedFrames.Count - 1; i++) + { + additionalSegments.Add(new Segment() + { + Start = selectedFrames[i].Marker, + End = selectedFrames[i + 1].Marker + }); + } + + return additionalSegments; + } + + private Segment GetEndingSegment(List selectedFrames) + { + // The last segment starts at the end of the last black frame to the end of the video + return new Segment() + { + Start = selectedFrames[^1].Marker, + End = Duration + }; + } + + private static Segment GetStartSegment(List selectedFrames) + { + // The first segment starts at the beginning of the video and ends at the start of the first black frame + return new Segment() + { + Start = new TimeSpan(0, 0, 0), + End = selectedFrames[0].Marker + }; + } } } diff --git a/BananaSplit/QueueManager.cs b/BananaSplit/QueueManager.cs new file mode 100644 index 0000000..c017f8b --- /dev/null +++ b/BananaSplit/QueueManager.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BananaSplit; +public class QueueManager(Scanner scanner) +{ + + public MainForm MainForm { get; set; } + + private readonly string[] supportedExtensions = + [ + ".avi", + ".flv", + ".m4p", + ".m4v", + ".mkv", + ".mov", + ".mp2", + ".mp4", + ".mpe", + ".mpeg", + ".mpg", + ".mpv", + ".ogg", + ".ts", + ".webm", + ".wmv" + ]; + + // QueueItemContextMenuRemove + public void RemoveQueueItem(object sender, EventArgs e) + { + QueueItem queueItem = (QueueItem)MainForm.QueueItemContextMenu.Tag; + + MainForm.QueueList.Items.RemoveByKey(queueItem.Id.ToString()); + MainForm.QueueItems.Remove(queueItem); + } + + // QueueListContextMenuRemove + public void RemoveQueueList(object sender, EventArgs e) + { + foreach (var queueItem in MainForm.QueueItems) + { + MainForm.QueueList.Items.RemoveByKey(queueItem.Id.ToString()); + } + + MainForm.QueueItems.Clear(); + } + + public void AutoSizeQueueList(object sender, EventArgs e) + { + MainForm.QueueList.Columns[0].Width = MainForm.QueueList.Width - 4; + } + + public void OpenQueueItemContextMenu(object sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Right) + return; + + if (MainForm.QueueList.FocusedItem != null && MainForm.QueueList.FocusedItem.Bounds.Contains(e.Location)) + { + MainForm.QueueItemContextMenu.Tag = MainForm.QueueList.FocusedItem.Tag; + } + + MainForm.QueueListContextMenu.Show(Cursor.Position); + } + + public void AddFilesToQueueDialog(object sender, EventArgs e) + { + string[] files; + using (OpenFileDialog openFileDialog = new OpenFileDialog()) + { + openFileDialog.Filter = $"Video Files (*{string.Join(",*", supportedExtensions)})|*{string.Join(";*", supportedExtensions)}"; + openFileDialog.FilterIndex = 2; + openFileDialog.RestoreDirectory = true; + openFileDialog.Multiselect = true; + + if (openFileDialog.ShowDialog() != DialogResult.OK) + return; + + files = openFileDialog.FileNames; + } + + AddFilesToQueue(files); + } + + public void AddFolderToQueueDialog(object sender, EventArgs e) + { + string[] files; + + using (FolderBrowserDialog openFolderDialog = new FolderBrowserDialog()) + { + var result = openFolderDialog.ShowDialog(); + + if (result != DialogResult.OK || string.IsNullOrWhiteSpace(openFolderDialog.SelectedPath)) + return; + + files = Directory.GetFiles(openFolderDialog.SelectedPath); + } + + AddFilesToQueue(files); + } + + public void AddFilesToQueue(string[] files) + { + if (files == null || files.Length == 0 || !files.Select(AddToQueue).Any(added => true)) + return; + + scanner.StartScanningThread(AddItemsToQueueList, MainForm.QueueItems); + } + + public bool AddToQueue(string path) + { + if (File.Exists(path) && supportedExtensions.Contains(Path.GetExtension(path).ToLowerInvariant())) + { + MainForm.QueueItems.Add(new QueueItem(path)); + return true; + } + + + if (Directory.Exists(path)) + { + bool addedAnything = false; + foreach (var file in Directory.GetFiles(path)) + addedAnything |= AddToQueue(file); + + foreach (var folder in Directory.GetDirectories(path)) + addedAnything |= AddToQueue(folder); + return addedAnything; + } + + return false; + } + + public static void SetDragOverEffect(object sender, DragEventArgs e) + { + e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Link : DragDropEffects.None; + } + + public void AddDragDropItemsToQueue(object sender, DragEventArgs e) + { + var files = ((DataObject)e.Data).GetFileDropList().Cast().ToArray(); + + AddFilesToQueue(files); + } + + public void AddItemsToQueueList(List items) + { + MainForm.QueueList.Invoke( + new MethodInvoker( + delegate () + { + foreach (var item in items) + { + MainForm.QueueList.Items.Add(new ListViewItem() + { + Text = Path.GetFileName(item.FileName), + ToolTipText = item.FileName, + Name = item.Id.ToString(), + Tag = item + }); + } + } + ) + ); + } +} diff --git a/BananaSplit/ReferenceFrame.cs b/BananaSplit/ReferenceFrame.cs index 13238f1..f73fe63 100644 --- a/BananaSplit/ReferenceFrame.cs +++ b/BananaSplit/ReferenceFrame.cs @@ -1,10 +1,11 @@ using System; +using System.Drawing; namespace BananaSplit { public class ReferenceFrame { public DateTime ExtractedOn { get; set; } - public byte[] Data { get; set; } + public Bitmap Bitmap { get; set; } = null; } } diff --git a/BananaSplit/Renamer.cs b/BananaSplit/Renamer.cs new file mode 100644 index 0000000..bb03959 --- /dev/null +++ b/BananaSplit/Renamer.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BananaSplit; +public class Renamer(Settings settings) +{ + public bool RenameOriginalIfWanted(ref string encodingFileName) + { + if (!settings.RenameOriginal) + return true; + + var fileInfo = new FileInfo(encodingFileName); + var path = Path.GetDirectoryName(encodingFileName); + var name = Path.GetFileNameWithoutExtension(encodingFileName); + var ext = Path.GetExtension(encodingFileName); + + encodingFileName = Path.Combine(path, name + "_original" + ext); + try + { + fileInfo.MoveTo(encodingFileName); + } + catch + { + MessageBox.Show($"There was an error renaming the original file: {encodingFileName}\nMake sure it's not being used by another process!", "Error", MessageBoxButtons.OK); + return false; + } + + return true; + } + + public string GetNewName(string fileName, int index) + { + var path = Path.GetDirectoryName(fileName); + var name = Path.GetFileNameWithoutExtension(fileName); + var original = name; + + var oldText = settings.RenameFindText; + var newText = settings.RenameNewText; + name = RenameEpisode(index, name, oldText, newText); + + // Add the index if necessary + name = AddIndexToName(index, name); + + // Make sure the name is different + if (name == original) + { + name += "-" + index; + } + + var newName = Path.Combine(path, name + ".mkv"); + + // Rename again if there's already a file with that name + if (File.Exists(newName)) + { + newName = Path.Combine(path, name + DateTimeOffset.Now.ToUnixTimeSeconds() + ".mkv"); + } + + return newName; + } + + private string RenameEpisode(int index, string name, string oldText, string newText) + { + switch (settings.RenameType) + { + case RenameType.Prefix: + name = newText + name; + break; + case RenameType.Suffix: + name += newText; + break; + case RenameType.AppendAfter: + var regex = new Regex(Regex.Escape(oldText)); + name = regex.Replace(name, $"{oldText}{newText}", 1); + break; + case RenameType.Replace: + name = name.Replace(oldText, newText); + break; + case RenameType.Increment: + name = RenameWithIncrement(index, name); + break; + } + + return name; + } + + private string RenameWithIncrement(int index, string name) + { + //TODO: Let user input this + string numPattern = @"(S\d{2,}E)(?'num'\d{2,})(-E\d{2,})?"; + name = Regex.Replace( + name, + numPattern, + match => + { + // Original episode number before renaming + var num = int.Parse(match.Groups["num"].Value); + // e.g. S02E + var season = match.Groups[1].Value; + + //Simple increment + if (settings.IncrementMultiplier == 0) + return season + (num + index - 1).ToString("D2"); + + + return season + + ( + ((num - 1) * settings.IncrementMultiplier + + ((index - 1) % settings.IncrementMultiplier)) + + 1 + ).ToString("D2"); + } + ); + return name; + } + + private string AddIndexToName(int index, string name) + { + if (name.Contains("{i}")) + { + if (settings.StartIndex == 0) + { + name = name.Replace("{i}", "" + index.ToString().PadLeft(settings.Padding, '0')); + } + else + { + name = name.Replace("{i}", "" + (settings.StartIndex + index - 1).ToString().PadLeft(settings.Padding, '0')); + } + + } + + return name; + } +} diff --git a/BananaSplit/Scanner.cs b/BananaSplit/Scanner.cs new file mode 100644 index 0000000..e62bde0 --- /dev/null +++ b/BananaSplit/Scanner.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BananaSplit; +public partial class Scanner(StatusBarManager statusBarManager, Settings settings, LogForm logForm) +{ + private readonly Ffmpeg ffmpeg = new(); + + public void StartScanningThread(Action> itemsAction, List queueItems) + { + new Thread(() => ScanQueueItems(itemsAction, queueItems)).Start(); + } + + + private void ScanQueueItems(Action> itemsAction, List inputItems) + { + ShowLog(); + + statusBarManager.SetStatusBarProgressBarValue(0, inputItems.Count); + + var i = 0; + int countedFrames = 0; + + var unscannedItems = inputItems.Where(qi => !qi.Scanned); + // Get all video durations and fps for a better progress bar + var totalNumFrames = GetDurationsAndFps(unscannedItems); + + // Parse items + List queueItems = []; + foreach (var item in unscannedItems) + { + i++; + + statusBarManager.SetStatusBarLabelValue($"Detecting frames for {Path.GetFileName(item.FileName)}"); + item.Scanned = true; + item.LastScanned = DateTime.Now; + item.BlackFrames = ffmpeg.DetectBlackFrameIntervals(item.FileName, settings.BlackFrameDuration, settings.BlackFrameThreshold, settings.BlackFramePixelThreshold, (s, e) => + { + string logMsg = e.Data; + Log(logMsg); + if (logMsg == null) + { + return; + } + + string framePattern = @"(\sframe:|frame=\s+)(?'frame'\d+)"; + Regex regex = new Regex(framePattern, RegexOptions.Singleline); + + Match m = regex.Match(logMsg); + if (m.Success && int.TryParse(m.Groups["frame"].Value, out int frame)) + { + statusBarManager.SetStatusBarProgressBarValue(countedFrames + frame, totalNumFrames); + } + }); + countedFrames += item.NumFrames; + statusBarManager.SetStatusBarProgressBarValue(countedFrames, totalNumFrames); + + var frameNum = 1; + foreach (var frame in item.BlackFrames) + { + long offset = (long)(settings.ReferenceFrameOffset * TimeSpan.TicksPerSecond); + TimeSpan referenceFramePosition = frame.End.Add(new TimeSpan(offset)); + + statusBarManager.SetStatusBarLabelValue($"Generating frame {frameNum} of {item.BlackFrames.Count} at {referenceFramePosition}"); + frame.ReferenceFrame = new ReferenceFrame(); + frame.ReferenceFrame.Bitmap = Utilities.BytesToImage(ffmpeg.ExtractFrame(item.FileName, referenceFramePosition, FfmpegLog)); + frameNum++; + } + + queueItems.Add(item); + } + + itemsAction(queueItems); + + statusBarManager.SetStatusBarLabelValue("Done!"); + statusBarManager.ClearStatusBarProgressBarValue(); + } + + + private void FfmpegLog(object sender, DataReceivedEventArgs e) + { + if (settings.ShowLog) + { + logForm.Invoke( + new MethodInvoker( + delegate () + { + logForm.Log(e.Data); + } + ) + ); + } + } + + private void ShowLog() + { + if (settings.ShowLog) + logForm.ShowLog(); + } + + private int GetDurationsAndFps(IEnumerable items) + { + var totalNumFrames = 0; + foreach (var item in items) + { + item.Duration = ffmpeg.GetDuration(item.FileName, (s, e) => LogMsgAndParseFps(e, item)); + + item.NumFrames = (int)Math.Ceiling(item.Duration.TotalSeconds * (item.Fps ?? 0)); + totalNumFrames += item.NumFrames; + } + + return totalNumFrames; + } + + private void LogMsgAndParseFps(DataReceivedEventArgs e, QueueItem item) + { + string logMsg = e.Data; + Log(logMsg); + if (logMsg == null || item.Fps != null) + return; + + Regex regex = FpsRegEx(); + + Match m = regex.Match(logMsg); + if (!m.Success || !float.TryParse(m.Groups["fps"].Value, out float fps)) + return; + + item.Fps = fps; + } + + private void Log(string text) + { + if (logForm.Visible) + { + logForm.Invoke(new MethodInvoker( + delegate () + { + logForm.Log(text); + }) + ); + } + } + + [GeneratedRegex(@"(?'fps'[.\d]+) fps,", RegexOptions.Singleline)] + private static partial Regex FpsRegEx(); +} diff --git a/BananaSplit/Settings.cs b/BananaSplit/Settings.cs index cf3662a..a76867e 100644 --- a/BananaSplit/Settings.cs +++ b/BananaSplit/Settings.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using AutoMapper; +using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using System.IO; @@ -11,7 +12,7 @@ public enum ProcessingType [Display(Name = "Split and Encode")] SplitAndEncode, [Display(Name = "MKVToolNix Split")] - MKVToolNixSplit + MkvToolNixSplit } public enum RenameType @@ -29,84 +30,39 @@ public enum RenameType } - public class Settings - { - public double BlackFrameDuration { get; set; } - public double BlackFrameThreshold { get; set; } - public double BlackFramePixelThreshold { get; set; } - public string FFMPEGArguments { get; set; } - public ProcessingType ProcessType { get; set; } - public bool ShowLog { get; set; } - public bool DeleteOriginal { get; set; } - public double ReferenceFrameOffset { get; set; } - public string RenameFindText { get; set; } - public string RenameNewText { get; set; } - public RenameType RenameType { get; set; } - public bool RenameOriginal { get; set; } - public int IncrementMultiplier { get; set; } - public int StartIndex { get; set; } - public int Padding { get; set; } - - public Settings() - { - BlackFrameDuration = 0.04; - BlackFrameThreshold = 0.98; - BlackFramePixelThreshold = 0.15; - FFMPEGArguments = "-i \"{source}\" -ss {start} -t {duration} -c:v libx264 -crf 18 -preset slow -c:a copy -map 0 \"{destination}\""; - ProcessType = ProcessingType.MKVToolNixSplit; - ShowLog = false; - DeleteOriginal = false; - ReferenceFrameOffset = 1; - RenameFindText = ""; - RenameNewText = "{i}"; - RenameType = RenameType.Increment; - RenameOriginal = true; - IncrementMultiplier = 2; - StartIndex = 1; - Padding = 2; - } + public class Settings(IMapper mapper) + { + + public double BlackFrameDuration { get; set; } = 0.04; + public double BlackFrameThreshold { get; set; } = 0.98; + public double BlackFramePixelThreshold { get; set; } = 0.15; + public string FmpegArguments { get; set; } = "-i \"{source}\" -ss {start} -t {duration} -c:v libx264 -crf 18 -preset slow -c:a copy -map 0 \"{destination}\""; + public ProcessingType ProcessType { get; set; } = ProcessingType.MkvToolNixSplit; + public bool ShowLog { get; set; } = false; + public bool DeleteOriginal { get; set; } = false; + public double ReferenceFrameOffset { get; set; } = 1; + public string RenameFindText { get; set; } = ""; + public string RenameNewText { get; set; } = "{i}"; + public RenameType RenameType { get; set; } = RenameType.Increment; + public bool RenameOriginal { get; set; } = true; + public int IncrementMultiplier { get; set; } = 2; + public int StartIndex { get; set; } = 1; + public int Padding { get; set; } = 2; public void Load() { - Settings settings; - - if (File.Exists("Settings.json")) - { - var json = File.ReadAllText("Settings.json"); - - try - { - settings = JsonConvert.DeserializeObject(json); - } - catch - { - settings = new Settings(); - settings.Save(); - } - } - else - { - settings = new Settings(); - settings.Save(); + try + { + var json = File.ReadAllText("Settings.json"); + mapper.Map(JsonConvert.DeserializeObject(json), this); + } + catch + { + Save(); } - - BlackFrameDuration = settings.BlackFrameDuration; - BlackFrameThreshold = settings.BlackFrameThreshold; - BlackFramePixelThreshold = settings.BlackFramePixelThreshold; - FFMPEGArguments = settings.FFMPEGArguments; - ProcessType = settings.ProcessType; - ShowLog = settings.ShowLog; - DeleteOriginal = settings.DeleteOriginal; - ReferenceFrameOffset = settings.ReferenceFrameOffset; - RenameFindText = settings.RenameFindText; - RenameNewText = settings.RenameNewText; - RenameType = settings.RenameType; - RenameOriginal = settings.RenameOriginal; - IncrementMultiplier = settings.IncrementMultiplier; - StartIndex = settings.StartIndex; - Padding = settings.Padding; - } - + } + + public void Save() { var json = JsonConvert.SerializeObject(this, Formatting.Indented); diff --git a/BananaSplit/SettingsForm.Designer.cs b/BananaSplit/SettingsForm.Designer.cs index ad92566..a6cf265 100644 --- a/BananaSplit/SettingsForm.Designer.cs +++ b/BananaSplit/SettingsForm.Designer.cs @@ -28,7 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SettingsForm)); + var resources = new System.ComponentModel.ComponentResourceManager(typeof(SettingsForm)); tabControl1 = new System.Windows.Forms.TabControl(); tabPage1 = new System.Windows.Forms.TabPage(); BlackFramePixelThresholdInput = new System.Windows.Forms.NumericUpDown(); @@ -88,11 +88,11 @@ private void InitializeComponent() tabControl1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; tabControl1.Controls.Add(tabPage1); tabControl1.Controls.Add(tabPage2); - tabControl1.Location = new System.Drawing.Point(26, 30); - tabControl1.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + tabControl1.Location = new System.Drawing.Point(13, 15); + tabControl1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); tabControl1.Name = "tabControl1"; tabControl1.SelectedIndex = 0; - tabControl1.Size = new System.Drawing.Size(912, 953); + tabControl1.Size = new System.Drawing.Size(456, 476); tabControl1.TabIndex = 0; // // tabPage1 @@ -110,22 +110,22 @@ private void InitializeComponent() tabPage1.Controls.Add(BlackFrameThresholdLabel); tabPage1.Controls.Add(BlackFrameDurationLabel); tabPage1.Controls.Add(FFMPEGArgumentsGroupBox); - tabPage1.Location = new System.Drawing.Point(8, 46); - tabPage1.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + tabPage1.Location = new System.Drawing.Point(4, 24); + tabPage1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); tabPage1.Name = "tabPage1"; - tabPage1.Padding = new System.Windows.Forms.Padding(7, 6, 7, 6); - tabPage1.Size = new System.Drawing.Size(896, 899); + tabPage1.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + tabPage1.Size = new System.Drawing.Size(448, 448); tabPage1.TabIndex = 0; tabPage1.Text = "General"; tabPage1.UseVisualStyleBackColor = true; // // BlackFramePixelThresholdInput // - BlackFramePixelThresholdInput.DecimalPlaces = 1; - BlackFramePixelThresholdInput.Location = new System.Drawing.Point(13, 117); - BlackFramePixelThresholdInput.Margin = new System.Windows.Forms.Padding(6); + BlackFramePixelThresholdInput.DecimalPlaces = 2; + BlackFramePixelThresholdInput.Increment = new decimal(new int[] { 1, 0, 0, 131072 }); + BlackFramePixelThresholdInput.Location = new System.Drawing.Point(6, 58); BlackFramePixelThresholdInput.Name = "BlackFramePixelThresholdInput"; - BlackFramePixelThresholdInput.Size = new System.Drawing.Size(260, 39); + BlackFramePixelThresholdInput.Size = new System.Drawing.Size(130, 23); BlackFramePixelThresholdInput.TabIndex = 12; BlackFramePixelThresholdInput.Tag = "BlackFramePixelThreshold"; BlackFramePixelThresholdInput.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; @@ -133,19 +133,20 @@ private void InitializeComponent() // bplabel1 // bplabel1.AutoSize = true; - bplabel1.Location = new System.Drawing.Point(290, 119); - bplabel1.Margin = new System.Windows.Forms.Padding(7, 0, 7, 0); + bplabel1.Location = new System.Drawing.Point(145, 60); + bplabel1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); bplabel1.Name = "bplabel1"; - bplabel1.Size = new System.Drawing.Size(238, 32); + bplabel1.Size = new System.Drawing.Size(118, 15); bplabel1.TabIndex = 11; bplabel1.Text = "Black Pixel Threshold"; // // ShowLogCheckbox // ShowLogCheckbox.AutoSize = true; - ShowLogCheckbox.Location = new System.Drawing.Point(13, 268); + ShowLogCheckbox.Location = new System.Drawing.Point(6, 134); + ShowLogCheckbox.Margin = new System.Windows.Forms.Padding(2); ShowLogCheckbox.Name = "ShowLogCheckbox"; - ShowLogCheckbox.Size = new System.Drawing.Size(150, 36); + ShowLogCheckbox.Size = new System.Drawing.Size(78, 19); ShowLogCheckbox.TabIndex = 7; ShowLogCheckbox.Text = "Show Log"; ShowLogCheckbox.UseVisualStyleBackColor = true; @@ -153,20 +154,20 @@ private void InitializeComponent() // ReferenceFrameOffsetLabel // ReferenceFrameOffsetLabel.AutoSize = true; - ReferenceFrameOffsetLabel.Location = new System.Drawing.Point(290, 170); - ReferenceFrameOffsetLabel.Margin = new System.Windows.Forms.Padding(7, 0, 7, 0); + ReferenceFrameOffsetLabel.Location = new System.Drawing.Point(145, 85); + ReferenceFrameOffsetLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); ReferenceFrameOffsetLabel.Name = "ReferenceFrameOffsetLabel"; - ReferenceFrameOffsetLabel.Size = new System.Drawing.Size(296, 32); + ReferenceFrameOffsetLabel.Size = new System.Drawing.Size(146, 15); ReferenceFrameOffsetLabel.TabIndex = 3; ReferenceFrameOffsetLabel.Text = "Reference Frame Offset (s)"; // // ReferenceFrameOffsetInput // ReferenceFrameOffsetInput.DecimalPlaces = 1; - ReferenceFrameOffsetInput.Location = new System.Drawing.Point(13, 168); - ReferenceFrameOffsetInput.Margin = new System.Windows.Forms.Padding(6); + ReferenceFrameOffsetInput.Increment = new decimal(new int[] { 1, 0, 0, 65536 }); + ReferenceFrameOffsetInput.Location = new System.Drawing.Point(6, 84); ReferenceFrameOffsetInput.Name = "ReferenceFrameOffsetInput"; - ReferenceFrameOffsetInput.Size = new System.Drawing.Size(260, 39); + ReferenceFrameOffsetInput.Size = new System.Drawing.Size(130, 23); ReferenceFrameOffsetInput.TabIndex = 5; ReferenceFrameOffsetInput.Tag = "Settings.BlackFrameDuration"; ReferenceFrameOffsetInput.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; @@ -174,10 +175,9 @@ private void InitializeComponent() // DeleteOriginalCheckbox // DeleteOriginalCheckbox.AutoSize = true; - DeleteOriginalCheckbox.Location = new System.Drawing.Point(13, 313); - DeleteOriginalCheckbox.Margin = new System.Windows.Forms.Padding(6); + DeleteOriginalCheckbox.Location = new System.Drawing.Point(6, 156); DeleteOriginalCheckbox.Name = "DeleteOriginalCheckbox"; - DeleteOriginalCheckbox.Size = new System.Drawing.Size(308, 36); + DeleteOriginalCheckbox.Size = new System.Drawing.Size(155, 19); DeleteOriginalCheckbox.TabIndex = 8; DeleteOriginalCheckbox.Text = "Delete/Replace Originals"; DeleteOriginalCheckbox.UseVisualStyleBackColor = true; @@ -185,41 +185,40 @@ private void InitializeComponent() // ProcessTypeLabel // ProcessTypeLabel.AutoSize = true; - ProcessTypeLabel.Location = new System.Drawing.Point(290, 222); - ProcessTypeLabel.Margin = new System.Windows.Forms.Padding(7, 0, 7, 0); + ProcessTypeLabel.Location = new System.Drawing.Point(145, 111); + ProcessTypeLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); ProcessTypeLabel.Name = "ProcessTypeLabel"; - ProcessTypeLabel.Size = new System.Drawing.Size(219, 32); + ProcessTypeLabel.Size = new System.Drawing.Size(109, 15); ProcessTypeLabel.TabIndex = 3; ProcessTypeLabel.Text = "Processing Method"; // // ProcessTypeComboBox // ProcessTypeComboBox.FormattingEnabled = true; - ProcessTypeComboBox.Location = new System.Drawing.Point(13, 219); - ProcessTypeComboBox.Margin = new System.Windows.Forms.Padding(6); + ProcessTypeComboBox.Location = new System.Drawing.Point(6, 110); ProcessTypeComboBox.Name = "ProcessTypeComboBox"; - ProcessTypeComboBox.Size = new System.Drawing.Size(260, 40); + ProcessTypeComboBox.Size = new System.Drawing.Size(132, 23); ProcessTypeComboBox.TabIndex = 6; ProcessTypeComboBox.Tag = "Settings.ProcessType"; // // BlackFrameDurationInput // - BlackFrameDurationInput.DecimalPlaces = 1; - BlackFrameDurationInput.Location = new System.Drawing.Point(13, 15); - BlackFrameDurationInput.Margin = new System.Windows.Forms.Padding(6); + BlackFrameDurationInput.DecimalPlaces = 2; + BlackFrameDurationInput.Increment = new decimal(new int[] { 1, 0, 0, 131072 }); + BlackFrameDurationInput.Location = new System.Drawing.Point(6, 8); BlackFrameDurationInput.Name = "BlackFrameDurationInput"; - BlackFrameDurationInput.Size = new System.Drawing.Size(260, 39); + BlackFrameDurationInput.Size = new System.Drawing.Size(130, 23); BlackFrameDurationInput.TabIndex = 5; BlackFrameDurationInput.Tag = "Settings.BlackFrameDuration"; BlackFrameDurationInput.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // BlackFrameThresholdInput // - BlackFrameThresholdInput.DecimalPlaces = 1; - BlackFrameThresholdInput.Location = new System.Drawing.Point(13, 66); - BlackFrameThresholdInput.Margin = new System.Windows.Forms.Padding(6); + BlackFrameThresholdInput.DecimalPlaces = 2; + BlackFrameThresholdInput.Increment = new decimal(new int[] { 1, 0, 0, 131072 }); + BlackFrameThresholdInput.Location = new System.Drawing.Point(6, 33); BlackFrameThresholdInput.Name = "BlackFrameThresholdInput"; - BlackFrameThresholdInput.Size = new System.Drawing.Size(260, 39); + BlackFrameThresholdInput.Size = new System.Drawing.Size(130, 23); BlackFrameThresholdInput.TabIndex = 4; BlackFrameThresholdInput.Tag = "BlackFrameThreshold"; BlackFrameThresholdInput.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; @@ -227,20 +226,20 @@ private void InitializeComponent() // BlackFrameThresholdLabel // BlackFrameThresholdLabel.AutoSize = true; - BlackFrameThresholdLabel.Location = new System.Drawing.Point(290, 68); - BlackFrameThresholdLabel.Margin = new System.Windows.Forms.Padding(7, 0, 7, 0); + BlackFrameThresholdLabel.Location = new System.Drawing.Point(145, 34); + BlackFrameThresholdLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); BlackFrameThresholdLabel.Name = "BlackFrameThresholdLabel"; - BlackFrameThresholdLabel.Size = new System.Drawing.Size(255, 32); + BlackFrameThresholdLabel.Size = new System.Drawing.Size(126, 15); BlackFrameThresholdLabel.TabIndex = 3; BlackFrameThresholdLabel.Text = "Black Frame Threshold"; // // BlackFrameDurationLabel // BlackFrameDurationLabel.AutoSize = true; - BlackFrameDurationLabel.Location = new System.Drawing.Point(290, 17); - BlackFrameDurationLabel.Margin = new System.Windows.Forms.Padding(7, 0, 7, 0); + BlackFrameDurationLabel.Location = new System.Drawing.Point(145, 8); + BlackFrameDurationLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); BlackFrameDurationLabel.Name = "BlackFrameDurationLabel"; - BlackFrameDurationLabel.Size = new System.Drawing.Size(273, 32); + BlackFrameDurationLabel.Size = new System.Drawing.Size(136, 15); BlackFrameDurationLabel.TabIndex = 1; BlackFrameDurationLabel.Text = "Black Frame Duration (s)"; // @@ -249,11 +248,9 @@ private void InitializeComponent() FFMPEGArgumentsGroupBox.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; FFMPEGArgumentsGroupBox.Controls.Add(FFMPEGArgumentsInput); FFMPEGArgumentsGroupBox.Controls.Add(FFMPEGArgumentsLegend); - FFMPEGArgumentsGroupBox.Location = new System.Drawing.Point(13, 361); - FFMPEGArgumentsGroupBox.Margin = new System.Windows.Forms.Padding(6); + FFMPEGArgumentsGroupBox.Location = new System.Drawing.Point(6, 180); FFMPEGArgumentsGroupBox.Name = "FFMPEGArgumentsGroupBox"; - FFMPEGArgumentsGroupBox.Padding = new System.Windows.Forms.Padding(6); - FFMPEGArgumentsGroupBox.Size = new System.Drawing.Size(862, 526); + FFMPEGArgumentsGroupBox.Size = new System.Drawing.Size(431, 263); FFMPEGArgumentsGroupBox.TabIndex = 10; FFMPEGArgumentsGroupBox.TabStop = false; FFMPEGArgumentsGroupBox.Text = "FFMPEG Arguments"; @@ -261,21 +258,19 @@ private void InitializeComponent() // FFMPEGArgumentsInput // FFMPEGArgumentsInput.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - FFMPEGArgumentsInput.Location = new System.Drawing.Point(12, 44); - FFMPEGArgumentsInput.Margin = new System.Windows.Forms.Padding(6); + FFMPEGArgumentsInput.Location = new System.Drawing.Point(6, 22); FFMPEGArgumentsInput.Multiline = true; FFMPEGArgumentsInput.Name = "FFMPEGArgumentsInput"; - FFMPEGArgumentsInput.Size = new System.Drawing.Size(664, 470); + FFMPEGArgumentsInput.Size = new System.Drawing.Size(334, 237); FFMPEGArgumentsInput.TabIndex = 9; // // FFMPEGArgumentsLegend // FFMPEGArgumentsLegend.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; FFMPEGArgumentsLegend.AutoSize = true; - FFMPEGArgumentsLegend.Location = new System.Drawing.Point(688, 47); - FFMPEGArgumentsLegend.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); + FFMPEGArgumentsLegend.Location = new System.Drawing.Point(344, 24); FFMPEGArgumentsLegend.Name = "FFMPEGArgumentsLegend"; - FFMPEGArgumentsLegend.Size = new System.Drawing.Size(147, 192); + FFMPEGArgumentsLegend.Size = new System.Drawing.Size(74, 90); FFMPEGArgumentsLegend.TabIndex = 11; FFMPEGArgumentsLegend.Text = "Legend:\r\n{source}\r\n{destination}\r\n{start}\r\n{end}\r\n{duration}"; // @@ -296,76 +291,83 @@ private void InitializeComponent() tabPage2.Controls.Add(FindTextBox); tabPage2.Controls.Add(panel1); tabPage2.Controls.Add(NewTextTextBox); - tabPage2.Location = new System.Drawing.Point(8, 46); + tabPage2.Location = new System.Drawing.Point(4, 24); + tabPage2.Margin = new System.Windows.Forms.Padding(2); tabPage2.Name = "tabPage2"; - tabPage2.Padding = new System.Windows.Forms.Padding(3); - tabPage2.Size = new System.Drawing.Size(896, 899); + tabPage2.Padding = new System.Windows.Forms.Padding(2); + tabPage2.Size = new System.Drawing.Size(448, 448); tabPage2.TabIndex = 1; tabPage2.Text = "Rename"; tabPage2.UseVisualStyleBackColor = true; // // MultiplierInput // - MultiplierInput.Location = new System.Drawing.Point(285, 249); + MultiplierInput.Location = new System.Drawing.Point(142, 124); + MultiplierInput.Margin = new System.Windows.Forms.Padding(2); MultiplierInput.Maximum = new decimal(new int[] { 1000, 0, 0, 0 }); - MultiplierInput.Minimum = new decimal(new int[] { 0, 0, 0, 0 }); MultiplierInput.Name = "MultiplierInput"; - MultiplierInput.Size = new System.Drawing.Size(131, 39); + MultiplierInput.Size = new System.Drawing.Size(66, 23); MultiplierInput.TabIndex = 25; MultiplierInput.Value = new decimal(new int[] { 2, 0, 0, 0 }); // // label8 // label8.AutoSize = true; - label8.Location = new System.Drawing.Point(18, 251); + label8.Location = new System.Drawing.Point(9, 126); + label8.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label8.Name = "label8"; - label8.Size = new System.Drawing.Size(232, 32); + label8.Size = new System.Drawing.Size(115, 15); label8.TabIndex = 24; label8.Text = "Increment Multiplier"; // // PaddingInput // - PaddingInput.Location = new System.Drawing.Point(286, 366); + PaddingInput.Location = new System.Drawing.Point(143, 183); + PaddingInput.Margin = new System.Windows.Forms.Padding(2); PaddingInput.Maximum = new decimal(new int[] { 10, 0, 0, 0 }); PaddingInput.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); PaddingInput.Name = "PaddingInput"; - PaddingInput.Size = new System.Drawing.Size(131, 39); + PaddingInput.Size = new System.Drawing.Size(66, 23); PaddingInput.TabIndex = 23; PaddingInput.Value = new decimal(new int[] { 2, 0, 0, 0 }); // // label7 // label7.AutoSize = true; - label7.Location = new System.Drawing.Point(19, 368); + label7.Location = new System.Drawing.Point(10, 184); + label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label7.Name = "label7"; - label7.Size = new System.Drawing.Size(100, 32); + label7.Size = new System.Drawing.Size(51, 15); label7.TabIndex = 22; label7.Text = "Padding"; // // StartIndexInput // - StartIndexInput.Location = new System.Drawing.Point(286, 307); + StartIndexInput.Location = new System.Drawing.Point(143, 154); + StartIndexInput.Margin = new System.Windows.Forms.Padding(2); StartIndexInput.Maximum = new decimal(new int[] { 1000, 0, 0, 0 }); StartIndexInput.Name = "StartIndexInput"; - StartIndexInput.Size = new System.Drawing.Size(131, 39); + StartIndexInput.Size = new System.Drawing.Size(66, 23); StartIndexInput.TabIndex = 21; StartIndexInput.Value = new decimal(new int[] { 1, 0, 0, 0 }); // // label6 // label6.AutoSize = true; - label6.Location = new System.Drawing.Point(19, 309); + label6.Location = new System.Drawing.Point(10, 154); + label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label6.Name = "label6"; - label6.Size = new System.Drawing.Size(127, 32); + label6.Size = new System.Drawing.Size(63, 15); label6.TabIndex = 20; label6.Text = "Start Index"; // // RenameOriginalCheckBox // - RenameOriginalCheckBox.Location = new System.Drawing.Point(19, 203); + RenameOriginalCheckBox.Location = new System.Drawing.Point(10, 102); + RenameOriginalCheckBox.Margin = new System.Windows.Forms.Padding(2); RenameOriginalCheckBox.Name = "RenameOriginalCheckBox"; RenameOriginalCheckBox.RightToLeft = System.Windows.Forms.RightToLeft.Yes; - RenameOriginalCheckBox.Size = new System.Drawing.Size(294, 36); + RenameOriginalCheckBox.Size = new System.Drawing.Size(147, 18); RenameOriginalCheckBox.TabIndex = 19; RenameOriginalCheckBox.Text = "Rename Original"; RenameOriginalCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleRight; @@ -374,35 +376,39 @@ private void InitializeComponent() // label1 // label1.AutoSize = true; - label1.Location = new System.Drawing.Point(18, 154); + label1.Location = new System.Drawing.Point(9, 77); + label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label1.Name = "label1"; - label1.Size = new System.Drawing.Size(65, 32); + label1.Size = new System.Drawing.Size(31, 15); label1.TabIndex = 17; label1.Text = "Type"; // // RenameTypeComboBox // RenameTypeComboBox.FormattingEnabled = true; - RenameTypeComboBox.Location = new System.Drawing.Point(286, 151); + RenameTypeComboBox.Location = new System.Drawing.Point(143, 76); + RenameTypeComboBox.Margin = new System.Windows.Forms.Padding(2); RenameTypeComboBox.Name = "RenameTypeComboBox"; - RenameTypeComboBox.Size = new System.Drawing.Size(178, 40); + RenameTypeComboBox.Size = new System.Drawing.Size(91, 23); RenameTypeComboBox.TabIndex = 16; // // RenameLabel // RenameLabel.AutoSize = true; - RenameLabel.Location = new System.Drawing.Point(18, 90); + RenameLabel.Location = new System.Drawing.Point(9, 45); + RenameLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); RenameLabel.Name = "RenameLabel"; - RenameLabel.Size = new System.Drawing.Size(96, 32); + RenameLabel.Size = new System.Drawing.Size(48, 15); RenameLabel.TabIndex = 15; RenameLabel.Text = "Replace"; // // label4 // label4.AutoSize = true; - label4.Location = new System.Drawing.Point(18, 24); + label4.Location = new System.Drawing.Point(9, 12); + label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label4.Name = "label4"; - label4.Size = new System.Drawing.Size(60, 32); + label4.Size = new System.Drawing.Size(30, 15); label4.TabIndex = 14; label4.Text = "Find"; // @@ -411,28 +417,31 @@ private void InitializeComponent() panel2.BackColor = System.Drawing.SystemColors.Control; panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; panel2.Controls.Add(textBox1); - panel2.Location = new System.Drawing.Point(18, 428); + panel2.Location = new System.Drawing.Point(9, 214); + panel2.Margin = new System.Windows.Forms.Padding(2); panel2.Name = "panel2"; - panel2.Size = new System.Drawing.Size(850, 283); + panel2.Size = new System.Drawing.Size(426, 142); panel2.TabIndex = 13; // // textBox1 // textBox1.Dock = System.Windows.Forms.DockStyle.Fill; textBox1.Location = new System.Drawing.Point(0, 0); + textBox1.Margin = new System.Windows.Forms.Padding(2); textBox1.Multiline = true; textBox1.Name = "textBox1"; textBox1.ReadOnly = true; textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - textBox1.Size = new System.Drawing.Size(848, 281); + textBox1.Size = new System.Drawing.Size(424, 140); textBox1.TabIndex = 26; textBox1.Text = resources.GetString("textBox1.Text"); // // FindTextBox // - FindTextBox.Location = new System.Drawing.Point(286, 21); + FindTextBox.Location = new System.Drawing.Point(143, 10); + FindTextBox.Margin = new System.Windows.Forms.Padding(2); FindTextBox.Name = "FindTextBox"; - FindTextBox.Size = new System.Drawing.Size(581, 39); + FindTextBox.Size = new System.Drawing.Size(292, 23); FindTextBox.TabIndex = 1; // // panel1 @@ -442,61 +451,66 @@ private void InitializeComponent() panel1.Controls.Add(label3); panel1.Controls.Add(label2); panel1.Controls.Add(OriginalLabel); - panel1.Location = new System.Drawing.Point(18, 752); + panel1.Location = new System.Drawing.Point(9, 376); + panel1.Margin = new System.Windows.Forms.Padding(2); panel1.Name = "panel1"; - panel1.Size = new System.Drawing.Size(850, 137); + panel1.Size = new System.Drawing.Size(426, 70); panel1.TabIndex = 11; // // ResultLabel // ResultLabel.AutoSize = true; - ResultLabel.Location = new System.Drawing.Point(167, 88); + ResultLabel.Location = new System.Drawing.Point(84, 44); + ResultLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); ResultLabel.Name = "ResultLabel"; - ResultLabel.Size = new System.Drawing.Size(250, 32); + ResultLabel.Size = new System.Drawing.Size(126, 15); ResultLabel.TabIndex = 3; ResultLabel.Text = "ExampleFilename.mkv"; // // label3 // label3.AutoSize = true; - label3.Location = new System.Drawing.Point(25, 88); + label3.Location = new System.Drawing.Point(12, 44); + label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label3.Name = "label3"; - label3.Size = new System.Drawing.Size(71, 32); + label3.Size = new System.Drawing.Size(36, 15); label3.TabIndex = 2; label3.Text = "After:"; // // label2 // label2.AutoSize = true; - label2.Location = new System.Drawing.Point(25, 21); + label2.Location = new System.Drawing.Point(12, 10); + label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); label2.Name = "label2"; - label2.Size = new System.Drawing.Size(89, 32); + label2.Size = new System.Drawing.Size(44, 15); label2.TabIndex = 1; label2.Text = "Before:"; // // OriginalLabel // OriginalLabel.AutoSize = true; - OriginalLabel.Location = new System.Drawing.Point(167, 21); + OriginalLabel.Location = new System.Drawing.Point(84, 10); + OriginalLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); OriginalLabel.Name = "OriginalLabel"; - OriginalLabel.Size = new System.Drawing.Size(253, 32); + OriginalLabel.Size = new System.Drawing.Size(127, 15); OriginalLabel.TabIndex = 0; OriginalLabel.Text = "ExampleFilename.mp4"; // // NewTextTextBox // - NewTextTextBox.Location = new System.Drawing.Point(286, 87); + NewTextTextBox.Location = new System.Drawing.Point(143, 44); + NewTextTextBox.Margin = new System.Windows.Forms.Padding(2); NewTextTextBox.Name = "NewTextTextBox"; - NewTextTextBox.Size = new System.Drawing.Size(581, 39); + NewTextTextBox.Size = new System.Drawing.Size(292, 23); NewTextTextBox.TabIndex = 2; // // SaveButton // SaveButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; - SaveButton.Location = new System.Drawing.Point(790, 980); - SaveButton.Margin = new System.Windows.Forms.Padding(6); + SaveButton.Location = new System.Drawing.Point(395, 490); SaveButton.Name = "SaveButton"; - SaveButton.Size = new System.Drawing.Size(139, 49); + SaveButton.Size = new System.Drawing.Size(70, 24); SaveButton.TabIndex = 10; SaveButton.Text = "Save"; SaveButton.UseVisualStyleBackColor = true; @@ -504,13 +518,13 @@ private void InitializeComponent() // SettingsForm // AcceptButton = SaveButton; - AutoScaleDimensions = new System.Drawing.SizeF(192F, 192F); + AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - ClientSize = new System.Drawing.Size(944, 1044); + ClientSize = new System.Drawing.Size(472, 522); Controls.Add(SaveButton); Controls.Add(tabControl1); - Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); Name = "SettingsForm"; ShowIcon = false; SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show; diff --git a/BananaSplit/SettingsForm.cs b/BananaSplit/SettingsForm.cs index 86edc82..5299ab1 100644 --- a/BananaSplit/SettingsForm.cs +++ b/BananaSplit/SettingsForm.cs @@ -15,66 +15,59 @@ public partial class SettingsForm : Form private const string SampleExtensionText = ".mkv"; private const string SampleIncrementText = "S02E0"; - public SettingsForm() + public SettingsForm(Settings settings) { - InitializeComponent(); - Settings = new Settings(); - Settings.Load(); - - // Default values / settings - BlackFrameDurationInput.Increment = (decimal)0.01; - BlackFrameThresholdInput.Increment = (decimal)0.01; - BlackFramePixelThresholdInput.Increment = (decimal)0.01; - ReferenceFrameOffsetInput.Increment = (decimal)0.1; - BlackFrameDurationInput.DecimalPlaces = 2; - BlackFrameThresholdInput.DecimalPlaces = 2; - BlackFramePixelThresholdInput.DecimalPlaces = 2; - ReferenceFrameOffsetInput.DecimalPlaces = 1; + PopulateCombos(); - Settings.RenameFindText = FindTextBox.Text; - Settings.RenameNewText = NewTextTextBox.Text; - - ProcessTypeComboBox.Items.Clear(); - ProcessTypeComboBox.Items.AddRange(typeof(ProcessingType).GetDisplayNames().ToArray()); - - RenameTypeComboBox.Items.Clear(); - RenameTypeComboBox.Items.AddRange(typeof(RenameType).GetDisplayNames().ToArray()); - - RenameOriginalCheckBox.Checked = true; - - MultiplierInput.Value = 2; - StartIndexInput.Value = 1; - PaddingInput.Value = 2; + AddEventHandlers(); + + Settings = settings; + Settings.Load(); + + ApplySettings(); - // Restore load values + UpdateExample(); + } + + private void ApplySettings() + { BlackFrameDurationInput.Value = (decimal)Settings.BlackFrameDuration; BlackFrameThresholdInput.Value = (decimal)Settings.BlackFrameThreshold; BlackFramePixelThresholdInput.Value = (decimal)Settings.BlackFramePixelThreshold; ReferenceFrameOffsetInput.Value = (decimal)Settings.ReferenceFrameOffset; ShowLogCheckbox.Checked = Settings.ShowLog; DeleteOriginalCheckbox.Checked = Settings.DeleteOriginal; - FFMPEGArgumentsInput.Text = Settings.FFMPEGArguments; + FFMPEGArgumentsInput.Text = Settings.FmpegArguments; ProcessTypeComboBox.SelectedItem = Settings.ProcessType.GetDisplayName(); FindTextBox.Text = Settings.RenameFindText; - NewTextTextBox.Text = Settings.RenameNewText; - // Make sure the event handler is set before we set the value of the combo to update the fields - RenameTypeComboBox.SelectedIndexChanged += RenameTypeComboBox_SelectedIndexChanged; + NewTextTextBox.Text = Settings.RenameNewText; RenameTypeComboBox.SelectedItem = Settings.RenameType.GetDisplayName(); RenameOriginalCheckBox.Checked = Settings.RenameOriginal; MultiplierInput.Value = Settings.IncrementMultiplier; StartIndexInput.Value = Settings.StartIndex; - PaddingInput.Value = Settings.Padding; - - UpdateExample(); + PaddingInput.Value = Settings.Padding; + } + + private void AddEventHandlers() + { + RenameTypeComboBox.SelectedIndexChanged += RenameTypeComboBox_SelectedIndexChanged; - // Event handlers SaveButton.Click += SaveButton_Click; FormClosing += SettingsForm_FormClosing; FindTextBox.TextChanged += FindTextBox_TextChanged; - NewTextTextBox.TextChanged += NewTextTextBox_TextChanged; + NewTextTextBox.TextChanged += NewTextTextBox_TextChanged; + } + + private void PopulateCombos() + { + ProcessTypeComboBox.Items.Clear(); + ProcessTypeComboBox.Items.AddRange(typeof(ProcessingType).GetDisplayNames().ToArray()); + + RenameTypeComboBox.Items.Clear(); + RenameTypeComboBox.Items.AddRange(typeof(RenameType).GetDisplayNames().ToArray()); } private void SaveButton_Click(object sender, System.EventArgs e) @@ -85,7 +78,7 @@ private void SaveButton_Click(object sender, System.EventArgs e) Settings.ReferenceFrameOffset = (double)ReferenceFrameOffsetInput.Value; Settings.ShowLog = ShowLogCheckbox.Checked; Settings.DeleteOriginal = DeleteOriginalCheckbox.Checked; - Settings.FFMPEGArguments = FFMPEGArgumentsInput.Text; + Settings.FmpegArguments = FFMPEGArgumentsInput.Text; Settings.RenameFindText = FindTextBox.Text; Settings.RenameNewText = NewTextTextBox.Text; Settings.RenameOriginal = RenameOriginalCheckBox.Checked; @@ -113,10 +106,6 @@ private void SaveButton_Click(object sender, System.EventArgs e) Hide(); } - public void PopulateInputs() - { - BlackFrameDurationInput.Text = Settings.BlackFrameDuration.ToString(); - } private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e) { @@ -124,82 +113,38 @@ private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e) e.Cancel = true; } + private void ToggleRenameType(bool enableFindText, bool enableNewText, string text) + { + FindTextBox.Enabled = enableFindText; + NewTextTextBox.Enabled = enableNewText; + RenameLabel.Text = text; + } + private void RenameTypeComboBox_SelectedIndexChanged(object sender, EventArgs e) { var type = GetRenameType(); switch (type) { case RenameType.Prefix: - FindTextBox.Enabled = false; - NewTextTextBox.Enabled = true; - RenameLabel.Text = "Prefix"; + ToggleRenameType(false, true, "Prefix"); break; case RenameType.Suffix: - FindTextBox.Enabled = false; - NewTextTextBox.Enabled = true; - RenameLabel.Text = "Suffix"; + ToggleRenameType(false, true, "Suffix"); break; case RenameType.AppendAfter: - FindTextBox.Enabled = true; - NewTextTextBox.Enabled = true; - RenameLabel.Text = "Append After"; + ToggleRenameType(true, true, "Append After"); break; case RenameType.Replace: - FindTextBox.Enabled = true; - NewTextTextBox.Enabled = true; - RenameLabel.Text = "Replace"; + ToggleRenameType(true, true, "Replace"); break; case RenameType.Increment: - FindTextBox.Enabled = false; - NewTextTextBox.Enabled = false; + ToggleRenameType(false, false, "Increment"); break; } UpdateExample(); } - private void PrefixRadioButton_CheckedChanged(object sender, EventArgs e) - { - FindTextBox.Enabled = false; - MultiplierInput.Enabled = false; - RenameLabel.Text = "Suffix"; - UpdateExample(); - } - - private void SuffixRadioButton_CheckedChanged(object sender, EventArgs e) - { - FindTextBox.Enabled = false; - MultiplierInput.Enabled = false; - RenameLabel.Text = "Prefix"; - UpdateExample(); - } - - private void AppendAfterRadioButton_CheckedChanged(object sender, EventArgs e) - { - FindTextBox.Enabled = true; - NewTextTextBox.Enabled = true; - MultiplierInput.Enabled = false; - RenameLabel.Text = "Append After"; - UpdateExample(); - } - - private void ReplaceRadioButton_CheckedChanged(object sender, EventArgs e) - { - FindTextBox.Enabled = true; - NewTextTextBox.Enabled = true; - MultiplierInput.Enabled = false; - RenameLabel.Text = "Replace"; - UpdateExample(); - } - - private void IncrementRadioButton_CheckedChanged(object sender, EventArgs e) - { - FindTextBox.Enabled = false; - NewTextTextBox.Enabled = false; - MultiplierInput.Enabled = true; - UpdateExample(); - } - private void FindTextBox_TextChanged(object sender, EventArgs e) { UpdateExample(); @@ -212,8 +157,7 @@ private void NewTextTextBox_TextChanged(object sender, EventArgs e) private RenameType GetRenameType() { - Enum.TryParse((RenameTypeComboBox.SelectedItem as string).Replace(" ", ""), out RenameType type); - return type; + return Enum.Parse((RenameTypeComboBox.SelectedItem as string).Replace(" ", "")); } private void UpdateExample() diff --git a/BananaSplit/SettingsForm.resx b/BananaSplit/SettingsForm.resx index c651e9d..9c41f80 100644 --- a/BananaSplit/SettingsForm.resx +++ b/BananaSplit/SettingsForm.resx @@ -1,4 +1,64 @@ - + + + diff --git a/BananaSplit/StatusBarManager.cs b/BananaSplit/StatusBarManager.cs new file mode 100644 index 0000000..2343782 --- /dev/null +++ b/BananaSplit/StatusBarManager.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace BananaSplit; +public class StatusBarManager() +{ + public MainForm MainForm { get; set; } + + public void SetStatusBarProgressBarValue(int value, int maximum) + { + MainForm.StatusBar.Invoke( + new MethodInvoker( + delegate () + { + MainForm.StatusBarProgressBar.Minimum = 0; + MainForm.StatusBarProgressBar.Maximum = maximum; + + if (value >= maximum) + value = maximum - 1; + + MainForm.StatusBarProgressBar.Value = value; + } + ) + ); + } + + public void ClearStatusBarProgressBarValue() + { + MainForm.StatusBar.Invoke( + new MethodInvoker( + delegate () + { + MainForm.StatusBarProgressBar.Minimum = 0; + MainForm.StatusBarProgressBar.Maximum = 1; + + MainForm.StatusBarProgressBar.Value = 0; + } + ) + ); + } + + public void SetStatusBarLabelValue(string value) + { + MainForm.StatusBar.Invoke( + new MethodInvoker( + delegate () + { + MainForm.StatusBarLabel.Text = value; + } + ) + ); + } +} diff --git a/BananaSplit/Utilities.cs b/BananaSplit/Utilities.cs index b66ec64..446feca 100644 --- a/BananaSplit/Utilities.cs +++ b/BananaSplit/Utilities.cs @@ -7,13 +7,16 @@ public static class Utilities { public static Bitmap BytesToImage(byte[] bytes) { - using (MemoryStream ms = new MemoryStream(bytes)) + if (bytes == null || bytes.Length == 0) { - Bitmap bmp = new Bitmap(ms); - - return bmp; + return null; } + using MemoryStream ms = new MemoryStream(bytes); + Bitmap bmp = new Bitmap(ms); + + return bmp; + } } } diff --git a/Iconicon-Veggies-Bananas.512.ico b/Iconicon-Veggies-Bananas.512.ico new file mode 100644 index 0000000000000000000000000000000000000000..cce5bc1308625ce295183de3cd98936ffe6bb1e1 GIT binary patch literal 28060 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 literal 0 HcmV?d00001 diff --git a/Test/Test.csproj b/Test/Test.csproj new file mode 100644 index 0000000..3aa9860 --- /dev/null +++ b/Test/Test.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/Test/UnitTest1.cs b/Test/UnitTest1.cs new file mode 100644 index 0000000..aa96002 --- /dev/null +++ b/Test/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace Test; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file