にゃみかんてっくろぐ

猫か百合を見守る壁になりたい

.NET 5: System.Drawing.Common と ImageSharp 、 Windows と Linux でテキストレンダリングの差をみてみる

クロスプラットフォーム。プラットフォーム間の差異に悩まされる地獄だ。

ということで、 .NET 5 でのテキストレンダリングの差を調べてみた。フォントは等幅な Roboto Mono とプロポーショナルな Roboto を用いた。

System.Drawing.Common 5.0.2

Windows の GDI+ API を使うライブラリ。ただし、 Unix 系でも Mono のオープンソース実装 libgdiplus を入れると動作する。ソースコードを拝見すると、 GdiplusNative.Unix.cs といったファイルがあって大変そうだ…

TextRenderingHint プロパティレンダリングモードが指定できるので、すべて試した。

Roboto Mono

f:id:no_clock:20210524013156p:plain
Windows 10 20H2 (200 % 拡大)

f:id:no_clock:20210524013210p:plain
Ubuntu 20.04.1 LTS / libgdiplus 6.0.4+dfsg-2 (200 % 拡大)

f:id:no_clock:20210524012841p:plain
不透明度 50 % で重ねた (200 % 拡大)

全く同じ結果は得られなかったが、 AntiAliasGridFit と ClearTypeGridFit はかなり近い。

Roboto

f:id:no_clock:20210524221129p:plain
Windows 10 20H2 (200 % 拡大)

f:id:no_clock:20210524221205p:plain
Ubuntu 20.04.1 LTS / libgdiplus 6.0.4+dfsg-2 (200 % 拡大)

f:id:no_clock:20210524221221p:plain
不透明度 50 % で重ねた (200 % 拡大)

結構な差が出てしまった。

SixLabors.ImageSharp 1.0.3 / SixLabors.ImageSharp.Drawing 1.0.0-beta11

クロスプラットフォームライブラリ。 System.Drawing 名前空間のドキュメントにも「代替手段」として例示されている。

図形や文字を描画するための SixLabors.ImageSharp.Drawing はまだベータ版で、 Getting StartedAPI 変えるかもよと注意書きが入っている。

カーニング (TextOptions.ApplyKerning) とアンチエイリアス (GraphicsOptions.Antialias) の有無をそれぞれ変更して試した。

Roboto Mono

f:id:no_clock:20210524031007p:plain
Windows 10 20H2 (200 % 拡大)

f:id:no_clock:20210524031028p:plain
Ubuntu 20.04.1 LTS (200 % 拡大)

f:id:no_clock:20210524031040p:plain
不透明度 50 % で重ねた (200 % 拡大)

同じ結果が得られた。

Roboto

f:id:no_clock:20210524221146p:plain
Windows 10 20H2 (200 % 拡大)

f:id:no_clock:20210524221241p:plain
Ubuntu 20.04.1 LTS (200 % 拡大)

f:id:no_clock:20210524221302p:plain
不透明度 50 % で重ねた (200 % 拡大)

プロポーショナルフォントでも同じ結果が得られた。

処理時間

こうなると ImageSharp を使わない理由はないように見えるが、処理時間に大きな差がある (以下 10 回の平均値)。

プラットフォーム System.Drawing.Common SixLabors.ImageSharp
Windows 10 20H2 27 ms 419 ms
Ubuntu 20.04.1 LTS 59 ms 282 ms

詳しく見てみると、 4 行あるテキストの 1 行目のレンダリングだけが遅い。フォントの読み込みだろうか。

タイミング 経過時間
(Windows 10 20H2)
経過時間
(Ubuntu 20.04.1 LTS)
新規画像生成
(640x480, 白塗りつぶし)
38 ms 43 ms
FontCollection, Font 作成 60 ms 72 ms
1 行目レンダリング 365 ms 228 ms
2 行目レンダリング 368 ms 231 ms
3 行目レンダリング 372 ms 234 ms
4 行目レンダリング 375 ms 238 ms
PNG 画像出力 419 ms 282 ms

AWS App Runner のオートスケールは GCP Cloud Run ほど滑らかではなさそう

先日、 AWS から App Runner というサービスが発表された。すごく GCP の Cloud Run っぽい。

ただ、新規サービス作成に 5 分待たされてしまった。

