空談録

http://artfulplace.net/blogs/ からひっこしつつ

pbs.twimgのファイル名が使えないので自分で規則を作ることにした

詳細は前の記事でもご確認ください

調子に乗ってついったーの画像を保存しているとこんな感じになってしまいます
f:id:fantasticswallow:20150627143707p:plain

まあよくある光景だと思うのですが、あとから見返したときにこの画像の持ち主は誰だったのだろうとふと悩むときがあります
画像の整理中にフォルダ分けの情報としても使えますし、フォローしたい!と思うかもしれません

ということで前回の記事につながるのですが、pbs.twimgのファイル名からツイートIDを参照するのはできたとしてもやり方が不明という残念な結果でした
(ファイル名でググれば時々断片的な情報が出てくるときもありますがそこからの追跡がだるい)
これは困る、ということでオレオレファイル規則で保存することにしましょう

規則を考えてみる

とりあえずpbs.twimgが基であることを思い出せる形がベストなので、BASE64の規則は同じ"A-Za-z0-9-_"でやってみましょう
次に整列を考えて生成パターンの最初に何らかのタイムスタンプが来るようにします
さらに、大幅にファイル名が変わるのはあれなのでsnowflakeを参照しましょう
ツイートが消えた場合を想定してユーザーIDも入れておきます

という風に書くと割と一瞬でパターンが決まります

status_id + photo id + user_id

という形でとりあえず組みます
photo_idが必要な理由は一つのツイート内に複数枚画像は入れられるので、競合回避用

次にビット数を決めます
status_idは下位23ビットは削っても問題がないと思いますが(タイムスタンプは41bitなので)、user_timelineでいちいちstatus_idをsince_idとして調べるのも面倒なだけなので64bit全部詰め込みます
user_idは現在unsigned intで32bit以内に収まってはいますが、29億くらいまで来ているのでもう少し余裕を持ちたいです
で、最終的なビット数は6の倍数にするとBASE64で変換するときとても楽です。(どうでもいい)

ということで適当に決めるとこんな感じ

status_id(64bit) + photo_id(4bit) + user_id(40bit)

合計108bitです。ちゃんと6の倍数です
画像はとりあえず1ツイートに16枚もこないだろうという甘い考えです
user_idは現在の500倍ユーザーが増えなければあふれないという計算
まあ7年くらいで現在の数値だった気もするので、50年後もこんなクソコードメンテナンスしてないだろうという発想
画像IDを6bitでuser_idを38bitでもよかった気はします
まあなんでもいいですが(ルール変えても私しか困らないし)

適当にstatus_id:614664282119143424で生成するとCIe56G_VAAAAAF6qFdみたいな感じになります
3文字ほど元のファイル名から延びますが、便利なことだけは確かなので放置

追記:status_idが取れれば、画像が存在しないでもなければ基本的にtwitter.com/aaa/{status_id}なりでアクセスすれば勝手にそのツイートをした人のところに飛んで行きます
ユーザー名は存在しようがしまいが関係ないです
api.twitter.comならユーザー名関係ないですがまあどっちでも
ユーザーIDの変換はAPIでやるしかない気もします…(ユーザー消えると追えないけどその場合どうしようもないしファイル名にスクリーンネームをいれる謎仕様しかなさそう)

実装は?

出したくない……

クソコードのサンプルを置いときます

internal static string NameCreate(long status_id, long user_id, int index)
{
    var s = Convert.ToString(status_id, 2);
    if (s.Length < 64)
    {
        s = s.PadLeft(64, '0');
    }
    var idxs = Convert.ToString(index, 2);
    if (idxs.Length > 4)
    {
        idxs = idxs.Substring(idxs.Length - 4);
    }
    else if (idxs.Length < 4)
    {
        idxs = idxs.PadLeft(4, '0');
    }
        
    var uid = Convert.ToString(user_id, 2);
    if (uid.Length > 40)
    {
        uid = uid.Substring(idxs.Length - 40);
    }
    else if (uid.Length < 40)
    {
        uid = uid.PadLeft(40, '0');
    }
    var comb = s + idxs + uid;
    var sb = new StringBuilder();
    var loop = true;
    while (loop)
    {

        var str = "00" + comb.Substring(0, 6);
        var i = Convert.ToByte(str, 2);
        sb.Append(pattern[i]);
        comb = comb.Remove(0, 6);

        if (comb.Length <= 0)
        {
            loop = false;
        }
    }
    return sb.ToString();
}

private static readonly string pattern = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

その通りに実装したってだけで最適化などは一切知りません。私が使う分には問題ない速度で問題なく動いたと思います(若干変えたのでコピペで動かない可能性はあり)

いったんすべて2進表記にしてくっつけて6bitずつ変換していくだけですね
Convert.ToString()は先頭が1になるところからしか出してくれないので、ほしいbit数になるまで0で埋める必要があります


ということで私が忘れた時用の謎のメモです
逆算するような何かはありません。必要になったら考えます
ってまあID逆算するのは先頭64bit拾ってきてConvert.ToInt64でいい気がしますが…

この辺で