2016/11/08

【アプリ紹介】ちょっと読書 このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク


今日は 2014/11/06 に公開した「ちょっと読書」の紹介です。



こんなアプリ

160人の文豪が、自分達の小説計 400 作品以上を自ら断片的につぶやく、ツイッター風の読書アプリです。

夏目漱石や森鴎外、宮沢賢治など有名どころは一通り揃っています(感じ方には個人差があります)。

気に入った一節があったらSNSなどでシェアすることもできます。



小説の最後までつぶやいたらまた最初に戻ります。文豪たちは毎日不定期につぶやくので偶然面白いサムシングと出会えるかもよ、というわけです。

作者のアイコンをタッチすると Wikipedia のページが開きます。作品一覧のページでは気になる作品をフォローすることができます。

てかそれツイッターbotでいいんじゃね?という気持ちも僅かばかりありますが、一応ですね、ちょっと読書ならネットに繋がってなくても読めるしわざわざフォローしなくても最初から読めるし、...まあとにかく、作ってみたかったんですよ。

開発の経緯とこれから

隙間時間で受動的に読めて意外な発見がある読書アプリが欲しかったのがきっかけです。

本を読み始める動機として、本屋をウロウロしてちょっと読んでみて良かったからとか友達からのおすすめとか書評ブログを見たとかいろいろあると思いますが、それ以外、例えば「なんとなく本文の一部が流れてきたけどなんか面白そう」みたいな仕組みが作れたら意外な本との素敵な出会いが待っているかもしれない、といったところです。

データは、権利的に問題ないやつということで青空文庫の作品のうち著作権が切れているものを使うことにしました。

で、作ってやってみた感想としては文脈を多少なりとも理解するには 140 字は足りないなというのと、あと古い文体の本が多くていまいち興味をそそられない、あたり。

今後の展開としては、もうちょっと最近の本とか雑誌、ブログとかが流れてくるようにできたら面白いんじゃないかなあと思っています。例えば RSS フィードと組み合わせるとか、カクヨムで活動している作家さんの作品宣伝ツールの 1 つとしてちょっと読書に流してもらうとか。

ランキングだけだといまいち未知との出会いがないし、かといって新着はすぐ流れていっちゃうから困るなあみたいな方がいて、なんかコラボできたら楽しいなあと思っています。

開発期間

2014/10/11(Sat)〜2014/10/15(Wed) の 5 日間

データ取得/整形とそこそこ凝った画面ということで少し手こずりました。


技術的な解説

まずはデータ取得/整形です。python スクリプトを書きました。青空文庫のランキングデータを DL して parse し、そこから図書カードを DL して parse し、そして作者の wikipedia へのリンクやアイコンを取得し、なんと XHTML の本文を DL して頑張ってタイトルや作者名を抽出します。

<meta name="DC.Title" content="坊っちゃん" />

だったり

<h1 class="title">詩集〈月に吠える〉全篇</h1>

だったりまちまちなので、"BE ADHOC!" なる勇ましい掛け声が脳内をこだまします。

さらに本文の読み仮名などのマークアップを消して、115字以内のきりがいいところでカットします。

// 115文字で切る.
// できるだけ句読点 (,.、。)で切るが, その結果50文字以下 or 116文字以上になってしまうようならやむなく115文字で切る.
// 改行が6つ出現したらそこで切る.

こんな感じのコメントが発掘されました。

そしたらマクロ呼出し形式でデータを出力します。以下のような感じです。


BEGIN_BOOK()
AUTHOR("ツルゲーネフ")
TITLE("はつ恋")
WIKIPEDIA_BOOK_URL("")
WIKIPEDIA_AUTHOR_URL("http://ja.wikipedia.org/wiki/%E7%A5%9E%E8%A5%BF%E6%B8%85")
AUTHOR_IMAGE("author_photo_0006.png")
PUBLISHER("青空文庫")
BEGIN_CONTENTS(782)
CONTENT("[#ページの左右中央]\n\n\nP・V・アンネンコフに捧げる\n\n")
:
:
これをこんな感じで include してデータ化します。

#define BEGIN_BOOK() { Book* book = Book.alloc.init; book.ID = (int)_books.size();
#define AUTHOR(s) book.authorName = @s;
#define TITLE(s) book.name = @s;
#define WIKIPEDIA_BOOK_URL(s) ss=@s; if(ss) book.wikipediaBookURL = [NSURL URLWithString:ss];
#define WIKIPEDIA_AUTHOR_URL(s) ss=@s; if(ss) book.wikipediaAuthorURL = [NSURL URLWithString:ss];
#define AUTHOR_IMAGE(s) book.authorIconURL = authorImage(@s);
#define PUBLISHER(s)
#define BEGIN_CONTENTS(n) book.contentsOffset = contentsOffset; book.contentsSize = n;
#define CONTENT(s)
#define END_CONTENTS() contentsOffset += book.contentsSize;
#define END_BOOK() /*cout<<"ct.size "<<book.contentsSize<<endl;*/ _books.push_back(book); }
#include "books.h"

ただし本文までこの形式で初期化したらたしかコンパイルがやたら遅くて困ったので本文だけは const char* theContents[] にすべて格納するようになっています。

そしたらデータが 150MB になってしまい大きすぎて困ったので、減らす仕組みも用意しました。データサイズは減らしたいけど収録する作品数は最大化したかったので、あるしきい値以下のサイズの作品だけ収録するようにして合計で指定したサイズ以下になるように二分探索してしきい値を決めるなどして、402 作品収録、データサイズ 21MB ほどに収めました。

各作者によるつぶやきのタイミングは、ランダムに見せかけて実はすべて決まっています。とはいえ明らかにパターンが分かってしまってはおもしろくないので 1 日 86400 秒のうち何秒目につぶやくかというつぶやきパターンを何種類も用意しておいてうまいこと不定期に見せかけるということをしています。実のところ、フォロー集合さえ決まれば、ある時刻における最新のタイムラインはすべて求まるようになっています。とするといろいろ実装が楽なのです。

あとはそれっぽい UI をいくつか作って繋ぎ合わせました。

無限スクロールが意外と手こずった気がします。UIScrollView の表示領域に加えて画面の高さ 5 個分の範囲内のつぶやきについてだけ UIView を配置するようにして、範囲に入ったら作成、外れたら破棄する制御をしているわけですが、新たに表示すべきデータが現れたとき、一度に全部を addSubview すると数ミリ秒かかってスクロールがカクつくし、かといって UI に関することなので別スレッドでやるわけにもいかず、結局 0.1 秒間に 3 回までしか addSubview しないようにしてヌルヌルスクロールを実現しました。


以上、「ちょっと読書」の紹介でした。