@@ -7,6 +7,7 @@ from __future__ import annotations
77import json
88import os
99import pathlib
10+ import re
1011import selectors
1112import signal
1213import subprocess
@@ -30,6 +31,9 @@ OBS_SCENE_FILE = pathlib.Path(
3031
3132child : subprocess .Popen [Any ] | None = None
3233stopping = False
34+ URL_CREDENTIALS_RE = re .compile (r"(?P<scheme>[A-Za-z][A-Za-z0-9+.-]*://)[^/\s:@]+(?::[^/\s@]*)?@" )
35+ RTMP_SECRET_PATH_RE = re .compile (r"(?i)\b(?P<prefix>rtmps?://[^\s/]+/)[^\s]+" )
36+ YOUTUBE_SECRET_PATH_RE = re .compile (r"(?i)\b(?P<prefix>https?://[^\s/]*(?:youtube|google)[^\s/]*/)[^\s]+" )
3337
3438
3539def log (message : str ) -> None :
@@ -71,22 +75,10 @@ def redact_url(url: str) -> str:
7175 return f"{ parts .scheme } ://{ host } { path } "
7276
7377
74- def redact_text (text : str , secrets : list [str ]) -> str :
75- redacted = text
76- for secret in secrets :
77- if not secret :
78- continue
79- redacted = redacted .replace (secret , redact_url (secret ))
80- parts = urlsplit (secret )
81- if parts .password :
82- redacted = redacted .replace (parts .password , "<redacted>" )
83- if parts .username :
84- redacted = redacted .replace (parts .username , "<redacted>" )
85- if parts .path and ("youtube" in (parts .hostname or "" ) or "rtmp" in parts .scheme ):
86- stream_key = parts .path .rsplit ("/" , 1 )[- 1 ]
87- if stream_key :
88- redacted = redacted .replace (stream_key , "<redacted>" )
89- return redacted
78+ def redact_text (text : str ) -> str :
79+ redacted = URL_CREDENTIALS_RE .sub (r"\g<scheme><redacted>@" , text )
80+ redacted = RTMP_SECRET_PATH_RE .sub (r"\g<prefix><redacted>" , redacted )
81+ return YOUTUBE_SECRET_PATH_RE .sub (r"\g<prefix><redacted>" , redacted )
9082
9183
9284def load_json (path : pathlib .Path ) -> dict [str , Any ]:
@@ -172,11 +164,11 @@ def source_available(url: str) -> bool:
172164 check = False ,
173165 )
174166 except subprocess .TimeoutExpired :
175- log (f "source probe timed out source= { redact_url ( url ) } " )
167+ log ("source probe timed out" )
176168 return False
177169 if result .returncode != 0 :
178- details = redact_text ((result .stderr or result .stdout ).strip (), [ url ] )
179- log (f"source probe failed: { details } " if details else f "source probe failed source= { redact_url ( url ) } " )
170+ details = redact_text ((result .stderr or result .stdout ).strip ())
171+ log (f"source probe failed: { details } " if details else "source probe failed" )
180172 return False
181173 return True
182174
@@ -334,7 +326,7 @@ def run_once() -> int:
334326 raise RuntimeError ("prepare-broadcast returned incomplete state" )
335327 input_url , ingest_url = stream_config ()
336328
337- log (f"starting ffmpeg mode={ env ('YTA_MODE' , 'copy' )} source= { redact_url ( input_url ) } target= { redact_url ( ingest_url ) } " )
329+ log (f"starting ffmpeg mode={ env ('YTA_MODE' , 'copy' )} " )
338330 child = subprocess .Popen (
339331 ffmpeg_args (input_url , ingest_url ),
340332 stderr = subprocess .STDOUT ,
@@ -355,7 +347,7 @@ def run_once() -> int:
355347 for key , _mask in selector .select (timeout = 1 ):
356348 line = key .fileobj .readline ()
357349 if line :
358- log ("ffmpeg: " + redact_text (line .rstrip (), [ input_url , ingest_url ] ))
350+ log ("ffmpeg: " + redact_text (line .rstrip ()))
359351 if stopping :
360352 break
361353 if max_runtime and time .monotonic () - started > max_runtime :
@@ -366,7 +358,7 @@ def run_once() -> int:
366358 rc = child .wait (timeout = 20 )
367359 if child .stdout is not None :
368360 for line in child .stdout :
369- log ("ffmpeg: " + redact_text (line .rstrip (), [ input_url , ingest_url ] ))
361+ log ("ffmpeg: " + redact_text (line .rstrip ()))
370362 return rc
371363 except subprocess .TimeoutExpired :
372364 log ("ffmpeg did not stop cleanly; killing" )
0 commit comments