届いたノートパソコンが悲しかったのでメモ

Thinkpad E420が届いたのだけれど、液晶に汚れ(ドット欠けじゃなく埃でも入ってるみたい)があり、さらにキーボードの右下隅が浮いていてペコペコと間抜けな音をたてるので到着早々なんだけど、修理の旅に出てもらうことにした。
先代のThinkpad Edge14も右下隅が浮いてる症状で入院した記憶がある。Thinkpadといえばキーボードのタッチと赤いポッチなわけで、ここで手を抜いてもらうと困ってしまう。僕は赤いポッチには興味が無いのでこれはなくなってもいい。でもキーボードだけは…。
幸いというかなんというか、Edge14からタッチは向上してると思う。素性はいいんだ。キーボードのフィーリング。エラーが出るわけでもない些細な問題と現場が思っているなら、悲しい事だなぁ。


謎の汚れ


右下隅を押すとわずかに沈む

StormのTopologyをグラフにして見る(手抜き

StormのTopologyBuilderの設計はいまいち好きじゃないんですが(idで指定なのと、setBolt・setSpoutが分かれているところ)、それはともかく、できあがったTopologyはグラフにしないとパッと理解できないので、Pythonで適当に書いてみました。

    builder = TopologyBuilder()
    builder.setSpout(1, Spout())
    builder.setBolt(2, Bolt()).allGrouping(0)
    builder.graphviz_format()
digraph topology {
1 [shape=box, label="1@Spout x(default)"];
2 [label="2@Bolt x(default)"];
1 -> 2 [label="all"];
}

    class TestWordSpout(Spout):
        pass
    class ExclamationBolt(Bolt):
        pass
    builder = TopologyBuilder()        
    builder.setSpout(1, TestWordSpout(), 10)        
    builder.setBolt(2, ExclamationBolt(), 3).shuffleGrouping(1)
    builder.setBolt(3, ExclamationBolt(), 2).shuffleGrouping(2)
    builder.graphviz_format()
digraph topology {
1 [shape=box, label="1@TestWordSpout x10"];
2 [label="2@ExclamationBolt x3"];
3 [label="3@ExclamationBolt x2"];
1 -> 2 [label="shuffle"];
2 -> 3 [label="shuffle"];
}

    class RandomSentenceSpout(Spout):
        pass
    class SplitSentence(Bolt):
        pass
    class WordCount(Bolt):
        pass
    builder = TopologyBuilder()
    builder.setSpout(1, RandomSentenceSpout(), 5)        
    builder.setBolt(2, SplitSentence(), 8).shuffleGrouping(1)
    builder.setBolt(3, WordCount(), 12).fieldsGrouping(2, Fields("word"))
    builder.graphviz_format()
digraph topology {
1 [shape=box, label="1@RandomSentenceSpout x5"];
2 [label="2@SplitSentence x8"];
3 [label="3@WordCount x12"];
1 -> 2 [label="shuffle"];
2 -> 3 [label="fields[word]"];
}

だいぶわかりやすいですね。

TopologyBuilderは

TopologyBuilder builder = new TopologyBuilder();
        
builder.setSpout(1, new RandomSentenceSpout(), 5);        
builder.setBolt(2, new SplitSentence(), 8)
        .shuffleGrouping(1);
builder.setBolt(3, new WordCount(), 12)
        .fieldsGrouping(2, new Fields("word"));

より

TopologyBuilder builder = new TopologyBuilder();
builder.set("random_sentence_spout", 
        new Node(new RandomSentenceSpout()).setPallalelismHint(5));
builder.set("split_sentence", 
        new Node(new SplitSentence()).setPallalelismHint(8).shuffleGrouping("random_sentence_spout")) ; 
builder.set("word_count", 
        new Node(new WordCount()).setPallalelismHint(12).fieldGrouping("split_sentence", new Fields("word"))) ; 

とかのほうがわかりやすいような気がしますが、僕はもう少し抽象的な定義からTopologyを生成して使おうとしているので、出来上がりさえ検証できれば問題ないですね。

コード

InputDeclarerのstreamIdは使わなそうなので実装していません。

Apple IDを変更した

AppleIDはエイリアスを使ってid+alias@gmail.comのかたちで登録していた。これが非常にややこしい。出先でApple関連のサービスにログインするとき、意味のわからないエイリアスを入力することを毎回のように強要され発狂しそうになった。誰だよエイリアスとか考えた天才!遅れてきた中ニ病と相性抜群じゃねーか!

方法

Apple IDの編集」→「名前、ID、メールアドレス」にて「代替メールアドレス」を登録する
登録したアドレスにメールが飛んでくるのでクリックして認証する
同じく「Apple IDの編集」→「名前、ID、メールアドレス」にて「AppleIDと主要メールアドレス」を編集する
編集したアドレスにメールが飛んでくるのでクリックして認証する
完了!

後始末

PCのiTunesにて、ログアウト→新しいIDでログイン
iPhone/iPod touchにてiCloudのログアウトができない?ので削除(これが実質ログアウト?)→新しいIDでログイン
iPhone/iPod touchにてStoreでログアウト→新しいIDでログイン


以上。これで問題なく使えてます。

かるーい気持ちでAndroidに移行しようとして泣いた話

僕はpdfやtxtファイルをDropboxに入れて、iPhoneのGoodReaderでフォルダを同期、ファイルを閲覧していました。このGoodReaderが神アプリで、

  • Dropboxとのフォルダ同期
  • pdf閲覧
  • txt閲覧
  • 他アプリに投げる

と、1アプリで僕のやりたいことができる、欠かせないアプリなわけです。毎日が充実していました。

…しかしその安穏とした日々にも転機が訪れます。ホワイトプラン二年縛りの終了です。iOSの対抗馬と目されてるAndroidでこの環境を果たしてどうすれば構築できるのか。かるーい気持ちでauにNMPして思い知ることになりました。

結果

Dropboxとのフォルダ同期

  Dropbox同期関係、マトモなソフトなし!皆無!
  Dropbox - Android マーケット https://market.android.com/details?id=com.dropbox.android&feature=related_apps
    同期に対応せず
  Dropsync - Android マーケット https://market.android.com/details?id=com.ttxapps.dropsync&feature=search_result
    ファイルサイズ制限あり。途中で止まる…
  Titanium Media Sync - Android マーケット https://market.android.com/details?id=com.keramidas.MediaSync
    有料。同期は完璧に動作する。しかし、Dropbox->Androidは、毎回全てのファイルをダウンロードする仕様みたい。開発者に質問メールを投げたけど返ってこない…

pdf閲覧

  公式アプリあり
  Adobe® Reader® - Android マーケット https://market.android.com/details?id=com.adobe.reader&feature=search_result
    なかなか重いですが、いちおう動きます。
  ezPDF Reader - Android マーケット https://market.android.com/details?id=udk.android.reader&hl=ja
    有料。いいらしいです。試そうかどうか悩んでいるところです。

txt閲覧

  …ほとんどのソフトが3MBも読めない。何を考えて実装(ry
  Androidのテキスト閲覧ソフトの比較 - たていすのメモ http://d.hatena.ne.jp/tateisu/20100822/1282445343
    読めるだけ。かなり使いづらい…

他アプリに投げる

  普通のファイラならイケます。ファイラはいろいろあって充実してます。


ううう〜〜ん??
Xperia acroを手に呆然としています。なう。

Flaskをさわってみる

軽量なWebフレームワークが使えたら便利な場面があったので、最近ちょこちょこ名前を聞くようになったFlaskをテスト。
軽くさわっただけですが、かなり好印象を受けました。使えそうな雰囲気です。

成果物

あまり良いサンプルではないですが、ゲームのスクリプトと音声ファイルから、あるキャラクターをずーっと喋らせるというもの。真紅ー



サーバ側はこれだけです。

# -*- coding:utf-8 -*-

__author__="rubyu"
__date__ ="$2011/09/14 11:25:52$"

import unittest
import logging
logging.basicConfig(
    level=logging.DEBUG,
    format="%(levelname)-8s %(module)-16s %(funcName)-16s@%(lineno)d - %(message)s"
)

import os
import sys
import re
import json

from flask \
    import Flask, make_response, render_template, request
app = Flask(__name__)

from sekai.voice_parser import Voice
from sekai.script_parser import Script
from config import Config

voice = None
script = None

@app.route("/")
def show_root():
    return render_template("index.html")

@app.route("/query/json")
def query_json():
    u"""
    真紅の発言について、キーワードが含まれるものだけを返す。
    
    jsonで、以下の形式で。
    [ [発言, ボイスID], ... ]
    """
    keyword = request.args.get("keyword", "")
    logging.debug("keyword: %s", keyword)
    arr = []
    for text in script.texts:
        str, name, vid = text
        if name == u"真紅" and \
           -1 != str.find(keyword):
            str = html_ruby(str)
            arr.append((str, vid))
    logging.debug("size: %s", len(arr))
    response = make_response()
    response.data = json.dumps(arr)
    response.headers["Content-Type"] = "application/json"
    return response

@app.route("/voice/ogg/<voice_id>")
def output_voice(voice_id):
    u"""
    指定のIDのボイスを切り出して返す。
    形式はもとのoggのまま。
    """
    logging.debug("voice id: %s", voice_id)
    response = make_response()
    response.data = voice.get(voice_id)
    response.headers["Content-Type"] = "audio/ogg"
    response.headers["Content-Disposition"] = "attachment; filename=%s.ogg" % voice_id
    return response

ruby_pat = re.compile("\[(.+?)\|(.+?)\]")
def html_ruby(str):
    u"""
    ...[ルビ|テキスト]...の形式を、HTMLのルビタグに変換する。
    複数個含まれる場合は全て変換する。
    """
    return ruby_pat.sub(r"<ruby>\2<rt>\1</rt></ruby>", str)


class ProcWrapper(object):
    u"""
    コマンドラインから呼び出されるラッパ。
    テスト時はダミーに差し替えられる。
    """
    def app_run(self, voice_path, script_path):
        global voice
        global script
        try:
            voice = Voice.restore("voice.p")
        except Exception, e:
            logging.debug("Excepted: %s", e)
            voice = Voice(voice_path)
            voice.save("voice.p")
        try:
            script = Script.restore("script.p")
        except Exception, e:
            logging.debug("Excepted: %s", e)
            script = Script(script_path)
            script.save("script.p")
        app.run()
            

class OptionParserTestCase(unittest.TestCase):
    u"""
    コマンドラインオプションのテスト。
    """
    class DummyProcWrapper(object):
        u"""
        ダミーのラッパクラス。
        コールされた関数のログを持つ。
        """
        def __init__(self):
            self._log = []

        def __getattr__(self, name):
            def _dummy(self, *args, **kwargs):
                pass
            self._log.append(name)
            return _dummy    
        
        
    @classmethod
    def setUpClass(cls):
        cls._path = "/tmp/sekai"
        cls._argv = sys.argv
    
    @classmethod
    def terDownClass(cls):
        sys.argv = cls._argv
        
    def setUp(self):
        self._wrapper = self.DummyProcWrapper()
        sys.argv = [self._argv[0]]
        
    def test_path(self):
        u"""
        引数pathが与えられた場合。
        """
        sys.argv.append("--path=%s" % self._path)
        parse(self._wrapper)
        self.assertEqual(["app_run"], self._wrapper._log)
        

def parser():
    u"""
    OptionParserのインスタンスを返す。
    """
    from optparse import OptionParser
    p = OptionParser("usage: shinku_player.py --path=game_installed")
    p.add_option(
        "-p", 
        "--path",
        type="string",
        help="directory that the game installed"
    )
    return p

def parse(wrapper):
    u"""
    コマンドラインオプションで分岐し、ラッパをコールする。
    """
    p = parser()
    options, args = p.parse_args()
    
    if options.path:
        config = Config()
        config.path = options.path
        config.save("config.p")
    
    try:
        config = Config.restore("config.p")
    except Exception, e:
        logging.debug("Excepted: %s", e)
        p.error("Program has not enough data. The 'path' parameter never had been given to this program!")
        sys.exit()
        
    voice_path = os.path.join(config.path, "voice.bin")
    script_path = os.path.join(config.path, "World.hcb")
    if not os.path.isdir(config.path) or \
       not os.path.isfile(voice_path) or \
       not os.path.isfile(script_path):
        p.error("File not found. 'voice.bin' and 'World.hcb' must be in the 'path=%s'!" % config.path)
        sys.exit()
    
    app.debug = True
    wrapper.app_run(voice_path, script_path)
    
if __name__ == "__main__":
    debug = False
#    debug = True
    
    if debug:
        unittest.main()
    else:
        parse(ProcWrapper())

Firefox4に移行したので例の如く多段タブ表示を最適化するCSSを書いた

Chromeと比べるとメモリ食べないのでいっぱいタブ開けるのはいいんだけど、標準のままだと数百タブも並べるのは厳しい。こうでもしないとやってられない!


TabMix Plusでの多段タブ設定用。

ホットピクセルシミュレータを作った

数日前の出来事

液晶モニタをぽちっ → 思ってたよりずっとよいクオリティ → ウキウキしながらDVD再生 → 黒帯の部分で何かが光っている… → イヤァアアアアアアアアアアア!!!!(絶叫

この絶望を気軽にご家庭で味わって頂くためにホットピクセルシミュレータを作りました。

8時間掛かった。

こんな感じドットを画面上の好きな位置に表示します。

  • 色( R, G, B )を指定する

か、あるいは

  • ホットピクセル( R | G | B | RG | RB | GB | RGB )を指定する

ことができます。
前者は指定の色で固定なので忠実ではありません。プロなら一発で見破られるでしょう。
後者はホットピクセルをある程度まで再現します。近傍のピクセルの色を取得して更新するので、初見で見破るのは難しいでしょう。ただ、更新の遅れによる違和感はあるので、これもプロなら見破られるはずです。

まとめ

ホットピクセルは赤とか青とか目に痛いので、これ白にしたら違和感が減るんじゃ?という実験のために作りはじめた。
気がついたらホットピクセルシミュレータになっていた。
白にすると若干違和感は減る。ただ、明るくなって目立つのも確かなので、なんともいえないなぁという感じ。

注意

  • テストもないですし、重いです。
  • 本ツールを使用した結果、いかなる問題が発生したとしても僕は責任を取れません。
  • お父さん自慢の大画面テレビにPCを繋いで…とかはやめてあげてください。心臓が止まる可能性があります。