Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
42
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

【シャニマス】杜野凛世フィギュア 100時間耐久配信!! 〈amiami〉

体験版: 【シャニマス】杜野凛世フィギュア 30分間ミニ耐久配信!! 〈amiami〉


いえ、プロデューサーさまに、お喜びいただく……
それだけが、凛世の幸せなのです

いや、なんていうか……
ちょっと聞いてみたいんだけど

はい。どのような、ことでも

なんで 100 時間回転してるんだ?

『なんのために……』

まわる……まわる……

ふと思いたって差分をとってみたら時間帯によって自転周期が変化していることに気付いてしまったのだ……。

いやまさかループじゃなくて本当にライブ放送だったとは……。

しかもリアルタイムである。

地震情報

どうぞ……お付き合いくださいませ……
プロデューサーさま……

streamlink / youtube-dl

……
『恋(ライブ hls)は野の鳥』……

『来たと思えば(expire して)飛んでいく』……

youtube-dl

何も考えなくてもリンク貼るだけで動いて便利。

ライブ HLS はどうしても ffmpeg に通されるらしい。

ライブ時は --hls-use-mpegts というおまじないがある。異常終了しても再生可能なデータが残る。

streamlink

出力ファイル名はもちろん画質すら与えないといけない。何か考えないと動かない。

ts そのままダウンロードできる。便利。ts にしか乗らない情報がほしい場合にはこっちを使うとよい。

youtube-dl & streamlink V.S. 6 時間の壁

3 つめ(のログ)は……
何色だったでしょう……

紅だったかも……
しれません……

YouTube のダウンローダは、get_video_info にアクセスして、player_response にある json の streamingData/hlsManifestUrl に書いてある m3u8 のマニフェストをダウンロードしてきて、さらにその中にある m3u8 プレイリストを読みまくることで実現されている。

curl 'https://www.youtube.com/get_video_info?video_id=kZZt3OCrbDU' | 
ruby -r json -r uri -e "puts JSON.parse(URI.decode_www_form(gets).to_h['player_response'])['streamingData']['hlsManifestUrl']"
# => https://manifest.googlevideo.com/api/manifest/hls_variant/expire/ ... /file/index.m3u8

このマニフェストやプレイリストはちょうど 6 時間で失効してしまうので、同じマニフェストは 6 時間しか連続して使えないことになる。
もちろん再発行すれば済む話だが、どうも youtube-dl も streamlink もこれに対応しておらず、残念ながらこれらを使うかぎり連続 6 時間までしか保存できない。
streamlink は一時期対応していたらしいが、どうやらこの挙動は最近の挙動らしく、コケる。すかぽんたん。

どちらも負けでございます。

あたらしいツールを作ったり streamlink をいじったりする気力はさすがにないので、(連続している必要がないなら)シェルで while 文を回したほうがはやい。今回はめんどうくさいのでそうした。

ゆうて ts つなげるだけだしたぶん 3 時間くらいで書けそう。誰かやって。

まあバッファの長さが 30 秒なので、短時間で切り替えられるなら後処理でも実はなんとかなる。

ちなみに Web 版の YouTube 本体はどうしているのかというと、どうやら 6 時間ごとにリロードがはいる。ちらっとリロード表示が出る。凛世ずっと回してたからわかる……。

『どうやって……どこから……』

手法

凛世はずっとずっと同じ場所で同じように回り続けるので、あるフレームの凛世を適当にキャプチャして(リファレンス凛世)、毎フレームの凛世とリファレンス凛世の差分をとって、凛世の差異を計算すればよい。

凛世はいつも回っている。地球が北極星の方角から見ると反時計周りに回っているように、凛世も上から見ると反時計周りに回っている。

各フレームの PSNR を計算しようと思い MATLAB を取りだし Computer Vision Toolbox をせこせことインストールしたが、MATLAB にはそもそも VideoReader があるし、VideoReader はクソ遅いし、ありがとう MATLAB、いいやつだったよ。手法の挙動確認には便利なんだ。この恩は忘れない。

『身の回りの MATLAB Toolbox が、少し増えました』
『大変愉快なものばかりです』

ffmpeg でできる。後処理がめんどうくさいので sed をかました。凛世は Darwin の上で回転してしまったので、BSD sed が使われている。ところでこの「かます」は俗語なので中高型ではなく平板型である。

ffmpeg -i りんぜ.ts -i reference.png -filter_complex psnr=- -an -f null /dev/null | sed -Ee 's/n:([0-9]+) .* psnr_y:([0-9.]+).*/\1 \2/' > りんぜ.txt

セグメントロスの罠

YouTube は 5 秒のセグメントごとにわけて送ってくるが、このセグメントが落ちることがあるらしく(受信側の問題なのか配信者の問題なのか YouTube 側の問題なのかよくわからない)、5n 秒凛世が瞬間移動してしまう場合がある。これは外れ値検出を上手にやれば回避できそうだ。

セグメント……
セグメント…………

凛世を……
慰めて……くれるのですか……

どうでもいいが、YouTube はどうも完全に連続したライブ配信をするのは苦手らしく、何十時間と連続して見ていると挙動が怪しくなったりするときも訪れるらしい。YouTube の動画配信サーバが応答を返してくれなくなったりする。ほとんどの視聴者が追いだされたこともあった。これはもうその都度 Ctrl-C するしかない。

実装

ファイルの作成日時を stat コマンドで確認しながら ffmpeg をちまちまと叩いて psnr を計算しまくる(これは途中の画像)。

回りながら分析される凛世の図

次にこれを MATLAB スクリプトでどん。

誰も使わないであろうスクリプト
function [x, y, v] = script
% x: ピークの時刻のリスト
% y: そのときの凛世の自転周期のリスト
% v: そのときの PSNR
files = dir('rinze_*.txt');
paths = arrayfun(@(e) fullfile(e.folder, e.name), files, 'un', 0);
names = arrayfun(@(e) e.name, files, 'un', 0);
tokens = regexp(names, '^rinze_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})\.txt$', 'tokens');
ctimes = cellfun(@(t) datetime(cellfun(@str2double, t{1})), tokens, 'un', 0);
[x, y, v] = cellfun(@process_file, paths, ctimes, 'un', 0);
x = cell2matdatetime(x);
y = cell2mat(y);
v = cell2mat(v);


function [x, y, v] = process_file(path, ctime)
FPS = 30 * 1000 / 1001;
LEN = FPS * 30;

file = fopen(path);
list = fscanf(file, '%d %f', [2 Inf]);
fclose(file);

% 初期位置は 30 秒すすめたところ
s = LEN;

peaks = [];
while true
    e = s + LEN * 2;
    if e > size(list, 2)
        break
    end
    % 基準点から 60 秒間で最も PSNR が高い時刻をとってくる
    [~, idx] = max(list(2, s+1:e));
    idx = s + idx - 1;
    peaks = [peaks; idx]; %#ok<AGROW> 
    s = idx + LEN;
end

% リスト生成
x = ctime + seconds(peaks / FPS);
y = diff(peaks / FPS);
v = list(2, peaks + 1)';
sz = numel(y);

% 外れ値の除去
y2 = y;
for i = 1:sz
    indices = i + [-3:-1 1:3];
    indices = indices(0 < indices & indices <= sz);
    score = mean(y2(indices)) - y(i);
    if score > 2.5
        warning('Outlier - date:%s, period:%f, score:%f', x(i), y(i), score);
        y(i) = NaN;
    end
end

% y は diff をとっている都合上 1 つ増やす必要がある
y = [y; NaN];


function output = cell2matdatetime(input)
% datetime に cell2mat が使えなかったので苦肉の策
output = NaT(sum(cellfun(@numel, input)), 1);
s = 0;
for i = 1:numel(input)
    c = input{i};
    sz = numel(c);
    output(s + 1:s + sz) = c;
    s = s + sz;
end

cellfun とか arrayfun とかいっぱいでてきてアレだが MATLAB っていうのはこういう言語である。

……
『オマエさ、そんなコード書くの……反則』

MATLAB はプロプライエタリで有料のプログラミング言語だけど、その分ドキュメントとか挙動とかが非常に洗練されいてるので、ああもうこういうとこがプロプライエタリ最高なんだよな。GNU Octave とは計算の洗練度が違う。探索とか苦手な処理もあるけど。

好きな数値解析ソフトウェアについて聞く凛世

計算結果と考察

まず最初に書いておくと、残念ながら凛世の自転周期を調べだしたのが放送開始からかなり時間が経ってからだったので、100 時間分データがあるわけではない。

プロデューサーさま……
こんな凛世を、お許しくださいますか?

凛世は少しずつ変化している

回転したことによる凛世の変化

