File size: 3,975 Bytes
35c2217
56bfa33
35c2217
943157c
 
35c2217
 
 
 
56bfa33
 
 
 
 
 
 
 
 
 
 
 
 
35c2217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56bfa33
35c2217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
943157c
35c2217
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
FROM python:3.11-slim-trixie
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg mkvtoolnix ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
RUN ffmpeg -hide_banner -h protocol=rtmps 2>&1 | grep -q rtmp_enhanced_codecs
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py relay.py youtube_api.py youtube_manager.py flv_bridge.py tiktok_auth.py index.html ./

# --- YouTube ingestion -------------------------------------------------------
# copy      = forward the source untouched (original quality, no extra CPU). DEFAULT.
#             The YouTube stream is created with cdn.resolution='variable' and
#             cdn.frameRate='variable', which per the Live Streaming API means
#             "YouTube should automatically detect the resolution of your streamed
#             video". So a mid-stream resolution change does NOT require a new
#             session, and YOUTUBE_FORMAT_GUARD=off keeps the RTMPS session alive
#             across it. Restarting the session was itself a visible gap and, when
#             the phone flipped resolution repeatedly, a reconnect storm.
# fixed1080 = constant 1080x1920 canvas, re-encodes to H.264. Zero gaps but costs
#             CPU: measured 2.26x real time on 2 vCPU (~44% for one stream).
# fixed720  = constant  720x1280 canvas, 3.72x real time (~27%). Lighter.
# Both canvas modes stay available from Settings if YouTube's own healthStatus ever
# reports videoResolutionUnsupported; they are opt-in because of the CPU cost.
#
# YOUTUBE_FORMAT_GUARD=on  -> tear the session down on a resolution change (old
#   behaviour: 1-3 s gap, but YouTube re-declares the new size explicitly).
# YOUTUBE_FLAP_LIMIT: if resolution flips this many times in 10 minutes, copy mode
#   falls back to YOUTUBE_FLAP_FALLBACK_MODE for the rest of the event. Dormant
#   while the guard is off, kept as a safety net.
#
# YOUTUBE_FIFO_TIMESHIFT: the push goes through FFmpeg's fifo pseudo-muxer, which
#   reconnects on its own when YouTube drops the connection instead of killing the
#   whole process (that is why the recording kept running while the broadcast died).
#   timeshift additionally delays the YouTube feed behind the live TikTok source and
#   keeps that many seconds buffered, so a source hiccup is covered instead of
#   starving YouTube's ingest, and packets survive a reconnect instead of being
#   dropped. Cost is latency only, no CPU. https://ffmpeg.org/ffmpeg-formats.html#fifo
#   NOTE: fifo's queue_size defaults to 60 packets, under one second of stream, so
#   timeshift requires it to be raised or the queue overflows and the stream is
#   destroyed instead of delayed. relay.py sizes it automatically (ts*250+500).
# YOUTUBE_HEALTH_INTERVAL: seconds between reads of YouTube's own
#   healthStatus/configurationIssues while live. This is what reports videoIngestion
#   Starved (viewers buffer), gopSizeLong, openGop, videoResolutionUnsupported and
#   videoCodec, i.e. the ground truth instead of a guess.
ENV YOUTUBE_SEND_MODE=copy \
    YOUTUBE_FORMAT_GUARD=off \
    YOUTUBE_FLAP_LIMIT=4 \
    YOUTUBE_FLAP_FALLBACK_MODE=fixed720 \
    YOUTUBE_X264_PRESET=veryfast \
    YOUTUBE_CRF=22 \
    YOUTUBE_RECOVERY_WAIT=1 \
    YOUTUBE_FIFO_TIMESHIFT=10 \
    YOUTUBE_HEALTH_INTERVAL=180

# --- TikTok session token (age-restricted 18+ rooms) -------------------------
# Set TIKTOK_SESSIONID in Settings > Variables and secrets, or paste it into the
# UI panel (no Factory reboot needed). Sessions are short-lived (~6 h per
# sid_guard), so the UI panel is the practical way to refresh.
# Per account: TIKTOK_SESSIONID_<USER>  (upper-case, non A-Z0-9 -> _)
# Compact map: TIKTOK_TOKENS=user1=token1;user2=token2
ENV TIKTOK_TT_TARGET_IDC=useast1a

ENV PORT=7860 PYTHONUNBUFFERED=1 YOUTUBE_VIDEO_MODE=original PASSWORD=admin
EXPOSE 7860
CMD ["sh", "-c", "exec uvicorn app:app --host 0.0.0.0 --port ${PORT:-7860} --timeout-graceful-shutdown 65"]