わいえむねっと

Contents
Categories
Calendar
2026/11
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Monthly Archives
~2000/01
Recent Entries
RSS1.0
Templates
Information
Processed: 0.051 sec
Chashed: -
2011/04/10 Sun
インターホンが鳴って起床。
ドアスコープを覗いたら猫のマークの帽子が見えたのでドアを開ける。
明日到着予定の荷物が前倒しで到着。

しばらくしてまたインターホンが鳴って。
ドアスコープを覗いたら女性が2人見えたので放置。
はい。エホバですね。 昼にカレー。
カレー缶を2缶買ってきて、夜もカレー。
千切りキャベツ山盛りのカレー。
というか、先週は月曜から金曜まで毎晩カレー。
カレー週間。
2011/04/09 Sat
曇天をぼけと眺めながら日本酒をニ合。
焼鳥を食べながら。
今日はなんだかちっとも酔わないし、焼鳥もうまくない。
2011/04/08 Fri
今週はずっと眠くてだるくて、風邪以前に疲れてるのかなと思っていたのですが、
そういえばと、就寝前に服用していた薬を止めてみました。

そうしたらどうです。今日はだるくないじゃないですか!

はい。
ザジテンです。

これは平日の服用は厳しい。

て言ってたんですが、余らせたまま花粉シーズンをやり過ごすのもなんだし、通常の服用量の

成人(15才以上)1回1カプセル、1日2回朝食後及び就寝前に服用してください。

の半分だけでもと、今週は就寝前だけ服用してみていたんですよ。

半分でも駄目か!
あと、道理で日が傾いたあたりから意識がはっきりしてくるわけだよ!(だるいのは継続してたけど) 週の最後に勘弁してくださいよ。
携帯の電源を切る。
2011/04/07 Thu
絶賛進捗停滞中。今日も。
隣のチームのモジュール更新待ちになると猛烈に眠い。


一昨日風邪をひいたと言っていた人が昨日は休んでいたのですが、今日は出社していました。
そして派手に咳き込んでいました。
明日あたり風邪が拡散しているんじゃなかろうかとかそんな気が。
自分もまだ調子悪いです。
2011/04/06 Wed
隣のチームからもらった最新モジュールがデプロイにこける状態のため絶賛進捗停滞中。

昨日からもめていた件についてはなんとかなりそうです。
原因が判明した時には隣のチームの実際の担当は既に帰宅していたので、作業の再開はまた明日ということで。
自分も帰ろう。

というか、多少持ち直したものの体調は依然としてよろしくないのです。
なので、帰りにコンビニに寄って栄養ドリンクを買って帰ろうと思っていたのです。
なのですが、駅前スーパーの営業時間が通常に戻ったらしく、開いていたのです。
開いていたので、ふらふらと吸い込まれてしまったのです。

気が付いたら玉ねぎとじゃがいもをぶら下げて帰宅していました。
なんでだ。
2011/04/05 Tue
別アプリケーションの打ち合わせに呼ばれていってみたりとか、
DB2のホストで一騒動あったので首を突っ込んできてみたりとか、
隣の隣の島の関係ないチームの人と話してきたりとか、
なんでも屋みたいになってる人にお願い事をしにいったりとか、
本来の仕事したりとか。


どう見ても花粉症なのに花粉症じゃないと言い張り続けていた人が今日はマスクをつけていたので「ついに認めたんですか?」と聞いたら「いや、風邪」と返ってきたのでまたまたーと思っていたのですが、気が付いたら自分も体の節々が痛くなったり寒気がしたりしてきました。
うつったのかこれ。
とりあえず早めのパブロン。
2011/04/04 Mon
久し振りにコートなしで出社。
マフラーくらいしていくかとも思ったけれど、それもなしで。

帰りは多少寒かったですが、そんなに寒いわけでもなく。
しばらくこれくらいの気候が続いてくれたらなと思う程度。
2011/04/03 Sun
「オライリーのebookから空白を除去……」に関する補遺(2) バイナリパッチによる空白の除去 - ただのにっき(2011-03-30)
http://sho.tdiary​.net/20110330.h​tml
一方、面白い手法の紹介もあった。わいえむねっと - 2011/03/30で紹介されているのは、PDFファイルの「CropBox」指定を直接書き換える方法。バイナリパッチだ! これはいいな(笑)。

ただただし氏に先日のスクリプトを紹介していただいてから、ちょっとした反響がありました。
せっかくなので、

あー、いや、メタ情報の問題が残ってるか。ううむ。

メタデータについても考えてみました。


PDF の仕様について


PDF は元のファイル内容を保持したまま、ファイル末尾に更新内容を追加することでファイル内容を編集することが可能なフォーマットになっています。
今回購入したオライリーの Ebook に限っていえば、そもそもメタデータを持っていないので、単純にメタデータのオブジェクトを追加してやればよさそうです。

なお、更新内容を追加するには、オブジェクト以外に

  • クロスリファレンステーブル
  • ファイルトレーラ

が必要になります。


暗号化ついて


オライリーの Ebook は 40-bit RC4 で暗号化されています。(パスワードは空ですが)
追加するオブジェクトも暗号化する必要があります。

暗号化の手順については、

PDF protection - SCHOOL OF COMPUTER SCIENCE, Carnegie Mellon
http://www.cs.cmu​.edu/~dst/Adobe/Gallery/anon21jul0​1-pdf-encryption​.txt

を参考にしました。


格納する情報について


Amazon API で取得しようかと思ったら、

JSON形式による書誌情報の提供をはじめました - O'Reilly Japan Community Blog
http://www.oreill​y.co.jp/community/blog/2010/11/bibliograp​hical-info-in-json.html
以前は手作業でファイル名を変更していたのですが、100タイトルを超えた辺りで心が折れました。Amazon APIを使って書名を取得すればいいことは分かっていたのですが、発行元がそれをしてしまったら負けな気がします。

そこでWebサイトの書誌情報を以下のようなURLで取得できるように、テンプレートを1つ追加しました。ファイル名をご覧いただくと分かるように、簡単な書誌情報をJSON形式でご提供しています。

オライリー自身が提供してくれていたので利用してみました。


スクリプト


例によって Perl です。

use strict;
use warnings;
use Crypt::RC4;
use Digest::MD5;
use Encode;
use JSON;
use LWP::UserAgent;

undef $/;

my $pass = '';
my $url = 'http://www.oreilly.co.jp/books/%s/biblio.json';

my $ua = LWP::UserAgent->new;

while(<*.pdf>)
{
    next unless /-(\d{13})/;
    my $bookinfo = get_bookinfo($1);

    open my $in, $_ or die;
    open my $out, '>', $bookinfo->{title}.'.pdf' or die;
    binmode $in;
    binmode $out;

    $_ = <$in>;
    my $len = length;

    my($owner, $perm, $trailer, $prev);
    $owner   = $1 while /\/O\s*\((.+?)\)/gs;
    $perm    = $1 while /\/P\s+(-?\d+)/g;
    $trailer = $1 while /(trailer\n<<.+?>>)/gs;
    $prev    = $1 while /startxref\n(\d+)/g;
    die unless $trailer;
    die unless $prev;
    die unless $owner;
    die unless $perm;

    $trailer =~ /<(.+?)>/;
    my $id = $1;
    $trailer =~ /\/Size (\d+)/;
    my $info = $1;

    my $filekey = generate_filekey($pass, $owner, $perm, $id);
    my $key = generate_key($filekey, $info, 0);

    my $title  = RC4($key, $bookinfo->{original});
    my $author = RC4($key, $bookinfo->{authors});
    escape($title);
    escape($author);

    my $obj = <<EOS;
$info 0 obj
<<
/Title ($title)
/Author ($author)
>>
endobj
EOS

    my $size = $info + 1;
    $trailer =~ s/(\/Size\s+)\d+/$1$size/;
    $trailer =~ s/(>>)/\/Prev $prev \/Info $info 0 R $1/;

    my $offset = sprintf '%010d', $len;
    my $startxref = $len + length $obj;
    my $xref = <<EOS;
xref
$info 1
$offset 00000 n

$trailer
startxref
$startxref
%%EOF
EOS

    print $out $_;
    print $out $obj;
    print $out $xref;
}

sub get_bookinfo
{
    my $req = HTTP::Request->new(GET => sprintf $url, $1);
    my $res = $ua->request($req);
    $res->is_success or die $res->status_line;

    my $bookinfo = decode_json $res->content;

    exists $bookinfo->{title} or die;
    exists $bookinfo->{authors} or die;
    exists $bookinfo->{original} or die;

    $bookinfo->{title} =~ s/[:\/\\]/ /g;
    $bookinfo->{title} =~ s/\s+/ /g;
    utf8::encode $bookinfo->{title};
    Encode::from_to $bookinfo->{title}, 'utf8', 'shiftjis';

    my @authors;
    foreach(@{$bookinfo->{authors}})
    {
        foreach(split /\,/, $_)
        {
            next unless /[a-zA-Z]/;
            s/[^\x20-\x7E]//g;
            s/^\s*(.*?)\s*$/$1/;
            push @authors, $_;
        }
    }
    $bookinfo->{authors} = join ', ', @authors;

    $bookinfo->{original} =~ s/[^\x20-\x7E]+//g;

    return $bookinfo;
}

sub generate_filekey
{
    my $pad = pack 'H*',
        '28BF4E5E4E758A4164004E56FFFA0108'.
        '2E2E00B6D0683E802F0CA9FE6453697A';

    my $md5 = Digest::MD5->new;
    $md5->add(substr $_[0].$pad, 0, 32);
    $md5->add($_[1]);
    $md5->add(pack 'V', $_[2]);
    $md5->add(pack 'H*', $_[3]);
    substr $md5->digest, 0, 5;
}

sub generate_key
{
    my $md5 = Digest::MD5->new;
    $md5->add($_[0]);
    $md5->add(substr pack('V', $_[1]), 0, 3);
    $md5->add(substr pack('V', $_[2]), 0, 2);
    substr $md5->digest, 0, 10;
}

sub escape
{
    $_[0] =~ s/\\/\\\\/g;
    $_[0] =~ s/\n/\\n/g;
    $_[0] =~ s/\r/\\r/g;
    $_[0] =~ s/\t/\\t/g;
    $_[0] =~ s/\f/\\f/g;
    $_[0] =~ s/\(/\\(/g;
    $_[0] =~ s/\)/\\)/g;
}

おおまかな処理の流れ

  1. ファイル名からISBNを抽出し、書籍情報を取得
  2. ファイルから更新内容の追加に必要な情報を抽出
  3. メタデータオブジェクトを作成して、暗号化した書籍情報を格納
  4. クロスリファレンステーブルとファイルトレーラを作成
  5. 書籍名でファイルを複製
  6. メタデータを追加


実行結果




できました!


既知の問題とか制限事項とか


あくまで、「今回自分が購入した6冊が Adobe Reader に怒られない」ようにした程度の実装なので、いろいろいい加減です。

  • 既存のメタデータがある場合を考慮していない
  • /O や /P の検索がいい加減
  • /Encrypt で指定したオブジェクトから取得するようにするべき
  • 追加データの /ID を更新していない
  • 暗号化時に「Adobe Reader が誤認しなさそうな結果が得られるまで、シードを変えて繰り返す」とかいう日和ったことをしている
  • 「あんまりだ」と言われたので、エスケープ処理を追加
  • 全角文字は非対応
  • データ構成が別物になるようなので。なんでだ。

などなど。


雑感


さすがに前回ほど簡潔にはなりませんでした。
書籍情報取得のあたりは含めないほうがよかったかなと後悔。

あと、DRM の解除をしているわけではありませんが、パスワードが分かっていることを前提としているので、行為的には DRM の解除と大して変わらないです。微妙。
まぁ、こんなこともできなくはない的な実験として。
Comments
> 書籍情報の取得を切り出すなど、ちょと修正。 -- 2011/04/05 00:28:43
2011/04/02 Sat
新入社員の歓迎会があったとかで社長から呼び出されていってきてみたら、「歓迎会があった」後のことで新入社員とかは特に関係ない食事会でした。なんなの。
2011/04/01 Fri
新年度ですか。

人が猛烈に少ないのは、昨日までが契約期間だった人が軒並みはけたからなのか、昨日が年度末ゆえに昨年度の鬱憤が発散されてきた結果ゆえかは神のみぞ知るところかと思いつつ、仕事をしたり仕事とは関係のないコードを書いてみたり。