条件に日時を使うような境界値テストのrequest specにrspec-parameterizedとRailsのfreeze_timeまたはTimecopを組み合わせると落ちる

はい。

皆様ご存知、rspec-parameterizedというgemがあります。
複数入力の組み合わせで出力が決まるような、素のまま書くとcontext地獄になってしまうテストを短く書けて便利なやつです。

github.com

なんですが、仕事のプロダクトのrequest specにおいて、事前状況セットアップに使う値とその結果のレスポンス内容として期待する値がセットになっているような境界値テストをrspec-parameterizedを使って書いたところ、通ったり通らなかったりする。

数人がかりで2時間弱ほど格闘したところ、下記のことがわかりました。

  • 通ったり通らなかったりしたのはDBのDATETIME型が秒単位だったせい
    • なのでrspec-parameterizedのせいではない
  • rspec-parameterizedはwhereの中身をその場で評価してテストケースの組み合わせを生成していそう
    • 遅延評価ではない?

最小の再現サンプルがこちら。 github.com

上記の再現サンプルではコード量を最小にするためにTimecopを使っていますが、この現象を発見した時はRailsActiveSupport::Testing::TimeHelpersのfreeze_timeとtravel_backを使っていました。
また、before :eachを:suiteにしても変わりませんでした。

ruby-jpのslackにて質問したところ、コミッタのsue445さんより

一応これで回避はできるのですが、自分で書いといてなんだけどすごい気持ち悪いですね…
https://github.com/sue445/rspec-parameterized-with-timecop-issue-minimum-reproduce-sample/commit/6b31bc782b29d007a63928f8ae3bd9e02f253c1d

> どうやらrspec-parameterizedにパラメータとして渡したDateTime型を返すものはRailsのfreeze_time(またはTimecop)によってDateTimeがモックされる前に評価されてしまうらしいということがわかりました。
これ厳密にはちょっと違ってて、rspec-parameterizedの where ブロックがrspecの before の処理よりも前に評価されるのが原因です。( where の中で let の値が参照できないのも同じ理屈)

という返答をいただきました。

別の方から下記記事を教えていただきましたが今回のケースはrequest specなのでこれも使用できず。

techlife.cookpad.com

他の方から下記のようにrspecの外側でnowを変数に束縛し、whereブロック中でパラメータとして書く日時はnowからの加減算で表し、テスト時にTimecop.freeze(now)、Railsではtravel_to(now)するという形を提案していただき、これを使うことにしました。

https://github.com/takayamaki/rspec-parameterized-with-timecop-issue-minimum-reproduce-sample/compare/fixed

書き方まで完璧とは言えませんが、テストの不確実性は排除できたので一旦よしとします…

反応いただいた皆様ありがとうございました 🙇‍♂️

ミリシタ3周年イベントで秋月律子4位になった話

ミリシタ3周年イベントCHALLENGE FOR GLOW-RY D@YS!!!、おつかれさまでした。

今回は総合477位、律子4位という結果でした。

f:id:takayamaki:20200719230447p:plain

本当はここまでの勢いで大爆走するつもりはなかったのですが、走り出したら止まれなくなってしまい。
時勢も相まっての大爆走だったので次回も同レベルで走ることはないでしょうが、貴重な経験だったので記録に残しておきます。

まず、僕の仕事は2月中旬、具体的にはシンデレラ7th大阪の後から完全自宅勤務をしています。
それと弊社は来歴上こういったオタク的諸々に理解がある会社であり、「去年同様アイマスの二週間戦争が始まるので二週間仕事の出力落ちます」と言ったら、苦笑しながらも許してもらえました。
そのため仕事の隙間時間でも走ることが可能でした。(走る隙間時間で仕事していたのではないか、とか言ってはいけない)

要約

長いので箇条書きにします。

  • イベントアイテムの貯蓄は適当に最短時間で頑張る
  • イベントアイテムの消費は手首や指の消耗を抑えるために2mix
    • 2mixなら音聴きながら中指と薬指で同時タップするだけでスコアSでクリアできるようになる
  • 貯め続けから何時ごろ消費に切り替えれば1日のアイテム収支が釣り合うかの予実管理表を作った
    • 後に1日あたりの目標貯蓄数まで考慮できるように改修した
  • 前半終了のタイミングで一旦アイテム全消費するつもりでミスった
  • 最後の1日はアイテム消費だけで走り切るつもりだった
    • おすすめ楽曲の巡り合わせにより丸1日貯める日が発生した結果、ほぼ3日走り続けた
  • これ以上の順位を目指すには端末課金が要りそう

ポイント推移

時刻 累計 日速
2020/6/29 0:00 0
2020/6/30 1:00 384746 384746
2020/7/1 3:00 785962 401216
2020/7/2 1:00 1080546 294584
2020/7/3 1:00 1421672 341126
2020/7/4 1:00 1810024 388352
2020/7/5 1:00 2120756 310732
2020/7/6 1:00 2530706 409950
2020/7/7 1:00 2772172 241466
2020/7/8 1:00 3144187 372015
2020/7/9 0:00 3379933 235746
2020/7/10 0:00 3930778 550845
2020/7/11 0:00 4580545 649767
2020/7/12 0:00 5244259 663714

f:id:takayamaki:20200713185008p:plain

消費した諸々のリソース量

正直面倒だったので正確な値は記録していません。PLvは309から373になりました。

スタドリは何個だったか覚えてませんが初日のうちに使い切り、ジュエル的にはたぶん14万個くらい、そのうち6万個くらいを貯蓄や未読アイドルコミュからの無償ジュエルで、 有償ジュエル84000個をGoogle Playで買い、Google Playポイントなどによる軽減があったので実際に使用した金額は9万3960円でした。

といっても、ミリオンクルーズ、SideMプロミ、シャニスプリングパーティ、シャニ2nd、ミリオン7thなどあらゆるイベントが中止になった関係でそれらの返金がチケット代だけで10万円を超えており、今回の費用は実質無料と言って差し支えないでしょう。

各日について

2020/6/29(月) 1日目

初日。 友人たちとdiscordに集まり、ワイワイしながら0時の開戦を迎えました。

この時点ではまだ大爆走する気はなかったのでとりあえず自宅勤務のなか一通りやってみてどれくらいの速度が出るか、というのを試していました。
2時から11時までリフレッシュ、そして翌1時までの16時間、2回支給されたバーストを両方使ったこともあって日速38万4746pt。

f:id:takayamaki:20200713173348j:plain

あと、この日一番短いおすすめ楽曲は星屑のシンフォニアだったらしいですが、Shooting Starsをやっていましたね…?

2020/6/30(火) 2日目

翌日が事情によりほぼ2ヶ月ぶりの物理出社だったので、リフレッシュタイムを少し後ろにずらして3時まで。
以降、17時くらいからほぼ毎日discordで通話が発生していました。

f:id:takayamaki:20200713173415j:plain

使用したおすすめ楽曲はGood-Sleep, Baby、10時から27時までの17時間走ったので、日速40万1216pt。

2020/7/1(水) 3日目

この日は物理出社の日でした。
本当に久々に外に出たので、最寄り駅のホームに来た段階で既に少し息が上がってしまっており、体力の危機を感じました。

f:id:takayamaki:20200713173426j:plain

使用したおすすめ楽曲は虹色letters、時間は12時から1時までの13時間、物理出社の影響もあって日速29万4584pt。

2020/7/2(木) 4日目

日中は特に特筆すべきことはなし。

f:id:takayamaki:20200713173440j:plain

使用したおすすめ楽曲はHOME, SWEET FRIENDSHIP、時間は11時から1時までの14時間、日速34万1126pt。

この日が終わった後に「なんかこのまま行けば1桁順位は取れそうだ」と思い、その時点までのジュエルの消費ペースより、終了までに使う金額を9万円と推計して覚悟を決めました。

ついでに、ほほえみDiaryを走った友人からバンテリンコーワの手首サポーターを勧められ、ヨドバシ.comで買いました。
東京23区内であれば翌日に届けてくれるヨドバシエクストリーム便は神。
ブルーライトカットな眼鏡は最終的に一切画面を見なくなったので不要でした。

2020/7/3(金) 5日目

バンテリンコーワの手首サポーターが届いたものの、1箱1枚という罠にはまりました。完全に見落としていた。
更に、注文していたふつうサイズでは僕には小さすぎた二重の罠。適切サイズはちゃんと確認しましょう。

どうするか迷いましたが7/5に大きめサイズを2枚注文しました。 余ったふつうサイズ1枚は母に献上。

また、本格的に走るのを決めたので手首、指の消耗を抑えるためにアイテム消費は2mixに切り替えました。(クリア回数Sはとっくに終わってたし)
ミリシタは2mixでもイベントptに差がつかないのがありがたいですね。
経験値は違うのでそこは差が出ますが、手指の消耗と経験値とどちらが大事かと言えば僕は考えるまでもなく手指が大事なので経験値は投げ捨てました。

f:id:takayamaki:20200713173450j:plain

使用したおすすめ楽曲はインヴィンシブル・ジャスティス、時間は10時から1時までの15時間、日速38万8352pt。

リフレッシュタイムに入った後でジュエルを補充しようとしたとき、それまで特に使わず貯まり続ける一方だったGoogle Playポイントの存在を思い出し、投入した日でもありました。

2020/7/4(土) 6日目

この日はリフレッシュタイム終了から貯め続け、何時何分ごろに消費に切り替えれば貯蓄と消費が釣り合うかを計算するような予実管理表を作りました。

f:id:takayamaki:20200713165147p:plain
青背景の時刻は貯蓄、赤背景の時刻から消費すると1日のアイテム収支が釣り合う

が、予実管理表の単位時間が当初30分だったのを15分に変更したタイミングでアイテムの時間当たり消費量を修正し忘れたことにより、ちょうど後半戦切り替わりのタイミングだというのに2倍で貯めたアイテムを余すというミス。 2時間くらい貯めすぎたので、ざっくり40分程度のロスでした。

f:id:takayamaki:20200713173503j:plain

使用したおすすめ楽曲はランニング・ハイッ、時間は10時から1時までの15時間、日速31万0732pt。

2020/7/5(日) 7日目

4位にいるのでせっかくならば3位に上がりたいと思って考えた結果、最後の1日はアイテムを使い続けるだけの日にしようという方針をこの日に決めました。
それに向け、予実管理表を1日に貯蓄したいアイテム数まで考慮できるように改修していました。

f:id:takayamaki:20200713172530p:plain
改修後の予実管理表。実績列にその時点での貯蓄数実績を入れると右列で誤差を修正してくれる

使用したおすすめ楽曲はラビットファー、時間は10時から1時までの15時間、日速40万9950pt。

f:id:takayamaki:20200713173749j:plain

2020/7/6(月) 8日目

予実管理表を日曜日に1日あたりの貯蓄アイテム数を考慮できるように改修したばかりですが、ここにきておすすめ楽曲最短が2分5秒のBigバルーン◎。 これはこの日のうちに貯めておかないと損ではないかと思い、バースト使用以外は丸1日貯めに徹しました。

f:id:takayamaki:20200713174014j:plain

使用したおすすめ楽曲はBigバルーン◎、時間は10時から1時までの15時間、日速24万1466pt。

2020/7/7(火) 9日目

この日のおすすめ楽曲は2分10秒台のSuper Duperだったので、予定通りに必要数の均等割り分だけ貯め、それ以上は消費していました。

それと、会社の追加有給制度を使えばよいのではないかということに気付いた結果、7/8から7/10まで仕事が休みに。 ちょうど仕事に余裕があるタイミングだったので助かりました。

f:id:takayamaki:20200713174023j:plain

使用したおすすめ楽曲はSuper Duper、時間は10時から1時までの15時間、日速37万2015pt。

2020/7/8(水) 10日目

もう2分0秒台の曲は来ないのではないかと思っていた中、まさかのPRETTY DREAMER、2分2秒。
予定を変更して完全に貯めに徹しました。 これによりバーストが一つ貯蓄され、最終日はバースト2発の見通しに。

この日が終わった時点で残り3日間(45時間)のうち40時間程度は走り続けられるだけのアイテムが貯まりました。

f:id:takayamaki:20200713174047j:plain

使用したおすすめ楽曲はPRETTY DREAMER、時間は10時から0時までの14時間、日速23万5746pt。

2020/7/9(木) 11日目

さすがに2分0秒台の曲は来ないのではないかと思っていたにも関わらず、ジレるハートに火をつけて、2分2秒。
しかし前述した通り40時間程度は走り続けられるだけのアイテムが既に貯まっていました。

そのため午前中に少しだけ貯めて、残り時間から計算できる理論上の消費可能数から1時間30分程度の余裕を持たせた数にした後は消費に切り替えました。

この頃には音さえ聴ければ2mixを見ずにクリアできるようになっていました。
死なずにスコアSが取れれば良いので左手の中指と薬指で両方のレーンをリズムに合わせてタッチしていれば十分で、右手が空いた結果諸々のニュースサイトなどを読むなどしていました。

f:id:takayamaki:20200713174058j:plain

使用したおすすめ楽曲はジレるハートに火をつけて、時間は9時から0時までの15時間、日速55万0845pt。

2020/7/10(金) 12日目

おすすめ楽曲の中で一番短いのは瞳の中のシリウスでしたが、この時点で既に7/11の23時ごろまで走れるだけのアイテムを貯めていたので一度もやりませんでした。

f:id:takayamaki:20200713174109j:plain

9時から0時までの15時間ひたすら消費し続け、日速64万9767pt。

2020/7/11(土) 13日目

最終日。 10時から夕方まで当初の予定通りずっと消費し続け、そのまま行けば22時50分ごろに消費し終わる見通し。

最終日なこともあってdiscordに人が観戦も含めて二桁人集まる中、美希のSHOWROOMを見てそのあまりの実在性に衝撃を受けたり、 100位1つと1000位2つを狙っていた友人が100位調整芸し始めて5分ごとに各アイドルのボーダーと10分速情報が飛び交うCIC(戦闘指揮所)の様相を呈したり、 僕自身は2mixでもう消費しきれば終わりなうえ、前述の通り左手と耳だけでクリアできる状態だったので右手を使って雀魂で四麻友人戦を打ち始めたり。

そのまま走り切れそうだったので、22時ごろに30分程度だけ貯め、それ以降また消費に切り替えた結果、23時56分に全て消費完了し、合計524万4229ptでゴール。

f:id:takayamaki:20200713174119j:plain

使用したおすすめ楽曲はEpisode. Tiara、時間は9時から0時までの15時間、7/8に貯めたものも合わせ2回ブーストしたので日速66万3714ptでした。

端末性能とiOSAndroidという越えられない壁

負け惜しみと言われても仕方ないのですが、今回4位に着地したのは端末性能の差によるものだと思われます。
今回のイベントで使用したのはドコモのLG-01K V30+でしたが、これはAndroidでSnapdragon 835の端末です。

Snapdragon 835は一応2年前のフラグシップなSoCだったわけですが、これと友人のiPad Pro(まだUSB-Cでない世代のもの)とでGlow map1周にかかる時間を比べたところ私のV30+の2分59秒に対して友人のiPad Proが2分56秒でした。

3秒差あるということは60回、つまり少なく見積もっても3時間で1周差が付きます。

今回の13日間で寝坊したのが合計で50分、それ以外に仕事しながらで理論値から離された時間が1日に45分程度であったことを考えると、それらを全てロスしなかったと仮定しても律子3位との32万pt差は埋まらなかっただろうと思われます。

これ以上を目指すには勤務中にリフレッシュタイムを割り当てて命を削るか、iOSに鞍替えしてiPad Proするか、よくてROG PhoneなどのいわゆるゲーミングAndroidスマホにするかしないとこれよりも上を目指すのは難しそうです。

走り切ってみて

今回からリフレッシュタイムが9時間になったことで、翌日スタートダッシュのためのチケット450枚貯めなどの準備をしても7時間ほどは睡眠時間を確保できたのでかなり人間的生活を保てたと思います。

また、17~18時ごろからdiscordにいると友人たちがわらわらと集まってきてそのまま話しながら走れたので、持つべきは一緒に走る仲間だなと思いました。 もっとも、僕以外は各々の担当アイドルの2桁や、3桁という冷静な目標だったので走り慣れた今回は途中から余裕が生まれはじめ、途中で切り上げて別ゲーム(…というかapex)で遊びに行ってしまったりして寂しさを味わうこともありましたが。 apexは3周年イベントが終わった後に始めました

来年についてですが、端末課金以外に更に石費用として10万は覚悟しなければならないであろうことを考えると、来年も1桁、はさすがに厳しいかなと思います。 来年はアイマスのイベントがいくらか開催でき、それに今年は浮いてしまった分のお金を払えていることを願っています。

ともあれ、ミリシタ3周年おめでとうございます。

M@STER BOWL #4 ご来場ありがとうございました & #5 開催予定のお知らせ

はい。

もう一ヶ月少々前になってしまいましたが、M@STER BOWL #4 @大阪日本橋へのご来場ありがとうございました。

初めてDJイベントに来たという方も全体の2~3割くらい見受けられ、「初めての人でも楽しめるイベントを目指す」を念頭に置いて開催しているM@STER BOWLとしてはそれなりに目的を達せたのではないかと思います。

f:id:takayamaki:20191128025954j:plain

普段なら当日のうちにセットリストをtwitterで画像として貼っているのですが、初めて大阪で回すとか翌日シャイニーカラーズのプロデューサー感謝祭の現地へ行くために早朝の新幹線でとんぼ返りしたなどの諸々で機会を逸してしまいました。

ので、今回ブログという形でセットリストの報告、そして普段はやらないのですがせっかくなのでライナーノーツというか、どうしてこのような構成のセットリストになったか、という展開のネタばらしをしてみます。

セットリスト

というわけでまずはセットリストを一覧で。

  1. G♡F / 秋月律子篠宮可憐
  2. きゅん・きゅん・まっくす / 棟方愛海一ノ瀬志希前川みく、 乙倉悠貴、 椎名法子
  3. きゅんっ!ヴァンパイアガール / 星井美希四条貴音我那覇響
  4. Sweet Witches' Night ~6人目はだぁれ~ / 三村かな子十時愛梨、森久保乃々、椎名法子及川雫
  5. Reversed Masquerade / Café Parade
  6. Halloween Code / 安部菜々、 乙倉悠貴、前川みく
  7. Frozen Tears / 北条加蓮
  8. Up!10sion♪Pleeeeeeeeease! / 松田亜利沙
  9. We can go now! / イルミネーションスターズ
  10. フェスタイルミネーション / 徳川まつり
  11. パステルピンクな恋 / 小早川紗枝佐久間まゆ輿水幸子緒方智絵里三村かな子
  12. プリコグ / 水谷絵理
  13. Private Sign / 塩見周子
  14. 99 Nights / 四条貴音
  15. 夜空を煌めく星のように / DRAMATIC STARS & High x Joker
  16. アルティメットアイズ / 玲音
  17. NEO THEORY FANTASY / アンティー
  18. アンデッド・ダンスロック / 白坂小梅松永涼
  19. Marionetteは眠らない / 星井美希、伊吹翼、北上麗花、ジュリア
  20. Needle Light / 荒木比奈、上条春菜
  21. Symphonic Brave / Legenders
  22. 朝焼けのクレッシェンド / 田中琴葉
  23. 空 / 音無小鳥
  24. Spread the Wings!! / アイドルマスターシャイニーカラーズ
  25. TRUE COLORS / 城ヶ崎美嘉高森藍子、アナスタシア、藤原肇、乙倉悠貴、黒埼ちとせ、白雪千夜、久川颯、久川凪
  26. Flyers!! / 765 MILLION ALLSTARS

ライナーノーツ(セットリストがこうなった経緯)

セットリストを組む時はだいたい、2~5曲程度のやりたい繋ぎの流れをいくつか考えて、それらを軸に組み立てています。

今回の場合は

  • きゅんっ!ヴァンパイアガール から Halloween Code まで
  • We can go now! から フェスタイルミネーション
  • 朝焼けのクレッシェンド から Flyers!! まで

の3か所がそれに該当します。

G♡F

これはもはや言うまでもないかもしれませんが、以前の記事でも書いたとおり10月上旬にミリシタでG♡Fのイベントがあり、これを大爆走したからです。

元々は鳥籠スクリプチュアあたりの少し重めの曲から始めて、Fascinateから吸血鬼繋がりできゅんパイアに繋げようと考えていたのですが、急にG♡Fが来たのでその構想は吹き飛びました。

きゅん・きゅん・まっくす

この曲はかなり最後のほうに決まった記憶があります。

前述の通り元々は違う展開を考えていたのですが、G♡Fときゅんパイアを繋げる必要性が生じ、「BPM150帯で、かわいい寄りで…」と探しているときに発見しました。

きゅんパイアと地味に「きゅん」繋がりしている点もちょっと気に入っています。

きゅんっ!ヴァンパイアガール から Halloween Code まで

一言で言えば、ハロウィンです。開催日がハロウィンの5日前だったので、ハロウィン的な曲のブロックは必ず入れようとかなり前から決めていました。

その中でも最初に決めたのはSweet Witches' NightとHalloween Codeの二曲でした。

ブロックのコンセプトとしてはドンピシャな二曲ですが、BPMが152と201とかけ離れているので直接繋ぐことはできません。 しかも間に別の曲をはさんでBPMを上げていこうにも、曲数が少なくDJの中では非常に使いづらい3拍子…しかし、Reversed Masqueradeを思いついたことで一気に現実味を帯びました。

Reversed MasqueradeはSideM 3rdの幕張で見て以来とても好きな曲なのですが、イントロが160の3拍子で始まり、曲本体では196へ加速したうえに4拍子へ切り替わるので、普通に4拍子の曲のみで構成するアイマスDJではきれいに繋ぐのがなかなか難しい曲でもあります。

しかし今回は152の3拍子なSweet Witches' Nightから201なHalloween Codeの間をぴったりと繋げられることに気付いたとき、思わず自画自賛してしまいました。

もちろん、加速して4拍子に戻ったReversed Masqueradeから3拍子のHalloween Codeに繋ぎなおすので拍子は再度変わることになるわけですが、落ちサビの直前、音が切れるタイミングでHalloween Codeに繋いだことで49ものBPM差を無理矢理繋ぐよりかは格段に綺麗につなげられたのではないかと思います。

そして150帯でハロウィンでSweet Witches' Nightに繋げられそうな曲、という基準できゅんパイアも選び、1サビ終わりの「きゅーん」からSweet Witches' Nightに繋ぎました。

終わった後、今回初めてだった人からもハロウィンブロックだったと理解してもらえていたので満足。

Frozen Tears

201の3拍子という普段なかなか来ない辺りにせっかく来たので、もう一曲くらい何か流せないかなと思ったときに思いついたのがこの曲。

8分の6拍子*1なので本当は3拍子ではないのですが、3拍子から3連符ならつながるだろうというのと、その珍しい拍子から普段なかなか流されづらいこの曲を大きなスピーカーで聴きたかったという理由で流しました。

Up!10sion♪Pleeeeeeeeease!

さて、Frozen Tearsも流せたわけですし、200帯の3拍子からそろそろ4拍子の世界に戻りたくなります。

バックスピンでリセットしても良かったのですが、曲の途中で3拍子または8分の6拍子になる曲*2たちの中からUp!10sion♪Pleeeeeeeeease!を使うことにしました。

Up10sionの当該部分(空を飛んでるみたい~)は音が少なく、176の曲を200で流す(+13%)でも音がそれほど変にならないこと、またHOT CUEで曲頭に戻れば曲の力で多少の違和感は持っていけるだろうという目算がありました。

We can go now! から フェスタイルミネーション

イルミネ繋ぎです。具体的にはWe can go now!のコール&レスポンス部分からフェスタイルミネーションの頭へ繋ぎました。

元々は #3 で使おうかどうか迷っていたネタだったのですが、 #4 に来てくれる友人の中にまつりPがいたのでそこを狙い撃ちに行きました。

We can go now!のコール&レスポンス部分が「every body let's go!」で終わるのでそのまま夢咲きAfter schoolに繋げる人が多いと後から聞き、逆に目から鱗が落ちた*3りしました。

パステルピンクな恋、プリコグ

この曲もかなり最後のほうに決めました。 フェスタイルミネーションからPrivate Signの夜っぽい感じへ繋ぐ、170帯の曲という基準で選んだ記憶があります。

Private Sign、99 Nights

この2曲の繋ぎ、実は #2 のときにもやっている*4んですが、好きなんですよねこの組み合わせ。

自由奔放にふるまっているように見えてその実気付いてほしい想いを内に秘めているPrivate Sign、おそらく百夜通い伝説が元ネタと言われていて秘めた好意を言えなかったことを独りかえりみる99 Nights。

音も内容もこの組み合わせ、好きなんですよ。

夜空を煌めく星のように

Private Sign、99 Nightsと来て夜になったので、夜空、煌めいていきましょう。

雲一つない透き通った夜空を埋め尽くす満点の星々の下で歌う彼らが目に浮かびますね。

たぶん23時くらいの雰囲気。

アルティメットアイズ

NEO THEORY FANTASYに向けて少し雰囲気を変えたくなって候補に挙がり、ミリシタ生配信で玲音の登場予告されてたし、玲音入れるか!となった曲。

アクセルレーションもこの曲もどちらも好きなんですが、今回はBPM的にアルティメットアイズを選びました。

NEO THEORY FANTASY

音楽のジャンルには詳しくないんですがNEO THEORY FANTASYはメロスピになるんですかね。

重厚なギターの音が、満月の光が差し込む苔むして鬱蒼とした北欧の森を想起させます。 このあたりがきっと0時過ぎごろ。

アンデッド・ダンスロック

1時を過ぎて人ならざる者の時間。

この曲を初めて聞いたときの印象は「ロックって踊れるんだなぁ」という身も蓋もないものでしたが、未だその理由を言葉して表せないものの好きな曲です。

Marionetteは眠らない

アンデッド・ダンスロックの最後「永遠の永遠のしみわたる世界」から入れてみたところ予想していた以上にきれいにつながったのでこれも好きな組み合わせ。

人ならざる者が踊る一方、人もまた妖しく躍る午前2時。

Needle Light

そしてNeedle Lightです。
担当アイドルの曲であること、デレ7th名古屋が控えていたこともあり、流すことは最初から決めていました。

自分に自信があるわけではないけれど、それでも、というこの曲を経て、少しずつ夜明けに向かっていきます。

Symphonic Brave

Legendersが力強く背中を押してくれる曲。
これもSideM 3rdの幕張Day2で見て、とても印象に残っている曲で。

このへんで午前5時くらいの、空が白み始めてきたあたりのイメージ。

朝焼けのクレッシェンド、空

少しずつ夜が明けて、朝焼けのクレッシェンド。
そして空。

水平線からの日の出と、それを受けて煌めく水面を情景として思い浮かべつつ、生真面目な性格ゆえに悩みがちな琴葉を、Legendersに背中を押し、小鳥さんに優しく包み込んでもらえたらな、と思ってこの曲順にしました。

当日はVJのファミエリくんに「朝焼けは黄金色」のMADを流してもらうように予めお願いしていました。
どれくらいの人に気付いてもらえたかはわかりませんが実はこの繋ぎ、「朝焼け」でも掛かっていたのです。

Spread the Wings!!、TRUE COLORS、Flyers!!

さて、最後のこの三曲、いずれも主題歌級というか、いわゆる全体曲で。

全体曲ですから掛ければある程度盛り上がってもらえることは想像に難くないのですが、全体曲だからこそ、それを掛けるに値する、不自然でない、いわゆるぶち込みではない流れを丁寧に作りたいという思いがあります。

その点、今回の空からの流れは夜が明けて朝焼けの空に翼を広げ、輝き、飛びあがると、自分で書いててちょっと恥ずかしくなるくらいド直球ではありますが、流れを作ることが出来たのではないかと思っています。

他の種類の終わり方も順次研究していきたくはありますね。

おわりに & M@STER BOWL #5 開催予定のお知らせ

はい。ということで、M@STER BOWL #4のセットリストを例にとってどのようなことを考えながらセットリストを組んだかをネタばらししました。

アイマスの曲を通勤や通学中に聴く、ライブを観る。
色々な楽しみ方でアイマスの音楽を楽しまれていることと思いますが、そこに第三の楽しみ方としてDJイベントでアイマスの曲を浴びる、踊るというのもまた一つの楽しみ方として加えてもらえたらと思っています。

ただ純粋に音を浴びて楽しむもよし、「この人はどうしてこの曲順で繋いだんだろう?」と繋がりを考えながら聴いてみるもよし。

そして来年3月20日(金祝)、シャイニーカラーズのスプリングフェスティバルの前日に次回、 #5 を開催することになりました。
twipla.jp

今回M@STER BOWLでは初めての試みとしてメインとラウンジの2フロア構成、そしてラウンジに出演されるDJの公募をします。
皆様のご来場、ご応募、お待ちしています。

*1:3連符が2つで1小節を構成する

*2:Up!10sion♪Pleeeeeeeeease!の他にはFlip Flopやエヴリデイドリームなどがあります

*3:同作品内での繋ぎをあまりしないので、その発想が頭から抜け落ちていた

*4:その時は後ろにLEMONADE、大都会交響楽と続いた

AWSの複数リージョンを扱うTerraformを0.11系から0.12系に更新するときにproviderまわりでハマった話

はい。 Terraformの小ネタです。

この記事は第二のドワンゴ Advent Calendar 2019の参加記事です。*1 ドワンゴ Advent Calendar 2019もありますのでそちらも併せてご覧ください。

Terraformの0.12系がリリースされてから半年ちょっと経ちましたね。

Terraformを0.11系以前から0.12系へアップデートする作業を公私合わせて片手に収まらないくらいの回数なぜか行っているのですが、複数リージョンを扱うようなmoduleを0.12対応させる際に微妙に手間取った点があるので、これはきっと他にも同じような状況の人がいるに違いない*2ということで書きます。

結論から言うと

  • 0.12系ではaliasで別名を付けたproviderをmoduleから暗黙には使えなくなった
  • module内でaliasを張られたプロバイダを使うにはmoduleを統合する側のtfファイルから明示的に渡す必要がある
  • 複数providerを受け取る側のmoduleでも空のprovider記述をして存在を示す必要がある

詳しく

この事象に遭遇するのは、terraform公式のRepository Structureのページで解説されているような、modulesディレクトリの中で各種リソースを定義するmoduleを作り、それを各環境ごとに作成したディレクトリの中で組み合わせているディレクトリ構成のterraformにおいて、SESなどの特定リージョンにしか存在しないサービスを使っているときや、cloudfrontで使うSSL証明書ACMで発行しているときだと思います。

具体的には、cloudfrontはどのリージョンのエンドポイントからも操作することができますが、cloudfrontで使用するACM発行のSSL証明書バージニア北部リージョンで発行しなければなりません。

また、SESもバージニア北部、オレゴンアイルランドでしかサービス提供されていないため、日本のサービスが使おうとするとだいたいバージニア北部かオレゴンを使うことになるでしょう。

とはいえ日本のサービスはほとんどのリソースを東京に置いているわけで、ここでterraform上で複数のリージョンを扱う必要が出てきます。

0.11系までは単に呼び出す環境側で
environments/dev/config.tf (0.11の構文)

provider "aws" {
  region  = "${var.region}"
  version = "1.31.0"
}

provider "aws" {
  alias  = "the_other_region"
  region  = "${var.the_other_region}"
}

としてaliasが書かれた二つ目のproviderを作り、参照される側のmoduleの中でも
modules/example/s3.tf (0.11の構文)

resource "aws_s3_bucket" "the_other_regions_bucket" {
    provider = "aws.the_other_region"
    bucket   = "the-other-regions-bucket"
    region   = "${var.the_other_region}"
}

とすればvar.the_other_regionで設定したリージョンにthe_other_regions_bucketを作ってくれました。

が、これを0.12系の構文に0.12upgradeサブコマンドなどを使って書き換えるとinitが通って一安心と思いきや、

To work with module.example.aws_s3_bucket.the_other_regions_bucket its original provider
configuration at module.example.provider.aws.the_other_region is required, but it
has been removed. This occurs when a provider configuration is removed while
objects created by that provider still exist in the state. Re-add the provider
configuration to destroy module.example.aws_s3_bucket.the_other_regions_bucket, after
which you can remove the provider configuration again.

などと表示されてplanが通りません。

Terraformのmultiple providerに関するセクションを参考に
environments/dev/modules.tf (0.12の構文)

module "example" {
  source = "../../modules/example"
  the_other_region = var.the_other_region

  providers = {
      aws = aws
      aws.the_other_region = aws.the_other_region
  }

としてもまだダメ。

実は上で示したエラーメッセージをよく見ると書いてあるのですが、module.example.provider.aws.the_other_regionと書いてある通り、moduleの側にもprovider.aws.the_other_regionが存在することを示すための記述が要ります。下記のように。

modules/example/variables.tf

provider "aws" {
}

provider "aws" {
  alias   = "the_other_region"
}
【以降略】

これで解決。

というわけで

今回の例のような、同種のproviderを複数個、aliasを付けて使用するようなTerraformを0.12系に更新する際、0.12upgradeサブコマンドはいろいろよしなに書き換えてくれるものの、providerの受け渡しに関する記述まではさすがにやってくれないので注意、という話でした。

余談

terraform全体としては複数のリージョンを扱うが、一つのmodule内では一つのリージョンしか扱わない場合、
environments/dev/modules.tf (0.12の構文)

module "example" {
  source = "../../modules/example"
  the_other_region = var.the_other_region