これはスケーラビリティにも不安があるな… と思い、ざっくりオートスケールの様子を調べた。

方法

  1. 「レスポンスに 1 秒かかる Web サーバ」を用意する
  2. コンテナあたりの同時実行数 (同時処理リクエスト数) を 1 としてサービス作成
  3. PC からリクエスト投げて観察

コンテナの必要数が、クライアントの同時接続数に比例して増えていくだろうという算段である。コードは Gist に掲載している。

GCP Cloud Run と AWS App Runner のスケーリングの様子をみる · GitHub

サービスの設定は以下の通り。

項目 AWS App Runner GCP Cloud Run
リージョン ECR: ap-northeast-1
App Runner: ap-northeast-1
(アジアパシフィック (東京))
GCR: asia (アジア)
Cloud Run: asia-northeast1 (東京)
vCPU 1 1
メモリ 2 GB 256 MiB *1
同時実行数 1 1
最大インスタンス 25 25
最小インスタンス 1 *2 0

同時接続数 4 で観察

同時接続数を 1 分毎に 1 ずつ増やし、 4 まで増やして観察した。

AWS App Runner: 20 インスタンスも起動する

上が リクエスト/秒 、下がインスタンス数のグラフである。

f:id:no_clock:20210521223935p:plain

f:id:no_clock:20210521223949p:plain

同時接続数 4 のため、計算上は 4 インスタンスあれば十分。しかし、実際は 20 インスタンス起動している瞬間もある。

リクエスト数にきっちり追従していくというより、 余力を多めに確保しながらゆるやかに追従 しているように見える。

GCP Cloud Run: 4 インスタンスだけ起動する

上が リクエスト/秒 、下がインスタンス数である。こちらはリクエスト数にきっちり追従しているように見える。

f:id:no_clock:20210521224742p:plain

f:id:no_clock:20210521224750p:plain

突然 20 同時接続して観察

先ほどの結果から、 突然大量のリクエストを投げたら、 App Runner では捌けないのでは? という疑問が湧いたので観察。

httperf で一気に 20 接続、合計 1,000 リクエストを試みた。

$ httperf --ssl --server <hostname> --rate 20 --num-conn 20 --num-call 50

AWS App Runner: 過半数がエラーに

正常なレスポンスはわずか 209 個で 2 割ちょっと。過半数がエラーとなってしまった。

Total: connections 20 requests 762 replies 749 test-duration 28.729 s

Connection rate: 0.7 conn/s (1436.4 ms/conn, <=20 concurrent connections)
Connection time [ms]: min 552.5 avg 19523.6 max 28361.4 median 21879.5 stddev 6986.8
Connection time [ms]: connect 28.6
Connection length [replies/conn]: 37.450

Request rate: 26.5 req/s (37.7 ms/req)
Request size [B]: 95.0

Reply rate [replies/s]: min 7.6 avg 29.7 max 56.0 stddev 17.6 (5 samples)
Reply time [ms]: response 503.2 transfer 0.0
Reply size [B]: header 205.0 content 153.0 footer 0.0 (total 358.0)
Reply status: 1xx=0 2xx=209 3xx=0 4xx=540 5xx=0

CPU time [s]: user 2.97 system 25.76 (user 10.3% system 89.7% total 100.0%)
Net I/O: 11.6 KB/s (0.1*10^6 bps)

Errors: total 13 client-timo 0 socket-timo 0 connrefused 0 connreset 13
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

1 分毎のメトリクスであるが、同時接続数 20 に対し 7 インスタンスほどしか起動できておらず、インスタンスの増加が間に合っていないように見える。

f:id:no_clock:20210521225738p:plain

GCP Cloud Run: すべて正常応答

全リクエスト正常にレスポンスを受信した。

Total: connections 20 requests 1000 replies 1000 test-duration 52.689 s

Connection rate: 0.4 conn/s (2634.5 ms/conn, <=20 concurrent connections)
Connection time [ms]: min 51237.7 avg 51576.9 max 52157.5 median 51512.5 stddev 252.3
Connection time [ms]: connect 48.2
Connection length [replies/conn]: 50.000

Request rate: 19.0 req/s (52.7 ms/req)
Request size [B]: 87.0

