Advertisement
AyrA

Color band generator

Jul 17th, 2016
295
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 10.75 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. using System.Drawing;
  6. using System.Diagnostics;
  7.  
  8. namespace imageJoin
  9. {
  10.     class Program
  11.     {
  12.         /// <summary>
  13.         /// Command line for FFMPEG.
  14.         /// Explanation: takes video input and renders it with 1 fps and downscaled to a single pixel to an array of png files
  15.         /// </summary>
  16.         const string CMDLINE = "-i \"{0}\" -lavfi fps=1,scale=1:{2}:flags=lanczos \"{1}\\%06d.png\"";
  17.  
  18.         /// <summary>
  19.         /// Main entry of Application
  20.         /// </summary>
  21.         /// <param name="args">Command line arguments</param>
  22.         static void Main(string[] args)
  23.         {
  24.             Color[] Colors;
  25.  
  26.             string TempDir = GetTempDir("frameband");
  27. #if DEBUG
  28.             //in debug mode I use hardcoded information because lazy.
  29.             string VideoFile = @"S:\Media\Video\Star Wars\Star Wars IV\Star Wars IV.mkv";
  30.             string OutputFile = @"C:\temp\band.png";
  31.             string FFmpegFile = @"D:\Programme\VIDEO\ffmpeg\ffmpeg.exe";
  32.             bool SingleColor = false;
  33.             int Height = 200;
  34. #else
  35.             string VideoFile = Ask("Source video file").Trim('"');
  36.             string OutputFile = Ask("Destination image file").Trim('"');
  37.             string FFmpegFile = @"ffmpeg.exe";
  38.             int Height = 0;
  39.             while (!int.TryParse("Image Height", out Height) || Height < 1) ;
  40.             bool SingleColor = Ask("Use single color [y/n]").ToLower() == "y";
  41. #endif
  42.             //Instead of all this ffmpeg path detection crap it would be better to bundle ffmpeg as resource and extract it at runtime.
  43.             while (!File.Exists(FFmpegFile))
  44.             {
  45.                 Console.WriteLine("Can't find {0}", FFmpegFile);
  46.                 FFmpegFile = Ask("ffmpeg.exe file").Trim('"');
  47.             }
  48.  
  49.             DateTime Start = DateTime.Now;
  50.  
  51.             //Generate single frames into PNG
  52.             //Note: check if input is a video with at least 1 frame at all.
  53.             CreateImages(VideoFile, TempDir, FFmpegFile, SingleColor ? 1 : Height);
  54.  
  55.             DateTime VideoGenerated = DateTime.Now;
  56.  
  57.             if (SingleColor)
  58.             {
  59.                 //Extract color information from the PNG images.
  60.                 //GetFiles is rather slow, especially for a large number of files,
  61.                 //but it is certainly easier to use than manually using the Windows API
  62.                 Colors = ExtractColorFromPixel(Directory.GetFiles(TempDir, "*.png"));
  63.  
  64.  
  65.                 //It is possible, that no colors were extracted,
  66.                 //for example if the input is an audio file.
  67.                 if (Colors.Length > 0)
  68.                 {
  69.                     RenderBand(Colors, OutputFile, Height);
  70.                 }
  71.                 else
  72.                 {
  73.                     //Probably invalid video file
  74.                     Console.WriteLine("No frames were extracted");
  75.                 }
  76.             }
  77.             else
  78.             {
  79.                 JoinImages(Directory.GetFiles(TempDir, "*.png"), OutputFile);
  80.             }
  81.  
  82.             //Remove temporary PNG files.
  83.             //Ideally this would be in a try/catch block at the end,
  84.             //that loops until it succeeds.
  85.             Directory.Delete(TempDir, true);
  86.  
  87.             //Stats for nerds
  88.             TimeSpan tsTotal = DateTime.Now.Subtract(Start);
  89.             TimeSpan tsVideo = VideoGenerated.Subtract(Start);
  90.             TimeSpan tsImage = DateTime.Now.Subtract(VideoGenerated);
  91.  
  92.             Console.WriteLine(@"Done. Durations:
  93. Total: {0:00}:{1:00}:{2:00}
  94. Video: {3:00}:{4:00}:{5:00}
  95. Image: {6:00}:{7:00}:{8:00}",
  96.                 tsTotal.Hours, tsTotal.Minutes, tsTotal.Seconds,
  97.                 tsVideo.Hours, tsVideo.Minutes, tsVideo.Seconds,
  98.                 tsImage.Hours, tsImage.Minutes, tsImage.Seconds);
  99. #if DEBUG
  100.             Console.WriteLine("#END");
  101.             //Flush key buffer
  102.             while (Console.KeyAvailable)
  103.             {
  104.                 Console.ReadKey();
  105.             }
  106.             Console.ReadKey();
  107. #endif
  108.         }
  109.  
  110.         /// <summary>
  111.         /// Renders colors into a frame band
  112.         /// </summary>
  113.         /// <param name="Colors">List of colors</param>
  114.         /// <param name="DestinationFile">Destination image file name</param>
  115.         /// <param name="Height">Height of band</param>
  116.         private static void RenderBand(Color[] Colors, string DestinationFile, int Height)
  117.         {
  118.             using (Bitmap Output = new Bitmap(Colors.Length, Height))
  119.             {
  120.                 //if height is 1, then use SetPixel
  121.                 if (Height == 1)
  122.                 {
  123.                     for (int i = 0; i < Colors.Length; i++)
  124.                     {
  125.                         Output.SetPixel(i, 0, Colors[i]);
  126.                         //This actually slows down the application quite a lot.
  127.                         Console.Write('-');
  128.                     }
  129.                 }
  130.                 else
  131.                 {
  132.                     using (Graphics G = Graphics.FromImage(Output))
  133.                     {
  134.                         //Note: Setting G.InterpolationMode to a "cheap" value has no effect,
  135.                         //as it is only used for scaling.
  136.                         //Creating a 1 pixel high image and then scale the height up does not increases the speed.
  137.                         for (int i = 0; i < Colors.Length; i++)
  138.                         {
  139.                             //Why is this an IDisposable?
  140.                             using (Pen P = new Pen(Colors[i]))
  141.                             {
  142.                                 //Drawing a line is by far faster than setting individual pixels.
  143.                                 G.DrawLine(P, new Point(i, 0), new Point(i, Height - 1));
  144.                                 //This actually slows down the application quite a lot.
  145.                                 Console.Write('-');
  146.                             }
  147.                         }
  148.                     }
  149.                 }
  150.                 //This detects the image format from the extension
  151.                 //See System.Drawing.Imaging.ImageFormat enumeration for supported types.
  152.                 Output.Save(DestinationFile);
  153.                 Console.WriteLine();
  154.             }
  155.         }
  156.  
  157.         /// <summary>
  158.         /// Create an array of frame images from a video file
  159.         /// </summary>
  160.         /// <param name="Video">Video file</param>
  161.         /// <param name="ImagePath">Path to hold frame band images</param>
  162.         /// <param name="FFmpeg">ffmpeg executable path</param>
  163.         /// <param name="Height">Height of generated frame bands.</param>
  164.         private static void CreateImages(string Video, string ImagePath, string FFmpeg, int Height)
  165.         {
  166.             using (Process P = Process.Start(FFmpeg, string.Format(CMDLINE, Video, ImagePath, Height)))
  167.             {
  168.                 //Instead of just waiting, spawn the Process hidden and show the progress of stderr.
  169.                 //I have some code for this already, but was too lazy to go and copy it.
  170.                 P.WaitForExit();
  171.             }
  172.         }
  173.  
  174.         /// <summary>
  175.         /// Extracts Color information from the top left pixel
  176.         /// </summary>
  177.         /// <param name="Files">List of files</param>
  178.         /// <returns>List of pixel colors</returns>
  179.         private static Color[] ExtractColorFromPixel(string[] Files)
  180.         {
  181.             List<Color> Colors = new List<Color>();
  182.             foreach (string s in Files)
  183.             {
  184.                 Bitmap I = null;
  185.                 try
  186.                 {
  187.                     //Ironically Bitmap.FromFile comes from the Image class and will return an Image object,
  188.                     //but it is fully compatible with the Bitmap type so we just cast.
  189.                     I = (Bitmap)Bitmap.FromFile(s);
  190.                 }
  191.                 catch
  192.                 {
  193.                     //I never had this happen but better add an error resolver.
  194.                     //In this case, we just ignore that frame.
  195.                     Console.Write('X');
  196.                     continue;
  197.                 }
  198.                 using (I)
  199.                 {
  200.                     //Extract image color
  201.                     Colors.Add(I.GetPixel(0, 0));
  202.                     //This actually slows down the application quite a lot.
  203.                     Console.Write('.');
  204.                 }
  205.             }
  206.             Console.WriteLine();
  207.             return Colors.ToArray();
  208.         }
  209.  
  210.         /// <summary>
  211.         /// Joins an array of images horizontally
  212.         /// </summary>
  213.         /// <param name="Files">List of images</param>
  214.         /// <param name="OutputFile">Destination File</param>
  215.         private static void JoinImages(string[] Files, string OutputFile)
  216.         {
  217.             int x = 0;
  218.             int Height = 0;
  219.             using (Image I = Image.FromFile(Files[0]))
  220.             {
  221.                 Height = I.Height;
  222.             }
  223.             using (Bitmap B = new Bitmap(Files.Length, Height))
  224.             {
  225.                 using (Graphics G = Graphics.FromImage(B))
  226.                 {
  227.                     foreach(string Name in Files)
  228.                     {
  229.                         using (Image I = Image.FromFile(Name))
  230.                         {
  231.                             G.DrawImageUnscaled(I, new Point(x++, 0));
  232.                         }
  233.                         Console.Write('-');
  234.                     }
  235.                 }
  236.                 B.Save(OutputFile);
  237.                 Console.WriteLine();
  238.             }
  239.         }
  240.  
  241.         /// <summary>
  242.         /// Prints a line of text and asks for input
  243.         /// </summary>
  244.         /// <param name="p">Text to show. Will append colon and space</param>
  245.         /// <returns>Line input</returns>
  246.         private static string Ask(string p)
  247.         {
  248.             Console.Write("{0}: ", p);
  249.             return Console.ReadLine();
  250.         }
  251.  
  252.         /// <summary>
  253.         /// Attempts to create a temporary folder
  254.         /// </summary>
  255.         /// <param name="Dirname">Name of the folder to create</param>
  256.         /// <returns>Full path of folder created</returns>
  257.         static string GetTempDir(string Dirname)
  258.         {
  259.             int i = 0;
  260.             string TempRoot = Path.Combine(Path.GetTempPath(), Dirname + "_");
  261.             while (true)
  262.             {
  263.                 string current = string.Format("{0}{1}", TempRoot, i++);
  264.                 try
  265.                 {
  266.                     Directory.CreateDirectory(current);
  267.                     return current;
  268.                 }
  269.                 catch
  270.                 {
  271.                     //you can increase i here instead if you want.
  272.                 }
  273.             }
  274.         }
  275.     }
  276. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement