requestFileSystemのラッパー改良版

前回のをちょっと変えたのと、update、removeを追加。

ファイルの作成:
//文字列で作成
fs.create({name: 'file.txt', data: 'Hello!'});
//blobで作成
fs.create({name: 'blob.zip', data: blobdata});
//callbackあり
fs.create({name: 'hoge.txt', data: 'hoge', success: function(fileEntry){alert(fileEntry.toURL())}});
//以前に同じファイル名のものを作っていると失敗
//flagsを独自に指定すると挙動を変えられる(上書きできる)が絶対に非推奨
fs.create({name: 'hoge.txt', data: 'fuga', error: function(e){alert(e.code)}});

ファイルの読み込み:
function success(result){alert(result)}
//テキストで読み込む(default)
fs.read({name: 'file.txt', success: success});
//encoding指定
fs.read({name: 'file.txt', encoding: 'UTF-8', success: success});
//BinaryStingで読み込む
fs.read({name: 'file.txt', readType: 'binarystring', success: success});
//ArrayBuffer
fs.read({name: 'file.txt', readType: 'arraybuffer', success: success});
//DataURL
fs.read({name: 'file.txt', readType: 'dataurl', success: success});

ファイルの更新(作成もできるけど):
//fs.createと大体同じ
fs.update({name: 'file.txt', data: 'update!', success: function(){
//確認用
fs.read({name: 'file.txt', success: success});
}});

ファイルの削除:
fs.remove({name: 'file.txt', success: function(e){
//確認用
fs.read({name: 'file.txt', error: function(e){alert(e.code)}});
}});

エラーコードはFileErrorオブジェクトを参照してください。
Chromeのインタラクティブシェルで調べればすぐわかります。

//tiny requestFileSystem Wrapper
//author @ukyo
//apache license

//refer: http://d.hatena.ne.jp/shirokurostone/20111014/1318593601
var fs = (function(window){

var fs = {},
BlobBuilder = window.WebKitBlobBuilder || window.MozBlobuilder || window.MSBlobBuilder,
requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem,
defaultWriteOptions = {
type: window.TEMPORARY,
size: 1024*1024,
name: null,
flags: {create: true, exclusive: true},
data: null,
success: function(fileEntry, e){},
error: function(e){}
},
defaultReadOptions = {
type: window.TEMPORARY,
size: 1024*1024,
name: null,
flags: null,
success: function(result, e){},
error: function(e){},
readType: 'text',
encoding: 'UTF-8'
},
defaultRemoveOptions = {
type: window.TEMPORARY,
size: 1024*1024,
name: null,
flags: {create: false},
success: function(e){},
error: function(e){}
},
readAs = {
arraybuffer: 'ArrayBuffer',
binarystring: 'BinaryString',
text: 'Text',
dataurl: 'DataURL'
};

/**
* Example:
* var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder;
* var bb = new BlobBuilder();
* bb.append('sample text');
* fs.create({
* size: 5*1024*1024,
* name: 'text.txt',
* success: function(fileEntry){
* alert(fileEntry.toURL());
* },
* error: function(e){
* alert(e.toString());
* },
* blob: bb.getBlob('text/plain')
* });
*/
fs.create = function(options){
options = overrideOptions(options, defaultWriteOptions);
requestFileSystem(options.type, options.size, function(fileSystem){
fileSystem.root.getFile(options.name, options.flags, function(fileEntry){
fileEntry.createWriter(function(fileWriter){
fileWriter.onwrite = function(e){
options.success(fileEntry, e);
};

fileWriter.onerror = options.error;

if(options.data.constructor == String){
var bb = new BlobBuilder();
bb.append(options.data);
options.data = bb.getBlob('text/plain');
}
fileWriter.write(options.data);
}, options.error);
}, options.error);
}, options.error);
};

/**
* Example:
* fs.read({
* size: 5*1024*1024,
* name: 'text.txt',
* success: function(result){
* alert(result);
* },
* error: function(e){
* alert(e.toString());
* }
* });
*/
fs.read = function(options){
options = overrideOptions(options, defaultReadOptions);
requestFileSystem(options.type, options.size, function(fileSystem){
fileSystem.root.getFile(options.name, options.flags, function(fileEntry){
fileEntry.file(function(file){
var fileReader = new FileReader();

fileReader.onloadend = function(e){
options.success(fileReader.result, e);
};

fileReader.onerror = options.error;

var args = [file];
if(options.readType) args.push(options.encoding);

fileReader['readAs' + readAs[options.readType.toLowerCase()]].apply(fileReader, args);
}, options.error);
}, options.error);
}, options.error);
};

fs.update = function(options){
options = overrideOptions(options, defaultWriteOptions);
var removeOptions = {
name: options.name,
type: options.type,
size: options.size,
success: function(){fs.create(options)},
error: function(e){
if(e.code === FileError.NOT_FOUND_ERR)
fs.create(options);
else options.error(e);
}
};
removeOptions = overrideOptions(removeOptions, defaultRemoveOptions);
fs.remove(removeOptions);
};

fs.remove = function(options){
options = overrideOptions(options, defaultRemoveOptions);
requestFileSystem(options.type, options.size, function(fileSystem){
fileSystem.root.getFile(options.name, options.flags, function(fileEntry){
fileEntry.remove(options.success, options.error);
}, options.error);
}, options.error);
};

function overrideOptions(options, defaultOptions){
var ret = {};
for(o in defaultOptions){
if(typeof options[o] === 'undefined'){
ret[o] = defaultOptions[o];
} else {
ret[o] = options[o];
}
}
return ret;
}

return fs;

})(this);
posted by 右京 | javascript

FileSystemAPIの簡単なラッパー

requestFileSystemを使おうとしたらcallback乱発で目眩が・・・。コード自体は実質80行ちょいかな?
ソースコード:tiny requestFileSytem Wrapper − Gist

//tiny requestFileSystem Wrapper
//author @ukyo
//apache license

//refer: http://d.hatena.ne.jp/shirokurostone/20111014/1318593601
var fs = (function(window){

var fs = {},
requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem,
defaultWriteOptions = {
type: window.TEMPORARY,
size: 1024*1024,
name: null,
flags: {create: true},
blob: null,
success: function(e, fileEntry){},
error: function(e){}
},
defaultReadOptions = {
type: window.TEMPORARY,
size: 1024*1024,
name: null,
flags: null,
success: function(e, result){},
error: function(e){},
readType: 'text',
encoding: 'UTF-8'
},
readAs = {
arraybuffer: 'ArrayBuffer',
binarystring: 'BinaryString',
text: 'Text',
dataurl: 'DateURL'
};

/**
* Example:
* var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder;
* var bb = new BlobBuilder();
* bb.append('sample text');
* fs.write({
* size: 5*1024*1024,
* name: 'text.txt',
* success: function(e, fileEntry){
* alert(fileEntry.toURL());
* },
* error: function(e){
* alert(e.toString());
* },
* blob: bb.getBlob('text/plain')
* });
*/
fs.write = function(options){
options = overrideOptions(options, defaultWriteOptions);
requestFileSystem(options.type, options.size, function(fileSystem){
fileSystem.root.getFile(options.name, options.flags, function(fileEntry){
fileEntry.createWriter(function(fileWriter){
fileWriter.onwrite = function(e){
options.success(e, fileEntry);
};

fileWriter.onerror = options.error;

fileWriter.write(options.blob);
}, options.error);
}, options.error);
}, options.error);
};

/**
* Example:
* fs.read({
* size: 5*1024*1024,
* name: 'text.txt',
* success: function(e, result){
* alert(result);
* },
* error: function(e){
* alert(e.toString());
* }
* });
*/
fs.read = function(options){
options = overrideOptions(options, defaultReadOptions);
webkitRequestFileSystem(options.type, options.size, function(fileSystem){
fileSystem.root.getFile(options.name, options.flags, function(fileEntry){
fileEntry.file(function(file){
var fileReader = new FileReader();

fileReader.onloadend = function(e){
options.success(e, fileReader.result);
};

fileReader.onerror = options.error;

var args = [file];
if(options.readType) args.push(options.encoding);

fileReader['readAs' + readAs[options.readType.toLowerCase()]].apply(fileReader, args);
}, options.error);
}, options.error);
}, options.error);
};

function overrideOptions(options, defaultOptions){
var ret = {};
for(o in defaultOptions){
if(typeof options[o] === 'undefined'){
ret[o] = defaultOptions[o];
} else {
ret[o] = options[o];
}
}
return ret;
}

return fs;

})(this);
posted by 右京 | javascript

JavaScriptでMP4の動画からAACを抽出してみた

JavaScript Advent Calendar 2011 (オレ標準コース) : ATNDの11日目の記事です。

HTML5ではArrayBuffer、TypedArray、BlobBuilderなどのバイナリを扱うのに適したAPIがあります。というわけで、試しにMP4の動画からAACを抽出してみました。ソースコードはgithubを参照してください。


MP4ファイルからAACを抽出する手順は以下のようになります。

1.MP4ファイルをパースする
2.パースした情報を元に、音声部分を連結する


じゃ、まずMP4ファイルをパースしてみます。

MP4や、その派生である携帯電話向けの3GPP、3GPP2などのファイルフォーマットはボックス(あるいはその基になったQuickTimeでの用語のAtom)と呼ばれるデータブロックで構成されます。ボックスによってはその内部にさらにボックスが入れ子になるツリー構造になっています。

各ボックスはその先頭8バイト(オクテット)がボックスを識別するためのヘッダで、最初の4バイト(オクテット)がボックスのサイズ、続く4バイトがそのタイプです

ウノウラボ by Zynga Japan: MP4/3GPP/3GPP2ファイルフォーマットの基礎知識

そういうわけなので、ハッシュ(オブジェクト)に関数をぶちこんでぐるぐる回せばツリー構造が取り出せます(今回の目的はAAC抽出するだけなので、関係ある部分だけ実装すればOK)。

Mp4.prototype.parseからArrayBufferからUint8Arrayでviewを作ってパース。
  parse: function(){
if(this.cache.parse) return this.cache.parse;
return getBox(new Uint8Array(this.data), -8, this.data.byteLength);
}

子ボックスをもつボックスは、基本的に以下のgetBox関数を使えば大丈夫なはず。
function getBox(bytes, offset, size){
var ret = {size: size},
last = offset + size,
boxInfo, box;

offset += 8;
while(offset < last){
boxInfo = getBoxInfo(bytes, offset);
box = boxes[boxInfo.type];
if(box) {
if(ret[boxInfo.type] && !isType(ret[boxInfo.type], Array)){
ret[boxInfo.type] = [ret[boxInfo.type]];
ret[boxInfo.type].push(box(bytes, offset, boxInfo.size));
} else if(isType(ret[boxInfo.type], Array)){
ret[boxInfo.type].push(box(bytes, offset, boxInfo.size));
} else {
ret[boxInfo.type] = box(bytes, offset, boxInfo.size);
}
} else {
break;
}
offset += boxInfo.size;
}

return ret;
}

  boxes = {
ID32: getBox,
albm: function(bytes, offset, size){},
auth: function(bytes, offset, size){},
//中略...
trak: getBox,
tref: getBox,
trex: function(bytes, offset, size){},
trgr: function(bytes, offset, size){},
trun: function(bytes, offset, size){},
tsel: function(bytes, offset, size){},
udta: getBox,
uinf: function(bytes, offset, size){},
UITS: function(bytes, offset, size){},
ulst: function(bytes, offset, size){},
'url ': function(bytes, offset, size){},
vmhd: function(bytes, offset, size){},
vwdi: function(bytes, offset, size){},
'xml ': function(bytes, offset, size){},
yrrc: function(bytes, offset, size){},

//codecs
mp4a: function(bytes, offset, size){
return {
dataReferenceIndex: getIntBE(bytes, offset += 12),
channels: getShortBE(bytes, offset += 12),
bitPerSample: getShortBE(bytes, offset += 2),
sampleRate: getIntBE(bytes, offset += 4),
esds: {
objectTypeIndication: bytes[offset += 25],
bufferSizeDB: getShortBE(bytes, offset += 3),
maxBitrate: getIntBE(bytes, offset += 2),
avgBitrate: getIntBE(bytes, offset += 4)
}
}
}
}


これでMP4ファイルがパース出来ました。次に、パースした情報を元にAACを抽出してみます。

mp4.png

MP4ファイルでは、Chunkという単位でぶつ切りにして動画と音声が格納されています。このようにすることによって、インターネットで途中までしか読み込んでいなくても再生できるわけです。さらに動画、音声はそれぞれSampleというもっと小さい単位で分割されていて、Chunkには複数のSampleが格納されています。

各SampleはSampleToChunkBox、SampleSizeBox、ChunkOffsetBoxというボックスの情報をもとに抽出します。

SampleToChunkBoxにはfirstChunk、samplesPerChunksという要素の配列が格納されています。これは、「n番目のfirstChunkの位置からn+1番目のfirstChunkまではChunkごとにsamplesPerChunks個入っている」ということを表しています。

SampleSizeBoxには単純に各Sampleのサイズが格納されています。

ChunkOffsetBoxにはChunkのオフセット(何バイト目から始まるか)が格納されています。