Reply rate [replies/s]: min 13.6 avg 19.0 max 20.0 stddev 1.9 (10 samples)
Reply time [ms]: response 1030.6 transfer 0.0
Reply size [B]: header 378.0 content 278.0 footer 0.0 (total 656.0)
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0

CPU time [s]: user 3.62 system 49.07 (user 6.9% system 93.1% total 100.0%)
Net I/O: 13.8 KB/s (0.1*10^6 bps)

Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

1 分毎のメトリクスにつき若干不正確だが、瞬時にインスタンス数が増加しているようだ。

f:id:no_clock:20210521225521p:plain

まとめ: App Runner のオートスケールは Cloud Run ほど滑らかではなさそう

観察した範囲内では、 App Runner は Cloud Run と比べてオートスケールの動きは緩やか。急激なアクセスの増加に対しては、追従しきれない懸念がありそうな結果だった。

リリースされたばかりのサービスなので、今後に期待。

*1:2048 MiB にしても結果ほぼ変わらず

*2:0 にできない

緊急地震速報の配信を支える技術(仮) / テレビとスマートフォン

緊急地震速報の概要(おさらい)

f:id:no_clock:20210511015605p:plain

地震波の P 波S 波 には速度差があります。この速度差を利用して、揺れの大きな S 波が来る前に予想して知らせるのが緊急地震速報です。

緊急地震速報には 予報警報 の 2 種類があります。テレビやスマートフォン(エリアメール・緊急速報メール)で見聞きするのは 警報 です。警報では、最大震度 5 弱以上が予想される場合に、震度 4 以上が予想される地域などが発表されます。

猶予時間はわずか。迅速な配信が必要

f:id:no_clock:20210511015749p:plain
図: 気象庁|緊急地震速報(警報)発表状況

気象庁|緊急地震速報(警報)発表状況 の「主要動到達時間」では、警報発表から主要動 (大きな揺れ) 到達までの時間が図で示されています。

これを見るとわかるように、猶予時間はごくわずかです(間に合わないこともあります)。そのため、緊急地震速報は迅速に配信する必要があります。

テレビ (NHK): 2 種類の音と表示

NHKデジタルテレビ放送は、 2 種類の音と表示で緊急地震速報を知らせています。

  1. ポーンポーン という音と、画面上部の 緊急地震速報 表示
  2. ピロンピロン という音と、画面下部の震源や地域名の表示

このうち、最初の表示は 文字スーパー 機能を活用していて、素早い配信を実現しています。

f:id:no_clock:20210511025838p:plain
画像: ゆれの前に流れる「緊急地震速報」 | NHK for School

「文字スーパー」で低遅延。 2010 年から実施

ピロンピロン… と後から出てくる映像や音声は、デジタル放送の圧縮・伸長処理によって 3~4 秒の遅延があります。一方で、 緊急地震速報 の文字スーパーは 1~2 秒の遅延で済みます。

f:id:no_clock:20210511023909p:plain

これを組み合わせることで、まずは文字スーパーですぐに知らせつつ、映像で詳細を伝える、ということが可能になっています。文字スーパーは 2010 年 8 月から利用されています(緊急地震速報 地上デジタル放送での迅速化について)。

なお、 NHK の放送設備については 2-1. 緊急地震速報の放送送出設備の概要 [岡本 隆] に概要があります。ラジオの音声を含めて全自動で送出できるような設備が整えられているようです。

スマートフォン: 2 回の表示

スマートフォンのエリアメール・緊急速報メールは、最初に「強い揺れに備えてください」と表示され、数秒後に「緊急地震速報 XX で地震発生。強い揺れに備えて下さい」と表示が差し替わります(端末により若干異なります)。これは不具合ではなく、迅速に配信するための工夫です。

f:id:no_clock:20210511031610p:plain

ETWS で低遅延に

緊急地震速報を含む緊急情報の配信は、 3GPPETWS (Earthquake and Tsunami Warning System) として標準化されています。

配信は 第 1 報 (Primary Notification) と 第 2 報 (Secondary Notification) に分かれています。第 1 報は種別のみに絞って短時間で配信し、第 2 報は続報としてメッセージを配信します。途中で表示が差し替わるのは、この第 1 報・第 2 報の受信によるものなのです。

