diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 024e5a5e..95a17e4d 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -842,19 +842,27 @@ class dl: try: if name == "shaka_packager": r = subprocess.run( - [str(binary), "--version"], capture_output=True, text=True, timeout=5 + [str(binary), "--version"], + capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5, ) version = (r.stdout or r.stderr or "").strip() elif name in ("ffmpeg", "ffprobe"): - r = subprocess.run([str(binary), "-version"], capture_output=True, text=True, timeout=5) + r = subprocess.run( + [str(binary), "-version"], + capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5, + ) version = (r.stdout or "").split("\n")[0].strip() elif name == "mkvmerge": r = subprocess.run( - [str(binary), "--version"], capture_output=True, text=True, timeout=5 + [str(binary), "--version"], + capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5, ) version = (r.stdout or "").strip() elif name == "mp4decrypt": - r = subprocess.run([str(binary)], capture_output=True, text=True, timeout=5) + r = subprocess.run( + [str(binary)], + capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5, + ) output = (r.stdout or "") + (r.stderr or "") lines = [line.strip() for line in output.split("\n") if line.strip()] version = " | ".join(lines[:2]) if lines else None diff --git a/unshackle/commands/util.py b/unshackle/commands/util.py index 7a3c42c0..67d7f4d1 100644 --- a/unshackle/commands/util.py +++ b/unshackle/commands/util.py @@ -282,6 +282,8 @@ def test(path: Path, map_: str) -> None: ], stderr=subprocess.PIPE, universal_newlines=True, + encoding="utf-8", + errors="replace", ) reached_output = False errors = 0 diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index 77ade8b6..cc02194b 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -1504,7 +1504,9 @@ def _binary_version(path: Any) -> Optional[str]: for flag in ("--version", "-version"): try: - proc = subprocess.run([str(path), flag], capture_output=True, text=True, timeout=5) + proc = subprocess.run( + [str(path), flag], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5 + ) except (OSError, subprocess.SubprocessError, ValueError): return None match = _VERSION_RE.search(proc.stdout or "") or _VERSION_RE.search(proc.stderr or "") diff --git a/unshackle/core/drm/clearkey_cenc.py b/unshackle/core/drm/clearkey_cenc.py index 5c07434a..2fe557f7 100644 --- a/unshackle/core/drm/clearkey_cenc.py +++ b/unshackle/core/drm/clearkey_cenc.py @@ -223,7 +223,10 @@ class ClearKeyCENC: ] try: - subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") + subprocess.run( + cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", errors="replace", + ) except subprocess.CalledProcessError as e: error_msg = e.stderr if e.stderr else f"mp4decrypt failed with exit code {e.returncode}" raise subprocess.CalledProcessError(e.returncode, cmd, output=e.stdout, stderr=error_msg) @@ -262,6 +265,8 @@ class ClearKeyCENC: stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True, + encoding="utf-8", + errors="replace", ) stream_skipped = False diff --git a/unshackle/core/drm/monalisa.py b/unshackle/core/drm/monalisa.py index 3b6f29a6..f0b4f0c9 100644 --- a/unshackle/core/drm/monalisa.py +++ b/unshackle/core/drm/monalisa.py @@ -247,6 +247,8 @@ class MonaLisa: stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", startupinfo=startupinfo, timeout=worker_timeout_s, ) diff --git a/unshackle/core/drm/playready.py b/unshackle/core/drm/playready.py index bfcfb8e5..1ab06313 100644 --- a/unshackle/core/drm/playready.py +++ b/unshackle/core/drm/playready.py @@ -438,7 +438,10 @@ class PlayReady: ] try: - subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") + subprocess.run( + cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", errors="replace", + ) except subprocess.CalledProcessError as e: error_msg = e.stderr if e.stderr else f"mp4decrypt failed with exit code {e.returncode}" raise subprocess.CalledProcessError(e.returncode, cmd, output=e.stdout, stderr=error_msg) diff --git a/unshackle/core/drm/widevine.py b/unshackle/core/drm/widevine.py index b48a930e..21a14023 100644 --- a/unshackle/core/drm/widevine.py +++ b/unshackle/core/drm/widevine.py @@ -378,7 +378,10 @@ class Widevine: ] try: - subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") + subprocess.run( + cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", errors="replace", + ) except subprocess.CalledProcessError as e: error_msg = e.stderr if e.stderr else f"mp4decrypt failed with exit code {e.returncode}" raise subprocess.CalledProcessError(e.returncode, cmd, output=e.stdout, stderr=error_msg) @@ -426,6 +429,8 @@ class Widevine: stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True, + encoding="utf-8", + errors="replace", ) stream_skipped = False diff --git a/unshackle/core/proxies/gluetun.py b/unshackle/core/proxies/gluetun.py index 5dc40b04..68ea4a2e 100644 --- a/unshackle/core/proxies/gluetun.py +++ b/unshackle/core/proxies/gluetun.py @@ -544,6 +544,8 @@ class Gluetun(Proxy): ["docker", "ps", "--format", "{{.Ports}}"], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=5, ) if result.returncode == 0: @@ -892,6 +894,8 @@ class Gluetun(Proxy): ], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=5, ) if result.returncode != 0: @@ -926,6 +930,8 @@ class Gluetun(Proxy): ["docker", "inspect", container_name, "--format", "{{.NetworkSettings.Ports}}"], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=5, ) @@ -1109,6 +1115,8 @@ class Gluetun(Proxy): ["docker", "inspect", container_name, "--format", "{{.State.ExitCode}}:{{.State.Error}}"], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=5, ) if result.returncode == 0: @@ -1310,6 +1318,8 @@ class Gluetun(Proxy): ["docker", "ps", "-a", "--filter", f"name=^{container_name}$", "--format", "{{.Names}}:{{.Status}}"], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=5, ) @@ -1328,6 +1338,8 @@ class Gluetun(Proxy): ["docker", "rm", "-f", container_name], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=10, ) return True @@ -1346,6 +1358,8 @@ class Gluetun(Proxy): ["docker", "rm", "-f", container_name], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=10, ) else: @@ -1354,6 +1368,8 @@ class Gluetun(Proxy): ["docker", "stop", container_name], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=10, ) except subprocess.TimeoutExpired: @@ -1363,6 +1379,8 @@ class Gluetun(Proxy): ["docker", "rm", "-f", container_name], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=5, ) except subprocess.TimeoutExpired: diff --git a/unshackle/core/proxies/hola.py b/unshackle/core/proxies/hola.py index 5f886e45..1189b49e 100644 --- a/unshackle/core/proxies/hola.py +++ b/unshackle/core/proxies/hola.py @@ -35,7 +35,7 @@ class Hola(Proxy): p = subprocess.check_output( [self.binary, "-country", query, "-list-proxies"], stderr=subprocess.STDOUT - ).decode() + ).decode("utf-8", errors="replace") if "Transaction error: temporary ban detected." in p: raise ConnectionError("Hola banned your IP temporarily from it's services. Try change your IP.") @@ -55,6 +55,6 @@ class Hola(Proxy): def get_countries(self) -> list[dict[str, str]]: """Get a list of available Countries.""" - p = subprocess.check_output([self.binary, "-list-countries"]).decode("utf8") + p = subprocess.check_output([self.binary, "-list-countries"]).decode("utf-8", errors="replace") return [{code: name} for country in p.splitlines() for (code, name) in [country.split(" - ", maxsplit=1)]] diff --git a/unshackle/core/tracks/hybrid.py b/unshackle/core/tracks/hybrid.py index 3f67121e..86c77099 100644 --- a/unshackle/core/tracks/hybrid.py +++ b/unshackle/core/tracks/hybrid.py @@ -225,6 +225,8 @@ class Hybrid: stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", ) if result_duration.returncode != 0: @@ -257,6 +259,8 @@ class Hybrid: stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", ) if result_streams.returncode != 0: @@ -300,6 +304,8 @@ class Hybrid: stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", ) # cropdetect outputs crop=w:h:x:y @@ -603,6 +609,8 @@ class Hybrid: stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", ) max_mdl = 1000 diff --git a/unshackle/core/tracks/tracks.py b/unshackle/core/tracks/tracks.py index 7907cd8b..e77aa5d0 100644 --- a/unshackle/core/tracks/tracks.py +++ b/unshackle/core/tracks/tracks.py @@ -697,7 +697,7 @@ class Tracks: errors = [] warnings = [] mux_start_time = time.monotonic() - p = subprocess.Popen(full_command, text=True, stdout=subprocess.PIPE) + p = subprocess.Popen(full_command, text=True, encoding="utf-8", errors="replace", stdout=subprocess.PIPE) for line in iter(p.stdout.readline, ""): if line.startswith("#GUI#error") or line.startswith("#GUI#warning"): errors.append(line) diff --git a/unshackle/core/tracks/video.py b/unshackle/core/tracks/video.py index 7082aac9..db9d17e1 100644 --- a/unshackle/core/tracks/video.py +++ b/unshackle/core/tracks/video.py @@ -456,6 +456,8 @@ class Video(Track): ], capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) vals = dict(line.split("=", 1) for line in probe.stdout.splitlines() if "=" in line) current = (vals.get("color_primaries"), vals.get("color_transfer"), vals.get("color_space")) diff --git a/unshackle/core/utils/dovi.py b/unshackle/core/utils/dovi.py index 3bd002ee..805120f7 100644 --- a/unshackle/core/utils/dovi.py +++ b/unshackle/core/utils/dovi.py @@ -84,7 +84,9 @@ def info_summary(rpu: Path) -> str: """Return the textual summary (`dovi_tool info -i ... -s`) for an RPU file.""" tool = _require_dovi_tool() info_start = time.monotonic() - p = subprocess.run([tool, "info", "-i", str(rpu), "-s"], capture_output=True, text=True) + p = subprocess.run( + [tool, "info", "-i", str(rpu), "-s"], capture_output=True, text=True, encoding="utf-8", errors="replace" + ) log_tool_run( "dovi_tool info", "dovi_tool", diff --git a/unshackle/core/utils/subprocess.py b/unshackle/core/utils/subprocess.py index 4189dc62..1eb0c3e8 100644 --- a/unshackle/core/utils/subprocess.py +++ b/unshackle/core/utils/subprocess.py @@ -66,7 +66,7 @@ def ffprobe(uri: Union[bytes, Path]) -> dict: context={"tool": "ffprobe", "duration_ms": round((time.monotonic() - start) * 1000, 1)}, ) return {} - result = json.loads(ff.stdout.decode("utf8")) + result = json.loads(ff.stdout.decode("utf-8", errors="replace")) if dl: dl.log( level="DEBUG", diff --git a/unshackle/core/utils/tags.py b/unshackle/core/utils/tags.py index d756f80a..602c0550 100644 --- a/unshackle/core/utils/tags.py +++ b/unshackle/core/utils/tags.py @@ -41,6 +41,8 @@ def apply_tags(path: Path, tags: dict[str, str]) -> None: check=False, capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) log_tool_run( "mkvpropedit tags",