0

エゴサーチツール feedim

エゴサーチツール feedimを作った。3ヶ月ぐらい使い続けている。
以前はtwitter検索で “shokai” とか検索した結果をRSSリーダーで読んでいたんだけど、最近ビリケン商会とか大塚商会とかキラキラ商会とか、 “shokai” をユーザ名やURLに含むtweetが増えてきたし、邪魔なbotからのtweetも除外したいのでなんとかするツールを作った。

で、feedを吐くのもいいけどせっかくAndroid持っているから、im.kayac.comを使ってAndroidにpush通知するようにした。
im.kayac.comを使っているのでGoogle TalkやiPhoneのpush通知でも受信できる。

主にtwitterで使っているけど、feedなら何でも定期的に監視できる。
ソースはgithubにある github.com/shokai/feedim


先にim.kayac.comでユーザ登録して通知先を設定しておいてください


■インストール

git clone git://github.com/shokai/feedim.git


■必要なもの
mongodb 1.6以上が必要。起動しておく。

必要なrubygemsは、bundlerで入れる。

cd feedim
gem install bundler
bundle install
mongoid2betaとか、俺が以前作ったim-kayacのgemとかがインストールされる。


■設定
config.yamlファイルを作る。

cp sample.config.yaml config.yaml
config.yamlファイルを編集する。上の方でim.kayac.comのユーザ名とか、認証タイプを選ぶ。MongoDBのDB名とかも選ぶ。



監視するfeedを列挙する。除外したい内容をfilterに正規表現で書く。
filterは本文とURL両方にかけるfilterで、description_filterとurl_filterはそれぞれ本文とURLどちらかにかけるフィルタ。
# list of feeds
feeds :
- "http://search.twitter.com/search.rss?q=shokai"
- "http://search.twitter.com/search.rss?q=%E6%A9%8B%E6%9C%AC%E5%95%86%E4%BC%9A"
- "http://search.twitter.com/search.rss?q=%E6%A9%8B%E6%9C%AC+%E7%BF%94"
- "http://search.twitter.com/search.rss?q=%E3%81%8B%E3%81%9A%E5%8A%A9"
- "http://search.twitter.com/search.rss?q=%E3%81%8B%E3%81%9A%E3%81%99%E3%81%91"
- "http://search.twitter.com/search.rss?q=%E3%83%8F%E3%82%B7%E3%83%A2%E3%83%86%E3%82%A3%E3%82%A6%E3%82%B9"

# filter by "url" and "description" property of entries
filters :
- "honeybee-cd"

# filter by "description" property of entries
description_filters:
- "_shokai"
- "shokai_"
- "\-shokai"
- "shokai\-"
- "shokai\.co"
- "shokai\d"
- "\dshokai"
- "キラキラ商会"
- "ビリケン"
- "大塚商会"

# filter by "url" property of entries
url_filters :
- "twitter\.com\/shokai\/"
- "twitter\.com\/shokai_log\/"
- "bot"
- "kirakira"
こんな感じに書くと、ほぼ橋本商会とかshokaiは全部漏らさずに、ビリケン商会とか大塚商会とかキラキラ商会とか、邪魔なbotを除外できる。



■動かす
クロールして、IMとAndroidに送る。
ruby store.rb
ruby publish.rb

crontabで10分おきに実行している。

0

wavファイルをRubyで編集する

Rubyでwavファイルのフォーマットを読んだり、データチャンクを編集するためのgemを作った。

ベースは以前作ったやつで、ついでにwavファイル操作のサンプルをたくさん追加してrubygems.orgに登録しておいた。
あくまでサーバーで音を合成して返すようなwebサービスで使う事を想定している物で、マイクからの入力をリアルタイムに音声処理するための物ではない。

■インストール

gem install wav-file


■使う

フォーマットとデータチャンクを読む
require 'rubygems'
require 'wav-file'

f = open("input.wav")
format = WavFile::readFormat(f)
dataChunk = WavFile::readDataChunk(f)
f.close

puts format

するとこんな感じにフォーマットが取れる。
Format ID:      1
Channels: 2
Sampling Ratio: 48000 (Hz)
Byte per Sec: 192000
Bit per Sample: 16
Block Size: 4


バイナリからwavの波形を配列として取り出す
bit = 's*' if format.bitPerSample == 16 # int16_t
bit = 'c*' if format.bitPerSample == 8 # signed char
wavs = dataChunk.data.unpack(bit) # read binary


