Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.IO;
- using System.Drawing;
- using System.Diagnostics;
- namespace imageJoin
- {
- class Program
- {
- /// <summary>
- /// 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
- /// </summary>
- const string CMDLINE = "-i \"{0}\" -lavfi fps=1,scale=1:{2}:flags=lanczos \"{1}\\%06d.png\"";
- /// <summary>
- /// Main entry of Application
- /// </summary>
- /// <param name="args">Command line arguments</param>
- static void Main(string[] args)
- {
- Color[] Colors;
- string TempDir = GetTempDir("frameband");
- #if DEBUG
- //in debug mode I use hardcoded information because lazy.
- string VideoFile = @"S:\Media\Video\Star Wars\Star Wars IV\Star Wars IV.mkv";
- string OutputFile = @"C:\temp\band.png";
- string FFmpegFile = @"D:\Programme\VIDEO\ffmpeg\ffmpeg.exe";
- bool SingleColor = false;
- int Height = 200;
- #else
- string VideoFile = Ask("Source video file").Trim('"');
- string OutputFile = Ask("Destination image file").Trim('"');
- string FFmpegFile = @"ffmpeg.exe";
- int Height = 0;
- while (!int.TryParse("Image Height", out Height) || Height < 1) ;
- bool SingleColor = Ask("Use single color [y/n]").ToLower() == "y";
- #endif
- //Instead of all this ffmpeg path detection crap it would be better to bundle ffmpeg as resource and extract it at runtime.
- while (!File.Exists(FFmpegFile))
- {
- Console.WriteLine("Can't find {0}", FFmpegFile);
- FFmpegFile = Ask("ffmpeg.exe file").Trim('"');
- }
- 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);
- DateTime VideoGenerated = DateTime.Now;
- 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
- Colors = 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);
- }
- else
- {
- //Probably invalid video file
- Console.WriteLine("No frames were extracted");
- }
- }
- else
- {
- JoinImages(Directory.GetFiles(TempDir, "*.png"), OutputFile);
- }
- //Remove temporary PNG files.
- //Ideally this would be in a try/catch block at the end,
- //that loops until it succeeds.
- Directory.Delete(TempDir, true);
- //Stats for nerds
- TimeSpan tsTotal = DateTime.Now.Subtract(Start);
- TimeSpan tsVideo = VideoGenerated.Subtract(Start);
- TimeSpan tsImage = DateTime.Now.Subtract(VideoGenerated);
- Console.WriteLine(@"Done. Durations:
- Total: {0:00}:{1:00}:{2:00}
- Video: {3:00}:{4:00}:{5:00}
- Image: {6:00}:{7:00}:{8:00}",
- tsTotal.Hours, tsTotal.Minutes, tsTotal.Seconds,
- tsVideo.Hours, tsVideo.Minutes, tsVideo.Seconds,
- tsImage.Hours, tsImage.Minutes, tsImage.Seconds);
- #if DEBUG
- Console.WriteLine("#END");
- //Flush key buffer
- while (Console.KeyAvailable)
- {
- Console.ReadKey();
- }
- Console.ReadKey();
- #endif
- }
- /// <summary>
- /// Renders colors into a frame band
- /// </summary>
- /// <param name="Colors">List of colors</param>
- /// <param name="DestinationFile">Destination image file name</param>
- /// <param name="Height">Height of band</param>
- 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();
- }
- }
- /// <summary>
- /// Create an array of frame images from a video file
- /// </summary>
- /// <param name="Video">Video file</param>
- /// <param name="ImagePath">Path to hold frame band images</param>
- /// <param name="FFmpeg">ffmpeg executable path</param>
- /// <param name="Height">Height of generated frame bands.</param>
- private static void CreateImages(string Video, string ImagePath, string FFmpeg, int Height)
- {
- using (Process P = Process.Start(FFmpeg, string.Format(CMDLINE, Video, ImagePath, Height)))
- {
- //Instead of just waiting, spawn the Process hidden and show the progress of stderr.
- //I have some code for this already, but was too lazy to go and copy it.
- P.WaitForExit();
- }
- }
- /// <summary>
- /// Extracts Color information from the top left pixel
- /// </summary>
- /// <param name="Files">List of files</param>
- /// <returns>List of pixel colors</returns>
- private static Color[] ExtractColorFromPixel(string[] Files)
- {
- List<Color> Colors = new List<Color>();
- 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.
- 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('.');
- }
- }
- Console.WriteLine();
- return Colors.ToArray();
- }
- /// <summary>
- /// Joins an array of images horizontally
- /// </summary>
- /// <param name="Files">List of images</param>
- /// <param name="OutputFile">Destination File</param>
- private 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();
- }
- }
- /// <summary>
- /// Prints a line of text and asks for input
- /// </summary>
- /// <param name="p">Text to show. Will append colon and space</param>
- /// <returns>Line input</returns>
- private static string Ask(string p)
- {
- Console.Write("{0}: ", p);
- return Console.ReadLine();
- }
- /// <summary>
- /// Attempts to create a temporary folder
- /// </summary>
- /// <param name="Dirname">Name of the folder to create</param>
- /// <returns>Full path of folder created</returns>
- 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.
- }
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement