Open9
持続可能なTwitch 配信者として

■ 基本情報
Twitch 開発者用サイト
コンソール
※ 当スクラップでは、REST Client (VScode) を前提としています。

■ 配信のバックアップ
API 経由での直接的な外部エクスポートは出来なさそう。
Twitch API => ローカル => Youtube
▽ Twitch Archive情報取得の流れ
### Service Info
@clientId= ...
@clientSecret= ...
@targetUsername= ...
### Service Account Login
# @name login
POST https:/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id={{clientId}}
&client_secret={{clientSecret}}
&grant_type=client_credentials
### USERS API
# @name userInfo
GET https:/helix/users?login={{targetUsername}} HTTP/1.1
Authorization: Bearer {{login.response.body.access_token}}
Client-Id: {{clientId}}
### VIDEO API
GET https:/helix/videos?user_id={{userInfo.response.body.data[0].id}}&type=archive HTTP/1.1
Authorization: Bearer {{login.response.body.access_token}}
Client-Id: {{clientId}}
▽ 動画のダウンロードの仕方(非公式)
API経由では無いので、注意。認証などは特に必要なし。当然いきなりパスが変わる可能性あり。
サムネURLの該当箇所から動画リソースが割り出せる。
### VIDEO API Response
{
...
"thumbnail_url": "https://static-cdn.jtvnw.net/cf_vods/{server_name}/{directory_name}//thumb/thumb0-%{width}x%{height}.jpg"
...
}
### Streaming resources
GET https: {{serverName}}.cloudfront.net/{{directoryName}}/chunked/index-dvr.m3u8 HTTP/1.1
GET https: {{serverName}}.cloudfront.net/{{directoryName}}/chunked/0.ts HTTP/1.1

■ クリップ整理用配信サーバ
nvidia で撮影したクリップをスマホで確認できる。
スマホアプリ と PC 間をP2Pの仮想VPNで接続して動画配信を可能にする。
■ 見どころINDEX機能
クリップはNvidiaだと最長20分、配信自体だと長時間に及ぶ。
タイムスタンプに対してディスクリプションやタグを残せるようにしたい。

■ 自動テロップ起こし。
動画編集時間を極限まで少なくする。文字起こしの手間を省く。

動画分割 ffmpeg で 6時間の動画を分割検証(環境 wsl2 。。。)
hunmatu@DESKTOP-C0J87CV:/mnt/c/Users/hunma/downloads$ ffmpeg -i input.mp4 -t 03:00:00 -c copy front_half.mp4
ffmpeg version 4.4.2-0ubuntu0.22.04.1 Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 11 (Ubuntu 11.2.0-19ubuntu1)
configuration: --prefix=/usr --extra-version=0ubuntu0.22.04.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libmfx --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 70.100 / 56. 70.100
libavcodec 58.134.100 / 58.134.100
libavformat 58. 76.100 / 58. 76.100
libavdevice 58. 13.100 / 58. 13.100
libavfilter 7.110.100 / 7.110.100
libswscale 5. 9.100 / 5. 9.100
libswresample 3. 9.100 / 3. 9.100
libpostproc 55. 9.100 / 55. 9.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf60.3.100
Duration: 06:32:00.74, start: 0.000000, bitrate: 8178 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 8003 kb/s, 60 fps, 60 tbr, 90k tbn, 120 tbc (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 159 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
File 'front_half.mp4' already exists. Overwrite? [y/N] y
Output #0, mp4, to 'front_half.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf58.76.100
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 8003 kb/s, 60 fps, 60 tbr, 90k tbn, 90k tbc (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 159 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame=648001 fps=2982 q=-1.0 Lsize=10780640kB time=02:59:59.99 bitrate=8177.3kbits/s speed=49.7x
video:10551212kB audio:208222kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.197092%

twitch は gqlリクエストでyoutube exportを利用している。
curl 'https://gql.twitch.tv/gql' \
-H 'accept: */*' \
-H 'accept-language: ja-JP' \
-H 'authorization: OAuth {{cookie: auth_token}}' \
-H 'client-id: {{未解析}}' \
-H 'client-integrity: {{未解析}}' \
-H 'client-session-id: {{localstorage: local_storage_app_session_id}}' \
-H 'client-version: {{sessionstorage: twilight.update_manager.known_builds}}' \
-H 'content-type: text/plain;charset=UTF-8' \
-H 'origin: https://dashboard.twitch.tv' \
-H 'priority: u=1, i' \
-H 'referer: https://dashboard.twitch.tv/' \
-H 'sec-ch-ua: "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "Windows"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-site' \
-H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36' \
-H 'x-device-id: {{cookie: unique_id}}' \
--data-raw '[{"operationName":"YoutubeExportModal_ExportVideoToYoutube","variables":{"input":{"videoID":"2546842919","title":"test","description":"","tags":null,"privacyStatus":"PUBLIC","doSplit":false}},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"{{オペレーションのクエリハッシュ値}}"}}}]'
必要なパラメータ
- auth header

