diff --git a/README.md b/README.md index f9b7e0f..f480782 100644 --- a/README.md +++ b/README.md @@ -2,34 +2,37 @@ Creates Color Bands from movies It is now the second time I see Color Bands on reddit without a source code provided, -as if it was made by some evil magic. +as if it was made by some evil magic. This time the person creating those is also selling these images. -# How to use -Just double click the .exe file and fill in the requested values. - -**Hint:** When asked for file names, you can drag the file itself on the console to fill in the full path. - -## Questions asked -List of questions you are asked to answer +Well now you can create them on your own. And it's fast too. +For most source files it encodes with at least 20x playback speed. -### Source video file -The path and name of the video file to use +# How to use +This application can be used in Windowed and Console mode. -### Destination image file -The path and name of the destination image (the color band) +## Windowed mode +Just double click the .exe file to get the graphical user interface. -### Image height -The height of the image. The width is determined by the number of frames in the video. +## Console mode +You can use it in command line mode (untested as fo now). +Type `ColorBand /?` for help. -### Use single color -If you answer `y`, then each frame is reduced to one color. -If you answer `n`, then each frame is reduced to a width of 1. +# Single color mode +Single color mode reduces a frame to a single pixel instead of a vertical line. +This causes the frame band to be only one color per frame. +The height value still works. This is what most people generate, but it looks less awesome. +Multi color mode examples can be found on [imgur](http://imgur.com/a/M9oIx). # TODO -- Make the programm accept command line arguments -- GUI -- Error checking +- [X] Adding graphical interface +- [X] Autodetect height from video +- [X] Use command line arguments instead of prompts +- [X] Some error checking +- [ ] Make image joiner async for UI -# FFMPEG +# License +This application is licensed under the [GNU agpl 3.0](https://www.gnu.org/licenses/agpl-3.0.txt). +FFmpeg is licensed under [this mess](https://www.ffmpeg.org/legal.html). +# FFMPEG This work contains the compiled ffmpeg binaries inside blob.bin. diff --git a/colorBand/Compressor.cs b/colorBand/Compressor.cs index b9b5888..8f9c57b 100644 --- a/colorBand/Compressor.cs +++ b/colorBand/Compressor.cs @@ -16,7 +16,8 @@ public static class Compressor /// During compression, the path information is lost /// Full file names. /// Destination stream - public static void Compress(string[] Files, Stream Destination) + /// Logs each compressed file if true + public static void Compress(string[] Files, Stream Destination, bool Verbose) { FileInfo[] FF = Array.ConvertAll(Files, delegate(string f) { return new FileInfo(f); }); @@ -27,9 +28,10 @@ public static void Compress(string[] Files, Stream Destination) BW.Write(Files.Length); foreach (FileInfo F in FF) { -#if DEBUG - Console.WriteLine("Compressing {0}", F.Name); -#endif + if (Verbose) + { + Console.Error.WriteLine("Compressing {0}", F.Name); + } byte[] Data = File.ReadAllBytes(F.FullName); BW.Write(Encoding.UTF8.GetByteCount(F.Name)); BW.Write(Encoding.UTF8.GetBytes(F.Name)); @@ -45,7 +47,8 @@ public static void Compress(string[] Files, Stream Destination) /// /// Destination directory /// Compressed source stream - public static void Decompress(string Directory, Stream Source) + /// Logs each decompressed file if true + public static void Decompress(string Directory, Stream Source, bool Verbose) { using (GZipStream GZ = new GZipStream(Source, CompressionMode.Decompress)) { @@ -55,9 +58,10 @@ public static void Decompress(string Directory, Stream Source) for (int i = 0; i < Count; i++) { string FileName = Encoding.UTF8.GetString(BR.ReadBytes(BR.ReadInt32())); -#if DEBUG - Console.WriteLine("Decompressing {0}", FileName); -#endif + if (Verbose) + { + Console.Error.WriteLine("Decompressing {0}", FileName); + } byte[] Content = BR.ReadBytes(BR.ReadInt32()); File.WriteAllBytes(Path.Combine(Directory, FileName), Content); } diff --git a/colorBand/Program.cs b/colorBand/Program.cs index 2fe1f40..8d85831 100644 --- a/colorBand/Program.cs +++ b/colorBand/Program.cs @@ -7,152 +7,86 @@ using System.IO.Compression; using colorBand.Properties; using AyrA.IO; +using System.Windows.Forms; namespace colorBand { - public struct StatusLine - { - public int frame; - public double fps, speed; - public TimeSpan Time; - - public StatusLine(string Line) - { - bool skip = false; - string Trimmed = string.Empty; - - frame = 0; - fps = speed = 0.0; - Time = new TimeSpan(0L); - - if (Line.StartsWith("frame=")) - { - foreach (char c in Line.Trim()) - { - if (skip) - { - if (c != ' ') - { - Trimmed += c; - skip = false; - } - } - else - { - if (c == '=') - { - skip = true; - } - Trimmed += c; - } - } - foreach (string Part in Trimmed.Split(' ')) - { - if (Part.Contains("=")) - { - switch (Part.Split('=')[0]) - { - case "time": - string[] Segments = Part.Split('=')[1].Split(':'); - - if (Segments.Length == 3) - { - Time = new TimeSpan( - TryInt(Segments[0], 0), - TryInt(Segments[1], 0), - TryInt(Segments[2].Split('.')[0], 0)); - } - - break; - case "frame": - int.TryParse(Part.Split('=')[1], out frame); - break; - case "fps": - double.TryParse(Part.Split('=')[1], out fps); - break; - case "speed": - double.TryParse(Part.Split('=')[1].Trim('x'), out speed); - break; - } - } - } - } - } - - private int TryInt(string s, int Default) - { - int i = 0; - return int.TryParse(s, out i) ? i : Default; - } - } - public class Program { - /// - /// Command line for FFMPEG. - /// Explanation: takes video input and renders it with 1 fps and downscaled to a single pixel to an array of png files - /// - const string CMDLINE = "-i \"{0}\" -lavfi fps=1,scale=1:{2}:flags=lanczos \"{1}\\%06d.png\""; - /// /// Main entry of Application /// /// Command line arguments + [STAThread] static void Main(string[] args) { +#if DEBUG + args = new string[] + { + @"C:\Temp\media\Sim City SNES TAS (Tool Assisted Speedrun) - last input 06_52.09 _ 600k people 47_00-2g6uPk-A1HY.mp4", + @"C:\Temp\Band.png" + }; +#endif + //Show Help and exit if requested + if (HasHelp(args)) + { + PrintHelp(); + return; + } + //Show UI if no Command line arguments given + if (args.Length == 0) + { + ShowUI(); + return; + } + + //Parse Arguments + Arguments A=ParseArgs(args); + if (!A.Valid) + { + return; + } + Color[] Colors; - string TempDir = GetTempDir("frameband"); + string TempDir = Tools.GetTempDir("frameband"); string FFmpegFile = Path.Combine(TempDir, "ffmpeg.exe"); -#if DEBUG - //in debug mode I use hardcoded information because lazy. - //The source is a Youtube video with the ID 2g6uPk-A1HY - string VideoFile = @"C:\Temp\media\Sim City SNES TAS (Tool Assisted Speedrun) - last input 06_52.09 _ 600k people 47_00-2g6uPk-A1HY.mp4"; - string OutputFile = @"C:\temp\band.png"; - bool SingleColor = false; - int Height = 240; -#else - string VideoFile = Ask("Source video file").Trim('"'); - string OutputFile = Ask("Destination image file").Trim('"'); - int Height = 0; - while (!int.TryParse(Ask("Image Height"), out Height) || Height < 1) ; - bool SingleColor = Ask("Use single color [y/n]").ToLower() == "y"; -#endif - Console.Write("Extracting FFmpeg..."); - WriteEncoder(TempDir); - Console.WriteLine("[DONE]"); + + Console.Error.Write("Extracting FFmpeg..."); + Tools.WriteEncoder(TempDir); + Console.Error.WriteLine("[DONE]"); DateTime Start = DateTime.Now; //Generate single frames into PNG //Note: check if input is a video with at least 1 frame at all. - CreateImages(VideoFile, TempDir, FFmpegFile, SingleColor ? 1 : Height); + CreateImages(A.InputFile, TempDir, FFmpegFile, A.SingleColor ? 1 : A.Height); DateTime VideoGenerated = DateTime.Now; - if (SingleColor) + if (A.SingleColor) { //Extract color information from the PNG images. //GetFiles is rather slow, especially for a large number of files, //but it is certainly easier to use than manually using the Windows API - Colors = ExtractColorFromPixel(Directory.GetFiles(TempDir, "*.png")); + Colors = Tools.ExtractColorFromPixel(Directory.GetFiles(TempDir, "*.png")); //It is possible, that no colors were extracted, //for example if the input is an audio file. if (Colors.Length > 0) { - RenderBand(Colors, OutputFile, Height); + Tools.RenderBand(Colors, A.OutputFile, A.Height); } else { //Probably invalid video file - Console.WriteLine("No frames were extracted"); + Console.Error.WriteLine("No frames were extracted"); } } else { - JoinImages(Directory.GetFiles(TempDir, "*.png"), OutputFile); + Tools.JoinImages(Directory.GetFiles(TempDir, "*.png"), A.OutputFile); } //Remove temporary PNG files. @@ -165,7 +99,7 @@ static void Main(string[] args) TimeSpan tsVideo = VideoGenerated.Subtract(Start); TimeSpan tsImage = DateTime.Now.Subtract(VideoGenerated); - Console.WriteLine(@"Done. Durations: + Console.Error.WriteLine(@"Done. Durations: Total: {0:00}:{1:00}:{2:00} Video: {3:00}:{4:00}:{5:00} Image: {6:00}:{7:00}:{8:00}", @@ -183,61 +117,6 @@ static void Main(string[] args) #endif } - private static void WriteEncoder(string Dir) - { - using(MemoryStream MS=new MemoryStream(Resources.blob,false)) - { - Compressor.Decompress(Dir, MS); - } - } - - /// - /// Renders colors into a frame band - /// - /// List of colors - /// Destination image file name - /// Height of band - private static void RenderBand(Color[] Colors, string DestinationFile, int Height) - { - using (Bitmap Output = new Bitmap(Colors.Length, Height)) - { - //if height is 1, then use SetPixel - if (Height == 1) - { - for (int i = 0; i < Colors.Length; i++) - { - Output.SetPixel(i, 0, Colors[i]); - //This actually slows down the application quite a lot. - Console.Write('-'); - } - } - else - { - using (Graphics G = Graphics.FromImage(Output)) - { - //Note: Setting G.InterpolationMode to a "cheap" value has no effect, - //as it is only used for scaling. - //Creating a 1 pixel high image and then scale the height up does not increases the speed. - for (int i = 0; i < Colors.Length; i++) - { - //Why is this an IDisposable? - using (Pen P = new Pen(Colors[i])) - { - //Drawing a line is by far faster than setting individual pixels. - G.DrawLine(P, new Point(i, 0), new Point(i, Height - 1)); - //This actually slows down the application quite a lot. - Console.Write('-'); - } - } - } - } - //This detects the image format from the extension - //See System.Drawing.Imaging.ImageFormat enumeration for supported types. - Output.Save(DestinationFile); - Console.WriteLine(); - } - } - /// /// Create an array of frame images from a video file /// @@ -247,15 +126,15 @@ private static void RenderBand(Color[] Colors, string DestinationFile, int Heigh /// Height of generated frame bands. private static void CreateImages(string Video, string ImagePath, string FFmpeg, int Height) { - Console.WriteLine("Press [q] to abort processing and use existing frames"); - using (Process P = Process.Start(new ProcessStartInfo(FFmpeg, string.Format(CMDLINE, Video, ImagePath, Height)) - { - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardInput = true, - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = false - })) + VideoInfo VI = Tools.GetVideoInfo(Video); + if (Height < 1) + { + Height = VI.Resolution.Height; + } + DateTime Start = DateTime.Now; + + Console.Error.WriteLine("Press [q] to abort processing and use existing frames"); + using (Process P = Tools.BeginConversion(Video, ImagePath, Height)) { using (StreamReader SR = P.StandardError) { @@ -263,18 +142,24 @@ private static void CreateImages(string Video, string ImagePath, string FFmpeg, int Y = Console.CursorTop; while (!SR.EndOfStream) { - if (Console.KeyAvailable && Console.ReadKey().Key == ConsoleKey.Q) + if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Q) { P.StandardInput.WriteLine("q"); } SL = new StatusLine(SR.ReadLine()); - if (SL.frame > 0) + TimeSpan Estimate = new TimeSpan(0, 0, (int)(VI.Duration.TotalSeconds / SL.Speed)); + //This creates a timespan with the milliseconds cut out of. + TimeSpan CurrentTime = new TimeSpan(0,0,(int)((DateTime.Now.Ticks-Start.Ticks)/10000000L)); + + if (SL.Frame > 0) { Console.SetCursorPosition(0, Y); - Console.WriteLine(@"Time : {0} -FPS : {1} -Speed : {2} -Frames: {3}",SL.Time,SL.fps,SL.speed,SL.frame); + Console.Error.WriteLine(@"Time : {0} +Speed : {1,-6} +Frames : {2,-6} + +Estimate: {3} +Current : {4}", SL.Time, SL.Speed, SL.Frame, Estimate, CurrentTime); } } } @@ -285,70 +170,136 @@ private static void CreateImages(string Video, string ImagePath, string FFmpeg, } /// - /// Extracts Color information from the top left pixel + /// Scans arguments for common help requests /// - /// List of files - /// List of pixel colors - private static Color[] ExtractColorFromPixel(string[] Files) + /// Arguments + /// True, if help requested + private static bool HasHelp(string[] Args) { - List Colors = new List(); - foreach (string s in Files) + foreach (string Arg in Args) { - Bitmap I = null; - try + if (Arg.ToLower() == "--help" || + Arg.ToLower() == "/h" || + Arg == "/?" || + Arg == "-?") { - //Ironically Bitmap.FromFile comes from the Image class and will return an Image object, - //but it is fully compatible with the Bitmap type so we just cast. - I = (Bitmap)Bitmap.FromFile(s); - } - catch - { - //I never had this happen but better add an error resolver. - //In this case, we just ignore that frame. - Console.Write('X'); - continue; - } - using (I) - { - //Extract image color - Colors.Add(I.GetPixel(0, 0)); - //This actually slows down the application quite a lot. - Console.Write('.'); + return true; } } - Console.WriteLine(); - return Colors.ToArray(); + return false; + } + + /// + /// Print Help message + /// + private static void PrintHelp() + { + Console.Error.WriteLine(@"ColorBand.exe [OutputFile] [Height] [/s] + +InputFile - Soure video file to extract frames +OutputFile - Destination file to write color band. If not specified, + The band is saved in the InputFile directory +Height - Height of color band. + If not specified, it uses the video height. +/s - If specified, reduces each frame to a single pixel instead of + a line. Not faster. Just looks differently"); } /// - /// Joins an array of images horizontally + /// Parses command line argument into formatted structure /// - /// List of images - /// Destination File - private static void JoinImages(string[] Files, string OutputFile) + /// Raw arguments + /// Command line arguments structure + private static Arguments ParseArgs(string[] Args) { - int x = 0; - int Height = 0; - using (Image I = Image.FromFile(Files[0])) + Arguments A = new Arguments() { - Height = I.Height; - } - using (Bitmap B = new Bitmap(Files.Length, Height)) + InputFile = null, + OutputFile = null, + SingleColor = false, + Height = 0, + Valid = true + }; + bool PNGSet = false; + + foreach (string Arg in Args) { - using (Graphics G = Graphics.FromImage(B)) + switch (Arg.ToLower()) { - foreach(string Name in Files) - { - using (Image I = Image.FromFile(Name)) + case "/s": + A.SingleColor = true; + break; + default: + //if integer, assume it is the height + if (Tools.IsInt(Arg)) { - G.DrawImageUnscaled(I, new Point(x++, 0)); + if (int.Parse(Arg) > 0) + { + if (A.Height == 0) + { + A.Height = int.Parse(Arg); + } + else + { + Console.Error.WriteLine("Height has been specified twice"); + A.Valid = false; + return A; + } + } + else + { + Console.Error.WriteLine("Height must be bigger than 0"); + A.Valid = false; + return A; + } } - Console.Write('-'); - } + else + { + //either input or output file + if (A.InputFile == null) + { + //First file is input file + A.InputFile = Arg; + if (File.Exists(Arg)) + { + //automatically set output file sot it becomes an optional argument + A.OutputFile = Tools.SwapExt(Arg, "png"); + } + else + { + Console.Error.WriteLine("InputFile does not exists or is inaccessible"); + A.Valid = false; + return A; + } + } + else if (!PNGSet) + { + //second file is output file + PNGSet = true; + try + { + File.Create(Arg).Close(); + File.Delete(Arg); + } + catch (Exception ex) + { + Console.Error.WriteLine("Can't create output file. Error: {0}", ex.Message); + A.Valid = false; + return A; + } + A.OutputFile = Arg; + } + else + { + Console.Error.WriteLine("More than two files specified"); + A.Valid = false; + return A; + } + } + break; } - B.Save(OutputFile); - Console.WriteLine(); } + return A; } /// @@ -363,27 +314,21 @@ private static string Ask(string p) } /// - /// Attempts to create a temporary folder + /// Show the graphical user interface /// - /// Name of the folder to create - /// Full path of folder created - static string GetTempDir(string Dirname) + private static void ShowUI() { - int i = 0; - string TempRoot = Path.Combine(Path.GetTempPath(), Dirname + "_"); - while (true) - { - string current = string.Format("{0}{1}", TempRoot, i++); - try - { - Directory.CreateDirectory(current); - return current; - } - catch - { - //you can increase i here instead if you want. - } - } + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new frmMain()); } + + } + + public struct Arguments + { + public string InputFile, OutputFile; + public int Height; + public bool SingleColor, Valid; } } diff --git a/colorBand/Properties/AssemblyInfo.cs b/colorBand/Properties/AssemblyInfo.cs index 7f8d8c8..7379173 100644 --- a/colorBand/Properties/AssemblyInfo.cs +++ b/colorBand/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/colorBand/StatusLine.cs b/colorBand/StatusLine.cs new file mode 100644 index 0000000..54f4a68 --- /dev/null +++ b/colorBand/StatusLine.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace colorBand +{ + public struct StatusLine + { + public int Frame; + public double FPS, Speed; + public TimeSpan Time; + + public StatusLine(string Line) + { + bool skip = false; + string Trimmed = string.Empty; + + Frame = 0; + FPS = Speed = 0.0; + Time = new TimeSpan(0L); + + if (Line.StartsWith("frame=")) + { + foreach (char c in Line.Trim()) + { + if (skip) + { + if (c != ' ') + { + Trimmed += c; + skip = false; + } + } + else + { + if (c == '=') + { + skip = true; + } + Trimmed += c; + } + } + foreach (string Part in Trimmed.Split(' ')) + { + if (Part.Contains("=")) + { + switch (Part.Split('=')[0]) + { + case "time": + string[] Segments = Part.Split('=')[1].Split(':'); + + if (Segments.Length == 3) + { + Time = new TimeSpan( + TryInt(Segments[0], 0), + TryInt(Segments[1], 0), + TryInt(Segments[2].Split('.')[0], 0)); + } + + break; + case "frame": + int.TryParse(Part.Split('=')[1], out Frame); + break; + case "fps": + double.TryParse(Part.Split('=')[1], out FPS); + break; + case "speed": + double.TryParse(Part.Split('=')[1].Trim('x'), out Speed); + break; + } + } + } + } + } + + private int TryInt(string s, int Default) + { + int i = 0; + return int.TryParse(s, out i) ? i : Default; + } + } +} diff --git a/colorBand/Terminal.cs b/colorBand/Terminal.cs new file mode 100644 index 0000000..6a7ade8 --- /dev/null +++ b/colorBand/Terminal.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Diagnostics; +using System.Globalization; + +namespace AyrA.IO +{ + /// + /// Provides functions to work with consoles + /// + public static class Terminal + { + [DllImport("kernel32.dll")] + private static extern bool AllocConsole(); + + [DllImport("kernel32.dll")] + private static extern bool FreeConsole(); + + [DllImport("kernel32.dll")] + private static extern bool AttachConsole(int dwProcessId); + + /// + /// For AttachToConsole, to attach to the parent process console + /// + public const int PARENT = -1; + + /// + /// Returns all console colors sorted from 0x0 to 0xF + /// + public static ConsoleColor[] ColorRow + { + get + { + ConsoleColor[] c = new ConsoleColor[16]; + for (int i = 0; i < 16; i++) + { + c[i] = (ConsoleColor)i; + } + return c; + } + } + + /// + /// creates a new console, an application can only have one + /// + /// true, if created + public static bool CreateConsole() + { + return AllocConsole(); + } + + /// + /// Removes your application from the console handle. + /// If you are the last application to have a handle, + /// the console is closed + /// + /// true, on success + public static bool RemoveConsole() + { + return FreeConsole(); + } + + /// + /// Attaches your application to a given processes console + /// + /// Process to attach to. null to attach to parent process + /// true, if success + public static bool AttachToConsole(Process P) + { + return AttachToConsole(P == null ? PARENT : P.Id); + } + + /// + /// Attach to a console of the given process ID + /// + /// process ID, or PARENT constant + /// true, on success + public static bool AttachToConsole(int ID) + { + return AttachConsole(ID); + } + + /// + /// Writes a line with colored text and adds a CRLF + /// + /// Text to be written + /// Foreground color map + /// Background color Map + public static void printColorL(string text, string mapF, string mapB) + { + printColor(text, mapF, mapB); + System.Console.WriteLine(); + } + + /// + /// Writes a single line with colored text + /// + /// Line to write + /// Foreground color map + /// Background color Map + public static void printColor(string text, string mapF, string mapB) + { + int color = 0; + + mapF = mapF.ToUpper().Replace(' ', '_'); + mapB = mapB.ToUpper().Replace(' ', '_'); + + if (text.Length != mapF.Length || mapF.Length != mapB.Length) + { + throw new Exception("All params must be of the same length"); + } + + for (int i = 0; i < text.Length; i++) + { + if (mapF[i] != '_') + { + if (!int.TryParse(mapF.Substring(i, 1), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out color)) + { + throw new Exception(string.Format("Foreground color at pos {0} is invalid. Is: '{1}'", i, mapF[i])); + } + } + if (mapB[i] != '_') + { + if (!int.TryParse(mapB.Substring(i, 1), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out color)) + { + throw new Exception(string.Format("Background color at pos {0} is invalid. Is: '{1}'", i, mapB[i])); + } + } + } + + + ConsoleColor[] C = new ConsoleColor[] { System.Console.ForegroundColor, System.Console.BackgroundColor }; + for (int i = 0; i < text.Length; i++) + { + if (mapF[i] != '_') + { + System.Console.ForegroundColor = (ConsoleColor)int.Parse(mapF.Substring(i, 1), NumberStyles.HexNumber); + } + if (mapB[i] != '_') + { + System.Console.BackgroundColor = (ConsoleColor)int.Parse(mapB.Substring(i, 1), NumberStyles.HexNumber); + } + System.Console.Write(text[i]); + } + System.Console.ForegroundColor = C[0]; + System.Console.BackgroundColor = C[1]; + } + } +} diff --git a/colorBand/Tools.cs b/colorBand/Tools.cs new file mode 100644 index 0000000..f285b07 --- /dev/null +++ b/colorBand/Tools.cs @@ -0,0 +1,403 @@ +using System.IO; +using System.Drawing; +using AyrA.IO; +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using colorBand.Properties; +using System.Collections.Generic; +using System.Threading; + +namespace colorBand +{ + public delegate void ThreadExitHandler(Thread T); + + public static class Tools + { + private const string DURATION = "-show_entries format -v quiet \"{0}\""; + private const string RESOLUTION = "-show_entries stream=width,height -select_streams v:0 -v quiet \"{0}\""; + + private const string FFMPEG = "-i \"{0}\" -lavfi fps=1,scale=1:{2}:flags=lanczos \"{1}\\%06d.png\""; + + private static string CurrentEncoder = "ffmpeg.exe"; + private static string CurrentInfoTool = "ffplay.exe"; + + public static bool LogToConsole = false; + + public static event ThreadExitHandler ThreadExit;// = delegate { }; + + /// + /// Attempts to create a temporary folder + /// + /// Name of the folder to create + /// Full path of folder created + public static string GetTempDir(string Dirname) + { + int i = 0; + string TempRoot = Path.Combine(Path.GetTempPath(), Dirname + "_"); + while (true) + { + string current = string.Format("{0}{1}", TempRoot, i++); + try + { + Directory.CreateDirectory(current); + return current; + } + catch + { + //you can increase i here instead if you want. + } + } + } + + /// + /// Renders colors into a frame band + /// + /// List of colors + /// Destination image file name + /// Height of band + public static void RenderBand(Color[] Colors, string DestinationFile, int Height) + { + using (Bitmap Output = new Bitmap(Colors.Length, Height)) + { + //if height is 1, then use SetPixel + if (Height == 1) + { + for (int i = 0; i < Colors.Length; i++) + { + Output.SetPixel(i, 0, Colors[i]); + if (LogToConsole) + { + //This actually slows down the application quite a lot. + Console.Error.Write('-'); + } + } + } + else + { + using (Graphics G = Graphics.FromImage(Output)) + { + //Note: Setting G.InterpolationMode to a "cheap" value has no effect, + //as it is only used for scaling. + //Creating a 1 pixel high image and then scale the height up does not increases the speed. + for (int i = 0; i < Colors.Length; i++) + { + //Why is this an IDisposable? + using (Pen P = new Pen(Colors[i])) + { + //Drawing a line is by far faster than setting individual pixels. + G.DrawLine(P, new Point(i, 0), new Point(i, Height - 1)); + if (LogToConsole) + { + //This actually slows down the application quite a lot. + Console.Error.Write('-'); + } + } + } + } + } + //This detects the image format from the extension + //See System.Drawing.Imaging.ImageFormat enumeration for supported types. + Output.Save(DestinationFile); + if (LogToConsole) + { + Console.Error.WriteLine(); + } + } + } + + /// + /// Joins an array of images horizontally + /// + /// List of images + /// Destination File + public static void JoinImages(string[] Files, string OutputFile) + { + int x = 0; + int Height = 0; + using (Image I = Image.FromFile(Files[0])) + { + Height = I.Height; + } + using (Bitmap B = new Bitmap(Files.Length, Height)) + { + using (Graphics G = Graphics.FromImage(B)) + { + foreach (string Name in Files) + { + using (Image I = Image.FromFile(Name)) + { + G.DrawImageUnscaled(I, new Point(x++, 0)); + } + Console.Write('-'); + } + } + B.Save(OutputFile); + Console.WriteLine(); + } + } + + /// + /// Extracts Color information from the top left pixel + /// + /// List of files + /// List of pixel colors + public static Color[] ExtractColorFromPixel(string[] Files) + { + List Colors = new List(); + foreach (string s in Files) + { + Bitmap I = null; + try + { + //Ironically Bitmap.FromFile comes from the Image class and will return an Image object, + //but it is fully compatible with the Bitmap type so we just cast. + I = (Bitmap)Bitmap.FromFile(s); + } + catch + { + //I never had this happen but better add an error resolver. + //In this case, we just ignore that frame. + if (LogToConsole) + { + Console.Error.Write('X'); + } + continue; + } + using (I) + { + //Extract image color + Colors.Add(I.GetPixel(0, 0)); + if (LogToConsole) + { + //This actually slows down the application quite a lot. + Console.Error.Write('.'); + } + } + } + if (LogToConsole) + { + Console.Error.WriteLine(); + } + return Colors.ToArray(); + } + + /// + /// Extract FFmpeg + /// + /// Destination Directory + public static void WriteEncoder(string Dir) + { + using (MemoryStream MS = new MemoryStream(Resources.blob, false)) + { + Compressor.Decompress(Dir, MS, LogToConsole); + CurrentEncoder = Path.Combine(Dir, "ffmpeg.exe"); + CurrentInfoTool = Path.Combine(Dir, "ffprobe.exe"); + } + } + + /// + /// Extracts some basic information from the supplied Video file + /// + /// Video file + /// Video information + public static VideoInfo GetVideoInfo(string FileName) + { + VideoInfo VI = new VideoInfo() + { + CodecName = string.Empty, + VideoFile = FileName, + Resolution = new Size(0, 0), + Duration = new TimeSpan(0) + }; + + double TempDuration = 0.0; + long TempValue = 0; + + //Basic video info + using (Process P = GetInfoProcess(DURATION, FileName)) + { + var Lines = GetLines(P); + foreach (string Line in Lines) + { + if (Line.Contains("=")) + { + var Name = Line.Substring(0, Line.IndexOf('=')).ToLower(); + var Value = Line.Substring(Line.IndexOf('=') + 1); + switch (Name) + { + case "format_long_name": + VI.CodecName = Value; + break; + case "duration": + if (double.TryParse(Value, out TempDuration)) + { + VI.Duration = new TimeSpan(0, 0, (int)TempDuration); + } + break; + case "bit_rate": + if (long.TryParse(Value, out TempValue)) + { + VI.Bitrate = TempValue; + } + break; + } + } + } + } + + //Video dimensions + + using (Process P = GetInfoProcess(RESOLUTION, FileName)) + { + int TempResolution = 0; + var Lines = GetLines(P); + foreach (string Line in Lines) + { + if (Line.Contains("=")) + { + var Name = Line.Substring(0, Line.IndexOf('=')).ToLower(); + var Value = Line.Substring(Line.IndexOf('=') + 1); + switch (Name) + { + case "width": + if (int.TryParse(Value, out TempResolution)) + { + VI.Resolution.Width = TempResolution; + } + break; + case "height": + if (int.TryParse(Value, out TempResolution)) + { + VI.Resolution.Height = TempResolution; + } + break; + } + } + } + } + return VI; + } + + /// + /// Calls an event when the thread exits + /// + /// Thread to watch for + public static void WatchThread(Thread T) + { + Thread Temp = new Thread(delegate() + { + T.Join(); + if (LogToConsole) + { + Console.Error.WriteLine("Thread {0} exited", T.Name); + } + ThreadExit(T); + + }) { IsBackground = true, Name = "Watcher of " + T.Name }; + Temp.Start(); + } + + /// + /// Gets a process object for ffprobe.exe + /// + /// Type of info + /// File name to scan + /// Process object (not yet started) + private static Process GetInfoProcess(string ArgType, string FileName) + { + return new Process() + { + StartInfo = new ProcessStartInfo() + { + FileName = CurrentInfoTool, + Arguments = string.Format(ArgType, FileName), + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + } + + private static string[] GetLines(Process P) + { + List Lines = new List(); + P.Start(); + P.WaitForExit(); + while (!P.StandardOutput.EndOfStream) + { + Lines.Add(P.StandardOutput.ReadLine().Trim()); + } + return Lines.ToArray(); + } + + /// + /// Begins conversion using FFmpeg + /// + /// Source Video File + /// Path to put extracted Frames + /// Height of video File + /// FFmpeg process (already started) + public static Process BeginConversion(string VideoFile, string TempPath, int VideoHeight) + { + Process P = new Process() + { + StartInfo = new ProcessStartInfo(CurrentEncoder, string.Format(FFMPEG, VideoFile, TempPath, VideoHeight)) + { + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardInput = true, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = false + }, + EnableRaisingEvents = true + }; + P.Start(); + return P; + } + + /// + /// Checks if a number is a valid integer + /// + /// Number as string + /// True if integer (int.Parse won't crash) + public static bool IsInt(string s) + { + int i = 0; + return int.TryParse(s, out i); + } + + /// + /// Swaps the extension of a file name with a new one + /// + /// File name (path optional) + /// New extension + /// New File name + public static string SwapExt(string FileName, string NewExt) + { + string[] Segments = FileName.Split(Path.DirectorySeparatorChar); + int Last = Segments.Length - 1; + + //if the name contains at least one dot, replace the part after the last occurence + if (Segments[Last].Contains(".")) + { + Segments[Last] = Segments[Last].Substring(0, Segments[Last].LastIndexOf('.') + 1) + NewExt; + } + else + { + //No extension present. Just append the extension + Segments[Last] += "." + NewExt; + } + return string.Join(Path.DirectorySeparatorChar.ToString(), Segments); + } + } + + public struct VideoInfo + { + public string VideoFile, CodecName; + public long Bitrate; + public Size Resolution; + public TimeSpan Duration; + } +} diff --git a/colorBand/colorBand.csproj b/colorBand/colorBand.csproj index b906a74..6d36570 100644 --- a/colorBand/colorBand.csproj +++ b/colorBand/colorBand.csproj @@ -34,10 +34,23 @@ + + + Form + + + frmEncoder.cs + + + Form + + + frmMain.cs + @@ -45,8 +58,18 @@ True Resources.resx + + + + + frmEncoder.cs + Designer + + + frmMain.cs + ResXFileCodeGenerator Resources.Designer.cs diff --git a/colorBand/frmEncoder.Designer.cs b/colorBand/frmEncoder.Designer.cs new file mode 100644 index 0000000..19ee0a5 --- /dev/null +++ b/colorBand/frmEncoder.Designer.cs @@ -0,0 +1,91 @@ +namespace colorBand +{ + partial class frmEncoder + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.pbStatus = new System.Windows.Forms.ProgressBar(); + this.btnAbort = new System.Windows.Forms.Button(); + this.lblEncodingProgress = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // pbStatus + // + this.pbStatus.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pbStatus.Location = new System.Drawing.Point(12, 12); + this.pbStatus.Name = "pbStatus"; + this.pbStatus.Size = new System.Drawing.Size(344, 23); + this.pbStatus.TabIndex = 0; + // + // btnAbort + // + this.btnAbort.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnAbort.Location = new System.Drawing.Point(362, 12); + this.btnAbort.Name = "btnAbort"; + this.btnAbort.Size = new System.Drawing.Size(70, 23); + this.btnAbort.TabIndex = 1; + this.btnAbort.Text = "&Abort"; + this.btnAbort.UseVisualStyleBackColor = true; + this.btnAbort.Click += new System.EventHandler(this.btnAbort_Click); + // + // lblEncodingProgress + // + this.lblEncodingProgress.AutoSize = true; + this.lblEncodingProgress.Location = new System.Drawing.Point(12, 45); + this.lblEncodingProgress.Name = "lblEncodingProgress"; + this.lblEncodingProgress.Size = new System.Drawing.Size(95, 13); + this.lblEncodingProgress.TabIndex = 2; + this.lblEncodingProgress.Text = "Starting Encoder..."; + // + // frmEncoder + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(444, 168); + this.ControlBox = false; + this.Controls.Add(this.lblEncodingProgress); + this.Controls.Add(this.btnAbort); + this.Controls.Add(this.pbStatus); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "frmEncoder"; + this.Text = "Encoding"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.frmEncoder_FormClosed); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ProgressBar pbStatus; + private System.Windows.Forms.Button btnAbort; + private System.Windows.Forms.Label lblEncodingProgress; + } +} \ No newline at end of file diff --git a/colorBand/frmEncoder.cs b/colorBand/frmEncoder.cs new file mode 100644 index 0000000..e013086 --- /dev/null +++ b/colorBand/frmEncoder.cs @@ -0,0 +1,159 @@ +using System; +using System.Windows.Forms; +using System.Threading; +using System.Diagnostics; +using System.IO; + +namespace colorBand +{ + public partial class frmEncoder : Form + { + private string TempDir, SourceFile, DestinationFile; + private int BandHeight; + private bool SingleColor; + private Thread Encoder; + private bool Aborted = false; + private VideoInfo VI; + + public frmEncoder(string TempDir, string SourceFile, string DestinationFile, int BandHeight, bool SingleColor) + { + this.TempDir = TempDir; + this.SourceFile = SourceFile; + this.DestinationFile = DestinationFile; + this.BandHeight = BandHeight; + this.SingleColor = SingleColor; + + Tools.ThreadExit += Tools_ThreadExit; + + VI = Tools.GetVideoInfo(SourceFile); + + InitializeComponent(); + + pbStatus.Maximum = (int)VI.Duration.TotalSeconds; + + Encoder = new Thread(delegate() + { + using (Process P = Tools.BeginConversion(SourceFile, TempDir, BandHeight)) + { + using (StreamReader SR = P.StandardError) + { + StatusLine SL; + while (!SR.EndOfStream) + { + if (Aborted) + { + P.StandardInput.WriteLine("q"); + } + SL = new StatusLine(SR.ReadLine()); + if (!Aborted) + { + TimeSpan Estimate = new TimeSpan(0, 0, (int)(VI.Duration.TotalSeconds / SL.Speed)); + if (SL.Frame > 0) + { + this.Invoke((MethodInvoker)delegate + { + lblEncodingProgress.Text = string.Format(@"Status: +Speed: {0} times playback speed +Current Time in Video: {1} +Current Frame: {2} +Progress: {3} +Estimated Runtime: {4}", + SL.Speed, SL.Time, SL.Frame, Perc(SL.Frame, (int)VI.Duration.TotalSeconds), Estimate); + //Set progress bar only if valid + if (pbStatus.Maximum >= SL.Frame) + { + pbStatus.Value = SL.Frame; + } + }); + } + } + } + } + P.WaitForExit(); + } + }) + { + IsBackground = true, + Name = "Encoding of " + SourceFile + }; + Encoder.Start(); + Tools.WatchThread(Encoder); + } + + private int Perc(double Current, double Max) + { + if (Current > Max) + { + return 100; + } + if (Current < 0.0 || Max <= 0.0) + { + return 0; + } + return (int)(Current / Max * 100.0); + } + + private void Tools_ThreadExit(Thread T) + { + if (T == Encoder) + { + if (!Aborted || MessageBox.Show("Do you still want to create the Frame Band?\r\nSelecting [YES] will use the existing frames", "Working with existing material", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) + { + //Encoding Done + this.Invoke((MethodInvoker)EncodePNG); + } + this.Invoke((MethodInvoker)Close); + } + } + + private void EncodePNG() + { + btnAbort.Enabled = false; + if (SingleColor) + { + //Extract color information from the PNG images. + //GetFiles is rather slow, especially for a large number of files, + //but it is certainly easier to use than manually using the Windows API + var Colors = Tools.ExtractColorFromPixel(Directory.GetFiles(TempDir, "*.png")); + + + //It is possible, that no colors were extracted, + //for example if the input is an audio file. + if (Colors.Length > 0) + { + Tools.RenderBand(Colors, DestinationFile, Height); + } + else + { + //Probably invalid video file + MessageBox.Show("No frames were extracted. Make sure the video file is not corrupted", "No frames extracted", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + } + } + else + { + Tools.JoinImages(Directory.GetFiles(TempDir, "*.png"), DestinationFile); + } + } + + private void btnAbort_Click(object sender, EventArgs e) + { + if (MessageBox.Show("Abort the encoding process?", "Abort encoding", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes) + { + Aborted = true; + //Encoder.Join(); + /* + if (MessageBox.Show("Do you still want to create the Frame Band?\r\nSelecting [YES] will use the existing frames", "Working with existing material", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) + { + EncodePNG(); + } + Close(); + //*/ + } + } + + private void frmEncoder_FormClosed(object sender, FormClosedEventArgs e) + { + Tools.ThreadExit -= Tools_ThreadExit; + } + } +} diff --git a/colorBand/frmEncoder.resx b/colorBand/frmEncoder.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/colorBand/frmEncoder.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/colorBand/frmMain.Designer.cs b/colorBand/frmMain.Designer.cs new file mode 100644 index 0000000..10251d4 --- /dev/null +++ b/colorBand/frmMain.Designer.cs @@ -0,0 +1,215 @@ +namespace colorBand +{ + partial class frmMain + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnSource = new System.Windows.Forms.Button(); + this.btnDestination = new System.Windows.Forms.Button(); + this.tbSource = new System.Windows.Forms.TextBox(); + this.tbDestination = new System.Windows.Forms.TextBox(); + this.btnStart = new System.Windows.Forms.Button(); + this.nudHeight = new System.Windows.Forms.NumericUpDown(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.OFD = new System.Windows.Forms.OpenFileDialog(); + this.SFD = new System.Windows.Forms.SaveFileDialog(); + this.tbInfo = new System.Windows.Forms.TextBox(); + this.cbSingleColor = new System.Windows.Forms.CheckBox(); + ((System.ComponentModel.ISupportInitialize)(this.nudHeight)).BeginInit(); + this.SuspendLayout(); + // + // btnSource + // + this.btnSource.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSource.Location = new System.Drawing.Point(503, 12); + this.btnSource.Name = "btnSource"; + this.btnSource.Size = new System.Drawing.Size(35, 23); + this.btnSource.TabIndex = 2; + this.btnSource.Text = "..."; + this.btnSource.UseVisualStyleBackColor = true; + this.btnSource.Click += new System.EventHandler(this.btnSource_Click); + // + // btnDestination + // + this.btnDestination.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnDestination.Location = new System.Drawing.Point(503, 41); + this.btnDestination.Name = "btnDestination"; + this.btnDestination.Size = new System.Drawing.Size(35, 23); + this.btnDestination.TabIndex = 5; + this.btnDestination.Text = "..."; + this.btnDestination.UseVisualStyleBackColor = true; + this.btnDestination.Click += new System.EventHandler(this.btnDestination_Click); + // + // tbSource + // + this.tbSource.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbSource.Location = new System.Drawing.Point(117, 14); + this.tbSource.Name = "tbSource"; + this.tbSource.Size = new System.Drawing.Size(380, 20); + this.tbSource.TabIndex = 1; + this.tbSource.Leave += new System.EventHandler(this.tbSource_Leave); + // + // tbDestination + // + this.tbDestination.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbDestination.Location = new System.Drawing.Point(117, 43); + this.tbDestination.Name = "tbDestination"; + this.tbDestination.Size = new System.Drawing.Size(380, 20); + this.tbDestination.TabIndex = 4; + // + // btnStart + // + this.btnStart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnStart.Location = new System.Drawing.Point(478, 70); + this.btnStart.Name = "btnStart"; + this.btnStart.Size = new System.Drawing.Size(60, 23); + this.btnStart.TabIndex = 9; + this.btnStart.Text = "Start"; + this.btnStart.UseVisualStyleBackColor = true; + this.btnStart.Click += new System.EventHandler(this.btnStart_Click); + // + // nudHeight + // + this.nudHeight.Location = new System.Drawing.Point(117, 70); + this.nudHeight.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.nudHeight.Name = "nudHeight"; + this.nudHeight.Size = new System.Drawing.Size(83, 20); + this.nudHeight.TabIndex = 7; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 17); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(53, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Video File"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 46); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(58, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Output File"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(12, 72); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(91, 13); + this.label3.TabIndex = 6; + this.label3.Text = "Color Band height"; + // + // OFD + // + this.OFD.Filter = "Common Video Files|*.avi;*.mpeg;*.mpg;*.mkv;*.flv;*.mp4|All Files|*.*"; + this.OFD.Title = "Video File"; + // + // SFD + // + this.SFD.DefaultExt = "png"; + this.SFD.Filter = "PNG files|*.png|All Files|*.*"; + this.SFD.Title = "Frameband File"; + // + // tbInfo + // + this.tbInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tbInfo.Location = new System.Drawing.Point(3, 99); + this.tbInfo.Multiline = true; + this.tbInfo.Name = "tbInfo"; + this.tbInfo.ReadOnly = true; + this.tbInfo.Size = new System.Drawing.Size(535, 198); + this.tbInfo.TabIndex = 10; + this.tbInfo.Text = "File info appears here once you select a video"; + // + // cbSingleColor + // + this.cbSingleColor.AutoSize = true; + this.cbSingleColor.Location = new System.Drawing.Point(206, 72); + this.cbSingleColor.Name = "cbSingleColor"; + this.cbSingleColor.Size = new System.Drawing.Size(82, 17); + this.cbSingleColor.TabIndex = 8; + this.cbSingleColor.Text = "Single Color"; + this.cbSingleColor.UseVisualStyleBackColor = true; + // + // frmMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(550, 309); + this.Controls.Add(this.cbSingleColor); + this.Controls.Add(this.tbInfo); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.nudHeight); + this.Controls.Add(this.btnStart); + this.Controls.Add(this.tbDestination); + this.Controls.Add(this.tbSource); + this.Controls.Add(this.btnDestination); + this.Controls.Add(this.btnSource); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.Name = "frmMain"; + this.Text = "Frameband Generator"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.frmMain_FormClosed); + ((System.ComponentModel.ISupportInitialize)(this.nudHeight)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnSource; + private System.Windows.Forms.Button btnDestination; + private System.Windows.Forms.TextBox tbSource; + private System.Windows.Forms.TextBox tbDestination; + private System.Windows.Forms.Button btnStart; + private System.Windows.Forms.NumericUpDown nudHeight; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.OpenFileDialog OFD; + private System.Windows.Forms.SaveFileDialog SFD; + private System.Windows.Forms.TextBox tbInfo; + private System.Windows.Forms.CheckBox cbSingleColor; + } +} \ No newline at end of file diff --git a/colorBand/frmMain.cs b/colorBand/frmMain.cs new file mode 100644 index 0000000..aa51a8a --- /dev/null +++ b/colorBand/frmMain.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using System.IO; + +namespace colorBand +{ + public partial class frmMain : Form + { + string TempPath = Tools.GetTempDir("frameband"); + + public frmMain() + { + Console.Error.WriteLine("Extracting FFmpeg..."); + Tools.LogToConsole = true; + Tools.WriteEncoder(TempPath); + Tools.LogToConsole = false; + AyrA.IO.Terminal.RemoveConsole(); + InitializeComponent(); + } + + private void btnSource_Click(object sender, EventArgs e) + { + if (OFD.ShowDialog() == DialogResult.OK) + { + tbSource.Text = OFD.FileName; + if (string.IsNullOrEmpty(tbDestination.Text)) + { + AutoName(); + } + SetInfo(); + } + } + + private void btnDestination_Click(object sender, EventArgs e) + { + if (SFD.ShowDialog() == DialogResult.OK) + { + tbDestination.Text = SFD.FileName; + if (!tbDestination.Text.EndsWith(".png")) + { + MessageBox.Show("Output file type must be PNG. It will be changed now"); + tbDestination.Text = Tools.SwapExt(tbDestination.Text, "png"); + } + } + } + + private void frmMain_FormClosed(object sender, FormClosedEventArgs e) + { + Directory.Delete(TempPath, true); + } + + private void btnStart_Click(object sender, EventArgs e) + { + if (nudHeight.Value < 1) + { + MessageBox.Show("Please specify a height\r\nYou can open the video again to have this automatically assigned", "Height invalid", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + } + else if (!File.Exists(tbSource.Text)) + { + MessageBox.Show("The input file doesn't exists.", "Input file not found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + } + else if (!tbDestination.Text.ToLower().EndsWith(".png")) + { + MessageBox.Show("Output file must be PNG. We change that now.", "Output file format invalid", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + tbDestination.Text = Tools.SwapExt(tbDestination.Text, "png"); + } + else + { + var Encoder = new frmEncoder(TempPath, tbSource.Text, tbDestination.Text, (int)nudHeight.Value, cbSingleColor.Checked); + Encoder.ShowDialog(); + //everything OK. Begin conversion + } + } + + private void tbSource_Leave(object sender, EventArgs e) + { + if (!string.IsNullOrEmpty(tbSource.Text)) + { + SetInfo(); + } + } + + private void AutoName() + { + tbDestination.Text = Tools.SwapExt(tbSource.Text, "png"); + } + + private void SetInfo() + { + if (File.Exists(tbSource.Text)) + { + VideoInfo VI = Tools.GetVideoInfo(tbSource.Text); + nudHeight.Value = VI.Resolution.Height; + tbInfo.Text = string.Format(@"File: {0} +Type: {1} +Runtime: {2} +Resolution: {3} +Bitrate: {4} kbit/s +Frames: {5} (estimated from runtime)", + VI.VideoFile, VI.CodecName, VI.Duration, VI.Resolution, VI.Bitrate/1000, Math.Floor(VI.Duration.TotalSeconds)); + } + else + { + MessageBox.Show("The input file doesn't exists.", "Input file not found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + } + } + } +} diff --git a/colorBand/frmMain.resx b/colorBand/frmMain.resx new file mode 100644 index 0000000..9a38770 --- /dev/null +++ b/colorBand/frmMain.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 93, 17 + + \ No newline at end of file