  providers = {
      aws =  aws.the_other_region
  }
}

とも書けます。

この場合modules/example/s3.tfaws_s3_bucket.the_other_regions_bucketのprovider指定とmodules/example/variables.tfの空のprovider定義を書く必要はありません。

というか多分providerが暗黙に渡されていた問題の解決とmoduleの再利用性を高めるという二つの目的のもとにこうしたのではないかな…という気がします。

*1:去年のアドベントカレンダーもTerraformネタだったな俺…

*2:また数か月後の未来の自分かもしれない…と思ったけど手が届く範囲はさすがに0.12系にしきったはず

ミリシタのG♡Fで64位になった話

はい。

ミリシタのプラチナスターシアター G♡Fで個人ポイントランキング64位になりました。 そして、ラウンジランキングで27位でした。 皆様お疲れさまでした。

ポイントを競い合う系のイベントランキングで2桁順位に入賞するのはデレステでのNeedle Light 52位以来*1ですが、Needle Lightのときは特に記録を残していなかったので、せっかくだしG♡Fをどのように走っていたかについて、書き残しておこうと思います。

開催予告された日(9/26のミリラジ)

ミリラジで予告のMVが流された瞬間は、行きつけの居酒屋で呑んでいるときでした。

一緒に呑んでいた友人から「あっ、次ガルフレ!」と言われ、5秒くらい理解できず、その後に出た言葉が「あっ俺? 俺ですか?」でした。笑われました。

周年イベントであるBRAND NEW PERFORMANCE!!ならびにUNI-ON@IR!!でどちらも律子100位以内を取った経験が一応あったこと、以前より「ミリシタでG♡F来ないかな」と言い続けていたこと、そして律子の初めてのPST報酬カード、それも上位だったこともあって、走る決心をするのに時間は要りませんでした。

その次にしたのは、会社のSlack*2のimasチャンネルで「時が来た」と宣言することでした。

f:id:takayamaki:20191012034157p:plain
会社でも同じアイコンだしハンドルネームOKな文化なのでフサギコと呼ばれている

弊社はSlackにimasチャンネルが存在する楽しい会社です。

イベント開始までにまず、過去のボーダーがまとまっているWebページを見て、ジャングル☆パーティー以降の同じ174時間のイベントと比較して、ボーダーは110万くらいかな、と見当を付けました。

そしてその予測に対しての安全圏として120万ptと目標を設定し、必要ライブ回数/ジュエルを概算できるWebページで計算したところ、必要ジュエルが52400で4倍イベント曲が418回程度と概算されたので、それに向けてコミュ石などの回収をしていました。

開幕、そして前半

さて、ここからはイベントTOPのスクリーンショットとともに振り返っていきます。

前半は体力消費30のお仕事を10回行い、Sentimental Venus*3を1回やる、というサイクルをひたすらこなしてアイテムを貯め続けるのですが、お仕事を10回もやっていれば大抵1枚はオートライブパスが落ちるのでそれほど大変ではありませんでした。

10/3(木)

…と始まった矢先から、実は初日から開催予告された日にもいた、行きつけの居酒屋で呑んでいました。 だって、カツオのたたきが日替わりで安かったので…一応、その間もずっとお仕事を回してはいました。 日付が変わって1時過ぎの様子がこちら。

f:id:takayamaki:20191012035021p:plain
10/4 01:22

10/4(金)

この日は普通に仕事に行き、普通に帰ってきてひたすらお仕事を回していました。特筆すべき事項はありません。

f:id:takayamaki:20191012035200p:plain
10/5 00:03

10/5(土)

土曜は少し出かけたあと、その日に20歳になった知り合いを祝いにまたしても行きつけの居酒屋に少しだけと思いながら顔を出したらラグビーの試合が熱すぎて試合終了するまで帰るに帰れなくなり…もちろん、その間もずっとお仕事を回してはいましたけれども。

f:id:takayamaki:20191012035249p:plain
10/6 00:02

10/6(日)

日曜は11時ごろに起床してお仕事を回し続け、シャイニーカラーズのストレイライトのリリイベに行きました。

リリイベの終演後、同じ夜の部に参加していた友人とストレイライトの話がしたくなってしまい、またしても行きつけの居酒屋に行き…その間もやっぱり、ずっとお仕事を回してはいましたけれども。

f:id:takayamaki:20191012043806p:plain
10/7 00:02

振り返ってみると4日のうち3日もお酒を飲んでいたことに気付きましたが、イベントランキングは100位以内を維持できてしまっているのが我ながら面白い点。

後半

そうこうしているうちに後半が始まりますが、前半とは違ってお仕事をせずに貯めたアイテムをひたすら4倍で消費していきます。お仕事を間に挟みませんから、オートライブパスが一瞬で底を尽き、手でやらざるを得なくなります。

そのため、アイテムを全て使い切るまでが兎にも角にも大変でした。

f:id:takayamaki:20191012044055p:plain
10/7 13:07

折り返し開始日の13時時点で貯めていたアイテムは上記の通り25万4000個でした。

10/7(月)~10/8(火)

この二日間は特筆事項がなかったのでまとめてしまいます。仕事以外で手が空いているタイミングではひたすら4倍でアイテムを消費していました。

f:id:takayamaki:20191012045321p:plain
10/8 00:03
f:id:takayamaki:20191012045400p:plain
10/9 00:03

画像より、1日で10万個くらい消費していることがわかります。

1回720個で、曲が2分22秒、それにロード等が入って2分50秒と仮定すると6.55時間、つまり1日6時間33分くらい費やしていた計算になります。

それだけの回数やっているとさすがに聴き飽きたり手が疲れたりしてしまうわけで、そういうときは聴きそびれてしまっていたアイマスMUSIC ON THE RADIOアーカイブや、音声編集をたまに手伝ったりしている友人のポッドキャストを聴きながら、6mixや4mixで回していました。

10/9(水)

この日も仕事をしていたわけですが、ボーダーの伸びが思ったより速く、まだまだ差こそあるものの最終日も仕事をすると負ける可能性が高いと思い、有給休暇を申請しました。 イベント最終日に有給休暇を申請したのは初めてです。

f:id:takayamaki:20191012045454p:plain
10/10 00:03

そして、前半で貯めたアイテムをやっと使い切ったのが深夜27時22分ごろ、105万8345ptでした。

f:id:takayamaki:20191012045728p:plain
10/10 03:22

10/10(木)

そんなわけでこの日は有休をとっていたので、12時すぎに起床してから21時の終戦までずっと走り続けていました。

f:id:takayamaki:20191012050557p:plain
10/10 12:36

最終日でしたから、走りつつも随時、ボーダーと自分、彼我のptの時速差を計算していました。

具体的には、もし仮にアイテムを消化し続けるとすると時速4万5千ptと計算されたのに対し、僕はアイテムを既に使い切っており、貯めては使ってを繰り返すと時速1万5千~2万ptという計測だったので、もしボーダーが最速で上がってきた場合にあと何時間で追いつかれるか、というのを計算していました。

実は100位ボーダーが19時半時点で111万4000ptなのに対して、その時点で僕は118万8000ptだったためその時点で既にほぼ勝ち確と判断できていたのですが、「律子上位なんてめったにないしせっかくだし」ということで最後まで走り続けた結果、1,216,435ptで64位にて決着となりました。

まとめ

ということで、G♡Fで64位になった話でした。

デレステのNeedle Lightで52位、ミリシタのBRAND NEW PERFORMANCE!!で律子66位/全体5263位、UNI-ON@IR!!で律子29位/5518位、そして今回のG♡Fで64位になった感想ですが、イベントで上位を取るというのは例えるならばマラソンに挑戦するようなもので、どれくらいのキツさかが予想できて、覚悟できて、対策できるなら社会人でも思ったよりなんとかなる、ということがわかりました。

デレステは放置編成を使えば時間効率は下がるにしても手を端末から離せるので割と楽だったのですが、ミリシタのプラチナスターシアターでは、折り返しを超えた瞬間にアイテムを一度使い切るまでひたすら叩き続ける必要があり、手を端末から離せないのがなかなか大変でした。

とはいえまあ、少なくとも今年はもうここまで大爆走することはないでしょう…来年のミリシタ周年イベントまではきっと。

次に律子が完走またはランキング報酬になるのは…Helloコンチェルトでしょうか。

自分に自信がなさすぎて見誤る律子は今回のイベントコミュで垣間見れたので、次は調子に乗りすぎて失敗しかけるほうの律子が見たい…とか思ってしまいますね。

そうだぞ。

*1:後日不正者BANで繰り上がって51位

*2:会社で使っている仕事用チャット https://slack.com/intl/ja-jp/

*3:ご存知の方もいると思いますが、Sentimental Venusがミリシタの曲の中で一番短い

SANsにワイルドカードが入ったACMのDNS認証なSSL証明書をTerraformで作るときのハマりどころ

この記事の内容はaws provider v2まで古いバージョンに関する内容です。

aws provider v3以降については下記URLの公式ドキュメントを参照してください。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate#referencing-domain_validation_options-with-for_each-based-resources

はい。

Terraform、便利ですよね。

AWSAmazon Certificate Managerも、CloudfrontやELBで使えるSSL証明書が無料で発行出来て便利ですよね。

ACMは2017年11月にDNS認証でのSSL証明書発行ができるようになりましたが、コモンネームのサブドメインワイルドカードでSANsに指定した証明書をTerraformからDNS認証で作ろうとすると、微妙にハマりどころがありますので、今回はそれについての記事です。

この記事はTerraform 0.12.9、terraform-provider-aws 2.30.0時点の情報です。

三行で

