アスペクト比がおかしい動画をその場で補正して観れるようにするプログラムを作ってみた

YouTube, Pandora.TV, Veohなどの動画投稿サイトにはよくアスペクト比がおかしくて人物の顔が縦に長くなっているような動画が投稿されている。こういう動画は普通は我慢していらいらしながら観るか、FLVをダウンロードしてFLVプレーヤで補正して観るものだと思うが、私はその場で直接補正しながら観たいと思っている。

解決方法は大体下記のようなものがあると思う。

  • ディスプレイドライバをいじって任意の解像度で表示できるようにする(1600x1200のディスプレイに1200x1200を引き伸ばして表示できるようにするなど)
  • ブラウザの中身をいじって全体を横に引き伸ばして表示できるようにする
  • FlashPlayerの中身をいじってFlashの表示を横に引き伸ばせるようにする
  • swfの中身を勝手にいじってアスペクト比の変更に対応する
  • 画面をキャプチャして別の場所に横に引き伸ばした絵を表示する

上4つは少なくとも私には簡単にはできなさそうなので5つ目のものを実験的に作ってみた。

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class Scaler extends JFrame {
	public static void main(String[] args) throws Exception {
		Scaler scaler = new Scaler();
		scaler.run();
	}

	private Robot robot;
	private Graphics g;

	Insets insets;
	int x, y, w, h;
	int cx, cy, cw, ch;

	float aspectRatio = 4f / 3;
	float zoomRatio = 1.7f;

	public Scaler() throws Exception {
		x = 100;
		y = 550;
		w = 800;
		h = 600;

		cx = 300;
		cy = 100;
		cw = 480;
		ch = 450;

		robot = new Robot();
		UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
		SwingUtilities.updateComponentTreeUI(this);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLayout(null);
		addNotify();
		insets = getInsets();
		setLocation(x, y);
		setSize(w + insets.left + insets.right, h + insets.top + insets.bottom);
		g = getGraphics();
		setVisible(true);
	}

	public void run() {
		for (;;) {
			try {
				BufferedImage img = captureImage(cx, cy, cw, ch);
				BufferedImage scaledImg = changeAspectRatio(img, Image.SCALE_FAST);
				g.drawImage(scaledImg, 0, 0, null);
				try {
					Thread.sleep(10);
				} catch (Exception e) {
				}
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}
		}
	}

	public BufferedImage captureImage(int x, int y, int w, int h) throws Exception {
		Rectangle bounds = new Rectangle(x, y, w, h);
		return robot.createScreenCapture(bounds);
	}

	public BufferedImage changeAspectRatio(Image img, int scaleType) {
		img = img.getScaledInstance((int)(img.getWidth(null) * zoomRatio),
		(int)(img.getHeight(null) / aspectRatio * zoomRatio), scaleType);
		BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
		Graphics g = bi.getGraphics();
		g.drawImage(img, 0, 0, null);
		return bi;
	}
}

見てのとおりjava.awt.Robotでスクリーンの一部をキャプチャし、java.awt.Image#getScaledInstanceでアスペクト比を修正し、ちょっと拡大して表示している。

で、結果は↓のようになった。

若干のラグはあるもののとりあえず問題なし。普通に観れる。
ちなみにImage.SCALE_FASTではなくImage.SCALE_SMOOTHを使うと私の環境では処理時間がかかりすぎてフレーム数が落ちてしまった。
気が向いたら動画部分の自動判別やウィンドウサイズとアスペクト比の連動なんかを実装するかもしれない。