Open9

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

ふんまと💩ふんまと💩

■ 配信のバックアップ

API 経由での直接的な外部エクスポートは出来なさそう。
Twitch API => ローカル => Youtube

▽ Twitch Archive情報取得の流れ

### Service Info
@clientId= ...
@clientSecret= ...
@targetUsername= ...

### Service Account Login
# @name login
POST https://id.twitch.tv/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://api.twitch.tv/helix/users?login={{targetUsername}} HTTP/1.1
Authorization: Bearer {{login.response.body.access_token}}
Client-Id: {{clientId}}

### VIDEO API
GET https://api.twitch.tv/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'