|
@@ -15,7 +15,7 @@ It is designed to run repeatedly over a media library. State is persisted via xa
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
zz_remux/
|
|
zz_remux/
|
|
|
-├── __init__.py — Public re-exports: Consts, Tag, Scan, Mux, Lang, Proc
|
|
|
|
|
|
|
+├── __init__.py — Public re-exports: Consts, FileInfo, Tag, Scan, Mux, Lang, Proc, ProcResult
|
|
|
├── __main__.py — Package entry (delegates to cli.main)
|
|
├── __main__.py — Package entry (delegates to cli.main)
|
|
|
├── cli.py — Argument parsing, logging setup, directory walk, orchestration
|
|
├── cli.py — Argument parsing, logging setup, directory walk, orchestration
|
|
|
├── consts.py — Shared constants: extensions, tag names/versions, defaults
|
|
├── consts.py — Shared constants: extensions, tag names/versions, defaults
|
|
@@ -37,7 +37,7 @@ cli.py: root.walk()
|
|
|
▼
|
|
▼
|
|
|
scan.Scan.scan(files, parent)
|
|
scan.Scan.scan(files, parent)
|
|
|
│ Parses each filename against the media regex.
|
|
│ Parses each filename against the media regex.
|
|
|
- │ Returns a list of "info" dicts (see File info dict below).
|
|
|
|
|
|
|
+ │ Returns a list[FileInfo] (see FileInfo below).
|
|
|
│ Reads mux_vers and lang_vers xattrs from each file.
|
|
│ Reads mux_vers and lang_vers xattrs from each file.
|
|
|
│
|
|
│
|
|
|
├─▶ mux.Mux.mux(media)
|
|
├─▶ mux.Mux.mux(media)
|
|
@@ -62,21 +62,20 @@ scan.Scan.scan(files, parent)
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## File info dict
|
|
|
|
|
|
|
+## FileInfo
|
|
|
|
|
|
|
|
-`Scan.scan()` returns a list of dicts. `Mux.mux()` mutates this list in-place (removing merged sources, appending the new output file). `Lang.lang()` reads from it and updates `lang_vers`.
|
|
|
|
|
|
|
+`FileInfo` is a `TypedDict` defined in `consts.py` and re-exported from `__init__.py`. `Scan.scan()` returns `list[FileInfo]`. `Mux.mux()` mutates this list in-place (removing merged sources, appending the new output file). `Lang.lang()` reads from it and updates `lang_vers`.
|
|
|
|
|
|
|
|
```python
|
|
```python
|
|
|
-{
|
|
|
|
|
- 'path': pathlib.Path, # absolute path to the file
|
|
|
|
|
- 'name': str, # filename only (no directory)
|
|
|
|
|
- 'ext': str, # lowercased extension: 'mkv', 'mp4', 'm4v', 'srt'
|
|
|
|
|
- 'group': str, # base name shared by the file group
|
|
|
|
|
- 'lang': str | None, # ISO 639-2/T alpha-3 code, or None
|
|
|
|
|
- 'sdh': bool, # True if filename contains the 'sdh' segment
|
|
|
|
|
- 'mux_vers': int | None, # value of zib.mux xattr, or None if not set
|
|
|
|
|
- 'lang_vers': int | None, # value of zib.lang xattr, or None if not set
|
|
|
|
|
-}
|
|
|
|
|
|
|
+class FileInfo(TypedDict):
|
|
|
|
|
+ path: pathlib.Path | None # absolute path to the file
|
|
|
|
|
+ name: str # filename only (no directory)
|
|
|
|
|
+ ext: str # lowercased extension: 'mkv', 'mp4', 'm4v', 'srt'
|
|
|
|
|
+ group: str # base name shared by the file group
|
|
|
|
|
+ lang: str | None # ISO 639-2/T alpha-3 code, or None
|
|
|
|
|
+ sdh: bool # True if filename contains the 'sdh' segment
|
|
|
|
|
+ mux_vers: int | None # value of zib.mux xattr, or None if not set
|
|
|
|
|
+ lang_vers: int | None # value of zib.lang xattr, or None if not set
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
@@ -149,10 +148,13 @@ Returns JSON; `Probe.probe()` parses and returns the `streams` list. Each stream
|
|
|
|
|
|
|
|
## Subprocess runner
|
|
## Subprocess runner
|
|
|
|
|
|
|
|
-`Proc.run(cmd)` is a thin wrapper around `subprocess.run`. It captures stdout and stderr, decodes them as UTF-8, and returns:
|
|
|
|
|
|
|
+`Proc.run(cmd)` is a thin wrapper around `subprocess.run`. It captures stdout and stderr, decodes them as UTF-8, and returns a `ProcResult` (a `TypedDict` defined in `proc.py` and re-exported from `__init__.py`):
|
|
|
|
|
|
|
|
```python
|
|
```python
|
|
|
-{ 'out': str | None, 'err': str | None, 'code': int }
|
|
|
|
|
|
|
+class ProcResult(TypedDict):
|
|
|
|
|
+ out: str | None
|
|
|
|
|
+ err: str | None
|
|
|
|
|
+ code: int
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
A sentinel exit code of `-111` is used if `returncode` cannot be read. Non-zero exit codes are logged as errors.
|
|
A sentinel exit code of `-111` is used if `returncode` cannot be read. Non-zero exit codes are logged as errors.
|
|
@@ -166,6 +168,3 @@ A sentinel exit code of `-111` is used if `returncode` cannot be read. Non-zero
|
|
|
- **Single video per group.** Groups with zero or more than one video file are skipped with an error log. There is no mechanism to resolve ambiguity.
|
|
- **Single video per group.** Groups with zero or more than one video file are skipped with an error log. There is no mechanism to resolve ambiguity.
|
|
|
- **xattrs are filesystem-dependent.** Tags are silently lost when files cross filesystem boundaries (e.g., moving from a staging share to final storage). This causes files to be reprocessed, which is safe but redundant.
|
|
- **xattrs are filesystem-dependent.** Tags are silently lost when files cross filesystem boundaries (e.g., moving from a staging share to final storage). This causes files to be reprocessed, which is safe but redundant.
|
|
|
- **No tests.** There is no test suite. External tools make unit testing awkward; integration tests against a fixture directory would be the natural approach.
|
|
- **No tests.** There is no test suite. External tools make unit testing awkward; integration tests against a fixture directory would be the natural approach.
|
|
|
-- **No type hints.** All public APIs take and return untyped dicts. A typed `FileInfo` dataclass or `TypedDict` would clarify contracts.
|
|
|
|
|
-- **Python version floor is understated.** `pyproject.toml` declares `requires-python = ">= 3.8"` but the codebase uses `int.from_bytes(x)` / `int.to_bytes()` without the `byteorder` argument (requires 3.11+) and f-strings with nested quotes (requires 3.12+). The floor should be `>= 3.12`.
|
|
|
|
|
-- **Build config is broken.** `pyproject.toml` sets `[tool.hatch.build] source = "src"` but no `src/` directory exists. The package lives at the project root and would not build correctly with hatchling without correction.
|
|
|