音量を半分にしてみる
wavs = wavs.map{|w| w/2}


逆再生にして、バイナリに戻す
dataChunk.data = wavs.reverse.pack(bit) # reverse


wavファイルに保存する
open("output.wav", "w"){|out|
WavFile::write(out, format, [dataChunk])
}


samplesディレクトリの中に色々と例を入れておいた。githubからも見れる。
  • 音量が小さいのを大きくする
  • 左右チャンネルを分けて保存する
  • wavファイル同士を連結する
  • wavファイル同士を重ねて同時に鳴らす
  • 再生速度を上げる
  • 波形をグラフにする
とかまあ色々入れておいた。


■tips
使う前にwavファイルのフォーマットについて理解しておいた方が良いかもしれない。
wav ファイルフォーマットが参考になる。


あと、複数のwavファイルを合成して新しいファイルを作る場合、操作する前にffmpegでformatを揃えた方が良い。その方が楽だし高速。
adjust_wav_format.rbにやり方を書いておいた。

サンプルを実行するのに必要なwavも、ffmpegでmp3とかから変換して作れる。
ffmpeg -i input.mp3 -ac 2 -ar 44100 output.wav

0

Androidのギャラリーの「共有」メニューから呼び出せるアプリを作る

Androidのカメラで撮影した画像はギャラリーに保存される。そこで画像を選んで共有またはshareなどを選択すると、暗黙的Intent呼び出しで画像を別アプリに渡せる。
Flickrアップローダとか、EvernoteとかDropboxのアプリに写真を渡しているのはこのしくみを使っている。


