ブログをePub化してみた
ブログをePub化しようとしています。 過去に書いた記事等を、そのままePubに出来そうだったので、HTMLファイルを読み込んでePub化するPHPスクリプトを書いてみました。
まだ、全部うまくいっているわけではありませんし、色々問題が残っているのですが、いまのところこんな感じということで。 公開してみることにしました。 たとえば、次のような感じになります。
ePub版:[新連載]インターネット技術妄想論 [第1回] 結局、IPv6ってどうなのよ?! (参考:Web版)
基本的にURLで渡す引数で過去の記事をePub化していますが、過去のHTMLがいい加減なので、結構駄目です。 今後、ePubにも出来るHTMLを心がけながらブログを書くという感じですかね。
ePubはxhtmlファイルなどをZIPで固めたもので、mimetypeのファイルがZIPの先頭で無圧縮状態で格納されている必要があるというフォーマットです。 今回は、以下の情報を参考にしながらePubファイルを作ってみました。
- Open Publication Structure (OPS) 2.0 v0.9871.0(日本語訳版)
- .ZIP File Format Specification
- phpmyadminに含まれているzip.lib.php
サンプルコード
ブログをePub化するコードそのものは、かなりごっちゃりしてしまったので、概要部分をサンプルとしてまとめてみました。 PHPで書いてあります。 テキストファイルの圧縮は行っていませんが、gzcompressを利用すれば比較的簡単に行えます。
何かの参考になれば幸いです。
<?php
$articletitle = "HOGE";
$bookid = 'urn:uuid:geekpage.jp_blog_hoge';
$creator = 'あきみち';
$publisher = 'あきみち';
$htmlbody = ' <p>hoge</p>' . "\n";
$htmlbody .= ' <p>hoge </p>' . "\n";
$file = "";
$cent = "";
$filenum = 0;
$offset = 0;
header('Content-Type: application/epub+zip; name="hoge.epub"');
header('Content-Disposition: attachment; filename="hoge.epub"');
/////
add("application/epub+zip", "mimetype");
/////
$metainf = '<?xml version="1.0"?>' . "\n";
$metainf .= '<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">' . "\n";
$metainf .= ' <rootfiles>' . "\n";
$metainf .= ' <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>' . "\n";
$metainf .= ' </rootfiles>' . "\n";
$metainf .= '</container>' . "\n";
add($metainf, "META-INF/container.xml");
/////
$content = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$content .= '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookID" version="2.0">' . "\n";
$content .= ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">' . "\n";
$content .= ' <dc:creator opf:role="aut">' . $creator . '</dc:creator>' . "\n";
$content .= ' <dc:publisher>' . $publisher . '</dc:publisher>' . "\n";
$content .= ' <dc:language>ja</dc:language>' . "\n";
$content .= ' <dc:identifier id="BookID">' . $bookid . '</dc:identifier>' . "\n";
$content .= ' <dc:title>' . $articletitle . '</dc:title>' . "\n";
$content .= ' </metadata>' . "\n";
$content .= ' <manifest>' . "\n";
$content .= ' <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>' . "\n";
$content .= ' <item id="main.xhtml" href="Text/main.xhtml" media-type="application/xhtml+xml"/>' . "\n";
$content .= ' </manifest>' . "\n";
$content .= ' <spine toc="ncx">' . "\n";
$content .= ' <itemref idref="main.xhtml"/>' . "\n";
$content .= ' </spine>' . "\n";
$content .= '</package>' . "\n";
add($content, "OEBPS/content.opf");
/////
$toc = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$toc .= '<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" ' . "\n";
$toc .= ' "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">' . "\n";
$toc .= '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">' . "\n";
$toc .= ' <head>' . "\n";
$toc .= ' <meta name="dtb:uid" content="' . $bookid . '"/>' . "\n";
$toc .= ' <meta name="dtb:depth" content="0"/>' . "\n";
$toc .= ' <meta name="dtb:totalPageCount" content="0"/>' . "\n";
$toc .= ' <meta name="dtb:maxPageNumber" content="0"/>' . "\n";
$toc .= ' </head>' . "\n";
$toc .= ' <docTitle>' . "\n";
$toc .= ' <text>' . $articletitle . '</text>' . "\n";
$toc .= ' </docTitle>' . "\n";
$toc .= ' <navMap>' . "\n";
$toc .= ' <navPoint id="navPoint-1" playOrder="1">' . "\n";
$toc .= ' <navLabel>' . "\n";
$toc .= ' <text>start</text>' . "\n";
$toc .= ' </navLabel>' . "\n";
$toc .= ' <content src="Text/main.xhtml"/>' . "\n";
$toc .= ' </navPoint>' . "\n";
$toc .= ' </navMap>' . "\n";
$toc .= '</ncx>' . "\n";
add($toc, "OEBPS/toc.ncx");
/////
$main = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
$main .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"' . "\n";
$main .= ' "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' . "\n";
$main .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">' . "\n";
$main .= '<head>' . "\n";
$main .= ' <title>' . $articletitle . '</title>' . "\n";
$main .= '</head>' . "\n";
$main .= '<body>' . "\n";
$main .= $htmlbody;
$main .= '</body>' . "\n";
$main .= '</html>' . "\n";
add($main, "OEBPS/Text/main.xhtml");
//
// End of Central Directory
//
// end of central dir signature 4 bytes (0x06054b50)
$end = pack('V', 0x06054b50);
$end .= pack('v', 0); // number of this disk 2 bytes
// number of the disk with the start of
// the central directory 2 bytes
$end .= pack('v', 0);
// total number of entries in the central directory
// on this disk 2 bytes
$end .= pack('v', $filenum);
// total number of entries in the central directory 2 bytes
$end .= pack('v', $filenum);
// size of the central directory 4 bytes
$end .= pack('V', strlen($cent));
// offset of start of central directory with respect to
// the starting disk number 4 bytes
$end .= pack('V', strlen($file));
$end .= pack('v', 0); // .ZIP file comment length 2 bytes
//
// OUTPUT
//
echo $file;
echo $cent;
echo $end;
// fwrite(STDOUT, $file, strlen($file));
// fwrite(STDOUT, $cent, strlen($cent));
// fwrite(STDOUT, $end, strlen($end));
function add($data, $filename) {
global $file;
global $cent;
global $filenum;
global $offset;
$fnlen = strlen($filename);
$datalen = strlen($data);
// local file header signature 4 bytes(0x04034b50)
$file .= pack('V', 0x04034b50);
$file .= pack('v', 0x0014); //version needed to extract 2 bytes
$file .= pack('v', 0x0000);//general purpose bit flag 2 bytes
$file .= pack('v', 0x0000); // bcompression method 2 bytes
$file .= pack('v', 0x0000); // last mod file time 2 bytes
$file .= pack('v', 0x0000); // last mod file date 2 bytes
$file .= pack('V', crc32($data)); //crc-32 4 bytes
$file .= pack('V', $datalen); // compressed size 4 bytes
$file .= pack('V', $datalen); // uncompressed size 4 bytes
$file .= pack('v', $fnlen); //file name length 2 bytes
$file .= pack('v', 0x0000); //extra field length 2 bytes
$file .= $filename; //file name (variable size)
// if you want to compress the data, you can use gzcompress().
// when using gzcompress, don't for get to set compressed size
// value appropriately.
$file .= $data; // file data
$filenum++;
// central file header signature 4 bytes(0x02014b50)
$cent .= pack('V', 0x02014b50);
$cent .= pack('v', 0); // version made by 2 bytes
$cent .= pack('v', 0); // version needed to extract 2 bytes
$cent .= pack('v', 0); // general purpose bit flag 2 bytes
$cent .= pack('v', 0); // compression method 2 bytes
$cent .= pack('v', 0); // last mod file time 2 bytes
$cent .= pack('v', 0); // last mod file date 2 bytes
$cent .= pack('V', crc32($data)); // crc-32 4 bytes
$cent .= pack('V', $datalen); // compressed size 4 bytes
$cent .= pack('V', $datalen); // uncompressed size 4 bytes
$cent .= pack('v', $fnlen); // file name length 2 bytes
$cent .= pack('v', 0); // extra field length 2 bytes
$cent .= pack('v', 0); // file comment length 2 bytes
$cent .= pack('v', 0); // disk number start 2 bytes
$cent .= pack('v', 0); // internal file attributes 2 bytes
$cent .= pack('V', 0); // external file attributes 4 bytes
// relative offset of local header 4 bytes
$cent .= pack('V', $offset);
$cent .= $filename; //
$offset = strlen($file);
}
?>
メモ
以下、いくつかメモです。
- 私のサイトのHTMLが怪しいので、そのままePub化できない記事が多い
- Content-Typeだけではなく、Content-Dispositionを使ってダウンロード時のファイル名も指定した方が良い
- ePubバリデータ便利 http://threepress.org/document/epub-validate
- RSSとかPodcast URLでサイトでのePub更新を受け取れる方法をそのうち考える予定
- 個人的には「電子書籍」というよりもWebへの出力フォーマットの一つという位置づけ
- そのうち、各個別記事からePubフォーマットへのリンクを張る予定
- Amazon Kindleで読めるフォーマット用のスクリプトも作りたい
- この記事自身もePubで読めます
おまけ
というか、本当は原稿書いてないといけないんですけど。。。 現実逃避です。はい。
最近のエントリ
- 「ピアリング戦記」の英訳版EPUBを無料配布します!
- IPv4アドレス移転の売買価格推移および移転組織ランキング100
- 例示用IPv6アドレス 3fff::/20 が新たに追加
- ShowNet 2024のL2L3
- ShowNet 2024 ローカル5G
- ShowNetのローカル5G企画(2022年、2023年)
過去記事