f:id:no_clock:20210511031940p:plain
画像: 次世代移動通信ネットワークにおける緊急情報の同報配信高度化

まとめ

  • テレビ・スマートフォンとも、配信を 2 段階にわけることで迅速な配信を実現しています。

参考


もともとライトニングトーク向けにスライドを作成していたのですが、発表する場がないので記事に起こしました。

プロダクションレディ開発プロセス ―SIer の開発標準に学ぶ

新しい Web サービスを「本番環境に載せられる品質で作っていく」のは大変です。一体何から考えればいいのでしょう。インフラ? テーブル? API 仕様?

「MVP (Minimum Viable Product) で」とか「アジャイルで」といった話はよく聞きます。一方で「どのような順番で、何を考えていくと、いい感じに出来上がるのか」という話はあまり聞きません。「Web サービス 作り方」などと調べても、個人開発にフォーカスしたものがほとんど。

これに対する私の解は、「SIer の開発標準に学ぶ」です。

SIer の開発標準

大手 SIer は、開発プロセスを標準化した「開発標準」を持っています。「この通りにやれば誰でも一定品質でシステムが作れる」というガイドラインで、ノウハウの塊でもあります*1

ありがたいことに、 TIS は CC BY-SA 4.0Nablarch 開発標準を公開しています。この Nablarch 開発標準をちょっと覗いてみましょう。

(参考) SIer の開発標準例:

Nablarch の開発標準をちょっと覗く

まずは「開発プロセス標準」にある WBS (Work Breakdown Structure) を見てみましょう。 Excel ファイルですが逃げてはいけません。

「1. 概要」シートには、典型的なウォーターフォールでの開発工程の図があります。

f:id:no_clock:20210225023226p:plain
標準WBS > 1. 概要 > 1.2. 開発プロセス より

なるほど。では、「要件定義」ではどういったことを考えるのでしょうか。「2. WBS」シートを見てみます。

要件定義: 非機能要件定義

要件定義に「非機能要件定義」なるワークパッケージがあって、補足説明にはこう記されています。

非機能要件定義書には下記のような内容を記載する。

  • セキュリティ要件
  • バックアップ要件
  • 可用性・信頼性要件
  • 拡張性要件
  • 保守容易性要件
  • 性能要件
  • システム環境要件
  • アプリケーション方式要件
  • 外部接続要件
  • システム監視要件
  • ジョブ運用要件

アーキとインフラが主担当となって作業を進める。

機能だけ実現しても、「ダウンしたことに気づかない(監視要件漏れ)」「ログが出ていなくて調査できない(監視要件漏れ)」「バックアップがなくて戻せない(バックアップ要件漏れ)」なんて事態は望ましくありません。非機能要件定義をすれば、これらを回避できるわけです。

慣れてしまえば「そらそうよ」という内容ですが、意外と忘れたり漏れたりしやすい部分です。

(補足:非機能要件を具体的に決めるには IPA非機能要求グレードがおすすめです)

外部設計: テーブル定義は INDEX も

要件定義の次は「外部設計」を見てみましょう。「データベース論理設計」ワークパッケージのアウトプットに「テーブル定義書」とあります。

テーブル定義書のサンプルがデータモデル設計にあるので見ましょう。

f:id:no_clock:20210225023341p:plain
テーブル定義書サンプル > 1. 企業 より

名称、データ型などのほか、 INDEX の指定まで記入欄があります。

インデックス設計をこの段階で行うかは若干悩ましいですが、少なくとも「インデックス設計は必要な作業である」と認識はできます。

インフラ構築: サイジング設計

今度は、並行する「インフラ構築」の「サイジング設計」ワークパッケージの補足説明を見てみましょう。

機器選定に必要な詳細情報のサイジングを計算する。サイジング設計書には、下記に示す容量について、必要量の見積もりとその根拠を記載する。

  • CPU
  • メモリ容量
  • ディスク容量

ポイントは 根拠を記載する です。インプットに「非機能要件定義書」や「方式設計書」とあり、そこには性能要件やアプリケーションのアーキテクチャなどが記載されています。これらに基づいて計算せよということです。

ただし、 Nablarch 開発標準はおそらく機器購入(オンプレミス)が前提になっています。クラウドサービス前提であれば、実際に動かすなり性能試験するなりして「根拠」が十分揃ってからサイジングするほうが良いでしょう。