auth ヘッダのみで行けそう。
curl 'https://gql.twitch.tv/gql' \
-H 'authorization: OAuth {{cookie: auth_token}}' \
--data-raw '[{"operationName":"VideoManager_User","variables":{"login":"hunmatu"},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"0273b2d9477ab760b65c8f39ad6b5711e54e8cb1ccfe3249f6a5f23001adaa6c"}}}]'

## video download 処理中
[
{
"operationName": "VideoManager_VideoDownload",
"variables": {
"videoID": "2495855094"
},
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "a255a0936da15f643246ba14fa59c6de10d56fc75b6915333c4728063e51035b"
}
}
}
]
### response
[
{
"data": {
"video": {
"id": "2495855094",
"download": {
"status": "DOWNLOADING",
"url": "",
"__typename": "VideoDownload"
},
"__typename": "Video"
}
},
"extensions": {
"durationMilliseconds": 24,
"operationName": "VideoManager_VideoDownload",
"requestID": "01K3DN8XQKKCTANWHJWVV5GGBQ"
}
}
]
## video download 完了後
[
{
"operationName": "VideoManager_VideoDownloadStatus",
"variables": {
"videoID": "2495855094",
"statusOnly": true
},
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "d7fcb83ace4cac5e0f558d12ba5643161960068dab1fc3c1279331b5c09e79f1"
}
}
}
]
### response
[
{
"data": {
"video": {
"id": "2495855094",
"download": {
"status": "COMPLETE",
"url": "https://d34yg9r6z987ci.cloudfront.net/2495855094-437446671-11f7b6c7-7d21-4aad-ade0-72a420285218.mp4",
"__typename": "VideoDownload"
},
"__typename": "Video"
}
},
"extensions": {
"durationMilliseconds": 23,
"operationName": "VideoManager_VideoDownloadStatus",
"requestID": "01K3DN95H3QSWP25FPVKZG7H56"
}
}
]

