拡張子がvboxのファイルが見つからなくて起動できない場合

VirtualBoxで拡張子がvboxのファイル(仮想マシン名.vbox)が消えており、起動ができなくなっていた。

とりあえず、仮想マシン名.vbox-tmpというファイルがあったので、コピーして仮想マシン名.vboxにリネームしたら動くようになった。

SConsをちょっと便利なMakeとして使ってみる

makeでちょっと複雑なことをやろうとすると結構しんどくなってきたので、SConsを試してみることにした。

まだ全然使いこなせてないが、とりあえずmakeでできることをSConsではどのようにやるのかのメモ。

単純な例

次のようなMakefileを考えてみる。

all: piyo.txt

hoge.txt:
	echo "This is $@" > $@

fuga.txt:
	echo "This is $@" > $@

piyo.txt: hoge.txt fuga.txt
	cat $+ > $@

SConsではSConstructというファイルが、makeにおけるMakefileに相当する。SConstructはPythonスクリプトである。

上記のMakefileと同様の動きをするSConstructは以下のように書ける。

Command("hoge.txt", None, 'echo "This is $TARGET" > $TARGET')
Command("fuga.txt", None, 'echo "This is $TARGET" > $TARGET')
Command("piyo.txt", ["hoge.txt", "fuga.txt"], 'cat $SOURCES > $TARGET')

Commandに引数として、ターゲット、ソース、アクションを渡す。Makefileでターゲット、ソース、コマンドを書く順と同じなので覚えやすいと思う。
複数のファイルを指定したい時はリストを渡せば良い。

Makefileではソースやターゲットを示す変数として、$+、$<、$@などを用いるが、SConsでは$SOURCES、$SOURCE、$TARGETなどを用いる。どのようなものが使えるかはmanのVariable Substitutionセクションに書いてある。

コマンドライン

makeでmakeコマンドでビルドを実行するのと同様に、SConsでもSConstructのあるディレクトリでsconsというコマンドを叩けばビルドが実行される。

sconsのオプションはmakeと同じものが多く覚えるのは比較的楽だと思われる(-jや-nなど)。

clean

SConsではscons -cを実行すると、ビルドしたターゲットを削除してくれる。clean用のコマンドを書く必要はない。

debug

makeとsconsではどのファイルをビルドするかの決定基準が違っているので、下記のようにそのファイルをビルドする理由を表示させると理解が深まるかもしれない。

scons --debug=explain
依存関係の表示

treeオプションを使うと、依存関係をツリー状に表示できる。

scons --tree=all

ファイル名の操作

makeでは関数を使って、ファイル名やパスを扱うことができる。

all: piyo.txt

TXTDIR := txt

$(TXTDIR):
	mkdir -p $@

$(TXTDIR)/hoge.txt:| $(TXTDIR)
	echo abspath: $(abspath $@) > $@
	echo dir: $(dir $@) >> $@
	echo notdir: $(notdir $@) >> $@
	echo basename: $(basename $@) >> $@
	echo suffix: $(suffix $@) >> $@

FUGA_TXT_NAME := fuga.txt
FUGA_TXT := $(TXTDIR)/$(FUGA_TXT_NAME)
FUGA_TXT_ABSPATH := $(abspath $(FUGA_TXT))
FUGA_TXT_DIR := $(dir $(FUGA_TXT))
FUGA_TXT_NOTDIR := $(notdir $(FUGA_TXT))
FUGA_TXT_BASENAME := $(basename $(FUGA_TXT))
FUGA_TXT_SUFFIX := $(suffix $(FUGA_TXT))

$(FUGA_TXT):| $(TXTDIR)
	echo abspath: $(FUGA_TXT_ABSPATH) > $@
	echo dir: $(FUGA_TXT_DIR) >> $@
	echo notdir: $(FUGA_TXT_NOTDIR) >> $@
	echo basename: $(FUGA_TXT_BASENAME) >> $@
	echo suffix: $(FUGA_TXT_SUFFIX) >> $@

piyo.txt: $(TXTDIR)/hoge.txt $(FUGA_TXT)
	cat $+ > $@

上記のMakefileと同様の動作をするSConstructは次のように書ける。