めくるめく開発標準の世界

あっさり目ですが、 SIer の開発標準例として Nablarch の開発標準を覗いてみました。

私は既に SIer にはいませんが、それでも次の場面で開発標準(のようなもの)を思い浮かべ、それに助けられていることが多いです。

  • 新しいサービス(マイクロサービス含む)を作り始めるとき。大雑把な進め方を決めたり、タスクを洗い出したり、決めるべきことを整理したり、ざっくりと見積もりしたりするとき
  • 本番環境へのデプロイ前に、「やるべきことを(やった|やらないという意思決定をした)か」確認するとき

何かのお役に立てれば幸いです。


本記事は Nablarch 開発標準のライセンスを継承し CC BY-SA 4.0 とします*2

*1:ただし、開発現場が守っているかどうかは定かではありません

*2:著作権法で認められている引用の範囲を超えている可能性があるため

VTuber が歌った曲をまとめたい 後半(実装編)

VTuber が歌った曲をまとめたい 前半(検討編) の続きです。

前半(検討編) のおさらい

  • VTuber のすべての動画から、過去どんな曲を歌っていたか列挙したい
  • 複数案を検討し、「動画コメント欄の時間指定コメントを使う」案で進むことにした

ざっくりイメージ

「チャンネル URL から曲目データベースができるまで」のざっくりイメージがこちらです。

f:id:no_clock:20210118225237p:plain

YouTube Data API v3 をフル活用し、 YouTube チャンネルを指定するだけで動画・コメントデータが抽出できるようにします。

ただし、「時間指定コメント」には曲以外のコメントも含まれるため、 データベース化するまでに手作業を挟みます。

データ抽出 (Ruby)

YouTube Data API Reference を参照してゴリゴリ書くだけです。書いたコードは GitHub にあります。

meliuta/utils/youtube-data-api-v3 at main · typewriter/meliuta · GitHub

以下は注意点です。

API クォータ

Quota usage | YouTube Data API Overview  |  Google Developers

  • YouTube Data API は標準で 10,000 queries / day に制限されています。
  • 今回使用する API のクォータ消費量はすべて 1 query / request のようです。日本語版ドキュメントにはパーツごとに追加消費がある旨の記述がありますが、英語版にはありません。実際に触ってみても追加消費があるように感じられませんでした。

チャンネル URL → アップロード済み動画 プレイリスト

Channels: list  |  YouTube Data API  |  Google Developers

  • チャンネル URL の表現方法は 3 種類あります。チャンネル ID (/channel/UCxxx) 、ユーザ名 (/user/xxx) 、 カスタムチャンネル URL (/c/xxx) です。このうち、カスタムチャンネル URL は非対応ですが、後述する Videos: list API に任意の動画 ID を投げるとチャンネル ID が取得できます。
  • 次のステップで用いるプレイリスト ID は、レスポンスに含まれる contentDetails.relatedPlaylists.uploads の値を使用します。

