diff --git a/extras/oekaki/finish.pl b/extras/oekaki/finish.pl
index eb28550..4e695af 100755
--- a/extras/oekaki/finish.pl
+++ b/extras/oekaki/finish.pl
@@ -19,6 +19,7 @@ BEGIN { require "strings_en.pl"; }
 BEGIN { require "oekaki_style.pl"; }
 BEGIN { require "oekaki_config.pl"; }
 BEGIN { require "oekaki_strings_en.pl"; }
+BEGIN { require "oekaki_util.pl"; }
 BEGIN { require "wakautils.pl"; }
 
 
@@ -89,21 +90,22 @@ elsif($task eq "post")
 
 	$ENV{SCRIPT_NAME}=~s/\w+\.pl$/wakaba.pl/;
 
-	if ($animated) {
-		my $anim_file;
-		open $anim_file,$tmpname_pch or die "Can't read pch file";
+	open TMPFILE,$tmpname or die "Can't read uploaded file";
 
-		my ($pch_newname,$pch_md5,$pch_width,$pch_height,$pch_thumb,$pch_twidth,$pch_theight) =
-			process_file($anim_file,"internal_animated.pch",time(),1);
+	my $postfix;
 
-		$animated = $pch_newname;
-		unlink $tmpname_pch;
+	if ($animated) {
+		$postfix = sub {
+			my $filename = shift;
+			my $pch_filename = oek_pch_mv($filename, $tmpname_pch);
+			return OEKAKI_INFO_TEMPLATE->(decode_srcinfo($srcinfo,$pch_filename));
+		};
+	} else {
+		$postfix = OEKAKI_INFO_TEMPLATE->(decode_srcinfo($srcinfo, $animated));
 	}
 
-	open TMPFILE,$tmpname or die "Can't read uploaded file";
-
 	post_stuff($parent,$name,$email,$subject,$comment,\*TMPFILE,$tmpname,$password,
-	0,$captcha,ADMIN_PASS,0,0,OEKAKI_INFO_TEMPLATE->(decode_srcinfo($srcinfo, $animated)));
+	0,$captcha,ADMIN_PASS,0,0,$postfix);
 
 	unlink $tmpname;
 }
diff --git a/extras/oekaki/oekaki_config.pl b/extras/oekaki/oekaki_config.pl
index 0dfb714..77cd7e9 100644
--- a/extras/oekaki/oekaki_config.pl
+++ b/extras/oekaki/oekaki_config.pl
@@ -9,7 +9,11 @@ use constant OEKAKI_MAX_X => 800;					# Max X dimension allowed for oekaki drawi
 use constant OEKAKI_MAX_Y => 800;					# Max Y dimension allowed for oekaki drawings
 use constant OEKAKI_MIN_X => 100;   	            # Min X dimension allowed for oekaki drawings
 use constant OEKAKI_MIN_Y => 100;   	            # Min Y dimension allowed for oekaki drawings
-use constant OEKAKI_ANIM_MAX => 0;              # Max file size (in KiB) for animation recording
+
+use constant OEKAKI_ANIM_MAX => 0;         # Max file size (in KiB) for animation recording
+use constant OEKAKI_ANIM_DIR => 'ani/';    # Directory to store .pch animation files
+                                           # this can be the same as IMG_DIR, but that
+                                           # is mildly not recommended. Must be writeable
 
 use constant OEKAKI_DEFAULT_PAINTER => "shi_norm";	# Default painter selection
 
diff --git a/extras/oekaki/oekaki_style.pl b/extras/oekaki/oekaki_style.pl
index 432264b..172c351 100644
--- a/extras/oekaki/oekaki_style.pl
+++ b/extras/oekaki/oekaki_style.pl
@@ -4,6 +4,7 @@ use strict;
 
 BEGIN { require 'oekaki_config.pl'; }
 BEGIN { require 'oekaki_strings_en.pl'; }
+BEGIN { require 'oekaki_util.pl'; }
 BEGIN { require 'futaba_style.pl'; }
 BEGIN { require 'wakautils.pl'; }
 
