iTunes向けオライリー本からepubを取り出すスクリプトApp2Epub.js

先日、Kindle用にmobiファイルを生成するスクリプトを作りましたが、
その過程で、epubファイルも生成されたりします。
で、たぶんepubファイルがほしいだけの人もいるんじゃないかと思って、
toc.ncxをいじらずにそのまま引き抜くスクリプトを作ってみました。
こっちのほうがもしかして需要があるかもしれません。
Windows用。

導入手順

  1. App2Epub.jsをダウンロード(右クリックして「リンク先を保存」で保存してね。)

以上。
ただダウンロードするだけです。

利用手順

  1. iTunesから目的のオライリーアプリをApp2Epub.jsにドラッグ&ドロップする。
  2. しばし待つ。
  3. epubファイルが出来上がる。

以上。
余計な機能がないのでいいかも。

目次作成について

そういえば、先のApp2EpubAndMobi.jsには、目次作成機能はないです。
id:shunsuk様が目次作成スクリプトrubyで作成されたようですので、もしかしたらボチボチやるかも。
やらないかも。

今回の収穫

先のApp2EpubAndMobi.jsを少し手直しすればいいかと思ったら、うまく動かない。
最後の最後、一時フォルダの削除に失敗するんですね。
コメントアウトしただけなのに…
で、いろいろやっていくうちに、

Windows Scripting Hostでは、どうやらGarbageCollectionが起きた時に初めてShell.Applicationの不要なフォルダロックが解除される

ような気がしてきました。
つまり

GCかからないうちは、Shell.Applicationがフォルダをつかんだままになっていて、そのフォルダを削除できない

と。
で、強制GCかけたら、ビンゴでした。
これはわからない。
フォルダ削除できない時点でGC走るなんて芸当はもちろんやるはずないですよね。
というわけで、
先のApp2EpubAndMobi.jsにもこの点反映させています(他の些末なバグの解消もこっそりしている)。

今回のスクリプトさらし

恥ずかしながら、さらすの巻。

if (WScript.Arguments.Length <= 0) {
	WScript.Echo('ファイルを指定してください。');
	WScript.Quit();
}

// extractZipHere(Zipファイル名)
var extractZipHere = function(fileInput) {
	var fso = new ActiveXObject('Scripting.FileSystemObject');
	var folderParent = fso.GetParentFolderName(fileInput);
	var folderExtract = fso.BuildPath(folderParent, fso.GetTempName());
	if (!fso.FolderExists(folderExtract)) {
		fso.CreateFolder(folderExtract);
	}
	var shell = new ActiveXObject('Shell.Application');
	shell.NameSpace(folderExtract).CopyHere(shell.NameSpace(fileInput).Items());
	return folderExtract;
}

// createZip(作成先のZipファイル名、元のフォルダ名)
var createZip = function(fileZip, folderSrc) {
	var fso = new ActiveXObject('Scripting.FileSystemObject');
	var shell = new ActiveXObject('Shell.Application');
	fso.CreateTextFile(fileZip);
	var ofileZip = fso.OpenTextFile(fileZip, 2); // 2 : for Write
	ofileZip.Write('PK' + String.fromCharCode(5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0));
	ofileZip.Close();
	var ofolderZip = shell.NameSpace(fileZip);
	var ofolderSrc = shell.NameSpace(folderSrc);
	var scount = ofolderSrc.Items().count;
	ofolderZip.CopyHere(ofolderSrc.Items());
	while (ofolderZip.items().count != scount ) {
		WScript.Sleep(100);
	}
}

//必要なオブジェクトの生成
var shell = new ActiveXObject('Shell.Application');
var wshell = new ActiveXObject('WScript.Shell');
var fso = new ActiveXObject('Scripting.FileSystemObject');

//指定されたファイルが.ipaファイルか確認
var fileOriginal = WScript.Arguments.Item(0);
if (fso.GetExtensionName(fileOriginal) != 'ipa') {
	WScript.Echo('iPhoneアプリをドラッグ&ドロップしてください。');
	WScript.Quit();
}

//スクリプトフォルダを取得
var folderScript = String(WScript.ScriptFullName).replace(WScript.ScriptName, '');

//オリジナルファイルを一時フォルダにコピー
var folderTmp = fso.GetSpecialFolder(2);
var fileInput = fso.BuildPath(folderTmp, fso.GetTempName() + '.zip' );
fso.CopyFile(fileOriginal, fileInput);

//zipファイルを展開
var folderExtract = extractZipHere(fileInput);

//appフォルダなどの取得
var folderPayload = fso.BuildPath(folderExtract, 'Payload');

//PayLoadフォルダ内の.appファイルのファイル名を取得する(1個しかないはず…)
var fileApp = shell.NameSpace(folderPayload).Items().Item(0).Name;
var folderApp = fso.BuildPath(folderPayload, fileApp);
var folderBook = fso.BuildPath(folderApp, 'book');
var folderOEBPS = fso.BuildPath(folderBook, 'OEBPS');

//.appフォルダ内をZipに固めて.epubファイルにリネーム
var fileZip = fso.BuildPath(folderScript, fso.GetBaseName(fileApp) + '.zip');

createZip(fileZip, folderBook);

var fileEpub = fso.BuildPath(folderScript, fso.GetBaseName(fileApp) + '.epub');
if (fso.FileExists(fileEpub)) {
	fso.DeleteFile(fileEpub);
}
fso.MoveFile(fileZip, fileEpub);

//アトシマツ 一時ファイルを削除
this.CollectGarbage(); //Shell.Applicationがフォルダを離さないことがあるので念のためGC
fso.DeleteFile(fileInput);
fso.DeleteFolder(folderExtract);

WScript.Echo('終了しました。');

↑CollectGarbage()しないと、削除できないんですフォルダ。
先のスクリプトではうまくGCが動いて、Shell.Applicationがフォルダを開放していたようなんです。
非常に絶妙なタイミングでうまく動いていたんだなぁ。