まずManifest.xmlにintent-filterを追加する。
image/*でもいいが、それだと動画も渡せてしまうので手当たり次第mimeTypeを指定してみる。

<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/jpeg"/>
<data android:mimeType="image/jpg"/>
<data android:mimeType="image/png"/>
<data android:mimeType="image/bmp"/>
<data android:mimeType="image/bitmap"/>
</intent-filter>
ようするに、アプリに「私はimageをSENDするというアクションを受け取って適当に処理できますよ」と宣言させておくと、
他のアプリが「誰でもいいからSENDしたimageを受け取ってくれる人いませんか」と呼び出したときに連携できるようになる。


で、このintent-filterと関連付けられているクラス(普通なら一番最初に生成したjavaクラス)の
onCreate()内でintentを受け取る。
あらかじめ、受け取った画像を表示するためのImageViewを配置しておく。
private ImageView imageView;
this.imageView = (ImageView)this.findViewById(R.id.ImageView01);


暗黙的Intentで呼び出されたのか、普通のIntent呼び出しで起動したのか(つまりホーム画面からアプリのアイコンを押して起動したのか)を判別して処理する。
呼び出し元の名前だけで判別してる
Uri imageUri = null;
try{
imageUri = Uri.parse(getIntent().getExtras().get("android.intent.extra.STREAM").toString());
}
catch(Exception e){
e.printStackTrace();
}
if (imageUri != null) {
Log.v("ImageIntent", "暗黙的intentから起動");
Bitmap bmp = null;
try {
bmp = Media.getBitmap(getContentResolver(), imageUri);
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
if (bmp != null) {
this.imageView.setImageBitmap(bmp);
}
}
else{
Log.v("ImageIntent", "普通に起動");
}
imageUriにはcontent://media〜〜というパスが入る。これはAndroid内のファイルシステムのパスではないが、Media.getBitmapに渡すとデータを読み出せる。
とりあえずImageViewに表示だけしておく。


あとEclipseだと日本語が化けるのでterminalからadb logcatしてる。


Bitmapを受け取ったら、後は適当に処理すれば良い。
リサイズとか
public Bitmap resize(Bitmap bmp, float scale){
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
}



form/multipart-dataでgyazo.comにアップロードするとか、色々できる。
Manifest.xmlにpermissionを追加
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
public void upload(Bitmap bmp) throws ParseException, IOException{
File dataDir = new File(Environment.getExternalStorageDirectory(), this.getPackageName());
dataDir.mkdirs();
FileOutputStream tmp = new FileOutputStream(new File(dataDir, "tmp.png"));
bmp.compress(Bitmap.CompressFormat.PNG, 80, tmp);
tmp.close();

HttpClient httpClient = new DefaultHttpClient();
HttpPost post = new HttpPost("http://gyazo.com/upload.cgi");
MultipartEntity entity = new MultipartEntity();

entity.addPart("id", new StringBody("your-gyazo-id"));
entity.addPart("imagedata", new FileBody(new File(dataDir, "tmp.png"), "image/png"));
post.setEntity(entity);
post.setHeader("User-Agent", "TestAndroidApp/0.1");

httpClient.execute(post);
}
httpでform/multipart-dataでアップロードするには
の2つが必要。


■参考
このへんが参考になった

0

serial-socket-gateway

よくある シリアル通信 <=> TCP Socket する物を作った。
しばらく使ってて特に問題なかったので、使い方とか書いておく。
Mac OSX LeopardとUbuntu 9.04,と10.04とWindows XPで動作した。

普通のsocketなので大抵のプログラム言語から使えるし、少なくとも10個ぐらいのプログラムを下にぶらさげてマイコンと通信させられる。
最近はコレでsocket化したシリアルデバイスにJRubyで作ったGUIをつないで色々やってる。


■ソースコード
githubにある
http://github.com/shokai/serial-socket-gateway


■インストール

git clone git://github.com/shokai/serial-socket-gateway.git

中のserial-socket-gatewayがそれ。Rubyで書かれている。
単体のファイルで動くので、そのまま /usr/local/bin/ とか適当なパスの通っているディレクトリに cp すればいいと思います


■必要なgemのインストール
ruby-serialportとeventmachineを使っているのでそれぞれインストールする。

gem install eventmachine ArgsParser


ruby-serialportは0.7が動かなかったので、俺は0.6を使っている。MacやUbuntuはソースからビルドしてインストーrうする
wget http://rubyforge.org/frs/download.php/72/ruby-serialport-0.6.tar.gz
tar -zxvf ruby-serialport-0.6.tar.gz
cd ruby-serialport-0.6
ruby extconf.rb
make
sudo make install


Windowsは、http://rubyforge.org/tracker/download.php/61/321/9924/1800/ruby-serialport-0.6.0-mswin32-gem.zipからwindows用のバイナリ(gem)を持ってきて、解凍してインストール。
gem install serialport-0.6.0-mswin32.gem


■起動
Arduino等をMacに接続すると /dev/ の下に、tty.usbなんとかというデバイスができているはず。
serial-socket-gateway /dev/tty.usbserial-A7006Rqn
デバイス名を引数に渡して起動。


接続してみる。telnetでok
telnet localhost 8782
デフォルトで8782番portで起動している。port番号はソースの上の方をいじれば変えられる。


■通信の仕様
改行(¥n)区切りで、1行毎に通信している。改行が来るまで送受信しない。


■サンプルプログラム Arduinoから送信
こんなかんじでADコンバータで電圧測って、Serial.println()を使って末尾に改行を付けて9600bpsパリティなしストップビット1で送信。
int ad_pin = 0;

void setup(){
Serial.begin(9600);
}

void loop(){
int ad = analogRead(ad_pin);
Serial.println(ad);
}


■サンプルプログラム Rubyで受信
間にserial-socket-gatewayを挟んでシリアルデバイスと通信する。

require 'socket'
s = TCPSocket.open("192.168.1.100", 8782)

loop do
res = s.gets
if res.to_s.size > 0
puts res
end
end
serial-socket-gatewayはclientとの接続が切れていないか確認する為に、たまに空文字列を送ってくる。
受信した文字列の長さを見て無視してください

送信は
s.puts "abc abc"
rubyのputsは末尾に改行(¥n)が付く。



■サンプルプログラム Rubyで送受信を同時に
標準入力をserial-socket-gatewayにそのまま送る。こういうのeventmachineでやると楽だね
#!/usr/bin/env ruby
require 'rubygems'
require 'socket'
require 'eventmachine'

HOST = 'localhost'
PORT = 8782

begin
s = TCPSocket.open(HOST, PORT)
rescue => e
STDERR.puts e
exit 1
end

EventMachine::run do
EventMachine::defer do
loop do
res = s.gets
exit unless res
if res.to_s.size > 0
puts res
end
end
end

EventMachine::defer do
loop do
s.puts gets
end
end
end


eventmachineが便利!!

0

chrome extensionの作り方+クロスドメインXMLHTTPリクエストのやり方

こないだ箱根合宿で学んだ。

単にchrome拡張を作ってみたかったというのと、chromeではgreasemonkeyが実行できるけど、Firefoxと違ってGM_xmlhttpRequestが無いのでクロスドメイン通信ができないのでなんとなくやってみたかった。
自分の見ているページの履歴を全部自分のサーバーに送って保存したい。

まずos0xさんのChrome拡張入門のスライドが勉強になる。
とくに

拡張コンテキスト、コンテントコンテキスト、ページコンテキストの3つのコンテキストが存在し、それぞれは完全に分かれているので、お互いが干渉してしまうことはない。さらに、拡張同士も独立したコンテキストで実行される。

拡張コンテキストはタブ操作やクロスドメイン通信などの特権を実行でき、コンテントコンテキストと通信したり、スクリプトを実行したりといったことができます。

コンテントコンテキスト(Content Scripts)は特権を持っていませんが、読み込んだページのDOMを操作することができ、拡張コンテキストと相互に通信できます。

ページコンテキストは通常のウェブページで実行されるコンテキストで、コンテントコンテキストとはDOM経由でしかやり取りできませんし、拡張コンテキストとは完全に分断されています

のあたりが今回は重要。


で、とりあえずお勉強として、自分の見ているページのURLを http://localhost:8888/page にpostするchrome extensionを作った。ページロード時に実行される。
github.com/shokai/post_location_href_crxにソースが置いてある。


■chrome拡張のビルドのしかた
rubyを使う人にはcrxmakeというgemが便利だと思う。
chrome拡張をビルドするには、ソースのディレクトリをchromeにドラッグアンドドロップすると.crx形式に固めてくれるが、
crxmakeだとterminalからビルドできる。
post_location_href_crxの中に適当なRakefileを入れておいたので、rake一発でビルドできる。

gem install crxmake
rake -T
rake
で、packageディレクトリができてその中にビルドされた.crxファイルができる。
macだとそのまま
open package/post_location_href.crx
すればchromeで開かれてインストールできるので楽だ。


■chrome拡張に必要なファイル
github.com/shokai/post_location_href_crx/tree/master/src/の中にある物が、クロスドメイン通信するのに最低限必要らしい。
src
|-- background.html
|-- icon.png
|-- icon128.png
|-- icon48.png
|-- main.css
|-- main.js
|-- manifest.json
`-- popup.html


iconを設定しておいたらchromeのツールバーに表示されるようになった
f364e597cb582884a2ba294163072ca5


それぞれのファイル名はmanifest.jsonの中で決めている。
{
"name":"post location.href",
"description":"POST location href to http://localhost:8888/page",
"version": "0.0.2",
"background_page": "background.html",
"permissions": ["tabs" ,"http://*/*", "https://*/*"],
"browser_action": {
"default_icon": "icon.png",
"default_title": "post location.href",
"popup": "popup.html"
},
"icons": {
"128": "icon128.png",
"48": "icon48.png"
},
"content_scripts": [
{
"js": [
"main.js"
],
"css": [
"main.css"
],
"matches": [
"http://*/*",
"https://*/*",
"ftp://*/*"
],
"run_at": "document_start",
"all_frames":true
}]
}


