diff --git a/.env.example b/.env.example index dd498af..198dd04 100644 --- a/.env.example +++ b/.env.example @@ -58,13 +58,18 @@ STREAM_BACKEND=selfbot # The VNC desktop runs on X display :1 (see docs/vnc-xfce-setup.md) VNC_DISPLAY=:1 VNC_RESOLUTION=1920x1080 -VNC_FRAMERATE=30 -VNC_BITRATE_KBPS=4000 +# 1080p60 broadcast. 8 Mbps suits 60fps (YouTube-style 1080p60 sits ~8-12 Mbps); +# drop to 30/4000 for a lighter stream. Max bitrate is 1.5x this value. +VNC_FRAMERATE=60 +VNC_BITRATE_KBPS=8000 # --- selfbot backend --- # A THROWAWAY/burner Discord user account token. NEVER your main account. # Using a selfbot violates Discord ToS and can get the account banned. DISCORD_SELFBOT_TOKEN= +# Hardware (NVENC) encode for the stream. 1 = use the GPU (recommended for +# 1080p60), 0 = software x264. Requires an NVIDIA GPU + ffmpeg built with nvenc. +STREAM_HW=1 # --- novnc backend --- # e.g. http://192.168.10.9:6080/vnc.html (websockify --web=/usr/share/novnc 6080 localhost:5901) diff --git a/bot/src/config.ts b/bot/src/config.ts index f7cdaa8..83e516d 100644 --- a/bot/src/config.ts +++ b/bot/src/config.ts @@ -35,8 +35,8 @@ export const config = { // x11grab source for the VNC display (TigerVNC runs the desktop on :1) vncDisplay: opt("VNC_DISPLAY", ":1"), vncResolution: opt("VNC_RESOLUTION", "1920x1080"), - vncFramerate: parseInt(opt("VNC_FRAMERATE", "30"), 10), - vncBitrateKbps: parseInt(opt("VNC_BITRATE_KBPS", "4000"), 10), + vncFramerate: parseInt(opt("VNC_FRAMERATE", "60"), 10), + vncBitrateKbps: parseInt(opt("VNC_BITRATE_KBPS", "8000"), 10), // selfbot backend (ToS-risk; use a throwaway account token, never your main) selfbotToken: opt("DISCORD_SELFBOT_TOKEN"), diff --git a/bot/src/stream/selfbot.ts b/bot/src/stream/selfbot.ts index 11ebc88..c8e5002 100644 --- a/bot/src/stream/selfbot.ts +++ b/bot/src/stream/selfbot.ts @@ -46,9 +46,9 @@ export class SelfbotStreamer implements ScreenStreamer { `원본 오류: ${(e as Error).message}`, ); } - if (!vs.Streamer || !vs.prepareStream || !vs.playStream) { + if (!vs.Streamer || !vs.prepareStream || !vs.playStream || !vs.Encoders) { throw new Error( - "@dank074/discord-video-stream v6 API(Streamer/prepareStream/playStream)를 찾지 못했습니다. " + + "@dank074/discord-video-stream v6 API(Streamer/prepareStream/playStream/Encoders)를 찾지 못했습니다. " + "package.json 버전을 ^6.0.0으로 맞추세요.", ); } @@ -64,7 +64,7 @@ export class SelfbotStreamer implements ScreenStreamer { return "셀프봇 송출은 음성 채널 안에서 호출해야 합니다."; } const { selfbot, vs } = await this.loadLib(); - const { Streamer, prepareStream, playStream } = vs; + const { Streamer, prepareStream, playStream, Encoders } = vs; this.streamer = new Streamer(new selfbot.Client()); await this.streamer.client.login(this.config.selfbotToken); @@ -77,13 +77,22 @@ export class SelfbotStreamer implements ScreenStreamer { // x11grab), then pipe that stream into the library. Relying on the lib's // bundled libav for the x11grab input device is not portable; piping the // system ffmpeg is. (Verified live against a real voice channel.) + // + // With streamHw on (default) the capture is encoded by the GPU + // (h264_nvenc, RTX 5050) so 1080p60 stays smooth without loading the CPU; + // the library then transcodes with NVENC too (see encoder below). Without + // hardware support we fall back to software x264. + const hw = this.config.streamHw; + const captureCodecArgs = hw + ? ["-c:v", "h264_nvenc", "-preset", "p4", "-tune", "ll"] + : ["-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency"]; const capture = spawn("ffmpeg", [ "-loglevel", "error", "-f", "x11grab", "-framerate", String(this.config.vncFramerate), "-video_size", this.config.vncResolution, "-i", this.config.vncDisplay, - "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", + ...captureCodecArgs, "-pix_fmt", "yuv420p", "-g", String(this.config.vncFramerate), "-f", "mpegts", "pipe:1", ]); @@ -101,6 +110,7 @@ export class SelfbotStreamer implements ScreenStreamer { videoCodec: "H264", bitrateVideo: this.config.vncBitrateKbps, bitrateVideoMax: Math.round(this.config.vncBitrateKbps * 1.5), + encoder: hw ? Encoders.nvenc() : Encoders.software(), }, this.controller.signal, );