AACの場合は、抽出した各Sampleごとに7バイトのヘッダーをつけて連結すれば完成です。

Mp4.prototype.extractAAC
  extractAAC: function(){
var tree = this.parse(),
tracks = tree.moov.trak,
audioTrack, mp4a, sampleToChunkEntries, sampleSizeEntries, chunkEntries,
i, j, k, n, m, l, fileSize, idx,
offset = 0,
bb = new BlobBuilder(),
aacHeader = new Uint8Array(new ArrayBuffer(7));

if(isType(tracks, Array)){
tracks.forEach(function(track){
if(track.mdia.hdlr.type === 'soun'){
audioTrack = track;
}
});
} else {
if(tracks.mdia.hdlr.type === 'soun'){
audioTrack = track;
} else {
throw 'This file does not have audio files.';
}
}

mp4a = audioTrack.mdia.minf.stbl.stsd.mp4a;
sampleToChunkEntries = audioTrack.mdia.minf.stbl.stsc.body;
sampleSizeEntries = audioTrack.mdia.minf.stbl.stsz.body;
chunkEntries = audioTrack.mdia.minf.stbl.stco.body;

aacHeader[0] = 0xFF;
aacHeader[1] = 0xF9;
aacHeader[2] = 0x40 | (sampleRateTable[mp4a.sampleRate] << 2) | (mp4a.channels >> 2);
aacHeader[6] = 0xFC;

for(i = 0, idx = 0, n = sampleToChunkEntries.length; i < n; ++i){
j = sampleToChunkEntries[i].firstChunk - 1;
m = i + 1 < n ? sampleToChunkEntries[i + 1].firstChunk - 1 : chunkEntries.length;
for(;j < m; ++j){
offset = chunkEntries[j];
for(k = 0, l = sampleToChunkEntries[i].samplesPerChunk; k < l; ++k, ++idx){
//AAC header.
fileSize = sampleSizeEntries[idx] + 7;
aacHeader[3] = (mp4a.channels << 6) | (fileSize >> 11);
aacHeader[4] = fileSize >> 3;
aacHeader[5] = (fileSize << 5) | (0x7ff >> 6);
bb.append(aacHeader.buffer);

//AAC body.
bb.append(this.blob.webkitSlice(offset, offset += sampleSizeEntries[idx]));
}
}
}

return bb.getBlob();
}

DEMO(MP4ファイルのパース結果を表示して抽出したAACをダウンロードする。ダウンロードしたファイルにhoge.aacとか名前をつけてね。あと、Chromeでしか動かない。):
http://dl.dropbox.com/u/142237/JsBlobBuilderSample/example/parsemp4.html

ソースコード:
ukyo/mp4.js - GitHub

参考:
デジタル映像の「アーカイブ&デリバリー」に関する技術情報サイト|mpeg.co.jp > MPEGラボ > 第26回
なんちゃって記 FAAD2を使ってAAC再生ソフトを作る(その1)
ウノウラボ by Zynga Japan: MP4/3GPP/3GPP2ファイルフォーマットの基礎知識
The 'MP4' Registration Authority
posted by 右京 | javascript

ECMA6にこんなものがくるかもリスト

このままいけばPython likeな構文が増えます、やったね!

//global汚染がなくなるぞ
import { $ } from "jquery.js";

//ブロックごとにスコープが
for (i=0;i<n;i++){
let x;
}

//ジェネレーターがきた
function counter(){
yield 1;
yield 2;
yield 3;
}

var g = counter();
alert(g.next());//1
alert(g.next());//2
alert(g.next());//3

spawn(function*(){
var foo = yield read("foo.txt"),
bar = yield read("bar.txt"),
buz = yield read("buz.txt");
yield sleep(1000);
});

//ラップできるのかな?
var p = new Proxy(obj, {
get:function(){},
set:function(){},
delete:function(){}
});

//bits, binary, blobs
var Point2D = new StructType({
x:uint32,y:uint32
})

//複数Return
var {r,g,b} = thing.color;
var [x,y] = circle.center;
[b,a] = [a,b];

//Pythonの*args的なやつとデフォルトの引数
function f(i,j,...rest){
return rest.slice(i,j);
}
var shape = new Shape(...points);

function img(src, width=320, height=240){}

//Private変数
var key = Name.create("secret");

function Container() { this[key] = 3}

