2006年 12月 02日

OSX スクリーンキャストツール

への布石

を組み合わせてスクリーンショットをとりまくって変換してみる。

#!/usr/bin/ruby

require 'rubygems'
require 'RMagick'
require 'ostruct'
require 'pathname'
require 'osx/cocoa'
require 'ext/osxscreen'
require 'zlib'
require 'progressbar'

include OSX

system('rm test/*')

size = eval( `./fullscreen.rb` )
p size

fps = 12 # frames per second

interval = 1.0 / fps

time = Time.now + 3

nextrun = 0
loop do
	true while Time.now.to_f < nextrun
	nextrun = Time.now.to_f + interval
	filename = "test/temp-#{Time.now.to_i}.#{Time.now.usec}.bin"

	File.open(filename, 'wb') do |f|
		f.write OSX::ScreenData.data
	end
	
#	Zlib::GzipWriter.open(filename + '.gz', Zlib::BEST_SPEED) do |f|
#		f.write OSX::ScreenData.data
#	end

	puts filename
	break if Time.now > time
end

frame = NSScreen.mainScreen.frame.size

files = Pathname.glob('test/*')
bar = ProgressBar.new('Convert', files.size)
files.each do |f|
	img = Magick::Image.new(frame.width, frame.height)
	img.import_pixels(0, 0, frame.width, frame.height, "BGRA", f.read, Magick::CharPixel)
	img.crop!(*size)

	img.write(f.to_s + '.png')
	f.unlink
	bar.inc
end

zlib は使わない方がいい気がする。ハードディスクあいてないときは使わざるを得ないけど……
fullscreen.rb (名前わるすぎ)

#!/usr/bin/ruby

require 'osx/cocoa'
include OSX

class SelectionView < NSView
	attr_accessor :delegate

	def drawRect(rect)
		NSColor.colorWithCalibratedRed(0, :green, 0, :blue, 0, :alpha, 0.1).set
		NSRectFill(bounds)

		if @start && @end
			NSColor.colorWithCalibratedRed(0.5, :green, 0.5, :blue, 0.5, :alpha, 0.5).set
			NSRectFill(calc_rect)
		end
	end

	def mouseDown(event)
		@start = NSEvent.mouseLocation
	end

	def mouseDragged(event)
		@end = NSEvent.mouseLocation
		setNeedsDisplay(true)
	end

	def mouseUp(event)
		@end = NSEvent.mouseLocation
		delegate.windowShouldClose(nil)
		window.close
	end

	def calc_rect
		x = [@start.x , @end.x].min
		y = [@start.y , @end.y].min
		w = (@start.x - @end.x).abs
		h = (@start.y - @end.y).abs
		[x, y, w, h]
	end
end

class TEST < NSObject
	def applicationDidFinishLaunching(aNotification)
		size = NSScreen.mainScreen.frame.size
		rect = [0, 0, size.width, size.height]
		@window = NSWindow.alloc.initWithContentRect(rect,
		                                  :styleMask, NSBorderlessWindowMask,
		                                    :backing, NSBackingStoreBuffered,
		                                      :defer, 0)
		@window.setDelegate(self)
		@window.setOpaque(0)
		@window.setHasShadow(0)
		@window.setLevel(1000) # NSScreenSaverWindowLevel

		@view = SelectionView.alloc.initWithFrame(rect)
		@view.delegate = self
		@window.setContentView(@view)

		@window.makeKeyAndOrderFront(nil)
		@window.orderFrontRegardless
	end

	def windowShouldClose(sender)
		size = NSScreen.mainScreen.frame.size
		rect =  @view.calc_rect
		rect[1] = size.height - rect[1] - rect[3]
		p rect
		exit
		true
	end
end

$App = NSApplication.sharedApplication
$App.setDelegate(TEST.alloc.init)
$App.run

ext/screendata は C

#include <Carbon/Carbon.h>
#include <ruby.h>

VALUE
screendata_data () {
	VALUE ret;
	int w = CGDisplayPixelsWide(kCGDirectMainDisplay);
	int h = CGDisplayPixelsHigh(kCGDirectMainDisplay);

	unsigned char* _screenBytesActual = (unsigned char*)CGDisplayBaseAddress(kCGDirectMainDisplay);
	unsigned char* _screenBytes       = (unsigned char*)malloc(w * h * 4);
	//NSAssert(_screenBytes != 0, "fail malloc");

	int bitPerPixel    = CGDisplayBitsPerPixel(kCGDirectMainDisplay);
	int bitPerSample   = CGDisplayBitsPerSample(kCGDirectMainDisplay);
	int samplePerPixel = CGDisplaySamplesPerPixel(kCGDirectMainDisplay);
	int bytePerRow     = CGDisplayBytesPerRow(kCGDirectMainDisplay);
	int bytePerPixel   = bitPerPixel / 8;

	int x, y;
//	for (y = 0; y < h; y++) {
//		unsigned long step = y * bytePerRow;
//		for (x = 0; x < w; x++) {
//			unsigned long pixel;
//			pixel =  *((unsigned long*)(_screenBytesActual + (x * bytePerPixel) + step));
//			// pixel on Intel => AABBGGRR
//			// bitmap data planes => AARRGGBB
//			pixel = ((pixel & 0x00ff0000) >> 16) | ((pixel & 0x000000ff) << 16) | (pixel & 0xff00ff00);
//			*((unsigned long*)(_screenBytes + x * 4 + y * w * 4)) = pixel;
//		}
//	}
	for (y = 0; y < h; y ++) {
		memcpy(_screenBytes + y * w * 4, _screenBytesActual + y * bytePerRow, w * bytePerPixel);
	}

	ret = rb_str_new((const char*)_screenBytes, w * h * 4);
	free(_screenBytes);
	return ret;
}

void
Init_osxscreen () {
	VALUE ScreenData;
	VALUE module;

	rb_eval_string("require 'osx/cocoa'");
	module = rb_eval_string("OSX");

	ScreenData = rb_define_module_under(module, "ScreenData");
	rb_define_module_function(ScreenData, "data", screendata_data, 0);
}

memcpy の引数を逆にしてひどいことになった。すぐなおるからいいけど(CGDisplayBaseAddress の先には直接書き込める。すなわち直接描画できる。)
ここから flv に変換したい。
rmovie という gem があるんだけど、コンパイルできない。ffmpeg を port で入れて云々がうまくいかない。深く追ってない。