Streaming サーバ?に謎のリクエスト送ってるやつ。処理に関係ないなら考えたくないが。
gql直たたきエクスポートの結果待ち。
curl 'https://video-edge-b8a8d6.pdx01.abs.hls.ttvnw.net/v1/segment/CmYslfAEIRZ8pF4CMOtyhuj_GbwDWQ7Hsjh6PonhCtOSvqDELjMDjdcg6u6h8u_XT9n1sCI8bUWTZKpAlnvDUXdl32qmykLvdkrWjJ33rLzh4A4D7AUMzwcVYNM7kKxf9exbiP1qUywN0UULzlCh5qMSj8xqzaIUszzpCG8LcYJYU9lmvWfnaoHfEMGeVgl3K8jSdS6Zz78hdH9AJKM0WAeGRkGxDbrBCvPIzFf7v_NZIHRKt6TdPWHxEbK-vb8nKeBW8iiBqA-1YlvHV4YBQUBsYgGi8aZugvVnB-mrEIOzUUee0Bt7weTm_g_ANrzTd_VneXBT3zrpS1NYaIFgGRdcEknhP_uRrfOVYqptNR8KuXJ3lSW9YUdwIC3jivq0MSStkypjvzL0UdRMkBMzMxfghxuKJDpiLU6uYUPw_vTz-VHFJ3Hajy6I8iAO2MvQ8AAJDhF3viE4WZnoOVQhZBOPONYSrRbjwF4mBxktlhrF6Fw8-4TOwQEXqthno6GYHZoMPWrzhodroJaMMBYuDyeNW6PmmAKxHG3rEdVsIhF8mC0bJzzDya6n9MpyZ7RtxoQguVjBEBP-vAaNpjNCAbbPEwLDEXpb-4LqHlZ7D6S5V4HyEJju9N7TeWABzIsoeLDKQNJuQkCsRgrm1ZHnSNQt6h6Mo3_BVHJfuMT5NdE7OoZsfwS1K_z9WwUG7yQ2bmnnHuBefmIjeYQUuvt79duha_RHzWJ9mZmJKDN5oIwr_oChiVlcxx2-sKN.ts' \
-H 'sec-ch-ua-platform: "Windows"' \
-H 'Referer: https://dashboard.twitch.tv/' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'twitch-repository: twilight' \
--data-raw 'data=W3siZXZlbnQiOiJiZW5jaG1hcmtfd2ViX3VpX3Ntb290aG5lc3MiLCJwcm9wZXJ0aWVzIjp7ImFwcF9zZXNzaW9uX2lkIjoiYjU0YjEwMDUzMWFkYjg0NyIsImFwcF92ZXJzaW9uIjoiNmI1NmUyZDAtY2U1Ni00YzQxLTkzNzYtMDhiNGFlYjQ3NjgwIiwiYmF0Y2hfdGltZSI6MTc1NjAyNjM0MywiY2xpZW50X3RpbWUiOjE3NTYwMjYzNDMuMjkzLCJkZXZpY2VfaWQiOiI2NGRlN2QyNTY1YTc5MDYwIiwiZG9tYWluIjoiZGFzaGJvYXJkLnR3aXRjaC50diIsImhvc3QiOiJkYXNoYm9hcmQudHdpdGNoLnR2Iiwib3NfbmFtZSI6IndpbmRvd3MiLCJvc192ZXJzaW9uIjoiMTEiLCJwbGF0Zm9ybSI6IndlYiIsInByZWZlcnJlZF9sYW5ndWFnZSI6ImphIiwicmVmZXJyZXJfaG9zdCI6ImRhc2hib2FyZC50d2l0Y2gudHYiLCJyZWZlcnJlcl91cmwiOiIiLCJ0YWJfc2Vzc2lvbl9pZCI6ImU4NDlkNjBmN2UwYzBmMDAiLCJ1cmwiOiJodHRwczovL2Rhc2hib2FyZC50d2l0Y2gudHYvdS9odW5tYXR1L2NvbnRlbnQvdmlkZW8tcHJvZHVjZXIiLCJ3ZWJfdWlfc21vb3RobmVzc19ibG9ja2luZ19kdXJhdGlvbl90b3RhbCI6MTgyLCJ3ZWJfdWlfc21vb3RobmVzc19sb25nX2ZyYW1lX2NvdW50IjoyLCJ3ZWJfdWlfc21vb3RobmVzc19sb25nX2ZyYW1lX2R1cmF0aW9uX3RvdGFsIjoyOTksIndlYl91aV9zbW9vdGhuZXNzX29ic2VydmVkX2R1cmF0aW9uIjo2MDc2NSwid2ViX3VpX3Ntb290aG5lc3Nfb2JzZXJ2ZWRfc3RhcnQiOjg4NjYsIndlYl91aV9zbW9vdGhuZXNzX292ZXJhbGxfcGVyY2VudF9ibG9ja2VkIjowLjAwMzAwMTYwNDU1NTI5MTMzLCJ3ZWJfdWlfc21vb3RobmVzc19wcmVsYXlvdXRfZHVyYXRpb25fdG90YWwiOjEsIndlYl91aV9zbW9vdGhuZXNzX3JlbmRlcl9kdXJhdGlvbl90b3RhbCI6MSwid2ViX3VpX3Ntb290aG5lc3Nfc3R5bGVfYW5kX2xheW91dF9kdXJhdGlvbl90b3RhbCI6MCwid2ViX3VpX3Ntb290aG5lc3Nfd29ya19kdXJhdGlvbl90b3RhbCI6Mjk4LCJjbGllbnRfYXBwIjoidHdpbGlnaHQiLCJsb2dpbiI6Imh1bm1hdHUiLCJsb2dnZWRfaW4iOnRydWUsInVzZXJfaWQiOjQzNzQ0NjY3MSwicmVjZWl2ZWRfbGFuZ3VhZ2UiOiJqYS1KUCJ9fV0%3D'