//イテレーション関連
for(x in [3,4,5]) //0,1,2
for(x of [3,4,5]) //3,4,5
for(x of keys(0)) //keys in o
for(x of values(o)) // values of o
for([k, v] of items(o)) // props of o

import iterate from "@iter";

obj[iterate] = function(){
next:function(){}
}

//pythonのアレ
[x*x for (x of obj1) for (y of obj2)]
(x*x for (x of obj1) for (y of obj2))

//String templates
`<p>
${hoge} ${fuga}
</p>`

safeHTML`<p>
${hoge} ${fuga}
</p>`

//Object-keyed tables

//javaのHashMapみたいなかんじ?
var scores = new Map();

//GCが働いてくれるよ、たぶん
var markers = new WeakMap();
posted by 右京 | javascript

Nico Friends Managerをリリースしました

small_tile.png

Chrome拡張でNico Friends Managerというものをリリースしました。

ダウンロードURL:
https://chrome.google.com/webstore/detail/henhejjkggfbanhnobemdoibjopkiaph?hl=en

説明に関してはDETAILページに書いた内容まんま貼り付けておきます。なんというか、ニコ動のフレンド管理って検索も一括削除もねーし、あと、まぁ、削除されたときに誰かわかるよって機能もついてます。


Nico Friends Managerはフレンドの一覧、検索と、フレンド増減の通知、フレンドの一括削除機能を提供します。


各タブの説明

フレンドタブ:
フレンドタブでは検索フォームよりフレンドの検索を行うことができます。さらに、フレンドのアイテム上でダブルクリックすることによって、そのフレンドをチェックすることができます(さらにダブルクリックすることで解除)。

チェックタブ:
チェックしたフレンドの一覧を表示します。『フレンドから外す』ボタンを押すことでフレンドの一括削除を行うことができます。

履歴タブ:
フレンドになったり、フレンドから外れたときの情報の一覧を表示します。


通知機能の説明

履歴タブに表示される情報はまずデスクトップ通知されます。特に何もしなければ一分後に表示が消えます。
posted by 右京 | javascript

画像からDataURLを作成する3つの方法

1つ目はMimeTypeをx-user-definedにする方法。大体のブラウザで動くけど、サイズの大きい画像だと重くなったり、失敗したりする。

2つ目はキャンバスに貼り付けて、canvas.toDataURLする方法。IE以外のブラウザで動いて実装も簡単。一回キャンバスに貼り付ける分遅くなる気がする。

3つ目はresponseTypeをArrayBufferにしてBlobBuilderでBlobにしてFileReader.readAsDataURLする方法。Chromeでは動いた。他はちょっと試してない。サイズの小さい画像だともたつくけど、サイズが大きくなっても結構速い。

//see this page. http://www.html5rocks.com/en/tutorials/file/xhr2/
var ImageLoader = (function(){

var BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

function loadImageDataURLWithLegacyXHR(url, type, callback, async){
callback = callback || function(){};
async = async != null ? async : true;
var xhr = new XMLHttpRequest();

xhr.open('GET', url, async);
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function(){
if(this.readyState == 4 && (this.status == 200 || this.status == 206)){
var res = this.responseText,
bytearray = [],
i, l;
for(i = 0, l = res.length; i < l; ++i){
bytearray[i] = res.charAt(i) & 0xff;
}
callback.call(this, 'data:image/'+type+';base64,'+window.btoa(String.fromCharCode.apply(null, bytearray)));
}
callback.call(this, 'error');
}
xhr.send();
}

function loadImageDataURLWithCanvas(url, type, callback){
callback = callback || function(){};
var image = new Image(),
canvas = document.createElement("canvas"),
type, ctx;

try {
image.src = url;
image.onload = function(){
canvas.width = image.width;
canvas.height = image.height;
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
callback(canvas.toDataURL("image/" + type));
};
} catch(e) {}
}

function loadImageDataURLWithXHR2(url, type, callback, async){
callback = callback || function(){};
async = async != null ? async : true;
var xhr = new XMLHttpRequest(),
bb = new BlobBuilder(),
fr = new FileReader();

xhr.open('GET', url, async);
xhr.responseType = 'arraybuffer';

xhr.onload = function(){
if(this.status == 200 || this.status == 206){
var self = this;
bb.append(this.response);
fr.onload = function(){
callback.call(self, this.result);
};
fr.readAsDataURL(bb.getBlob('image/' + type));
}
callback.call(this, 'error');
}
xhr.send();
}

return {
loadImageDataURLWithLegacyXHR: loadImageDataURLWithLegacyXHR,
loadImageDataURLWithCanvas: loadImageDataURLWithCanvas,
loadImageDataURLWithXHR2: loadImageDataURLWithXHR2
};

})();
posted by 右京 | javascript

