diff options
| author | Schark <jordan@schark.online> | 2026-02-28 02:08:19 -0500 |
|---|---|---|
| committer | Schark <jordan@schark.online> | 2026-02-28 02:08:19 -0500 |
| commit | 45ea44733cceb39f477b55c58193df5b69647a80 (patch) | |
| tree | bf5a422a1ea0c26ab066fd197ae9cabb24d9705a | |
| parent | e9619b313bd78cabd397a844f44e84f36e4a1458 (diff) | |
| download | cs2pov-45ea44733cceb39f477b55c58193df5b69647a80.tar.gz cs2pov-45ea44733cceb39f477b55c58193df5b69647a80.zip | |
Finish config file support
| -rw-r--r-- | cs2pov/cli.py | 43 | ||||
| -rw-r--r-- | cs2pov/settings.py | 44 |
2 files changed, 57 insertions, 30 deletions
diff --git a/cs2pov/cli.py b/cs2pov/cli.py index faa440d..f5e09d2 100644 --- a/cs2pov/cli.py +++ b/cs2pov/cli.py @@ -765,17 +765,21 @@ Examples: help="X display number (default: 0)") recording_args.add_argument("--no-hud", action="store_true", default=None, help="Hide HUD elements") + recording_args.add_argument("--hud", action="store_false", dest="no_hud", + help="Show HUD (override config no_hud)") recording_args.add_argument("--no-audio", action="store_true", default=None, help="Disable audio recording") + recording_args.add_argument("--audio", action="store_false", dest="no_audio", + help="Enable audio (override config no_audio)") recording_args.add_argument("--audio-device", default=None, help="PulseAudio device (auto-detected)") recording_args.add_argument("--cs2-path", type=Path, default=None, help="Custom CS2 installation path") - recording_args.add_argument("--tick-nav", action="store_true", default=None, + recording_args.add_argument("--tick-nav", action=argparse.BooleanOptionalAction, default=None, help="Enable tick-based navigation (skip deaths in real-time)") verbose_args = argparse.ArgumentParser(add_help=False) - verbose_args.add_argument("-v", "--verbose", action="store_true", default=None, + verbose_args.add_argument("-v", "--verbose", action=argparse.BooleanOptionalAction, default=None, help="Verbose output") # INFO command @@ -798,6 +802,8 @@ Examples: ) pov_parser.add_argument("--no-trim", action="store_true", default=None, help="Skip post-processing trim") + pov_parser.add_argument("--trim", action="store_false", dest="no_trim", + help="Enable trimming (override config no_trim)") # RECORD command subparsers.add_parser( @@ -1156,22 +1162,39 @@ def cmd_run(args) -> int: print("Hint: Create one with 'cs2pov init' or specify with --config", file=sys.stderr) return 1 + project = load_config(config_path) + if not project.jobs: + print(f"Error: No jobs defined in {config_path.name}", file=sys.stderr) + print("Hint: Add jobs to the 'jobs' array in your config file", file=sys.stderr) + return 1 + return _run_batch(args, "pov") -def cmd_trim(args) -> int: - """Handle 'trim' command - post-process existing video.""" - # Apply config defaults for verbose if not explicitly set +def _apply_config_defaults(args) -> None: + """Apply config defaults to args for commands that don't use _run_batch. + + Non-fatal: config errors are silently ignored. + """ try: config_path = find_config(getattr(args, "config", None)) if config_path: project = load_config(config_path) - if args.verbose is None: - args.verbose = project.defaults.get("verbose", False) + for key, value in project.defaults.items(): + if key in HARDCODED_DEFAULTS and getattr(args, key, None) is None: + setattr(args, key, value) except ConfigError: - pass # Config errors are non-fatal for trim - if args.verbose is None: - args.verbose = False + pass + + # Fill remaining None values with hardcoded defaults + for key, value in HARDCODED_DEFAULTS.items(): + if getattr(args, key, None) is None: + setattr(args, key, value) + + +def cmd_trim(args) -> int: + """Handle 'trim' command - post-process existing video.""" + _apply_config_defaults(args) video_path = args.video.resolve() if not video_path.exists(): diff --git a/cs2pov/settings.py b/cs2pov/settings.py index 41b316f..f44796f 100644 --- a/cs2pov/settings.py +++ b/cs2pov/settings.py @@ -31,14 +31,14 @@ HARDCODED_DEFAULTS: dict[str, Any] = { "verbose": False, } -# Keys valid in "defaults" and as job overrides -VALID_DEFAULT_KEYS = set(HARDCODED_DEFAULTS.keys()) +# Keys valid in "defaults" and as job overrides (includes per-job fields that can be defaulted) +VALID_DEFAULT_KEYS = set(HARDCODED_DEFAULTS.keys()) | {"player", "demo", "output"} -# Keys required in each job -REQUIRED_JOB_KEYS = {"demo", "player", "output"} +# Keys required after merging job with defaults (not per-job — defaults can provide them) +REQUIRED_MERGED_KEYS = {"demo", "player", "output"} -# Keys valid in a job entry (required + overridable + job-only) -VALID_JOB_KEYS = REQUIRED_JOB_KEYS | VALID_DEFAULT_KEYS | {"type"} +# Keys valid in a job entry +VALID_JOB_KEYS = REQUIRED_MERGED_KEYS | VALID_DEFAULT_KEYS | {"type"} # Valid job types VALID_JOB_TYPES = {"pov", "record"} @@ -50,9 +50,9 @@ CURRENT_VERSION = 1 @dataclass class JobConfig: """A single recording job from config.""" - demo: str - player: str - output: str + demo: Optional[str] = None + player: Optional[str] = None + output: Optional[str] = None type: str = "pov" overrides: dict[str, Any] = field(default_factory=dict) @@ -127,10 +127,6 @@ def load_config(path: Path) -> ProjectConfig: if not isinstance(job_raw, dict): raise ConfigError(f"Job {i+1} must be an object") - missing = REQUIRED_JOB_KEYS - set(job_raw.keys()) - if missing: - raise ConfigError(f"Job {i+1} missing required keys: {', '.join(sorted(missing))}") - unknown = set(job_raw.keys()) - VALID_JOB_KEYS if unknown: raise ConfigError(f"Job {i+1} has unknown keys: {', '.join(sorted(unknown))}") @@ -144,13 +140,14 @@ def load_config(path: Path) -> ProjectConfig: f"Job {i+1}: invalid type '{job_type}', must be one of: {', '.join(sorted(VALID_JOB_TYPES))}" ) - overrides = {k: v for k, v in job_raw.items() if k not in REQUIRED_JOB_KEYS and k != "type"} + overrides = {k: v for k, v in job_raw.items() + if k not in REQUIRED_MERGED_KEYS and k != "type"} _validate_setting_types(overrides, f"job {i+1}") jobs.append(JobConfig( - demo=job_raw["demo"], - player=job_raw["player"], - output=job_raw["output"], + demo=job_raw.get("demo"), + player=job_raw.get("player"), + output=job_raw.get("output"), type=job_type, overrides=overrides, )) @@ -176,6 +173,9 @@ def _validate_setting_types(settings: dict[str, Any], context: str) -> None: "tick_nav": (bool,), "no_trim": (bool,), "verbose": (bool,), + "player": (str,), + "demo": (str,), + "output": (str,), } for key, value in settings.items(): @@ -191,9 +191,13 @@ def resolve_job(job: JobConfig, defaults: dict[str, Any]) -> dict[str, Any]: """Merge job overrides onto defaults. Returns flat dict with all keys.""" merged = dict(defaults) merged.update(job.overrides) - merged["demo"] = job.demo - merged["player"] = job.player - merged["output"] = job.output + # Job fields override defaults (only set if job provides them) + if job.demo is not None: + merged["demo"] = job.demo + if job.player is not None: + merged["player"] = job.player + if job.output is not None: + merged["output"] = job.output return merged |