  • ACMDNS認証なSSL証明書において、ワイルドカードドメインの認証レコードはその親ドメインと同じ内容
  • そのため素直に記述するとterraformが同じレコードを2つ作ろうとしてしまいコンフリクトして失敗する
  • そのためdistinct関数で重複を除く必要があるが、それにも工夫が要る

エラーになる書き方

SANsにワイルドカードも含んだ状態の認証レコードを素直に書こうとするとこうなると思います。 *1

resource "aws_acm_certificate" "example" {
  validation_method = "DNS"
  domain_name       = "example.fusagiko.jp"

  subject_alternative_names = [
    "*.example.fusagiko.jp"
  ]
}

resource "aws_route53_record" "validation_records" {
  count   = length(aws_acm_certificate.example.domain_validation_options)
  zone_id = data.aws_route53_zone.zone.zone_id
  ttl     = 300

  name    = element(aws_acm_certificate.example.domain_validation_options, count.index).resource_record_name
  type    = element(aws_acm_certificate.example.domain_validation_options, count.index).resource_record_type
  records = [
    element(aws_acm_certificate.example.domain_validation_options, count.index).resource_record_value
  ]
}

しかし、これを適用しようとすると、下記のようなエラーが出てしまいます。

------------------------------------------------------------------------

Error: Invalid count argument

  on test.tf line 39, in resource "aws_route53_record" "validation_records":
  39:   count   = length(aws_acm_certificate.example.domain_validation_options)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.

なぜならば、aws_acm_certificate.exampleが作成されるまでaws_acm_certificate.example.domain_validation_optionsが何要素の配列になるかわからないため、aws_route53_record.validation_recordsを何個作ればよいか決定できないからです。

これを回避するためにはエラーメッセージに書いてある通り-targetオプションでaws_acm_certificate.exampleを先に作成すればいいのですが、それでもエラーが出てしまいます。

aws_route53_record.validation_records[0]: Creating...
aws_route53_record.validation_records[1]: Creating...
aws_route53_record.validation_records[1]: Still creating... [10s elapsed]
aws_route53_record.validation_records[1]: Still creating... [20s elapsed]
aws_route53_record.validation_records[1]: Still creating... [30s elapsed]
aws_route53_record.validation_records[1]: Creation complete after 34s [id=HOGEHOGEHOGEHO__4a20d7faeee244c473bf78955c41b7a0.example.fusagiko.jp._CNAME]

Error: [ERR]: Error building changeset: InvalidChangeBatch: [Tried to create resource record set [name='_4a20d7faeee244c473bf78955c41b7a0.example.fusagiko.jp.', type='CNAME'] but it already exists]
        status code: 400, request id: 5f34c889-5549-4e34-b217-f252400ab52e

