リアルタイム動画配信サービス「ツイキャス」の録画をしてくれるシェルスクリプトを書いてみました。…と言っても、ffmpeg の入力ソースに食わせるだけですので、スクリプトというよりは例によって自分の覚え書きのようなものになっています。
使い方は、実行権限を付与して
./twitcast.sh ツイキャスアカウント名
という具合です。配信中であれば ffmpeg による録画が開始されます。
なお、パスワード付き配信は試していませんが未対応だと思います。
リアルタイム動画配信サービス「ツイキャス」の録画をしてくれるシェルスクリプトを書いてみました。…と言っても、ffmpeg の入力ソースに食わせるだけですので、スクリプトというよりは例によって自分の覚え書きのようなものになっています。
使い方は、実行権限を付与して
./twitcast.sh ツイキャスアカウント名
という具合です。配信中であれば ffmpeg による録画が開始されます。
なお、パスワード付き配信は試していませんが未対応だと思います。
先日の呑み会で Web フレームワークの話が出て、「サーバ挙動からまとめて書けたら便利だよね」という話の流れから久々に sinatra で遊んでみました。
下記、参考にしたページです。
sinatra は簡単に目的の Web アプリケーションが書けるというのが最大のメリットですが、かならずしも「初心者にも簡単」というわけではありません。 それぞれ、何のために何をやっているのかを十分に理解しておかないと変なところで詰まづくと思われます。
かくいう自分も、仕事でも趣味でも Web アプリケーションを書く機会に恵まれず、Rails すらまともに使ったことがない程度の知識。 実は数年前に nginx で Ruby を使ってちょっとしたツールを書こうと思った際に sinatra で一通り動くものを作ったことがありました。数年経つとほとんど忘れていて怖いです。
そんな自分のための覚書です。
よく使う構成はテンプレート化してどこかに置いておけばいいんですが、やるたびに忘れているので毎回ゼロから構築してしまいます。かえって手間をかけてしまう…。
app/
+- assets/ … CSS や JavaScript のコードが格納されているディレクトリ。
+- models/
| +- user.rb … MongoDB に格納してもらうオブジェクトを定義してる User クラス
+- spec/
+- vender/
| +- bundle/ … 利用する gem 置き場
+- views/
+- Gemfile … 利用する gem 定義
+- app.rb … Web アプリ本体
+- config.ru … Web アプリの定義ファイル
+- mongoid.yml … Web アプリが利用する MongoDB の定義ファイル
カレントディレクトリを app ディレクトリにしておいて、次のコマンドを使えばいろいろとできます。
bundle init # … このディレクトリを bundle アプリとして設定する
# bundle とは依存 gem 一式をローカルに配置して用いる仕組み
bundle exec shotgun # … サーバを起動
# shotgun はサーバを停止しなくても、スクリプトファイルの
# 更新時に自動リロードしてくれる便利な gem
bundle install --path vender/bundle
# … Web アプリに必要な gem ファイルを vender/bundle にインストール
# Gemfile に明示的に書いたものをインストールしてくれる
使うものを使うだけ定義。ローカルの sinatra を使おうとしたら、バグがあったので最新リビジョンのやつを取ってきています。
# A sample Gemfile
source "https://rubygems.org"
#gem "sass"
gem "haml"
gem "bcrypt"
#gem "coffee-script"
gem "mongoid"
gem "shotgun"
gem 'sinatra', git: 'git@github.com:sinatra/sinatra.git',
ref: '5f6168bfc92280892e819df524d4508cf9032f6d'
# for https://github.com/sinatra/sinatra/issues/961
group :test do
gem 'rspec'
end
root = ::File.dirname(__FILE__)
require ::File.join(root, 'app')
run Server
MongoDB をインストール後、使う DB やホスト名を指定します。
development:
sessions:
default:
database: sinatora_test
hosts:
- localhost:27017
production:
sessions:
default:
uri: <%= ENV['MONGOHQ_URL'] %>
ログイン前に表示されるページのテンプレート。
%fieldset
%legend ログイン
%form{:action => '/session', :method => 'post'}
%label{:for => "name"} 名前:
%input{:name => "name", :type => "text", :value => ""}
%label{:for => "password"} パスワード:
%input{:name => "password", :type => "password", :value => ""}
%input{:type => "submit", :value => "ログイン"}
%fieldset
%legend 新規登録
%form{:action => '/regist', :method => 'post'}
%label{:for => "name"} 名前:
%input{:name => "name", :type => "text", :value => ""}
%label{:for => "password"} パスワード:
%input{:name => "password", :type => "password", :value => ""}
%input{:type => "submit", :value => "新規登録"}
ログイン後に表示されるページのテンプレート。
%h1 Dashboard
%table.table
%tbody
%tr
%td Name
%td= @user.name
%tr
%td Password(hashed)
%td= @user.password_hash
%a(href="/logout") ログアウト
User クラス
require 'mongoid'
require 'bcrypt'
Mongoid.load!('./mongoid.yml')
class User
include Mongoid::Document
field :name
field :password_hash
field :password_salt
attr_readonly :password_hash, :password_salt
validates :name, presence: true
validates :name, uniqueness: true
validates :password_hash, confirmation: true
validates :password_hash, presence: true
validates :password_salt, presence: true
# パスワードを暗号化するメソッド
def encrypt_password(password)
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
# ユーザーが存在するか
def self.has_user(name)
self.where(name: name).first != nil
end
# ユーザー認証
def self.authenticate(name, password)
user = self.where(name: name).first
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
end
メインスクリプト
require 'sinatra/base'
require 'haml'
require_relative 'models/user'
class Server < Sinatra::Base
enable :sessions
set :session_secret, "My session secret"
get '/login' do
session[:user_id] ||= nil
if session[:user_id]
redirect '/'
end
if params['failed'] == "1"
@msg = "ログインに失敗しました。"
elsif params['failed'] == "2"
@msg = "登録に失敗しました。"
end
haml :login
end
get '/logout' do
session[:user_id] = nil
redirect '/login'
end
post '/session' do
if session[:user_id]
redirect "/"
end
user = User.authenticate(params[:name], params[:password])
if user
session[:user_id] = user._id
redirect '/'
else
redirect "/login?failed=1"
end
end
post '/regist' do
if User.has_user(params[:name])
redirect "/login?failed=2"
else
user = User.new(name: params[:name])
user.encrypt_password(params[:password])
if user.save!
session[:user_id] = user._id
redirect "/"
else
redirect "/login?failed=2"
end
end
end
get '/' do
@user = User.where(_id: session[:user_id]).first
if @user
haml :dashboard
else
redirect '/login'
end
end
end
sinatra や MongoDB が悪いわけではなく、自分の頭が悪いだけなんですが、ひっぱってくるものが多すぎて普段使わないと思い出すだけで頭が痛くなります…(´・ω・`) 「sinatra」「bundle」「Rack」「haml」「slim」「erb」「mongodb」…普段全然使ってないものをひっぱってこようとすると、バックエンドまでちゃんと知らないとまともに使えないのはどの環境でも同じことですが…。 普段から使い始めて、自由自在に使えるようになったらこういう手順も楽チンなんだろうなあと憧れるばかりです。
とりあえず、仕事や趣味のプロジェクトでも比較的絡めやすそうな MongoDB の取り扱いから慣れていこうと考えてます。
去年の11月、フレームバッファに直接値を書き込んでプログラムからお絵描きがするような、手軽なプログラミングをやってみたい人向けに LCD 風ディスプレイシミュレータを書きました。
ある通常ファイルを /dev/fb0
のようなデバイスファイルに見立て、ファイルを開いてシークして値を書き込むことによって、疑似画面上にドットを打つことができます。
Gist に Base64 エンコードした display.zip を置いておきました。Linux や Mac OS の場合は、base64
コマンドに -d
オプションをつけることによって、元のバイナリにデコードできます。Windows の場合は、Lhaplus などのアーカイバでバイナリに戻せます。
display.exe はコマンドラインからオプション付きで実行します。
display 横ドット数 縦ドット数 1ドットあたりの大きさ 中間ファイルパス
次のように実行します。
display 64 24 5 fb0.bin
上の例では、解像度 64×24 ドットのグラフィック LCD ディスプレイとしてシミュレートし、1ドットあたりの表示ピクセル数は5、ファイルに fb0.bin を用いて起動しています。
プログラム側から、
の一連の作業を行います。アニメーションさせる場合は、1 から 3 を sleep()
を入れて繰りかえします。
次のサンプルでは、ファイル fb0.bin を中間ファイルとして display.exe から監視させています。
このサンプルを実行しながら display.exe で表示を行うと、次のような絵が表示されて、波がアニメーションします。
ufw コマンドは、複雑な設定を要する iptables を直接操作することなく、簡単な操作だけでファイアウォールの設定を行うことができる便利なコマンドです。 以下、簡単な操作のはずなのに、設定するたびに忘れる私のダメな脳味噌のための備忘録です。
ポートを許可する、制限する
ufw allow 1234
ufw allow 1234/TCP
ufw denny 1234
ファイアウォールを有効、無効にする
ufw enable
ufw disable
現在の設定状況を調べる
ufw status
※ ちなみに、設定方法から設定例まで man ページに丁寧に書いてあります。
今月いよいよ30歳になることもあり、身の周りを綺麗にしておこうと、年が明けてから週末はもっぱら家の掃除をしています。引っ越してからずっと開けていなかった段ボールを開いてみると懐しいものがたくさん出てきました。 (断捨離的側面から考えると、そういう段ボールは開封せずにそのまま捨てた方がいいのかもしれませんが…)
特に中学から高校にかけて、自分のパソコンのバックアップやデータ移動のために焼いた CD-R やフロッピーディスクには、その当時やっていたこと・興味を持っていたことがたくさん詰まっています。
その中に、ちょうど1年前の今日他界した祖父の写真データが3枚だけありました。 大正9年生まれの祖父は、特攻で有名な鹿児島県の知覧飛行場の目と鼻の先にある川辺中学校を卒業後、江田島の海軍兵学校に第70期生として入学しました。
写真は岩国海軍航空隊で航空術教練中の祖父(主翼上中央)です。江田島からだと岩国よりも呉海軍航空隊の方が近いですが、予科練生やその他実習生の教練はもっぱら岩国の方だったようです。飛行機はあんまり詳しくないのですが、写っている機体はどうも九九式艦上爆撃機のようです。生前の祖父から聞いた話では「赤トンボ(九三式中間練習機)に乗って飛んだ」という話ですので、写真撮影用に主力の艦爆と一緒に撮影したのかもしれません。
次の写真は、航空母艦「隼鷹」の第一士官次室記念撮影です。前列左から5番目が祖父で、部下の皆さんと昭和20年の正月に撮ったようです。隼鷹の飛行甲板上で撮影したと思われる写真です。祖父は海軍兵学校卒業後、潜水母艦「長鯨」で洋上任務についた後、「隼鷹」の後部機銃座の指揮(ケップガン)をやっていました。
昭和19年の暮れ、戦艦「武蔵」の生存者200名を乗せた隼鷹は、戦艦「榛名」、駆逐艦「槇」とともに台湾から日本へ帰投する途中、12月9日の夜に米潜水艦の攻撃を受けて船体と機関部を損傷しています。祖父から「浸水によるすさまじい傾斜だったが、なんとか佐世保まで持ち堪えた」と聞いています。時系列に関しては、Wikipedia に詳しい記事が載っています。
なお、後列左から2番目に上を見上げている人がいますが、祖父曰く「正月で酒を飲んだ後の撮影だったから、酔っぱらってふざけているんだろう」とのことでした。
最後の写真は、駆逐艦「楠」の准士官以上記念撮影です。これは終戦直後の昭和20年〜21年ごろ撮られた写真で、祖父は前列左から2番目です。復員輸送に従事していた際に「楠」の副長をしていました。その向かって右隣が艦長、さらにその右の顎髭を蓄えた方は軍医だと聞いています。なお、最終的な階級は大尉だったようです。
大量に保存した画像ファイルなどの重複潰しも兼ねて、ファイル名を MD5 ハッシュ値にリネームして保存しています。
まったく同一のファイルであれば、ハッシュ値が同一になりますので、ファイル名が同一になります。上のスクリプト「hashmv」ではリネーム時に同一ファイルを上書きしますので、結果、重複ファイルを除去することができます。
実行イメージ
$ hashmv foobar.jpg
foobar.jpg -> ./d41d8cd98f00b204e9800998ecf8427e.jpg
hashmv では、32 文字のハッシュ値のそのままファイル名に変換しているので、ちょっと長いなと思いました。md5sum
のオプションを見ても、もうちょっとコンパクトな出力を得るオプションが見当たらなかったので、車輪の再発明上等でシェルスクリプトを書いてみました。
#!bash
function compress_hash()
{
echo $2 | sed \
-e 's/aaa*/A/g' \
-e 's/abb*/B/g' \
-e 's/acc*/C/g' \
-e 's/add*/D/g' \
-e 's/aee*/E/g' \
-e 's/aff*/F/g' \
-e 's/baa*/G/g' -e 's/ecc*/g/g' \
-e 's/bbb*/H/g' -e 's/edd*/h/g' \
-e 's/bcc*/I/g' -e 's/eee*/i/g' \
-e 's/bdd*/J/g' -e 's/eff*/j/g' \
-e 's/bee*/K/g' -e 's/faa*/k/g' \
-e 's/bff*/L/g' -e 's/fbb*/l/g' \
-e 's/caa*/N/g' -e 's/fcc*/n/g' \
-e 's/cbb*/M/g' -e 's/fdd*/m/g' \
-e 's/ccc*/O/g' -e 's/fee*/o/g' \
-e 's/cdd*/P/g' -e 's/fff*/p/g' \
-e 's/cee*/Q/g' -e 's/0[0-9][0-9]*/q/g' \
-e 's/cff*/R/g' -e 's/1[0-9][0-9]*/r/g' \
-e 's/daa*/S/g' -e 's/2[0-9][0-9]*/s/g' \
-e 's/dbb*/T/g' -e 's/3[0-9][0-9]*/t/g' \
-e 's/dcc*/U/g' -e 's/4[0-9][0-9]*/u/g' \
-e 's/ddd*/V/g' -e 's/5[0-9][0-9]*/v/g' \
-e 's/dee*/W/g' -e 's/6[0-9][0-9]*/w/g' \
-e 's/dff*/X/g' -e 's/7[0-9][0-9]*/x/g' \
-e 's/eaa*/Y/g' -e 's/8[0-9][0-9]*/y/g' \
-e 's/ebb*/Z/g' -e 's/9[0-9][0-9]*/z/g' \
| cut -c -$1
}
for hashstr in $@
do
compress_hash 6 $hashstr
done
元のハッシュ値は [a-f0-9]
の文字集合ですが、パターンマッチングによって [a-fA-Z0-9]
の文字集合に置き替え、桁数を落としています。見たとおり、ある一定のパターンの繰り返しを単一文字に置き換えているので、上のスクリプトでは元ハッシュ値に対する可逆性はありません。さらに、破壊的に圧縮したハッシュ値をcut
で任意の桁数に切り落としています。
次に、評価用のコードを示します。
while :
do
hash=$(date +%s | md5sum | cut -f 1 -d ' ')
compress_hash 6 $hash
sleep 1
done
1秒おきに、UNIX タイム文字列を変換して印字する評価コードです。実行すると
dFc7wa
vqA5cx
rc1a1d
ucufyV
1d2b8c
qm9Le4
6sqc5c
atavIr
7bqc5r
3auf7b
....
のように、6 桁の圧縮されたハッシュ値が出てきます。
短縮 URL などの用途に代表されるように、URL セーフな文字列で桁数を抑えつつもある程度のパターン数は稼ぎたい場合に有効かもしれません。なお、元のハッシュ値は基数が a-z0-f
の 16 ですが、これは a-zA-Z0-9
の 62 となる為、6 桁でも約 568 億パターンも稼ぐことが可能です。
# 62^10 = 839299365868340224 (約84京パターン)
# 62^9 = 13537086546263552 (約1.3京パターン)
# 62^8 = 218340105584896 (約218兆パターン)
# 62^7 = 3521614606208 (約3.5兆パターン)
# 62^6 = 56800235584 (約568億パターン)
# 62^5 = 916132832 (約9億パターン)
# 62^4 = 14776336 (約1477万パターン)
6 桁なら長くなく、覚えられない長さではないし、1/568億の確率で衝突するくらいの強さは持ってますので、ファイル管理には十分だと思いました。
なお、a-zA-Z0-9
の文字集合に加えて記号 /+=
を許可する場合は、上のようなスクリプトではなくハッシュ値をそのまま base64 エンコードした方が手っ取り早いです。
1970年代の中盤に開発された極小インタプリタ言語 VTL (Very Tiny Language) の基本的な演算構文を7行で書いてみました。
VTL に関しては、以下に詳しい記述があります。
一つめのリンクで公開されている実装とほぼ同じ挙動をするものを C# で書いてみましたが、こういう小さな実装はポインタをガリガリ動かしながら式を解釈していくのが面白そうだと思い、C で七行プログラミングに挑戦してみました。
結果からいうと、自分の力ではプログラミングモードや GOTO
を7行内に実装することができませんでした。次のコードは、ダイレクトモードで VTL の基本演算処理を行うものです(これは一応7行)。
※ 七行プログラミングということもあり、エラーチェックなどはほぼ省いてあります。変な値を入力するとすぐにセグフォ吐いて死にますので、自己責任で実行してください。
gcc -o vtl7 vtl7.c
./vtl7
本家 VTL は実装の小ささが売りです。手元の環境の 32 bit Debian + GCC 4.7.2 で -Os をつけて strip したところ、3.7 キロバイトでした。うちの子はそこまで小さくないけど、昨今の ELF 形式ならこんなもんかな…?
実行すると、対話型シェル(というほどたいそうなものではありませんが…)が起動します。
OK
* A=123
OK
* B=2
OK
* ?=A*B+4
250
OK
*
上の例では、変数 A
に 123
を、変数 B
に 2
を代入し、A*B+4
の式を計算して印字しています。 なお、OK だろうが ERR だろうが、OK と出てきます。本当にエラーが発生した場合は「Segmentation fault」と出て死にます。こわい。
シェルを終了するには、C-d
を押します。
A
から Z
の25文字が使えます。+
, -
, *
, /
に加え、比較演算子 =
, >
, <
が使えます。A=100
や B=A*2
のように変数に値や式の評価結果を代入することができます。?=A
や ?=1+2+3
のように変数の内容や式の評価結果を端末に印字することができます。また、
=
ではないものは、すべてコメントとして読み飛ばされます。1+2*3
は 7
ではなく、9
になります。1
を返し、偽の時は 0
を返します。>
, <
はそれぞれ、C でいう >=
, <=
として解釈されます。A=0-100
とすると変数 A
に -100
を代入でき、式で使えます。%
もサポートしません。という感じです。
ジャンプ構文がないと、ただの「変数が使える電卓」レベルですね。 あと4、5行余裕があれば実装できるかもしれません。
元のコードも一応貼りつけておきます。
gets()
を使っているところは論外ですが、getval()
内の strtol()
の引数 p
と &p
も本来ならばやっちゃいけない投げ方だと思います。
strtol()
は文字列を数値にするのはもちろんですが、演算子と数値部分のパースに使っています。 ポインタ p
が進んでいれば数値化もできて文字列の式もその分だけパース完了、進んでいなければ *p
は変数名であるという解釈をしています。
フロッピーディスクを整理しようと Amazon で 1680 円だった USB フロッピーディスクドライブを買いました。一応 Windows 9x・古い MacOS 向けのインストール CD が付属していましたが、Linux Mint は挿すだけで認識しました。便利。
nemo にアイコンが表示され、そこからクリックや右クリックメニューを使ってマウントをすることができるけど、右クリックメニューの「フォーマット」は USB フラッシュメモリー向けのダイアログが表示されるので使えません。
ということでマニュアルでフォーマットします。
/def/sdf
とする。root で fdformat /dev/sdf
すると Invalid argument と怒られるので、mtools の mformat
を使う。/etc/mtools.conf を次のように編集。
- drive a: file="/dev/fd0" exclusive
+ drive a: file="/dev/sdf" exclusive
mformat a: && mkfs -t msdos -I /dev/sdf
する。
マウントしてみて読み書きできたら成功。
もちろん、マウント済みの場合は一旦アンマウントしてから実行します。
手元の FD は論理的に壊れているものはフォーマット。物理的に壊れているものは処分しました。 ちゃんと読み書きできたものの中に 1996年の Mac 版 NetScape インストーラの FD などがありました。
FD ドライブを使うの自体、10年ぶりくらいです。読み書きの音が懐しいですね。
今使っているラップトップは、SONY VAIO Z の BTO で SSD ストレージなのですが、予算をケチって 128 GB にしています。というのも、ストレージはどんどん安くなっていくので「必要になったら後から足せばいいか」と考えていたからです。
SD カードも SDHC カード、SDXC カードと進化しつつもどんどん安くなっていて、64 GB の SDXC カードが3200円〜3800円程度で買えるようになってきました。
そろそろ容量を確保しておきたくなったので、Amazon で 64 GB SDXC カードを購入。 Linux で SDXC カードを使う際の注意事項が、調べてみてもあまり情報がなかったので購入時は少し不安でした。
参考までにいくつかの側面からポイントを書いておきます。(もちろん保証はできませんのであしからず…)
SDHC カードを想定していたハードウェアで使えるか?ということです。/dev/mmc* (本体に搭載されたSDカードリーダー)や /dev/sd* (USB SDカードリーダー)として認識し、今のところちゃんと使えているようです。
問題なく、普通のストレージと同じようにパーティションが切れます。GParted でも確認済み。
インターネット上の SDXC カードに関する記事は exFAT とペアで掲載されていますが、ファイルシステムを exFAT しか許していないわけではなく、何でもいいようです。普通に mkfs
コマンドでファイルシステムを作れます。うちでは ext4 を使っています。もちろん、SDXC カード対応と謳ったデジカメなどの類は exFAT しか読み書きできないはずです。なお、うちの Linux Mint では、購入直後にフォーマットされていたであろう exFAT な SDXC カードをマウントできませんでした。
cryptsetup や LUKS で魔法をかけることもできました。
# cryptsetup luksFormat /dev/mmcblk0p1
WARNING!
========
This will overwrite data on /dev/mmcblk0p1 irrevocably.
Are you sure? (Type uppercase yes): YES
Enter passphrase:
Verify passphrase:
# cryptsetup luksOpen /dev/mmcblk0p1 sdxc
Enter passphrase for /dev/mmcblk0p1:
# ls /dev/mapper/
control sdxc
# mkfs.ext4 /dev/mapper/sdxc
# 略
# cryptsetup luksClose sdxc
SDXC カードを用いて Windows コンピュータやデジタル家電とデータをやりとりするには、Linux 側での exFAT サポートが必要になってくると思います。(「linux exfat」で検索するとたくさん情報が出てくるので可能なはずです。)しかし、単に Linux だけで完結する小さな拡張ストレージという用途では、普通に使えるようです。
C言語で半加算器、全加算器を書いてみました。 入力ビット列がA[]
とB[]
、結果ビット列がS[]
になります。