JavaScriptでいろいろな方法を使って文字列を整数に変換する

※整数でないと結果が一緒にならないので注意。

var a = "1";
console.log(
parseInt(a),
Math.floor(a),
+a,
~~a,
a|0,
a*1,
a/1,
a%Infinity,
a>>0,
eval(a) //実際こんなことしちゃダメだよ
);

ついでにベンチマーク結果。ブラウザによって結構変わるみたい。
http://jsperf.com/str2intbench
posted by 右京 | javascript

jQueryを用いたAJAX通信中に画面をロックする

面倒くさがりのためのスニペット。とりあえずAJAX使っているところ全部で画面をロックしたいときに有用かもしれん。ajaxのformを多用していると特に。

html(bodyの最後辺りに挿入しておく)
<div id="lock"><img src="/media/image/ajax-loader.gif" /></div>

css
#lock {
z-index: 10000;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #FFF; //お好みの色で
text-align: center;
display: none;
opacity: 0.7; //これもお好みの透過率で
}

JavaScript
$(function(){
function lockWindow(){
var $window = $(window),
$img = $("#lock > img");
$img.css({"marginTop":(($window.height() - $img.height()) / 2) + "px"});
$("#lock").fadeIn();
}

function unlockWindow(){
$("#lock").fadeOut();
}

$(document).ajaxComplete(unlockWindow)
.ajaxSend(lockWindow)
.ajaxStop(unlockWindow);
});
posted by 右京 | javascript

JavaScriptのコードレビューがうんたらかんたら

Smashing Codingから。コードレビューから学びましょうってタイトル。
Lessons From A Review Of JavaScript Code - Smashing Coding

コードレビューしてもらったら勉強になるよとか、具体的にこんなサイトあるよって話があって、さらにコードレビューの具体例が出てるかんじ。

(気になったところのメモ)
配列への振り分けの書き方
var waiting = [];
var dreading = [];
(deed === 'fulfilling ? waiting : dreading').push(func);

JSONオブジェクトを整形して出力
JSON.stringify(obj, null, 4);
posted by 右京 | javascript

魔法の呪文

JavaScriptの小ネタ。これをコードに混ぜるとデバッグできなくなる。
alert = null;
console = null;
posted by 右京 | javascript

String.prototypeに置換メソッド追加

Sugar: A Javascript library for working with native objects.のStringの置換だけ版。String.prototypeにメソッドを追加して使うと、結構遅くなるのでfor文の中で使う場合は慎重に。

