From 5fe1013be804b8fb8423e446a322e1518634d504 Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 4 Jun 2026 04:50:16 -0700 Subject: [PATCH] Add CLAUDE.md and harden .gitignore Document the fork's architecture and dev/verify workflow for future work (no test framework; verify via pure-logic functions and generated command_var). Extend .gitignore to exclude .claude/ local config (machine-specific paths + personal permission allowlists), common secret file patterns, and editor/OS junk, while keeping the shipped base_*.mpg package data tracked. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 29 +++++++++++++++++++++++ CLAUDE.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 1691c8b..ab7d506 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,36 @@ +# Python build/cache artifacts __pycache__/ *.pyc *.pyo build/ dist/ *.egg-info/ + +# Compiled translations (generated from po/ at build time) src/devedeng/data/locale/ + +# Claude Code local config / session state (machine-specific, may include +# local paths and personal permission allowlists; not for sharing) +.claude/ +CLAUDE.local.md + +# Local environment / secrets (defense-in-depth; nothing here today) +.env +.env.* +*.pem +*.key +*.p12 +id_rsa* +credentials* +*.secret + +# Editor / OS junk +.vscode/ +.idea/ +*.swp +*~ +.DS_Store + +# Runtime/output artifacts this project can produce +*.iso +*.mpg.progress diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f9a58be --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,68 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +This is a **fork** of DeVeDe-NG (a GTK3 video-DVD authoring app) that adds, on top of upstream: embedded-subtitle **hardsubbing** (burn-in), selectable DVD **encode profiles**, and **auto-splitting** of over-long projects across multiple ISOs. See `FORK_NOTES.md` for the feature-level description of the fork's additions; this file covers how to work in the code. + +## Running and installing + +Run straight from the source tree without installing (best for iterating): + +```bash +PYTHONPATH=src python3 -c "import devedeng.devedeng as d; d.py()" +``` + +`d.py()` calls `Gtk.main()` and blocks. When driving it from a tool/agent, launch detached so an editor/terminal crash can't wedge it: + +```bash +nohup env PYTHONPATH=src python3 -c "import devedeng.devedeng as d; d.py()" >/tmp/devede_run.log 2>&1 & disown +``` + +Install (system): `sudo python3 -m pip install .` (add `--break-system-packages` if pip refuses). Entry point is `devede_ng` → `devedeng/devedeng.py:py()`. + +Runtime external tools (must be on PATH): `ffmpeg`/`ffprobe` (or `avconv`/`avprobe`), `dvdauthor`, `mkisofs`/`genisoimage`, `spumux`, and optionally a player (vlc/mpv/mplayer) and burner (brasero/k3b/xfburn). The full dependency list lives in `stdeb.cfg` / `stpacman.cfg`. + +## Testing + +There is **no test framework or test suite.** Verify changes two ways: + +1. **Pure logic** (no GTK/display) — e.g. the split planner. Import and exercise directly: + ```bash + cd src && python3 -c "from devedeng.project import plan_disc_split; ..." + ``` + `plan_disc_split`, `_movie_chapter_seconds`, and `_nearest_chapter_before` in `project.py` are deliberately module-level pure functions so they can be unit-tested without instantiating GTK. + +2. **Generated ffmpeg commands** — build a converter and inspect `command_var` without running anything. The config singleton works headless: + ```python + cfg = devedeng.configuration_data.configuration.get_config(); cfg.disc_type = "dvd" + conv = devedeng.ffmpeg.ffmpeg(); conv.convert_file(mock_file_movie, "/tmp/out.mpg", 0) + print(conv.command_var) # the exact ffmpeg argv + ``` + `convert_file` takes the `file_movie` object as `file_project` and reads `hardsub_index`/`hardsub_kind`, the encode profile from config, etc. For a full integration check, run a real short encode through the executor's GLib loop and watch a fake progress bar (GTK is installed but there is no Xvfb here, so the *windowed* UI can only be verified by actually launching the app on a display). + +`contrib/dvd-safe-converter.sh` is the known-good baseline the **Compatibility** profile is modelled on — use it as a regression reference for the no-subtitle DVD encode (mpeg2video / 720x480 / 29.97 / AC3 192k / 48k). + +## Architecture + +### Pluggable backend layer (`converter.py`) +Every external program is wrapped in a small class (`ffmpeg.py`, `avconv.py`, `ffprobe.py`, `avprobe.py`, `mkisofs.py`, `dvdauthor_converter.py`, players, burners). Each declares capability flags (`supports_convert`, `supports_analize`, `supports_menu`, `supports_mkiso`, `supports_burn`) and a `check_is_installed()` staticmethod. `converter` is a singleton (`converter.get_converter()`) that probes which tools are installed and exposes the chosen analyzer/converter/menuer/mkiso/burner. **Implication:** there are two parallel implementations of most things — `ffmpeg`/`ffprobe` (default) and `avconv`/`avprobe` (legacy libav). A change to the encode path or analyzer usually needs mirroring across both backends. + +### Process execution & progress (`executor.py`) +All external commands run through `executor` (subclassed by every backend). A command is built into `self.command_var` (an argv list), launched via `launch_process`, and its stdout/stderr are read **asynchronously** through `GLib.IOChannel` watches that call `process_stdout`/`process_stderr` — this keeps the GTK main loop alive during long encodes. `executor.run()` and `wait_end()` call optional `start_progress_polling()`/`stop_progress_polling()` hooks (ffmpeg uses them). **Critical constraint:** ffmpeg progress is written to a `-progress` *file* and polled on a timer, deliberately **not** a pipe — a pipe deadlocks the encoder if the GUI ever stops draining it. + +### Job orchestration (`runner.py` + executor dependency graph) +A `runner` shows the progress window with one bar per core (`config.multicore`, default cores−1) and runs queued processes in parallel. Processes express ordering via `add_dependency()` / `add_child_process()`; the runner launches a process only when its dependencies have finished, freeing the bar for the next. A typical DVD job: N parallel `convert_file` encodes → `dvdauthor` (depends on all encodes) → `mkisofs` (depends on dvdauthor). + +### Project model & UI binding (`interface_manager.py`, `project.py`, `file_movie.py`) +`interface_manager` auto-binds named GLADE widgets to Python attributes via `add_toggle`/`add_text`/`add_group`/etc., and serializes only those registered fields (`serialize`/`unserialize`). `devede_project` (`project.py`) is the main window and owns the file list, disc settings, and the create-disc flow. `file_movie` is one movie/title. **Gotcha:** attributes that are *not* registered with the `add_*` helpers (e.g. `hardsub_index`, `hardsub_kind`) are not serialized automatically — they are persisted explicitly in `file_movie.store_element`/`restore_element`, and skipped in group ("multiproperties") mode. Some newer UI (the hardsub picker, the encode-profile selector) is built **in Python at runtime** and packed into existing containers rather than added to the `.ui` files. + +### Bitrate / size model (the math that ties profiles + splitting together) +`file_movie.set_final_rates()` maps the encode profile to `video_rate_final` / `audio_rate_final` (Compatibility = 4500k/192k, High = audio 448k + disc-fit video, Custom = user). `get_estimated_size()` / `get_kbytes_per_second()` derive size from those rates. `project.get_dvd_size()` returns per-disc capacity (already with a ~10% margin). The pure `plan_disc_split()` uses these to distribute whole titles across discs and time-split a single over-long movie at the chapter boundary nearest each disc's capacity; `dvdauthor_converter.create_dvd_project` takes a parallel `segment_lengths` list so a split segment's chapter markers stay inside the segment. NTSC DVD audio is AC3 for all profiles in this fork (upstream used MP2 for NTSC). + +## Conventions worth matching +- ffmpeg/avconv commands are assembled by appending to `command_var`; keep additions in that style and mirror across both backends where the feature applies to both. +- MPEG-2/yuv420p needs **even** width/height — round pad/crop targets and use positive crop offsets (see the pad/crop block in `ffmpeg.py`). +- All user-visible strings go through `_()` (gettext); `.ui` labels use `translatable="yes"`. Translations live in `po/`. +- The build writes compiled locale `.mo` files into `src/devedeng/data/locale/` (gitignored).