os.pathモジュールを使うこともできるが、${TARGET.abspath}のような書き方もできる。この書き方についての説明もmanのVariable Substitutionにある。

import os.path

txtdir = "txt"

Command(os.path.join(txtdir, "hoge.txt"), None,
        [
            "echo abspath: ${TARGET.abspath} > $TARGET",
            "echo dir: ${TARGET.dir} >> $TARGET",
            "echo notdir: ${TARGET.file} >> $TARGET",
            "echo basename: ${TARGET.base} >> $TARGET",
            "echo suffix: ${TARGET.suffix} >> $TARGET"
        ])

fuga_txt_name = "fuga.txt"
fuga_txt = os.path.join(txtdir, fuga_txt_name)
fuga_txt_abspath = os.path.abspath(fuga_txt)
fuga_txt_dir = os.path.dirname(fuga_txt)
fuga_txt_notdir = os.path.basename(fuga_txt)
fuga_txt_basename = os.path.splitext(fuga_txt)[0]
fuga_txt_suffix = os.path.splitext(fuga_txt)[1]


Command(fuga_txt, None,
        """
            echo abspath: %(fuga_txt_abspath)s > $TARGET
            echo dir: %(fuga_txt_dir)s >> $TARGET
            echo notdir: %(fuga_txt_notdir)s >> $TARGET
            echo basename: %(fuga_txt_basename)s >> $TARGET
            echo suffix: %(fuga_txt_suffix)s >> $TARGET
        """ % locals()
       )

Command("piyo.txt", [os.path.join(txtdir, "hoge.txt"), fuga_txt],
        "cat $SOURCES > $TARGET")
ディレクトリの自動作成

上記の例ではhoge.txtとfuga.txtをtxtというディレクトリの中に作っている。makeの場合自分でtxtを作成する必要があるが、SConsではターゲットの置かれるディレクトリが自動で作成されるためディレクトリを作成するコマンドを書いたり、依存関係にディレクトリを含めたりする必要はない。

関数とか

makeでは関数をうまく使うと記述が簡単になることがある。

TXT_FILES := $(wildcard *.txt)
TXT_FILES_SHELL := $(shell echo *.txt)
PATSUBST := $(patsubst %.txt,%.TXT,$(TXT_FILES))
SUBST := $(subst World,Sekai,Hello World!)


all:
	echo TXT_FILES: $(TXT_FILES)
	echo TXT_FILES_SHELL: $(TXT_FILES_SHELL)
	echo PATSUBST: $(PATSUBST)
	echo SUBST: $(SUBST)

SConsでも同様のことはできるが、makeの関数のようにひとつのやり方があるわけでなく、Pythonの処理を組み合わせて実現している場合があるので、Pythonについて知らないとわかりにくいかもしれない。makeにある関数をSConsで実現するには、User's Guideの最後のHandling Common Tasksを参考にすると良い。

アクションの中に${}で囲まれたPythonのコードを書くと、実行結果に置き換えられる。

import os, re

env = DefaultEnvironment()

txt_files = [str(f) for f in Glob("*.txt")]
txt_files_shell = os.popen("echo *.txt").read()
patsubst = [os.path.splitext(f)[0] + ".TXT" for f in txt_files]
subst = re.sub("World", "Sekai", "Hello World!")

env["patsubst"] = patsubst

all = Command(".", None,
        """
            echo TXT_FILES: ${" ".join(%(txt_files)s)}
            echo TXT_FILES_SHELL: %(txt_files_shell)s
            echo PATSUBST: ${" ".join(patsubst)}
            echo SUBST: %(subst)s
        """ % locals())
AlwaysBuild(all)

参考にした文献

  1. http://www.scons.org/doc/HTML/scons-user/book1.html
  2. http://www.scons.org/doc/production/HTML/scons-man.html

とくに、User's Guideの最後のHandling Common Tasksやmanの最後の書き方の例は参考になる。

今回はMakefileと対応がとれるようにCommandのみを使ったが、Environmentを使って環境を使い分けたり、自分でビルダーを書いたり、オプションを使いなしたりすればより効率的な書き方ができるようになると思う。

Distance Matrix APIで遊んでみる