■クロスドメイン通信のしかた
このmanifest.jsonの内容だと、main.jsがコンテントスクリプトとして最初に実行される。
main.js内の最初の方に、バックグラウンドページのコンテキストと通信するコードを書いておけば、新しいページをロードした時にコンテントスクリプト→バックグラウンドページと通信できる。バックグラウンドページではクロスドメインXMLHTTPRequestが使えるので、そこで http://localhost:8888/page に location.href を送れば自分の見た全てのサイトのURLを保存できる。

main.js
var connection = chrome.extension.connect();

connection.onMessage.addListener(function(info, con) {
console.log(info, con);
});

connection.postMessage({url:location.href});

background.html
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>test app</title>
<script>
var api_endpoint = "http://localhost:8888";

chrome.self.onConnect.addListener(function(port, name) {
port.onMessage.addListener(function(info, con) {
xhr = new XMLHttpRequest();
xhr.open("POST", api_endpoint+"/page", true);
xhr.setRequestHeader("Content-Type" , "application/x-www-form-urlencoded");
xhr.send("url="+encodeURIComponent(info.url));
port.postMessage({url:info.url});

});
});
</script>
</html>



受信は、sinatraとかだと適当にこうして受け取れる
require 'rubygems'
require 'sinatra'
require 'json'

post '/page' do
url = params['url']
unless url
status 403
@mes = {
:error => '"url" required'
}.to_json
else
# 適当に保存するコード
@mes = {
:url => url
}.to_json
end
end