f:id:no_clock:20210118233847p:plain
チャンネル URL の例(上から OSTER projectテスロ&ロゼットあらゐけいいちMew Project

アップロード済み動画 プレイリスト → プレイリストアイテム

PlaylistItems: list  |  YouTube Data API  |  Google Developers

  • 50 を超える動画は 1 回では取得できません。レスポンスの nextPageToken がなくなるまで繰り返し取得する必要があります。
  • レスポンスに含まれる snippet.publishedAt はプレイリストへの追加日時であり、動画の公開日時とは異なります。
  • 次のステップで用いる動画 ID は、レスポンスに含まれる contentDetails.videoId の値を使用します。

プレイリストアイテム → 動画情報

Videos: list  |  YouTube Data API  |  Google Developers

  • レスポンスのサムネイル (snippet.thumbnails.(key)) のキーに注意します。日本語版は default, medium, high の 3 種類しかありませんが、英語版のドキュメントには standard, maxres も記述されており、こちらが正確です。大きい順に maxres, standard, high medium, default です。

動画情報 → コメント情報

CommentThreads: list  |  YouTube Data API  |  Google Developers

  • リクエストパラメタの orderrelevance にすると、 YouTube サイト上での「評価順 (Top comments)」と同じ順序になるようです。
  • PlaylistItems: list と同様、 100 を超えるコメントは 1 回では取得できません。レスポンスの nextPageToken を利用して繰り返し取得します。
  • 曲目を列挙したコメントは評価が高いケースも多いため、 API クォータを意識して途中で打ち切るのも手です。

コメント情報 → 時間指定コメントの抽出

f:id:no_clock:20210117033635p:plain
【歌】今度こそちょっとだけ歌う配信【戌亥とこ/にじさんじ】 コメント欄より引用

  • コメントの snippet.textOriginal正規表現 /(d+:\d+:\d+|\d+:\d+)/ に掛けるだけのシンプルな方法で抽出します。
  • コメントは自由形式で、曲名が同一行にあったり次行にあったりします。あまり最適化をせず、時間指定コメントをとにかく抽出することに割り切っています。

データベース化(手作業)

抽出したデータからデータベースを作っていきます。これは完全に手作業です。

  1. 曲名っぽい時間指定コメントを取捨選択
  2. 動画を再生して確認
  3. 曲名・アーティストなどを検索・記録

今回は素敵な素敵な「Google スプレッドシート」を使用しました。だいたい 1,800 コメントから 100 曲ほどをデータベース化できました。

f:id:no_clock:20210118230308p:plain
出来上がったデータベース (Google スプレッドシート)

なお、動画の時間指定リンクは https://www.youtube.com/watch?v=sshoDk2CQVQ&t=14118 のように、 t=秒数 を指定するだけです。

Web サイト化 (TypeScript, React)

こちらもゴリゴリ書くだけです。書いたコードは GitHub にあります。

API サーバを立てたくなかったため、フロントエンドでがんばる方向にしました。 Web サーバに生成物をポン置きするだけです。

  • データベースは tsv ファイル
  • TypeScript + React
    • tsv ファイルの取得・解析
    • material-table を使った表(検索・ページング機能含む)

出来上がった Web サイトがこちらです。

メリうた🐝 - メリッサ・キンレンカさんのお歌非公式まとめサイト

まとめ

  • YouTube Data API を用いてチャンネル URL から全動画の時間指定コメントを抽出しました。
  • 時間指定コメントをデータベース化し、 Web サイト化しました。

なお、開発にあたり レヴィ・エリファ アーカイブス から着想を得ました。この場を借りて御礼申し上げます。

VTuber が歌った曲をまとめたい 前半(検討編)

VTuber にハマり気味です。

その中で、「過去、どんな曲を歌っていたのかな」と知りたくなりました。その欲求を満たしていきます。

対象動画は「すべて」

対象の動画は「すべて」です。

ライブ配信する VTuber の配信スタイルは多種多様です。 はっきり「歌枠(カラオケ配信的なもの)」と題しているものもあれば、 そうでない配信で突発的にアカペラや歌が始まることもあります。

そのため、すべての動画(配信アーカイブ)を対象にします。

検討: 曲の列挙方法

曲の列挙方法はいくつか思い浮かびます。結論は [2.] ですが、そこへ至るために検討した内容を整理していきます。

方法 所要時間 実現可能性 網羅性
1. 配信アーカイブをすべて見る 長い 低い 高い
2. 動画コメント欄にある時間指定コメントを活用する 短い 高い 中くらい
3. ライブ配信のチャット欄メッセージを活用する - 不可 中くらい
4. YouTube の自動生成字幕を活用する - 不可 低い
5. 楽曲認識 API を使う 中くらい 中くらい 中くらい

1. 配信アーカイブをすべて見る

手作業で動画をすべて見ていく方法です。

これは非常に大変です。ほとんどの VTuber は配信アーカイブが 100 時間以上。 1000 時間を超える方も珍しくありません。

たくさん配信があるのは嬉しい限りですが、「曲を列挙する」ために見直すのはいささか辛いものがあります。

2. 動画コメント欄にある時間指定コメントを活用する

動画コメント欄にある、下記のような「時間指定コメント」を用いる方法です。

f:id:no_clock:20210117033801p:plain
【歌枠】ゆどうふってやっぱポン酢【メリッサ・キンレンカ/にじさんじ】 コメント欄より引用

YouTube Data API v3CommentThreads があり、 API で取得可能です。

網羅性はユーザのコメントに依存しますが、私の観測範囲では比較的高い確率で書かれているようです。

3. ライブ配信のチャット欄メッセージを活用する (不可)

ライブ配信の右側にあるチャット欄メッセージを用いる方法です。

f:id:no_clock:20210117024232p:plain
YouTube ライブ配信の画面構成

ただし、これは実現不可です。過去のチャット欄を取得する API がないためです。

API を確認すると、 YouTube Data API v3 Videos に次の記述があります。つまり、 activeLiveChatId があるのは配信中のみです。

liveStreamingDetails.activeLiveChatId

string
(省略) This field is filled only if the video is a currently live broadcast that has live chat. Once the broadcast transitions to complete this field will be removed and the live chat closed down. (省略)

activeLiveChatId がないため、チャットメッセージの取得 API (YouTube Live Streaming API / LiveChatMessages) は利用できません。

残るはスクレイピングですが、利用規約で禁止されています。

本サービスの利用には制限があり、以下の行為が禁止されています。

(省略)

3. 自動化された手段(ロボット、ボットネット、スクレーパなど)を使用して本サービスにアクセスすること。ただし、(a)公開されている検索エンジンYouTuberobots.txt ファイルに従って使用する場合、または(b)YouTube が事前に書面で許可している場合を除きます。

利用規約 (YouTube)

4. YouTube の自動生成字幕を活用する (不可)

自動生成されている字幕データを用いる方法です。

f:id:no_clock:20210117034221p:plain
【SNOW MIKU 公式曲】好き!雪!本気マジック feat. 初音ミク【Mitchie M】 (CC-BY 4.0)
(本動画の字幕は自動生成ではないが、字幕の表示例として掲載)

ただし、これも実現不可です。

YouTube Data API v3 Captions download で取得を試みると 403 が返却されます。

{
  "error": {
    "code": 403,
    "message": "The permissions associated with the request are not sufficient to download the caption track. The request might not be properly authorized, or the video order might not have enabled third-party contributions for this caption.",
    "errors": [
      {
        "message": "The permissions associated with the request are not sufficient to download the caption track. The request might not be properly authorized, or the video order might not have enabled third-party contributions for this caption.",
        "domain": "youtube.caption",
        "reason": "forbidden",
        "location": "id",
        "locationType": "parameter"
      }
    ]
  }
}

公式ドキュメントには明記されていませんが、本人もしくは字幕追加の協力者 (2020/9/28 をもって機能終了) でないとダウンロード出来ないのではないか、という話があるようです。

youtube api - Downloading captions always returns a 403 - Stack Overflow

スクレイピングは [3.] と同様で利用規約に引っかかります。

5. 楽曲認識 API を使う

音声データを API に投げて曲名を教えてもらう方法です。 ACRCloudAudD などが提供しています。ただ、いくつか問題があります。

認識精度。 別サービスですが Google アシスタントの「近くで流れている曲について調べる」機能をちょっと触った限りでは、一致率 (?) が低いものもありました。悪くはありませんが、十分とも言い難い印象です。

f:id:no_clock:20210117030806p:plain
ヴィーナスとジーザス (アカペラ配信) と KING (カバー) の認識結果

オリジナル曲の扱い。 楽曲データベースの構築方法はわかりませんが、 Google アシスタントだと何らかの商用配信が必要そうな気配があります。 YouTube にアップロードされているのみのオリジナル曲は認識されませんでした。

料金。 大量の配信アーカイブAPI に通すにはかなりのお金が掛かってしまいます。

前半のまとめ

ここまで、 VTuber が歌った曲を列挙する方法を検討してきました。表を再掲します。

方法 所要時間 実現可能性 網羅性
1. 配信アーカイブをすべて見る 長い 低い 高い
2. 動画コメント欄にある時間指定コメントを活用する 短い 高い 中くらい
3. ライブ配信のチャット欄メッセージを活用する - 不可 中くらい
4. YouTube の自動生成字幕を活用する - 不可 低い
5. 楽曲認識 API を使う 中くらい 中くらい 中くらい

挙げた方法の中では、「2. 動画コメント欄にある時間指定コメントを活用する」が比較的よさそうです。後半でこれを具現化していきます。

yarn/npm outdated は、古いパッケージがあると終了コード 1 を返すバージョンもある

コードリーディングのメモ。

まとめ

outdated コマンドで古いパッケージが見つかった場合の終了コード:

パッケージマネージャ バージョン リリース日 終了コード
Yarn >=0.26.0 2017/06/06 1
<0.26.0 - 0
npm >=7.0.0 2020/10/13 0
>=4.0.0 <7.0.0 2016/10/21 1
<4.0.0 - 0

※記事執筆時点の最新バージョンは、 Yarn 1.22.10 (2020/10/02), npm 7.3.0 (2020/12/19)

outdated コマンド

yarn outdated あるいは npm outdated は、 古いパッケージを表示してくれるコマンド。 upgrade コマンドと異なり、アップグレードされることはない。

$ npm outdated
Package                 Current  Wanted  Latest  Location
@holiday-jp/holiday_jp    2.2.3   2.3.0   2.3.0  yarn-update-check

これを CI で活用するとして、終了コードが 0 以外になればとてもお手軽だ。 outdated コマンドを CI で実行させておくだけで、最新バージョンがあると勝手に CI が失敗するようになる。

しかし、残念ながらドキュメントに終了コードの記載はない。実際の動きを確かめつつ、ソースコードで歴史を紐解く必要がある。

Yarn

Yarn は yarnpkg/yarn@c4f264f で終了コード 1 を出力するようになっている。タグを見る限り v0.26.0 (2017/6/6) のようだ。

yarn/outdated.js at master · yarnpkg/yarn · GitHub

export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<number> {
  // 略
  let deps = await PackageRequest.getOutdatedPackages(lockfile, install, config, reporter);
  // 略
  if (deps.length) {
    // 略
    return 1;
  }
  return 0;
}
$ yarn outdated
yarn outdated v1.22.10
info Color legend :
 "<red>"    : Major Update backward-incompatible updates
 "<yellow>" : Minor Update backward-compatible features
 "<green>"  : Patch Update backward-compatible bug fixes
Package                Current Wanted Latest Package Type URL
@holiday-jp/holiday_jp 2.2.3   2.2.3  2.3.0  dependencies https://github.com/holiday-jp/holiday_jp-js
Done in 0.11s.

$ echo $?
1

この変更の元となる Issue #3483 には、「 npm outdated はこういうとき 1 を返している」とある。

npm

>=4.0.0 <7.0.0

npm はリリースノートによれば v4.0.0 (2016/10/21) で終了コード 1 を出力するようになっている。

BRIEF OVERVIEW OF BREAKING CHANGES
(略)
- npm outdated exits with exit code 1 if it finds any outdated packages.

npm/outdated.js at v4.0.0 · npm/npm · GitHub

function outdated (args, silent, cb) {
  // 略
      if (er || silent || list.length === 0) return cb(er, list)
      // 略
      process.exitCode = 1
$ npm -v
6.14.0
$ npm outdated
Package                 Current  Wanted  Latest  Location
@holiday-jp/holiday_jp    2.2.3   2.2.3   2.3.0  yarn-update-check

$ echo $?
1

>=7.0.0

ただし、 v7.0.0 からはふたたび終了コード 0 を出力するようになっているようだ。リリースノートには特に破壊的変更のアナウンスは見当たらず、意図したものかそうでないものかを調べきれていない。

cli/outdated.js at v7.0.0 · npm/cli · GitHub

$ npm -v
7.3.0
$ npm outdated
Package                 Current  Wanted  Latest  Location                             Depended by
@holiday-jp/holiday_jp    2.2.3   2.2.3   2.3.0  node_modules/@holiday-jp/holiday_jp  node15

$ echo $?
0

おわりに

例えば grep のマニュアルには終了コードについて明記されている。

通常では、選択される行が見つかったときの終了ステータスは 0 であり、 見つからなかったときは 1 であり、エラーが起きた場合は 2 です。 ただし、 -q , --quiet , --silent といったオプションが使われていて、選択される行が見つかったときは、 エラーが起きたときでも終了ステータスは 0 です。

Man page of GREP

yarn/npm outdated のマニュアルには記載がない。そして、仕様として挙動が維持されるかは分からない。これをどこまでアテにしていいのだろうか、いささか不安が残る結果になってしまった。 --json オプションをつけて JSON を解釈したほうがいいだろうか。