どこかへ遊びにいく時など、自分の家から目的地までの所要時間で行き先を選ぶことがある。Google mapsなどで目的地の候補を一つ一つ入力して所要時間を調べることはできるが候補が多くなるとちょっと面倒くさいし、前に検索した結果を忘れてしまって再度検索するなんてことをしていた。

Distance Matrix APIを使うと出発地と目的地を複数指定して検索をすることができる。

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

import json, urllib

DISTANCE_MATRIX_BASE_URL = "http://maps.googleapis.com/maps/api/distancematrix/json"

def distance_matrix(origins, destinations, sensor="false", **args):
    args.update({
            "origins": "|".join(origins),
            "destinations": "|".join(destinations),
            "sensor": sensor
            })
    url = DISTANCE_MATRIX_BASE_URL + "?" + urllib.urlencode(args)
    result = json.load(urllib.urlopen(url))

    for i, origin in enumerate(origins):
        for j, destination in enumerate(destinations):
            data = result["rows"][i]["elements"][j]
            if data["status"] == "OK":
                print origin, "から", destination
                print "距離", data["distance"]["text"]
                print "時間", data["duration"]["text"]
                print

if __name__ == '__main__':
    distance_matrix(["仙台", "福岡"], ["東京", "大阪"], mode="walking", language="ja")
仙台 から 東京
距離 343 km
時間 2日 22時間

仙台 から 大阪
距離 830 km
時間 6日 4時間

福岡 から 東京
距離 1,054 km
時間 6日 12時間

福岡 から 大阪
距離 578 km
時間 2日 13時間

本当は交通手段に鉄道や飛行機を使いたかったが、今のところはできないようだ。

複数の出力をもつプログラムのルールの記述にflockを使ってみる

昨日のGNU Parallelを使った書き方を試していて、make -jを実行して並列に実行しようとしているのに、実行を待っているタスクが多くなってしまうという問題があることがわかった。

そこで、どうせ同じコマンドを実行するだけなので、ロックされていたら終了するように変えてみることにした。semコマンドではタイムアウトがまだ実装されていないので、今回はflockコマンドを使って書いてみた。

data.c data.h data.w data.x: data.foo data.bar
    lockfile=/tmp/data.lock; touch $$lockfile; \
    flock -nx $$lockfile -c 'if [ -e $@ ] && [ $@ = `ls -td $@ $+ | head -1` ]; then :; \
    else \
        foo data.foo data.bar; \
    fi'; \
    result=$$?; if [ $$result -eq 1 ]; then exit 0; else exit $$result; fi

ロックされていた場合flock -nが1を返すので、その時はエラーを出さないように0を返すようにしてみた。fooが失敗したときに1を返すようになっていると区別がつかないので、その時は、fooコマンドの実行後に終了ステータスを確認して失敗時に1以外を返すようにするといいかもしれない。

複数の出力をもつプログラムのルールの記述にGNU Parallelを使ってみる

Makefileに複数の出力をもつプログラムのルールを記述する方法を調べてみたところ結構ややこしかった。

以下のリンクによると、

fooコマンドが入力としてdata.fooを受け取ってdata.cとdata.hを出力する場合、ルールを次のように書ければ簡単なのだが、

data.c data.h: data.foo
        foo data.foo

これは、次のように解釈され make -j で並列に実行するとfooが同時に2回実行されて失敗してしまうことがある。

data.c: data.foo
        foo data.foo
data.h: data.foo
        foo data.foo

リンク先にはこの問題を解決するためのやり方が幾つか書かれていて、最終的な完成形が以下の書き方だった。

data.stamp: data.foo data.bar
        @rm -f data.tmp
        @touch data.tmp
        foo data.foo data.bar
        @mv -f data.tmp $@

data.c data.h data.w data.x: data.stamp
## Recover from the removal of $@
        @if test -f $@; then :; else \
          trap 'rm -rf data.lock data.stamp' 1 2 13 15; \
## mkdir is a portable test-and-set
          if mkdir data.lock 2>/dev/null; then \
## This code is being executed by the first process.
            rm -f data.stamp; \
            $(MAKE) $(AM_MAKEFLAGS) data.stamp; \
            result=$$?; rm -rf data.lock; exit $$result; \
          else \
## This code is being executed by the follower processes.
## Wait until the first process is done.
            while test -d data.lock; do sleep 1; done; \
## Succeed if and only if the first process succeeded.
            test -f data.stamp; \
          fi; \
        fi

実際に欲しいファイル以外にdata.stampとdata.lockという2つのファイルとディレクトリを作っていてルールがややこしい。
また、ルールが2つになって依存とターゲットが別れてしまっている。

これを解決するためにGNU Parallelのsemaphore(semコマンド)を使って次のように書いてみた。

http://www.gnu.org/software/parallel/sem.html (semコマンドのドキュメント)

data.c data.h data.w data.x: data.foo data.bar
	sem --fg --id data_lock 'if [ -e $@ ] && [ $@ = `ls -td $@ $+ | head -1` ]; then :; \
                        else \
                            foo data.foo data.bar; \
                        fi'

依存とターゲット以外のファイルをルールに書かずに済み(代わりにidを書いているが)、1つのルールで済むようになった。

いまのところ、うまく動いているようなので、しばらく試してみようと思う。

VLCプレイヤーでYouTubeの動画を音声付き高速再生

今さらながら下記記事でVLCメディアプレイヤーでYouTubeの動画を直接見られることを知った。

http://www.lifehacker.jp/2010/07/100706_vlc_youtube.html

試してみたところ、音声付きで倍速再生もできた。ニュースや見たいシーンが一部しかない動画を見るときには便利だと思う。

やり方は、下記のどちらか。

  • メディア->ネットワークストリーム (Ctrl+N) を開いて動画のURLを入力する
  • 動画のURLをコピーしておいてメディア->クリップボードからURLを開く (Ctrl+V)

Dailymotionでも同じことができた。

追記 (2011-08-08)

先ほど試してみたらURLを読み込むとクラッシュするようになっていた。

下記リンクあたりを参考にすれば直りそうだが、まだうまくいっていない。

http://forum.videolan.org/viewtopic.php?f=14&t=92502

Luaスクリプトを書くといろいろできそう。後で調べてみよう。

追記 (2011-08-29)

youtube.luaを以下のリンクのものに置き換えたらみられるようになった。

http://git.videolan.org/?p=vlc.git;a=blob;f=share/lua/playlist/youtube.lua

UMPlayerでもYouTubeを高速再生できるが音が高くなってしまうのが気に入らなかったので、VLCがまた使えるようになってうれしい。

AutoHotkeyでVLCメディアプレイヤーを操作して、書き起こしを楽にする

講義等を録音したファイルを書き起こすことがあったのだが、VLCメディアプレイヤーを操作するためにウィンドウを切り替えるのが面倒だったので、AutoHotkeyから操作できるようにしてみた。

;; 再生・停止
^Space::
    SetTitleMatchMode, 2
    ControlSend, , {space}, VLCメディアプレイヤー
    Return

;; 再生速度を上げる (0.10x)
^]::
    SetTitleMatchMode, 2
    ControlSend, , ], VLCメディアプレイヤー
    Return

;; 再生速度を下げる (0.10x)
^[::
    SetTitleMatchMode, 2
    ControlSend, , [, VLCメディアプレイヤー
    Return

;; 3秒戻る
^Left::
    SetTitleMatchMode, 2
    ControlSend, , +{Left}, VLCメディアプレイヤー
    Return

;; 3秒進む
^Right::
    SetTitleMatchMode, 2
    ControlSend, , +{Right}, VLCメディアプレイヤー
    Return

;; 10秒戻る
!Left::
    SetTitleMatchMode, 2
    ControlSend, , !{Left}, VLCメディアプレイヤー
    Return

;; 10秒進む
!Right::
    SetTitleMatchMode, 2
    ControlSend, , !{Right}, VLCメディアプレイヤー
    Return

;; 音量を上げる
^Up::
    SetTitleMatchMode, 2
    ControlSend, , ^{Up}, VLCメディアプレイヤー
    Return

;; 音量を下げる
^Down::
    SetTitleMatchMode, 2
    ControlSend, , ^{Down}, VLCメディアプレイヤー
    Return