  on example.tf line 34, in resource "aws_route53_record" "validation_records":
  34: resource "aws_route53_record" "validation_records" {

これはaws_acm_certificate.example.domain_validation_optionsをoutputしてみればわかるのですが、ワイルドカードサブドメインはその親ドメインと認証レコードが同じであり、重複してしまうためです。

acm_validation_records = [
  {
    "domain_name" = "example.fusagiko.jp"
    "resource_record_name" = "_4a20d7faeee244c473bf78955c41b7a0.example.fusagiko.jp."
    "resource_record_type" = "CNAME"
    "resource_record_value" = "_4c37d9c32e02cb55007e1f91b58bfe9f.olprtlswtu.acm-validations.aws."
  },
  {
    "domain_name" = "*.example.fusagiko.jp"
    "resource_record_name" = "_4a20d7faeee244c473bf78955c41b7a0.example.fusagiko.jp."
    "resource_record_type" = "CNAME"
    "resource_record_value" = "_4c37d9c32e02cb55007e1f91b58bfe9f.olprtlswtu.acm-validations.aws."
  },
]

ところでTerraformにはdistinct関数が存在し、これを使うと配列中の重複する要素を除ける…のですが。
上記outputを見ていただくとわかるのですがdomain_nameが異なるために単にdistnict関数に渡すだけでは重複が解消されません。

エラーにならない書き方

というわけで、aws_acm_certificate.example.domain_validation_optionsの中身のmapから、for~inを使ってdomain_name以外を持ったmapを作ってdistnictし、これを使います。

全体像がこちら。

locals {
  acm_validation_records_distincted = distinct([
    for record in aws_acm_certificate.example.domain_validation_options.* :
    {
      resource_record_name = record.resource_record_name
      resource_record_type = record.resource_record_type
      resource_record_value = record.resource_record_value
    }
  ])
}

resource "aws_acm_certificate" "example" {
  validation_method = "DNS"
  domain_name       = "example.fusagiko.jp"

  subject_alternative_names = [
    "*.example.fusagiko.jp"
  ]
}

resource "aws_route53_record" "validation_records" {
  count   = length(local.acm_validation_records_distincted)
  zone_id = data.aws_route53_zone.zone.zone_id
  ttl     = 300

  name    = element(local.acm_validation_records_distincted, count.index).resource_record_name
  type    = element(local.acm_validation_records_distincted, count.index).resource_record_type
  records = [
    element(local.acm_validation_records_distincted, count.index).resource_record_value
  ]
}

こうすることでSANにワイルドカードが含まれていたとしてもエラーにならずにapplyすることができます。

とはいえ、aws_acm_certificate.exampleをtarget指定しての先行applyは変わらず必要です。

これはplanの時点で全てのリソースの作成数などを確定しておかなければならないterraformの仕様上、現時点では仕方ないと思われます。
Terraformの今後の開発に期待しましょう。

もしくはこれの回避方法をご存知の方がおられましたらご教授ください。

*1:aws_route53_zoneやaws_acm_certificate_validationは省略しています

ニコニコ動画にできるだけ高画質な動画を比較的少ない労力で投稿する

はい。

この記事はKaku-tail THE@TERやMSC2019 Re:で本編動画制作を担当した際に使用した、ニコニコ動画にできるだけ高画質な動画を比較的少ないエンコード時間で投稿する方法について述べる記事です。

下記がこの方法でエンコードし、投稿した動画です。

この記事はあくまで投稿用の動画エンコードの労力を少なくする方法であって、フルHDでの動画制作そのものが厳しい性能のPCである場合は想定していません。

3行で

Flash亡き今、再エンコードはどうしても必要

この節はエンコードの方法については一切言及していません。 読みたい場合はクリックすると開きます かつて、ニコ動は「どのデバイスでも再生できる」という点の担保をFlashに頼ることで、一定の条件下で再エンコードをしないという非常に特殊な仕様を実現していました。
これにより動画投稿者の手元で頑張ってエンコードすればそれだけ良い"画質"で見てもらうことが可能だったわけです。

しかし、H.264にはパラメータが膨大な数あり、再エンコードされない一定の条件を守るのも大変で、結果としてパラメータの解説記事が膨大な数書かれたり、つんでれんこという偉大なx264のラッパー*1が誕生したりしました。

しかし、AppleFlashをサポートしない方針になったことで再エンコードをしないという非常に特殊な仕様の根底となっていたFlashに頼れなくなり、ChromeでもFirefoxでもSafariでも、さらには2011年発売の携帯ゲーム機である3DSでも(!)等しく再生できるようにするという担保をニコ動と動画投稿者のどちらかが負う必要が出てきたと。

mp4やH.264というのは非常に複雑なもので、動画投稿者があらゆるデバイスで再生できることを担保するのは事実上不可能です。
一か所指定を間違えたり忘れたりしただけであっさりと、特定のデバイスでだけ再生できないmp4が出来上がったりします。

ので、ニコ動がその辺を全て請け負うために再エンコードをするようになったわけです。
これはもうそういうものとして受け入れるしかないです。Flashはもう帰ってこない。

3GBという投稿サイズ制限をギリギリまで使う

さて、再エンコードから先に干渉できないならどこを変えるかというと話は単純で、ニコ動のエンコーダに渡すフルHDの動画の画質を良くしましょう。

フルHD指定なのは単純に、ニコ動の投稿動画は投稿されたファイルがフルHD以上かつ30分以内という条件を満たしたときにフルHD画質の動画が生成され、これが最もビットレートが高いためです。

とすると「良い画質」って何、という話になるわけですが、ものすごく簡略化すると、

  • ビットレートが同じ場合、エンコードに長い時間(正確には演算量)を使うと画質は良くなる傾向にある
    • が、長い時間をかけるようにしても増えた時間に対して少しずつ効果は減っていく
    • 時間を2倍使っても画質が2倍良くなるわけではない*2
  • エンコーダの設定が同じ場合、ビットレートを高くすると画質は良くなる傾向にある
    • 高いビットレートにしても増やしたビットレートに対して少しずつ効果は減っていく
    • 元動画をそのまま表せるようになったらそれ以上増やしても当然意味がない

という関係になっていることは直感的にご存知かと思います。

つんでれんこはそこそこの長い時間(多くの演算量)を使って、数十から数百MB程度のファイルサイズの範囲内でできるだけ良い画質を実現できるように実写向け、アニメ向け、など複数のプリセットを用意していました。 *3
数百MB程度のファイルサイズに収まるようにしているのはかつてのニコ動には投稿した動画のファイルサイズの合計に制限があったからですが、現在その制限はなく、単純に投稿動画の数に上限があるのみとなっています。*4

であるならば、ビットレートを高くして3GBギリギリまで使えば、それほど多くの演算量を使わずとも、より良い画質の元動画をエンコードできるでしょう。

ハードウェアエンコードを使う

3GBがどれくらい大きいサイズであるかと言うと、5分の動画であれば81.92Mbps、20分の動画でも20.48Mbpsもの高いビットレートにできます。
ニコニコ動画アイドルマスタータグには2019年9月23日時点で411,952個の動画が投稿されており、そのうち5分以内が268,880個、5分より長く20分未満が115,056個、20分超は28,016個であり、97%くらいの動画は20.48Mbps以上のビットレートを元動画で使用できる計算です。

ブルーレイの規格上の上限が40Mbpsで、多くの場合は25Mbps程度ですから、ここまで来るとたかが数時間程度多めの時間をソフトウェアエンコードに使ったところで人の目で見分けるのは難しい領域になってきます。

そこで選択肢として浮上してくるのがハードウェアエンコードです。
ハードウェアエンコードと言うと画質が悪いというイメージを持たれる方がいるかもしれませんが、数十Mbpsの領域になってくるとそもそも人の目で見分けるのは難しい領域ですから、ハードウェアエンコードエンコードにかかる待ち時間を短くしたほうが制作過程でのストレスが少なく済みます。

aviutlを使用している人はrigayaさんのハードウェアエンコードによる出力が可能となるプラグインシリーズ、NVEncQSVEncVCEEncを使うとよいでしょう。 拡張編集で編集した動画をハードウェアエンコードで直接H.264として書き出せるので、投稿直前の修正とチェックのサイクルを短時間で回すことができます。

実際の設定例

下記がMSC2019Reの決勝ブロックの動画で実際に使用したNVEncの設定です。
このうちビットレートの項目のみ動画の長さによって変わります。

f:id:takayamaki:20190924032520p:plainf:id:takayamaki:20190924032602p:plain

実際どれくらいの画質劣化があるのか

「数十Mbpsの領域になってくるとそもそも人の目で見分けるのは難しい」と一旦書きはしましたが、実際どの程度の劣化があるのか気になったので検証してみました。

下記の6枚のpng画像は、Ut Video YUV420 BT.709で書き出した可逆圧縮のaviファイルと、NVEncを使用してエンコードした実際に投稿に使用したファイルの差分*5を取り、それを16回重ねて目視できるようにしたものです。

いずれも動画中のどの箇所であるか判別はできますが、16回重ねて目立たせてやっとこの程度なので、「数十Mbpsの領域になってくるとそもそも人の目で見分けるのは難しい」と主張できるのではないかと思います。

f:id:takayamaki:20190924032045p:plainf:id:takayamaki:20190924032047p:plainf:id:takayamaki:20190924032052p:plain
f:id:takayamaki:20190924100519p:plainf:id:takayamaki:20190924100526p:plainf:id:takayamaki:20190924100534p:plain

ここからどのような画質の動画が再エンコードによって生成されるかはニコ動のみぞしるところですが、少なくとも元動画まではそれほど頑張らずとも十二分な画質を実現できていると思います。

まとめ

ということで、ニコニコ動画にできるだけ高画質な動画を比較的少ない労力で投稿する方法でした。

なんか大仰な記事タイトルにしてしまいましたが、やってることは別に凄いことでもなんでもなく、単に「画質面は3GBの投稿サイズ制限ギリギリまで使った高ビットレートの力技で、数十Mbpsのビットレートではもはや画質を人の目では区別できないのでハードウェアエンコードエンコード待ち時間を減らす」というだけのことです。

ソフトウェアエンコードの所要時間はCPUの性能に大きく左右されますが、ハードウェアエンコードはソフトウェアエンコードほど所要時間に差が出ませんので、古めのCPUでソフトウェアエンコードを頑張っていた人ほど待ち時間が短くなると思います。

ハードウェアエンコード、うまいこと使っていきましょう。

余談

x264でpreset veryslowでcrf 0(=ロスレス)のエンコードを試したところ、i9 9900Kで0.3倍速という激遅エンコードでしたが9.42GBになりました。
つまり、ニコ動の投稿サイズ制限が10GBくらいになったら「x264でpreset veryslowでcrf 0(=ロスレス)のエンコードを頑張りましょう」が画質面での最適解になります。
所要時間も考慮に入れると、画質面では次善になりますが今回ご紹介した「投稿サイズ制限ギリギリのビットレートでNVEncを使ってエンコードする」が引き続き候補になると思います。

更にNVEncのロスレスモードを試したところ、28.8GBになったのでニコ動の投稿サイズ制限が30GBくらいになったら「NVEncでサクッとロスレスエンコードしましょう」が画質、所要時間両面での最適解となるでしょう。
※ストレージの転送速度やインターネット回線の上り速度は考慮しないものとする

*1:扱いを簡単にするために包む(wrap)ものという意味

*2:まず画質が2倍良くなる、の2倍って何という定義の話も今回はしない

*3:いわゆるつんでれんこ完結版、TDEncのリリースノートに「1080p制限緩和(30分、3GB)に対応」と書いてあるのですが3GBいっぱい使うような挙動にはなっていないように見える https://tdenc.com/blog/2018/04/27/tdenc-tdenc2-298/

*4:http://blog.nicovideo.jp/niconews/ni065283.html

*5:これが不可逆圧縮の誤差、すなわち"劣化した画質"といえる