April 5, 2009

ds ii

it's been almost eighteen months since i last processed a ds run, and i've learned a lot in that time. i knew that if dsgamer or anyone else submitted a new run, i'd do things differently from last time.

well, dsgamer submitted not only an improvement on his any percent run of prime hunters, but a new 100% run, as well. on top of that, greenalink did a run of mario 64 ds. when all three runs were accepted by verifiers, i knew it was "time to get serious" (as mega man would say) about processing ds footage.

perl has become my new best friend, and i knew it had integration with imagemagick, gd, etc. a few google searches later, i had decided on using imagemagick ("perlmagick"), since it came with a well-tested autocrop function.

originally i was going to try to determine the z-distortion (i don't know the proper term) of the ds screen(s) and correct for that (basically if the top of the screen is further away from the camera than the bottom), but the only implementation i found of that functionality i couldn't get working with any of my test footage (it never changed anything), so i decided to put that off until next time. i felt like i had enough on my plate with making sure the autocrop threshold was correct all the time.

basically, i set a default threshold and try to autocrop the current frame. if the autocrop succeeds, then i note how much was cropped and move on to the next frame. if it doesn't succeed, then i gradually turn up the threshold until it does succeed. if it never succeeds, i don't save the crop data. if it succeeds but it's cropped off way too much as determined by a sanity check, then i don't save the crop data.

i do this for a certain number of frames (i ended up using two seconds' worth for dsgamer's runs), then go back and crop all of those frames by the means of the values i saved for each side. this is much improved from how i was doing it before using avisynth, which could only look at a single frame to determine values. i also don't think i could get the values that the avisynth plugin was using very easily due to how simple the avisynth language is, but i could be wrong on that.

in other words, the old method was very high maintence - i had to go five seconds at a time and manually adjust the threshold. a lot of times bloom from the screen (if the screen was very bright and you could see the ds's body glowing in the dark, for example) would throw off the threshold. i got around that this time by starting with a rather high threshold and then throwing out crop data that deviated too much from an arbitrary norm. (by "deviated too much" i am talking about how if the screen was very dark the high starting threshold would sometimes cause it to crop off too much, for example the area outside the hud in human form.)

a new feature for next time might be to determine the norm on the fly. i guess that would be means for the whole segment. actually i thought i was going to have to do that this time, but dsgamer's runs came out perfect on the first try. it was a good thing, too, because it took the better part of a week for one of the computers in the lab where i work to process them (i think most of it was i/o though since i had to read twice and write once a .png file for every single frame in the runs). i'd also like to learn how to read and write yv12 and do everything through mplayer/mencoder pipes next time for a big speedup. normally i wouldn't care so much about the speed, but i think this may make it into anri someday (even though only two people have ever submitted ds runs to the site), and it's totally unworkable for most people's computers the way it is now.

i did the audio on dsgamer's runs the same way i did it last time - by putting the over-the-air audio on the left channel and the audacity-recorded audio on the right channel, wearing headphones, and adjusting the audacity audio's offset until i couldn't distinguish the right and left channels. (i then cut the over-the-air audio.)

greenalink's run i did totally differently because he recorded it totally differently. he was able to keep his ds much more still than dsgamer was probably because of how the game is controlled differently, so autocrop was not really needed. i just figured out a set of rotation and cropping values for each individual segment and applied those. he also recorded both screens all the time. he and i talked about this quite a bit and we decided that since mplayerc lets you zoom in on one screen in the video, leaving in both screens would be the best option since some people might want to see what's going on on the bottom screen even when he's controlling a character. it also goes along with the site's general ethos, not throwing out potentially valuable data that could be used by someone to improve the run.

greenalink's run didn't need anything done to the audio (except maybe normalization) since he recorded both the video and the audio using a dvd recorder, so it was already synced.

all three runs will be going up on the site soon, and i hope you enjoy them.

here are the tools i made or used to process them this time:
mplayer/mencoder (to dump to pngs/encode video from pngs)
ds.pl (to process the pngs)
avisynth rotate (to rotate greenalink's segments before cropping)

by the way, getting perlmagick to compile under os x was a total pain in the ass. it took me several hours of googling and experimentation. i ended up manually linking the perlmagick sources against the imagemagick libraries delivered by macports.

Posted by njahnke at 7:11 PM | Comments (0)

February 1, 2009

no more musical chairs

i forgot to put the segments of the rosenkreuzstilette run in proper order before making the verification-stage avisynth scripts: segment 8 was actually segment 5, and segments 5-7 all had to move up one. it sounds simple enough, but there is no way to rename everything in situations like this without having at least one file have a temporary name. and if it's me doing the renaming i will often second-guess what i am doing halfway through the operation because i am ocd like that.

so i cooked up a quick perl script to just give every file in the directory a temporary name and then rename everything back based on a transformation rule. the old segment numbers are the keys of %rehash and the values are the new numbers. i use %files to remember what the names used to be after the files are given their temporary names. i made the temporary names random (with a check to make sure they're unused) since i may have more than one copy of the same file in the directory (so renaming them to their md5 hash or something is out).

enjoy.

#!/usr/bin/perl -w
#nathan jahnke 

my $natename = 'rks';
my $thedir = "/p/${natename}";
my %rehash = ( #old, new
	8=>5,
	5=>6,
	6=>7,
	7=>8,
);
my %files;

opendir(P,$thedir);
my @files = readdir(P);
closedir(P);

for (@files) {
	if (-f "${thedir}/${_}") {
		my $newname = &newname;
		system('mv', '-nv', "${thedir}/${_}", "${thedir}/${newname}");
		$files{$newname} = $_;
	}
}

for (keys %files) {
	my $newname;
	if ($files{$_} =~ m/^${natename}-v-(\d+)\.(.+)$/) {
		my $segnum;
		$segnum = $1;
		$segnum = $rehash{$segnum} if $rehash{$segnum};
		$newname = "${natename}-v-${segnum}.${2}";
	} else {
		$newname = "${files{$_}}";
	}
	system('mv', '-nv', "${thedir}/${_}", "${thedir}/${newname}");
}

exit 0;


sub newname {
	my $retvar = '';
	while (-e "${thedir}/".$retvar) { #try until we get a unused name
		$retvar = &random;
	}
	return $retvar;
}

sub random {
	my $retvar = rand(1);
	$retvar =~ s/^0\.//;
	return $retvar;
}

Posted by njahnke at 6:13 PM | Comments (0)