2006年01月13日
Yahoo Blogの画像付きエントリーをMovableTypeインポート形式に出力
新年お蔵入りPHPスクリプト集そのいち。
Yahoo Blog(とlivedoor Blog)の記事と画像を、このサイトに移転するのに作ったもので、二度と使うことはない筈なので、記念カキコ。
エントリーをMovableTypeのインポート形式に書き出すだけならさして面倒ではないわけですが、自分の場合、Yahoo Blogは(「2GBのディスク容量」という謳い文句にダマされて)写真置き場にしていたので、かなりの数の画像があり、それらをアップロードし直して、サムネイルを作って、本文のリンクを修正して、という気の遠くなる作業はやりたくねえや、ということで、その辺りを自動化するために作ったのがこれ。
注意点とか
- allow_url_fopenがONで、GD2が利用可能なPHPが必要です。
- エントリーの日付は、HTMLソース中に埋め込まれたRDF(RSS 1.0)データのdc:dateの値(W3C DTF形式)を、PHPのstrtotime()関数で整形していまが、PHP4のstrtotime()関数は、+9:00などのように、タイムゾーン指定のあるW3C DTF形式を正しく解釈しないようなので、PHP5が必要です(あるいは適宜改造してください)。
- これはあくまで、自分のYahoo Blogからこのサイトへの移転用に作ったもので、決して汎用のものではありません。
少なくともこのままでは、Yahoo Blogで画像をアップロードする際に、「サムネイルを表示する」にして「javascript:wiki_img_view()」リンクでオリジナル画像を表示するパターンのエントリーにしか使えませんし、そもそもJPEG以外には対応してません。もちろん、Yahoo BlogのHTMLテンプレート変更になっちゃたら動作しないし。
もっとも、単純なスクリプトなので改造は簡単だと思います。実際自分も、これから改造してlivedoor Blog版を作ってるし。 - エントリーを手動で編集することはできますが、編集後のエントリーをあらためてプレビューし直す機能はありません。編集後の見栄えが心配な場合は、onChangeハンドラなどでプレビュー部分が変化するクライアントサイドスクリプトとかを追加しとくといい感じだと思われ。
ウェブブラウザでスクリプトにアクセスして表示される画面で、「Next Entry」欄にエントリーのURLを入力して送信すると、そのエントリーのHTMLソースを読み込み、本文中にあるJPEG画像のオリジナル(最拡大版)を保存し、そのサムネイルを作成し、リンクなどを書き換えた本文、題名、日付、及び「次の」エントリーのURLを入力フォームと、現状でのプレビューを表示します。プレビューを確認し、必要に応じてフォームの各フィールドに編集を加えた上で、「Export Current Entry」をチェックして送信すると、MovableTypeのインポート形式ファイルを出力し、「次の」エントリーに移動します。以下繰り返し。
JPEG画像のファイル名は、日付+同日のエントリーの連番+同一エントリー内の画像の連番で、サムネイルには「-thumb」が付きます。
文章にすると判りづらいな、これ。つうかぶっちゃけ作ってから随分経つので、かなり忘れてるし。
<?php
// --------------------------------------------------------------------
// 設定
// --------------------------------------------------------------------
// 出力する「MovableTypeの読み込みフォーマット」ファイル名(拡張子は任意)
define('EXPORT_FILE', './exports.txt');
// EXPORT_FILEの文字コード
define('PUBLISH_CHARSET', 'UTF-8');
// Yahooブログの文字コード(通常はEUC-JP)
define('ORIGINAL_CHARSET', 'EUC-JP');
// JPEGファイルを保存するローカルパス
define('LOCAL_IMAGE_STORE', './contents/images/');
// LOCAL_IMAGE_STOREのURL(本文に反映されるのでhttpからのフルパス推奨)
define('REMOTE_IMAGE_STORE', 'http://www.rcdtokyo.com/ucb/contents/images/');
// プレビュー用のCSSファイルのURL
define('CSS_URL', 'http://www.rcdtokyo.com/ucb/styles-site.css');
// 画像のサムネイルの横幅(ピクセル単位)
define('THUMB_IMAGE_WIDTH', 160);
// --------------------------------------------------------------------
// メイン
// --------------------------------------------------------------------
$message = null;
$next = null;
$title = null;
$date = null;
$entry_date = null;
$url = null;
$body = null;
$node_img = array();
// 書き込み指定のチェックボックスがチェックされていれば
if (isset($_POST['p']) and $_POST['p'] == 'on') {
// EXPORT_FILEに同じ内容があれば書き込みをせず
// 書き込み済みである旨のメッセージを用意する
if ($fetched = @file_get_contents(EXPORT_FILE)
and mb_strpos($fetched, $_POST['t'])
and mb_strpos($fetched, $_POST['ed'])
and mb_strpos($fetched, $_POST['b'])) {
$message = 'The entry seems to have exported already.';
} else {
// EXPORT_FILEに書き込む内容
$export =
<<<EXPORT
AUTHOR:
TITLE: {$_POST['t']}
STATUS: Publish
ALLOW COMMENTS: 1
CONVERT BREAKS: 0
ALLOW PINGS: 1
PRIMARY CATEGORY:
CATEGORY:
DATE: {$_POST['ed']}
-----
BODY:
{$_POST['b']}
-----
EXTENDED BODY:
-----
EXCERPT:
-----
KEYWORDS:
-----
--------
EXPORT;
// 以上をEXPORT_FILEの末尾に追加する
$handle = fopen(EXPORT_FILE, 'a');
fwrite($handle, $export);
fclose($handle);
}
}
// URLの指定がなければなにもしない
if (isset($_POST['u']) and $_POST['u'] !== null) {
// 指定されたURLのエントリーのHTMLソースを取得し文字コードを変換
$fetched = file_get_contents($_POST['u']);
mb_convert_variables(PUBLISH_CHARSET, ORIGINAL_CHARSET, $fetched);
// 次のエントリーのURLを抽出
if (preg_match('/^.*<a\shref="(.+?)">次の記事へ<\/a>.*$/si', $fetched, $matches)) {
$next = 'http://blogs.yahoo.co.jp'.preg_replace('/(.+\.html)\??.*$/si', '$1', $matches[1]);
}
// DC:TITLEから現在のエントリーのタイトルを抽出
if (preg_match('/^.*\sdc:title="(.+?)"\s.*$/si', $fetched, $matches)) {
$title = $matches[1];
}
// DC:DATEから現在のエントリーの日付を抽出(画像ファイル名の生成用YY/MM/DD形式)
if (preg_match('/^.*\sdc:date="(\d{2}(\d{2})-(\d{2})-(\d{2})T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2})"\s.*$/si', $fetched, $matches)) {
$date = $matches[2].$matches[3].$matches[4];
$entry_date = date('m/d/Y h:i:s A', strtotime($matches[1]));
}
// 直前に処理したエントリーと同日のエントリーなら日付連番を加算
if ($_POST['d'] !== null and $_POST['s'] !== null and $_POST['d'] == $date) {
$serialnum = (int) $_POST['s'] +1;
} else {
$serialnum = 1;
}
// 日付連番が1桁なら先頭に0を加える
if (preg_match('/^\d$/', $serialnum)) {
$serial = '0'.$serialnum;
} else {
$serial = $serialnum;
}
// 現在のエントリーの本文を抽出
if (preg_match('/^.*<tr>\s*?<td style="padding:5 0 10 0;line-height:180%;word-break:break-all;line-height:normal">\s*?<p align="justify">(.+?)<\/p>\s*?<\/td>\s*?<\/tr>.*$/si', $fetched, $matches)) {
$body = $matches[1];
}
unset($fetched);
// 現在のエントリーのIMG要素を抽出
if (preg_match_all('/<p align=center><a href="javascript:wiki_img_view\(\'(http:\/\/img\.blogs\.yahoo.co\.jp\/.+?)\'\)"><img\s[^>]+?><\/a><\/p>/si', $body, $matches)) {
for ($i = 0; $i < count($matches[0]); $i++) {
// ファイル連番(1桁なら先頭に0を加える)
$count = $i +1;
if (preg_match('/^\d$/', $count)) {
$count = '0'.$count;
}
$imagefile = $date.$serial.$count;
// 画像をLOCAL_IMAGE_STOREに保存
if (!file_exists(LOCAL_IMAGE_STORE.$imagefile.'.jpg')
and $fetched = file_get_contents($matches[1][$i])) {
$handle = fopen(LOCAL_IMAGE_STORE.$imagefile.'.jpg', 'w');
fwrite($handle, $fetched);
fclose($handle);
unset($fetched);
}
// 保存した画像ファイルからサムネイルを生成
if (!file_exists(LOCAL_IMAGE_STORE.$imagefile.'-thumb.jpg')
and $size = getImageSize(LOCAL_IMAGE_STORE.$imagefile.'.jpg')) {
$thumb_height = round($size[1] * THUMB_IMAGE_WIDTH / $size[0]);
$original = imageCreateFromJPEG(LOCAL_IMAGE_STORE.$imagefile.'.jpg');
$new = imageCreateTrueColor(THUMB_IMAGE_WIDTH, $thumb_height);
imageCopyResampled($new, $original, 0, 0, 0, 0, THUMB_IMAGE_WIDTH, $thumb_height, $size[0], $size[1]);
imageJPEG($new, LOCAL_IMAGE_STORE.$imagefile.'-thumb.jpg', 75);
} elseif ($size = getImageSize(LOCAL_IMAGE_STORE.$imagefile.'-thumb.jpg')) {
$thumb_height = $size[1];
}
if (file_exists(LOCAL_IMAGE_STORE.$imagefile.'.jpg')
and file_exists(LOCAL_IMAGE_STORE.$imagefile.'-thumb.jpg')) {
// IMG要素の置換
$body = str_replace($matches[0][$i], null, $body);
$node_img[$i] = '<a href="'.
REMOTE_IMAGE_STORE.$imagefile.'.jpg"><img src="'.
REMOTE_IMAGE_STORE.$imagefile.'-thumb.jpg" width="'.
THUMB_IMAGE_WIDTH.'" height="'.
$thumb_height.'" /></a>';
} else {
$message = 'File creation seems to have been failed.';
}
}
}
// エントリー本文の整形
$body = str_replace('<A ', '<a ', $body);
$body = str_replace(' HREF=', ' href=', $body);
$body = preg_replace('/\starget="?_blank"?/si', null, $body);
$body = preg_replace('/\[(<a\s[^>]+?>).+?<\/a>\s(.+?)\]/si', '$1$2</a>', $body);
$body = preg_replace('/<div class=\'wiki\'>\s*(.+?)\s*<\/div>\s*/si', '$1', $body);
$body = preg_replace('/(?:<br \/>){2,}\s*/si', '</p>
<p>', $body);
$body = '<p>'.$body.'</p>';
$body = preg_replace('/<br \/><\/p>/si', '</p>', $body);
if ($node_img) {
foreach ($node_img as $value) {
$body .= '
'.$value;
}
}
}
// --------------------------------------------------------------------
// 以下HTML出力
// --------------------------------------------------------------------
header('Content-Type:text/html; charset='.PUBLISH_CHARSET);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta http-equiv="content-style-type" content="text/css" />
<link rel="stylesheet" href="<?php echo CSS_URL ?>" type="text/css" />
<style type="text/css">
input[type="text"], textarea {width:100%}
textarea {height:10em}
</style>
<body>
<div id="container">
<div class="content">
<?php echo $message? '<em>'.$message.'</em>': null ?>
<!--入力フォーム-->
<form action="" method="post">
<input type="hidden" name="d" value="<?php echo $date ?>" />
<input type="hidden" name="s" value="<?php echo $serialnum ?>" />
<h3><label for="next">Next Entry</label></h3>
<p><input id="next" type="text" name="u" value="<?php echo $next ?>" /></p>
<h3><label for="title">Current Entry Title</label></h3>
<p><input id="title" type="text" name="t" value="<?php echo $title ?>"></p>
<h3><label for="body">Current Entry Body</label></h3>
<p><textarea id="body" name="b"><?php echo htmlspecialchars($body) ?></textarea></p>
<h3><label for="date">Current Entry Date</label></h3>
<p><input id="date" type="text" name="ed" value="<?php echo $entry_date ?>"></p>
<p><input id="export" type="checkbox" name="p" <?php echo ($body and $entry_date)? 'checked': null ?>/>
<label for="export">Export Current Entry</label>
<input type="submit" /></p>
</form>
<!--プレビュー-->
<?php echo $title? '<h2><a href="'.$_POST['u'].'" target="_blank">'.$title.'</a></h2>': null ?>
<?php echo $body ?>
</div>
</div>
</body>
</html>
お約束
- このプログラムはGNU GPLでライセンスされます。著作権は放棄されていませんが、なんの保証もありません。利用や改造はGNU GPLの定めに従って、自前のリスクでご自由に。お蔵入りにするくらいなので、ちゃんと検証してたりするわけもなく、あれこれ不具合がある筈ですが、なんらかの不利益を被っても一切関知しません。
- ヤフー株式会社と、このプログラム及びその作者とはなんの関係もありませんよ。
Category: ウェブ制作
Posted 2006年01月13日 23:06
トラックバック
このエントリーのトラックバックURL:
http://www.rcdtokyo.com/mt/mt-rcdtokyo5428-tb.cgi/697