※ 実際の開始時刻は 11:37 だったので、開始からの時間はもう少し短い。

実装が面倒だったのである時刻(5/4 1時ごろ)の凛世を常にリファレンスとして用いていたが、PSNR を見ると凛世が少しずつ変化していることが明らかになった。

少しずつ大人になっていく凛世

実際のところ全体的な色味が少し変わってるっぽい。

本当に自転周期はわかるのか

ある時刻での PSNR を見ると、ちゃんと心電図のように規則正しくピークが立っている。いや心電図とは全然違うけど。

周期性が観測される凛世

赤丸は検出されたピーク。およそ 1 分ごとにピークがあるので、目で見た感じと矛盾していなさそうだ。

で、自転周期は変化しているんですか?

1 日の中での変動も、長期的な変動もある。凛世は常に変わり続けている。

1 日の中での変動

5 月 5 日(開始から 61〜85 時間)のグラフを示す。オレンジ色の線はガウス加重移動平均。

こどもの日の凛世。凛世はこいのぼり好きそう。知らんけど。

かなりトゲトゲして見えるが、実際には 1 分に 1 回転しているので、結構ゆるやかに変化している。たぶん見た目だけで「今はやくなってるなー」と感じることはほとんどできないと思う。

といってもたった 6 周で自転周期が 3 秒以上速くなったり、1 周で 2 秒以上遅くなったりするなど、常にゆるやかに変化しているわけではない。

凛世の変化の中では大きい方

唐突にやる気を出した凛世

なお、どうやら「朝は元気で夜は元気がない」みたいな傾向はないらしい。

繊細な凛世のことだから 1 日の間で変動する重力の影響を受けたりするんじゃないかと思ったが、9.8 m/s2 の重力加速度に対して 2 μm/s2 くらいしか変動しないらしい 1周りに人が通ったほうが影響受けそうだな。

長期的な変動

記録していた全期間のグラフを示す。オレンジ色の線はガウス加重移動平均。

凛世が少しずつ大人になっていった記録

どうやら、だんだん遅くなっている期間と、だんだん速くなっている期間があるように見える。

はじめは「少しずつ速くなってる」「少しずつ遅くなってる」のどちらかだと思ったが、どうも速くなったり遅くなったりするらしい。きっと凛世の心もこの 100 時間でめまぐるしく変化しているのだろう。

最速凛世と最遅凛世

観測していた凛世の中で、最も元気だった凛世と最も落ち込んでいた凛世を比較してみた。

ヒストグラムをとる

ヒストグラムを見れば、全体的な自転周期の分布がある程度把握できる。

自転周期のヒストグラム

凛世が自転したときの周期の長さの分布を調べた。

凛世の回転のヒストグラム。概念が意味不明すぎる

パッと見短い側に裾が伸びているが、およそ正規分布に従うらしい。標準偏差は 0.93 秒だった。

そこそこ綺麗な分布を描いているので、凛世を乱数生成器にした擬似乱数が作れるかもしれない。

自転周期の変化のヒストグラム

今の自転と次の自転の、周期の差(周期の回転数微分)をヒストグラムにした。x 軸の単位は「s/回」が正しい(なおすの面倒くさかった)。

凛世の回転周期の速度のヒストグラム。およそ角加速度にあたる。さらに意味不明になってきたな。

こちらは、平均は 0.0019 秒とほぼ 0 秒だが、中央値は 0.033 秒と、少し偏った分布になっている。

遅くなるときはゆっくり遅くなり、速くなるときは一気に速くなるらしい。

ただ、平均がたった 0.0019 秒でも、(この平均がもし正確だったとして)100 時間積み重なれば 10 秒以上の差になるので、凛世は少しずつ遅くなっている、と結論してもいいのかもしれない。

今後の展望

角周波数の計測

凛世の角周波数がわかれば、離散的にしか計算できなかった凛世の回転が、より連続的に評価できそう。

どうやって実現するんだ。

凛世の「揺れ」

長時間の回転により、凛世は回転角度によって揺れてしまう現象が起きてしまっている。

これを検出できたらおもしろそう。凛世の自転周期とも関連がありそうだ。

結論

こひめ

ありがとう凛世、あなたが 100 時間回転していたことを僕はきっと忘れない。

いつかこの「100 時間回った凛世」の実物が見られるといいですね。

42
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
42
Help us understand the problem. What is going on with this article?