diff --git a/extras/oekaki/oekaki_util.pl b/extras/oekaki/oekaki_util.pl
new file mode 100644
index 0000000..ac1186a
--- /dev/null
+++ b/extras/oekaki/oekaki_util.pl
@@ -0,0 +1,47 @@
+BEGIN { require "config.pl"; }
+BEGIN { require "config_defaults.pl"; }
+
+sub oek_pch_filename($) {
+	my ($filename) = @_;
+	my $pch_filename = $filename;
+	# trim dir
+	$pch_filename =~ s:^.*/([^/]+)$:$1:;
+	# trim extension
+	$pch_filename =~ s/[.][^.]+$//;
+	# put in the anim dir, and give a .pch extension
+	$pch_filename = OEKAKI_ANIM_DIR.$pch_filename.".pch";
+
+	return $pch_filename;
+}
+
+sub oek_pch_mv($$) {
+	my ($img_filename, $tmpname) = @_;
+	my $pch_filename;
+	my $buffer;
+
+	open PCHTMP,$tmpname or die "Can't read temporary PCH file";
+	binmode PCHTMP;
+
+	$pch_filename = oek_pch_filename($img_filename);
+	if (-e $pch_filename) {
+		unlink $img_filename;
+		make_error(S_DUPENAME);
+	}
+
+	if (not open (OUTFILE,">>$pch_filename")) {
+		unlink $img_filename;
+		make_error(S_NOTWRITE);
+	}
+	binmode OUTFILE;
+	while (read(PCHTMP,$buffer,4096)) {
+		print OUTFILE $buffer;
+	}
+	close PCHTMP;
+	close OUTFILE;
+
+	unlink $tmpname;
+
+	return $pch_filename;
+}
+
+1;
diff --git a/extras/oekaki/pchview.html b/extras/oekaki/pchview.html
index adc139b..5970c50 100755
--- a/extras/oekaki/pchview.html
+++ b/extras/oekaki/pchview.html
@@ -8,64 +8,66 @@ table.nospace { border-collapse:collapse; }
 table.nospace tr td { margin:0px; }
 .menu { background-color:#CFCFFF; border: 1px solid #666666; padding: 2px; margin-bottom: 2px; }
 </style>
+</head>
+<body>
+<noscript>Javascript is required to use this PCH animation viewer. Sorry! You
+can still get the PCH file directly, if you can tell where it is.</noscript>
 <script language="javascript">
 <!--
+function err(str) {
+	document.write("<p>"+str+"</p>\n");
+}
+
 function onLoad() {
+
 	var varstr = "?pchfile=";
 	var pch = document.pch;
 	var href = window.location.href;
 
-	var start = href.indexOf(varstr);
-	if (start < 0) {
-		document.getElementById("errormessage").innerHTML = "No PCH file specified?";
-		return;
-	}
-	start += varstr.length;
+var start = href.indexOf(varstr);
+if (start < 0) {
+	err("No PCH file specified?");
+	return;
+}
+start += varstr.length;
 
-	var amp = href.indexOf("&", start);
-	var hash = href.indexOf("#", start);
-	if (hash < 0) {
-		hash = amp;
-	}
-	if (amp < 0) {
-		amp = hash;
-	}
-	var end = amp;
-	if (hash < amp) {
-		end = hash;
-	}
-	var filename;
-	if (end < 0) {
-		filename = href.slice(start);
-	} else {
-		filename = href.slice(start, end);
-	}
-	if (filename.length < 1) {
-		document.getElementById("errormessage").innerHTML = "No PCH file specified?";
-		return;
-	}
-	document.title = "PCHViewer: Playing " + filename;
-	pch.loadPCH(filename);
-	pch.setSpeed("1");
-	pch.playPCH();
+var amp = href.indexOf("&", start);
+var hash = href.indexOf("#", start);
+if (hash < 0) {
+	hash = amp;
 }
-//-->
-</script>
-</head><body onload="onLoad();">
+if (amp < 0) {
+	amp = hash;
+}
+var end = amp;
+if (hash < amp) {
+	end = hash;
+}
+var filename;
+if (end < 0) {
+	filename = href.slice(start);
+} else {
+	filename = href.slice(start, end);
+}
+if (filename.length < 1) {
+	err("No PCH file specified?");
+	return;
+}
+document.title = "PCHViewer: Playing " + filename;
 
-<noscript>Javascript is required to use this PCH animation viewer. Sorry! You
-can still get the PCH file directly, if you can tell where it is.</noscript>
+document.write('<table class="nospace" width="100%" height="100%"><tbody><tr>\n' +
+               '<td width="100%">\n' +
+               '<applet name="pch" code="pch2.PCHViewer.class" codebase="./" archive="PCHViewer.jar" width="100%" height="100%">\n' +
+               '<param name="pch_file" value="'+filename+'">\n' +
+               '<param name="speed" value="1">\n' +
+               '<param name="run" value="true">\n' +
+               '<param name="res.zip" value="res.zip">\n' +
+               '</applet></td></tr></tbody></table>\n');
+}
+onLoad();
 
-<span id="errormessage"></span>
+//-->
+</script>
 
-<table class="nospace" width="100%" height="100%"><tbody><tr>
-<td width="100%">
-<applet name="pch" code="pch2.PCHViewer.class" codebase="./" archive="PCHViewer.jar" width="100%" height="100%">
-<param name="speed" value="1">
-<param name="run" value="false">
-<param name="res.zip" value="res.zip">
-</applet>
-</td>
-</tr></tbody></table>
 </body>
 </html>
diff --git a/wakaba.pl b/wakaba.pl
index 96fe451..86dd00d 100755
--- a/wakaba.pl
+++ b/wakaba.pl
@@ -527,15 +527,20 @@ sub post_stuff($$$$$$$$$$$$$$)
 	# fix up the email/link
 	$email="mailto:$email" if $email and $email!~/^$protocol_re:/;
 
+	# $postfix can be a function reference, which is run with the filename of
+	# the posted file to generate the actual postfix contents
+	my $postfix_code = 0;
+	$postfix_code = 1 if (ref($postfix) eq 'CODE');
+
 	# format comment
 	$comment=format_comment(clean_string(decode_string($comment,CHARSET))) unless $no_format;
-	$comment.=$postfix;
+	$comment.=$postfix unless ($postfix_code);
 
 	# insert default values for empty fields
 	$parent=0 unless $parent;
 	$name=make_anonymous($ip,$time) unless $name or $trip;
 	$subject=S_ANOTITLE unless $subject;
-	$comment=S_ANOTEXT unless $comment;
+	$comment=S_ANOTEXT unless ($comment or $postfix_code);
 
 	# flood protection - must happen after inputs have been cleaned up
 	flood_check($numip,$time,$comment,$file);
@@ -551,6 +556,8 @@ sub post_stuff($$$$$$$$$$$$$$)
 	# copy file, do checksums, make thumbnail, etc
 	my ($filename,$md5,$width,$height,$thumbnail,$tn_width,$tn_height)=process_file($file,$uploadname,$time) if($file);
 
+	$comment .= &$postfix($filename) if ($postfix_code);
+
 	# finally, write to the database
 	my $sth=$dbh->prepare("INSERT INTO ".SQL_TABLE." VALUES(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);") or make_error(S_SQLFAIL);
 	$sth->execute($parent,$time,$lasthit,$numip,
@@ -947,25 +954,21 @@ sub get_file_size($)
 	return($size);
 }
 
-sub process_file($$$;$)
+sub process_file($$$)
 {
-	# if $force is true, this is an internal request, so force it to succeed
-	# if we can (don't e.g. fail because of an unknown file format)
-	my ($file,$uploadname,$time,$force)=@_;
+	my ($file,$uploadname,$time)=@_;
 	my %filetypes=FILETYPES;
 
-	$force = $force ? 1 : 0;
-
 	# make sure to read file in binary mode on platforms that care about such things
 	binmode $file;
 
 	# analyze file and check that it's in a supported format
 	my ($ext,$width,$height)=analyze_image($file,$uploadname);
 
-	my $known=($width or $filetypes{$ext} or $force);
+	my $known=($width or $filetypes{$ext});
 
 	make_error(S_BADFORMAT) unless(ALLOW_UNKNOWN or $known);
-	make_error(S_BADFORMAT) if(!$force and grep { $_ eq $ext } FORBIDDEN_EXTENSIONS);
+	make_error(S_BADFORMAT) if(grep { $_ eq $ext } FORBIDDEN_EXTENSIONS);
 	make_error(S_TOOBIG) if(MAX_IMAGE_WIDTH and $width>MAX_IMAGE_WIDTH);
 	make_error(S_TOOBIG) if(MAX_IMAGE_HEIGHT and $height>MAX_IMAGE_HEIGHT);
 	make_error(S_TOOBIG) if(MAX_IMAGE_PIXELS and $width*$height>MAX_IMAGE_PIXELS);
@@ -976,10 +979,6 @@ sub process_file($$$;$)
 	my $thumbnail=THUMB_DIR.$filebase."s.jpg";
 	$filename.=MUNGE_UNKNOWN unless($known);
 
-	# make sure we don't get thumbnails, and we don't rename the file to
-	# its original name, or anything like that
-	($ext,$width,$height) = ('',0,0) if ($force);
-
 	# do copying and MD5 checksum
 	my ($md5,$md5ctx,$buffer);
 
@@ -1171,6 +1170,12 @@ sub delete_post($$$$)
 					unlink $$res{image};
 					unlink $$res{thumbnail} if($$res{thumbnail}=~/^$thumb/);
 				}
+
+				if (defined &OEKAKI_ENABLE_ANIM and eval 'OEKAKI_ENABLE_ANIM') {
+					my $pch_filename = eval 'oek_pch_filename($$res{image})';
+					die $@ if $@;
+					unlink $pch_filename;
+				}
 			}
 
 			# remove post and possible replies
@@ -1187,6 +1192,12 @@ sub delete_post($$$$)
 				unlink $$row{image};
 				unlink $$row{thumbnail} if($$row{thumbnail}=~/^$thumb/);
 
+				if (defined &OEKAKI_ENABLE_ANIM and eval 'OEKAKI_ENABLE_ANIM') {
+					my $pch_filename = eval 'oek_pch_filename($$row{image})';
+					die $@ if $@;
+					unlink $pch_filename;
+				}
+
 				$sth=$dbh->prepare("UPDATE ".SQL_TABLE." SET size=0,md5=null,thumbnail=null WHERE num=?;") or make_error(S_SQLFAIL);
 				$sth->execute($post) or make_error(S_SQLFAIL);
 			}
