summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSchark <jordan@schark.online>2026-02-28 02:08:19 -0500
committerSchark <jordan@schark.online>2026-02-28 02:08:19 -0500
commit45ea44733cceb39f477b55c58193df5b69647a80 (patch)
treebf5a422a1ea0c26ab066fd197ae9cabb24d9705a
parente9619b313bd78cabd397a844f44e84f36e4a1458 (diff)
downloadcs2pov-45ea44733cceb39f477b55c58193df5b69647a80.tar.gz
cs2pov-45ea44733cceb39f477b55c58193df5b69647a80.zip
Finish config file support
Diffstat (limited to '')
-rw-r--r--cs2pov/cli.py43
-rw-r--r--cs2pov/settings.py44
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