(function(){
this.nl2br = function() {
return this.replace(/(\r\n|\r|\n)/g, '<br/>\n');
};

this.escapeHtml = function() {
return this.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};

this.url2link = function() {
return this.replace(/((https?|ftp)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#])+)/g, '<a href="$1">$1</a>');
};

this.mention = function() {
return this.replace(/@([a-zA-Z0-9_]+)/g, '<a href="http://twitter.com/$1">@$1</a>');
};

this.hashTag = function() {
return this.replace(/#([a-zA-Z0-9_]+)/g, '<a href="http://twitter.com/search/%23$1">#$1</a>');
};

this.anchorLink = function() {
return this.replace(/&gt;&gt;([1-9]{1}[0-9]*)/g, '<a href="%23$1">&gt;&gt;$1</a>');
};
}).call(String.prototype);

//example
text.escapeHtml().nl2br().url2link();
posted by 右京 | javascript

cacheありのpjax #javascript

https://github.com/ukyo/jquery-pjax/tree/cached-pjax

jquery-pjaxですでに読み込んだページをキャッシュしておいてサーバアクセスを減らすようにしてみたよ。キャッシュはsessionStorageに保管しておくのでウィンドウを消すとキャッシュは消えるよ。ただしリロードだとキャッシュは残ったままだよ。

使い方:
デフォルトだと無効だよ。enableCacheをtrueにしてね。
$("a.pjax").pjax("#main", { enableCache: true })

//なにか変更したら適宜$.pjax.setCache()を呼ぶとその時点の状態がキャッシュされるよ。
$(".hoge").live('click', function(e){
foo();
$.pjax.setCache();
});

ソースコードの変更点:
//41行目
if ( options.enableCache ) {
($.pjax.setCache = function() {
var $container = $(options.container)
if ($container.length) {
sessionStorage.setItem(window.location.href, $container.html())
}
})()
$(document).bind("pjax:end", $.pjax.setCache)
} else {
$.pjax.setCache = $.noop
}

//184行目
var content = sessionStorage.getItem(options.url)
if ( content ) {
var d = $(document)
d.trigger('pjax:start', [xhr, pjax.options])
// start.pjax is deprecated
d.trigger('start.pjax', [xhr, pjax.options])
$container.success = options.success
$container.success(content)
d.trigger('pjax:end', [xhr, pjax.options])
// end.pjax is deprecated
d.trigger('end.pjax', [xhr, pjax.options])
return null
}

使いどころ:
ページ遷移が多い、あまり変更が少ないようなサービスだと効果は大きいかな。

具体例:
https://github.com/ukyo/KayPjaxSample
posted by 右京 | javascript

ニコニコ動画のフレンドを一気に消す

※この記事は8/31現在は使えると思いますが、ニコニコ動画側の仕様変更で使えなくなる可能性があります。

ニコニコ動画のフレンド削除ってめちゃくちゃ面倒ですよね?少なくても俺は面倒だと思っています。Ajax使っているくせにリロードしやがるのは正気とは思えません。そこで、お手軽にフレンドを全削除する方法を紹介したいとおもいます。ただし、実際には試していないので、動作保証はしませんよ。やるときは自己責任で。

まずはJavascriptのインタラクティブシェルを搭載しているブラウザを準備しましょう。Chromeあたりがおすすめです。次にAutoPager的なプラグインをインストールしましょう。AutoPagerとかAutoPagerizeとかで検索すれば出てくるんじゃないでしょうか。

これで準備完了です。http://www.nicovideo.jp/my/friendにアクセスしましょう。そうしたら、ホイールを勢いよく回転させて(サイドバーでもいいです)全ページ表示しましょう。表示したら「フレンドから外す」ボタンの挙動を変更します。調べてみるとわかるのですが、このボタンはaタグのonclick属性からdelete_friend関数を呼び出しています。ChromeではScriptsタブから検索できるのでそこから色々やるとmy3.jsにそれっぽいのを見つけることが出来ます。それが以下になります。

function delete_friend(id){
if (confirm('ご利用中のフレンド対応サービスでのつながりがすべてなくなります。よろしいですか?')) {
var form_data = [];
form_data.push({name : 'target_user_id', value : id});
form_data.push({name : 'token', value : Globals.hash || ''});
jQuery.ajax({
type: "post",
url: "/api/friendlist/delete",
dataType: "json",
data: form_data,
success: function(data){
if (data.status === "ok") {
document.location.reload();
} else if (data.status === "fail" && data.error.description) {
alert(data.error.description);
} else {
alert('フレンド解除に失敗しました');
}
},
error: function(xhr, textStatus, errorThrown){
jQuery.raiseError(xhr.responseText);
}
});
}
return false;
}

これを下のような感じで書き換えます。リロードを阻止して、コンソールにokと表示するようにしてみました。

function delete_friend(id){
var form_data = [];
form_data.push({name : 'target_user_id', value : id});
form_data.push({name : 'token', value : Globals.hash || ''});
jQuery.ajax({
type: "post",
url: "/api/friendlist/delete",
dataType: "json",
data: form_data,
success: function(data){
if (data.status === "ok") {
console.log("ok")
} else if (data.status === "fail" && data.error.description) {
alert(data.error.description);
} else {
alert('フレンド解除に失敗しました');
}
},
error: function(xhr, textStatus, errorThrown){
jQuery.raiseError(xhr.responseText);
}
});
return false;
}

このdelete_friend関数はユーザーIDを受け取るということがわかっていますので、ユーザIDを上から下まで全部引っ張ってきて、突っ込めばよさそうです。ちょっと調べてみるとclass="report"ごとにユーザ情報が分けられているみたいです。jQueryを使っているので簡単にひっぱってこれますね!で、具体的には下のような感じのを実行すると全部消去できると思います。ただし、登録数が多いと、もしかしたらはじかれるかもしれないので、テキトーに1秒ごとに実行するとかしてください。あと、トークンには期限があるのでページを表示したら早めに実行してください。

jQuery(".report>h4>a").each(function(){
delete_friend(this.href.split('/')[4]);
});
posted by 右京 | javascript

ソフトウェアキーボードを作る2 #javascript

キーボード入力のnグラムデータを受け取って調整するようにした。式は

-(a * log(キーまでの距離*2)) + (b * log(キー入力のnグラムデータ))

これをそれぞれのキーに対して計算して、最大のものを選ぶようにしてある。

DEMO

コード:
$(function(){
function Key(x, y, name){
this.x = x;
this.y = y;
this.name = name;
}

function KeyBoard(){
this.keys = {};
}

KeyBoard.prototype = {
addKey: function(key, x, y){
if(key instanceof Key){
this.keys[key.name] = {x: key.x, y: key.y};
} else {
this.keys[key] = {x: x, y: y};
}
},
press: function(x, y){
var result = {};
$.each(this.keys, function(k, v){
result[k] = (v.x - x) * (v.x - x) + (v.y - y) * (v.y - y);
});
return result;
}
};

function NgramTable(csv, spliter, smoosing){
this.spliter = spliter || ',';
this.smoosing = smoosing || 0.1;
this.init(csv);
}

NgramTable.prototype = {
init: function(csv){
var cols = csv.replace('\r\n', '\n').split('\n');
this.rowLabels = cols.shift().split(this.spliter).slice(1);
this.colLabels = [];
this.table = [];
var self = this;
$.each(cols, function(i, v){
var arr = v.split(self.spliter);
self.colLabels.push(arr.shift());
self.table.push($.map(arr, function(v){
return parseInt(v) + self.smoosing;
}));
});
},
probability: function(colKey){
var index = this.colLabels.indexOf(colKey);
var sum = 0;
var result = {};
var self = this;
$.each(this.table[index], function(i, v){sum += v});
console.log(sum);
$.each(this.table[index], function(i, v){
result[self.rowLabels[i]] = v / sum;
});
return result;
}
};

function hashToSortedArray(hash){
var result = [];
$.each(hash, function(k, v){
result.push({k: k, v: v});
});
return result.sort(function(a, b){
return a.v == b.v ? 0 : a.v < b.v ? 1 : -1;
});
}

function infer(pressData, ngramData, a, b){
var result = {};
$.each(pressData, function(k, v){
result[k] = -(a * Math.log(v)) + b * Math.log(ngramData[k]);
});
return result;
}

keyboard = new KeyBoard();
keyboard.addKey(new Key(46, 47, 'q'));
//...

ngramTable = new NgramTable($("#2gram").text(), ' ');

var inputText = '^';
$("#keyboard").click(function(e){
console.log('Click (' + e.offsetX + ',' + e.offsetY + ')');
var pressData = keyboard.press(e.offsetX, e.offsetY);
var ngramData = ngramTable.probability(inputText.charAt(inputText.length-1));
var hoge = infer(pressData, ngramData, 1, 0.5);
var rank = hashToSortedArray(hoge);
inputText += rank[0].k;
$("#pressed").text(inputText);
$.each(rank, function(i, v){console.log(v.k, v.v, pressData[v.k], Math.log(pressData[v.k]), ngramData[v.k], Math.log(ngramData[v.k]))});
});

});

一応、スムージングもしてるよ。
posted by 右京 | javascript

ソフトウェアキーボードを作る #javascript

方針はあらかじめキーの名前と座標を記録しておいて、クリックした位置に近い順に結果を返す。こうすると真偽値だけで判定する方法と比べて応用が効くはず。

DEMO

コード:
$(function(){
function Key(x, y, name){
this.x = x;
this.y = y;
this.name = name;
}

function KeyBoard(){
this.keys = [];
}

KeyBoard.prototype = {
addKey: function(key){
this.keys.push(key);
},
press: function(x, y){
var ranking = [];
for(var i = 0, n = this.keys.length; i < n; i++){
var key = this.keys[i];
var x_ = Math.abs(x - key.x);
var y_ = Math.abs(y - key.y);
ranking.push({
'name': key.name,
'r': Math.sqrt(x_ * x_ + y_ * y_)
});
}
ranking.sort(function(a, b){
return (a.r == b.r) ? 0 : ((a.r > b.r) ? 1 : -1);
});
return ranking;
}
};

var keyboard = new KeyBoard();
keyboard.addKey(new Key(46, 47, 'q'));
keyboard.addKey(new Key(138, 47, 'w'));
//...

$("#keyboard").click(function(e){
console.log('Click (' + e.offsetX + ',' + e.offsetY + ')');
var ranking = keyboard.press(e.offsetX, e.offsetY)
var pressed = ranking[0];
$("#pressed").text(pressed.name + ' is pressed.');
});

});
posted by 右京 | javascript
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。