using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Diagnostics;
namespace imageJoin
{
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
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
}
///
/// 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
///
/// Video file
/// Path to hold frame band images
/// ffmpeg executable path
/// Height of generated frame bands.
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();
}
}
///
/// Extracts Color information from the top left pixel
///
/// List of files
/// List of pixel colors
private 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.
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();
}
///
/// Joins an array of images horizontally
///
/// List of images
/// Destination File
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();
}
}
///
/// Prints a line of text and asks for input
///
/// Text to show. Will append colon and space
/// Line input
private static string Ask(string p)
{
Console.Write("{0}: ", p);
return Console.ReadLine();
}
///
/// Attempts to create a temporary folder
///
/// Name of the folder to create
/// Full path of folder created
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.
}
}
}
}
}