devedeng-extended/CLAUDE.md
Luna 5fe1013be8 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 <noreply@anthropic.com>
2026-06-04 04:50:16 -07:00

68 lines
7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 cores1) 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).