Improving Thumbnailing Code

From the prior post about getting thumbnails from a video in .Net, it was just prototype code and wasn’t properly abstracted.  Now it is time to fix it.  We’ll create a class called VideoScreenShot.  This class will function in both an asynchronous and synchronous mode.  This still could be improved by queuing up work but this is a nice refactoring.

To get access to the tester app, head over to codeplex and source code can be found there too!

public delegate void CaptureWorkerDelegate(BitmapFrame frame, object state);
public static void CaptureScreenAsync(Uri source, TimeSpan timeSpan, object state, 
	CaptureWorkerDelegate finalWorkerPrimary)
{
	CaptureScreenAsync(source, timeSpan, -1, 
		state, finalWorkerPrimary, null);
}

public static void CaptureScreenAsync(Uri source, TimeSpan timeSpan, double scale, object state, 
	CaptureWorkerDelegate finalWorkerPrimary, CaptureWorkerDelegate finalWorkerThumbnail)
{
	ThreadPool.QueueUserWorkItem(
		delegate { CaptureScreen(source, timeSpan, scale, 
			state, finalWorkerPrimary, finalWorkerThumbnail); });
}

public static void CaptureScreen(Uri source, TimeSpan timeSpan, object state, 
	CaptureWorkerDelegate finalWorkerPrimary)
{
	CaptureScreen(source, timeSpan, -1, state, 
		finalWorkerPrimary, null);
}

public static void CaptureScreen(Uri source, TimeSpan timeSpan, double scale, object state, 
	CaptureWorkerDelegate finalWorkerPrimary, CaptureWorkerDelegate finalWorkerThumbnail)
{
	var player = new MediaPlayer { Volume = 0, ScrubbingEnabled = true };

	player.Open(source);
	player.Pause();
	player.Position = timeSpan;
	Thread.Sleep(1000);

	var width = player.NaturalVideoWidth;
	var height = player.NaturalVideoHeight;

	var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
	var dv = new DrawingVisual();

	using (DrawingContext dc = dv.RenderOpen())
		dc.DrawVideo(player, new Rect(0, 0, width, height));

	rtb.Render(dv);
	var frame = BitmapFrame.Create(rtb).GetCurrentValueAsFrozen();
	if (finalWorkerPrimary != null)
		finalWorkerPrimary(frame as BitmapFrame, state);

	if (scale > 0 && finalWorkerThumbnail != null)
	{
		var thumbnailFrame = BitmapFrame.Create(
			new TransformedBitmap(frame as BitmapSource, 
				new ScaleTransform(scale, scale))).GetCurrentValueAsFrozen();
		var encoder = new JpegBitmapEncoder();
		encoder.Frames.Add(thumbnailFrame as BitmapFrame);

		finalWorkerThumbnail(thumbnailFrame as BitmapFrame, state);
	}
	
	player.Close();
}

In the form’s code behind, here is my code.  This is just a sample of what can be used.

private delegate void setImageDelegate(string controlName, BitmapFrame frame);
private void setImage(string controlName, BitmapFrame frame)
{
	var control = FindName(controlName);
	if (control != null)
		((Image)control).Source = frame;
}

private void makeThumbnails(BitmapFrame frame, object state)
{
	Dispatcher.Invoke(new setImageDelegate(setImage), (string)state, frame);
}

private void makeJpeg(BitmapFrame frame, object state)
{
	var encoder = new JpegBitmapEncoder();
	encoder.Frames.Add(frame);

	string filename = (string)state + ".jpg";
	using (var fs = new FileStream(filename, FileMode.Create))
		encoder.Save(fs);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
	var source = video.Source;

	VideoScreenShot.CaptureScreenAsync(source, TimeSpan.FromSeconds(10), .1, 
		"image0", makeJpeg, makeThumbnails);
	VideoScreenShot.CaptureScreenAsync(source, TimeSpan.FromSeconds(43) + TimeSpan.FromMilliseconds(760), 
		"image1", makeThumbnails);
}

Now it isn’t so much cleaner?

Yura Jan 28, 2010 @ 5:27 AM

# re: Improving Thumbnailing Code
not very universal solution, as it depends on MediaElement. Better one is constructing DirectShow graph using DShowLib, seeking to required position and calling IBasicVideo.GetCurrentImage.

Clint Feb 14, 2010 @ 1:17 PM

# re: Improving Thumbnailing Code
@Yura true.

Post a Comment

Please add 4 and 8 and type the answer here: