From 1a11710df731e769dc7a4ae3552d8c7b2821126c Mon Sep 17 00:00:00 2001 From: Javanaut Date: Mon, 15 Jun 2026 11:14:21 +0200 Subject: [PATCH] Convert docs to sphinx --- .gitignore | 4 +- .vscode/extensions.json | 11 + .vscode/settings.json | 18 ++ SCRATCHPAD.md | 8 + docs/Makefile | 21 ++ docs/api.rst | 31 +++ docs/conf.py | 44 ++++ docs/development.rst | 50 +++++ docs/esbonio.db | Bin 0 -> 4096 bytes docs/file_formats.md | 170 --------------- docs/file_formats.rst | 192 ++++++++++++++++ docs/index.rst | 25 +++ docs/installation.rst | 52 +++++ docs/make.bat | 42 ++++ docs/usage.rst | 75 +++++++ pyproject.toml | 6 + tests/prepare.sh | 471 ++++++++++++++++++++++++++++++++++++++++ 17 files changed, 1048 insertions(+), 172 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/conf.py create mode 100644 docs/development.rst create mode 100644 docs/esbonio.db delete mode 100644 docs/file_formats.md create mode 100644 docs/file_formats.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/usage.rst create mode 100755 tests/prepare.sh diff --git a/.gitignore b/.gitignore index c624ec6..25d914e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ __pycache__/ *.py[cod] junk/ -.vscode .ipynb_checkpoints/ tools/ansible/inventory/hawaii.yml tools/ansible/inventory/peppermint.yml @@ -17,6 +16,7 @@ dist/ *.egg-info/ .venv/ venv/ +docs/_build/ .codex @@ -24,4 +24,4 @@ venv/ *.webm *.mp4 ffmpeg2pass-0.log -*.sup \ No newline at end of file +*.sup diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..6c7e62c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "swyddfa.esbonio", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.debugpy", + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "DavidAnson.vscode-markdownlint" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..daa653b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "esbonio.sphinx.pythonCommand": "${venv:.venv}/bin/python", + "esbonio.sphinx.buildCommand": [ + "sphinx-build", + "-b", + "html", + "docs", + "docs/_build/html" + ], + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": [ + "--ignore=tests/legacy", + "--ignore=tests/support", + "tests" + ], + "restructuredtext.confPath": "${workspaceFolder}/docs" +} diff --git a/SCRATCHPAD.md b/SCRATCHPAD.md index 89eaddd..25f63bb 100644 --- a/SCRATCHPAD.md +++ b/SCRATCHPAD.md @@ -69,3 +69,11 @@ ## Delete When - Delete this scratchpad once the optimization backlog is either converted into issues/work items or distilled into durable project guidance. + + + diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..38f13a3 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,21 @@ +SPHINXOPTS ?= +VENV_SPHINXBUILD = ../.venv/bin/sphinx-build +SPHINXBUILD ?= $(if $(wildcard $(VENV_SPHINXBUILD)),$(VENV_SPHINXBUILD),sphinx-build) +SOURCEDIR = . +BUILDDIR = _build + +.PHONY: help clean html linkcheck + +help: + @echo "Please use 'make ' where is one of" + @echo " html to make standalone HTML files" + @echo " linkcheck to check all external links for integrity" + +clean: + rm -rf "$(BUILDDIR)" + +html: + @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) + +linkcheck: + @$(SPHINXBUILD) -b linkcheck "$(SOURCEDIR)" "$(BUILDDIR)/linkcheck" $(SPHINXOPTS) diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..7abda7a --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,31 @@ +API Reference +============= + +This section exposes selected modules that are useful when working on tests, +diagnostics, process execution, metadata editing, and file probing. + +CLI Helpers +----------- + +.. automodule:: ffx.cli + :members: + :undoc-members: + +Process Helpers +--------------- + +.. automodule:: ffx.process + :members: + :undoc-members: + +File Probing +------------ + +.. automodule:: ffx.file_properties + +Metadata Editing +---------------- + +.. automodule:: ffx.metadata_editor + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..54a69b5 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError, version as package_version +from pathlib import Path +import sys + + +ROOT_DIR = Path(__file__).resolve().parents[1] +SRC_DIR = ROOT_DIR / "src" +sys.path.insert(0, str(SRC_DIR)) + +project = "FFX" +author = "javanaut@maveno.de" +copyright = "2026, Maveno" + +try: + release = package_version("ffx") +except PackageNotFoundError: + release = "0.0.0" +version = release + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_copybutton", +] + +source_suffix = { + ".rst": "restructuredtext", +} + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +html_theme = "sphinx_rtd_theme" +html_title = "FFX" +html_static_path = [] + +autodoc_typehints = "description" +autodoc_member_order = "bysource" +napoleon_google_docstring = True +napoleon_numpy_docstring = True + diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000..0d9281e --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,50 @@ +Development +=========== + +The repo-local ``.venv`` is the preferred environment for contributors working +on tests or documentation: + +.. code-block:: sh + + tests/prepare.sh + +The preparation script installs the package in editable mode with both test and +documentation extras: + +.. code-block:: text + + .[test,docs] + +Run Tests +--------- + +Run the modern pytest suite: + +.. code-block:: sh + + .venv/bin/python -m pytest --ignore=tests/legacy --ignore=tests/support tests + +The legacy harness remains available separately and is intentionally not part of +the default pytest run. + +Build Docs +---------- + +Build HTML documentation: + +.. code-block:: sh + + .venv/bin/sphinx-build -b html docs docs/_build/html + +The same command is wrapped by the Sphinx ``Makefile``: + +.. code-block:: sh + + make -C docs html + +VS Code +------- + +The repository includes ``.vscode/extensions.json`` with recommended +extensions, including Esbonio for Sphinx language-server support. The workspace +settings point Python tooling and Esbonio at the repo-local ``.venv``. diff --git a/docs/esbonio.db b/docs/esbonio.db new file mode 100644 index 0000000000000000000000000000000000000000..0de02ecf623141161c863ee065d9f7dd83cbe849 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYDWBNUL 2>NUL +if errorlevel 9009 ( + echo. + echo The 'sphinx-build' command was not found. Make sure Sphinx is installed, + echo then set SPHINXBUILD to the full path if needed. + exit /b 1 +) + +if "%1" == "" goto help +if "%1" == "html" goto html +if "%1" == "linkcheck" goto linkcheck +echo. +echo Unknown target "%1". +goto help + +:html +%SPHINXBUILD% -b html %SOURCEDIR% %BUILDDIR%\html %SPHINXOPTS% +goto end + +:linkcheck +%SPHINXBUILD% -b linkcheck %SOURCEDIR% %BUILDDIR%\linkcheck %SPHINXOPTS% +goto end + +:help +echo. +echo Please use 'make.bat ^' where ^ is one of +echo html to make standalone HTML files +echo linkcheck to check all external links for integrity + +:end +popd diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..5599e8d --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,75 @@ +Usage +===== + +FFX exposes a single ``ffx`` command with subcommands for inspection, +conversion, metadata editing, setup, and maintenance. + +Inspect Files +------------- + +Open the inspection workflow for one or more files: + +.. code-block:: sh + + ffx inspect /path/to/episode.mkv + +Print resolved season-shift mappings without opening the TUI: + +.. code-block:: sh + + ffx inspect --shift /path/to/episode.mkv + +Convert Files +------------- + +Convert one or more source files using stored rules where available: + +.. code-block:: sh + + ffx convert /path/to/episode.mkv + +Useful overrides include: + +* ``--no-pattern`` to skip database pattern matching +* ``--show``, ``--season``, and ``--episode`` for explicit episode identity +* ``--output-directory`` for generated output placement +* ``--subtitle-directory`` and ``--subtitle-prefix`` for sidecar subtitle + imports +* ``--copy-video`` or ``--copy-audio`` to preserve selected stream types +* ``--rename-only`` for filename normalization without media rewriting + +Manage Shows And Patterns +------------------------- + +Open the Textual interface for show and pattern management: + +.. code-block:: sh + + ffx shows + +Extract Streams +--------------- + +Extract streams from a file: + +.. code-block:: sh + + ffx unmux /path/to/episode.mkv + +For subtitle-only extraction: + +.. code-block:: sh + + ffx unmux --subtitles-only --label show-name /path/to/episode.mkv + +Detect Crop +----------- + +Ask FFmpeg to suggest crop parameters: + +.. code-block:: sh + + ffx cropdetect /path/to/episode.mkv + +The default sampling window is controlled by the application defaults and can be +overridden with command options. diff --git a/pyproject.toml b/pyproject.toml index 1d50c85..6a75f42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,12 @@ Issues = "https://gitea.maveno.de/Javanaut/ffx/issues" test = [ "pytest", ] +docs = [ + "esbonio", + "sphinx", + "sphinx-copybutton", + "sphinx-rtd-theme", +] [build-system] requires = [ diff --git a/tests/prepare.sh b/tests/prepare.sh new file mode 100755 index 0000000..f5a3ec5 --- /dev/null +++ b/tests/prepare.sh @@ -0,0 +1,471 @@ +#!/usr/bin/env bash + +set -u + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +VENV_DIR="${FFX_TEST_VENV_DIR:-${ROOT_DIR}/.venv}" +VENV_BIN_DIR="${VENV_DIR}/bin" +VENV_PYTHON="${VENV_BIN_DIR}/python" +VENV_PIP="${VENV_BIN_DIR}/pip" + +CHECK_ONLY=0 +READINESS_FAILURES=0 +INSTALL_FAILURES=0 + +MISSING_REQUIRED_SYSTEM=() + +COLOR_RESET="" +COLOR_GREEN="" +COLOR_YELLOW="" +COLOR_RED="" + +if [ -t 1 ]; then + COLOR_RESET="$(printf '\033[0m')" + COLOR_GREEN="$(printf '\033[32m')" + COLOR_YELLOW="$(printf '\033[33m')" + COLOR_RED="$(printf '\033[31m')" +fi + +usage() { + cat </dev/null 2>&1 +} + +check_python_venv_support() { + python3 -m venv --help >/dev/null 2>&1 +} + +check_system_command() { + command_exists "$1" +} + +check_venv_python() { + [ -x "${VENV_PYTHON}" ] +} + +check_venv_pip() { + check_venv_python && "${VENV_PIP}" --version >/dev/null 2>&1 +} + +check_venv_ffx() { + check_venv_python && "${VENV_PYTHON}" -m ffx version >/dev/null 2>&1 +} + +check_venv_pytest() { + check_venv_python && "${VENV_PYTHON}" -m pytest --version >/dev/null 2>&1 +} + +check_venv_sphinx() { + check_venv_python && "${VENV_BIN_DIR}/sphinx-build" --version >/dev/null 2>&1 +} + +check_venv_docs_packages() { + check_venv_python && "${VENV_PYTHON}" - <<'PY' >/dev/null 2>&1 +import esbonio +import sphinx +import sphinx_rtd_theme +PY +} + +check_editable_install() { + check_venv_python && FFX_REPO_ROOT="${ROOT_DIR}" "${VENV_PYTHON}" - <<'PY' >/dev/null 2>&1 +from __future__ import annotations + +import os +from pathlib import Path +import ffx + +repo_root = Path(os.environ["FFX_REPO_ROOT"]).resolve() +package_path = Path(ffx.__file__).resolve() + +raise SystemExit(0 if repo_root in package_path.parents else 1) +PY +} + +check_python_environment_ready() { + check_venv_python && + check_venv_pip && + check_venv_pytest && + check_venv_sphinx && + check_venv_docs_packages && + check_venv_ffx && + check_editable_install +} + +command_detail() { + command -v "$1" || printf "command '%s' not found" "$1" +} + +python_venv_detail() { + if check_python_venv_support; then + printf 'python3 -m venv is available' + else + printf 'python3 venv support is unavailable' + fi +} + +venv_python_detail() { + if check_venv_python; then + printf '%s' "${VENV_PYTHON}" + else + printf 'missing %s' "${VENV_PYTHON}" + fi +} + +venv_pip_detail() { + if check_venv_pip; then + "${VENV_PIP}" --version + else + printf 'missing pip in %s' "${VENV_DIR}" + fi +} + +venv_ffx_detail() { + if check_venv_ffx; then + printf 'ffx import and CLI entry are available' + else + printf 'ffx is not installed in %s' "${VENV_DIR}" + fi +} + +venv_pytest_detail() { + if check_venv_pytest; then + "${VENV_PYTHON}" -m pytest --version 2>/dev/null | head -n 1 + else + printf 'pytest is not installed in %s' "${VENV_DIR}" + fi +} + +venv_sphinx_detail() { + if check_venv_sphinx; then + "${VENV_BIN_DIR}/sphinx-build" --version 2>&1 + else + printf 'sphinx-build is not installed in %s' "${VENV_DIR}" + fi +} + +venv_docs_packages_detail() { + if check_venv_docs_packages; then + printf 'Sphinx, Read the Docs theme, and Esbonio packages are importable' + else + printf 'one or more docs packages are missing in %s' "${VENV_DIR}" + fi +} + +editable_install_detail() { + if check_editable_install; then + printf 'ffx resolves from %s' "${ROOT_DIR}" + else + printf 'ffx does not resolve from the project source tree' + fi +} + +report_required_command() { + local label="$1" + local command_name="$2" + + if check_system_command "${command_name}"; then + report_component ok "${label}" "$(command_detail "${command_name}")" + else + report_component failed "${label}" "$(command_detail "${command_name}")" + MISSING_REQUIRED_SYSTEM+=("${command_name}") + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi +} + +print_system_status() { + MISSING_REQUIRED_SYSTEM=() + + echo "System toolchain status:" + report_required_command "git" "git" + report_required_command "python3" "python3" + + if check_system_command "python3" && check_python_venv_support; then + report_component ok "python3 venv" "$(python_venv_detail)" + else + report_component failed "python3 venv" "$(python_venv_detail)" + MISSING_REQUIRED_SYSTEM+=("python3-venv") + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + report_required_command "ffmpeg" "ffmpeg" + report_required_command "ffprobe" "ffprobe" + report_required_command "cpulimit" "cpulimit" +} + +print_python_status() { + echo "Repo test and docs virtualenv status:" + + if check_venv_python; then + report_component ok "test virtualenv" "$(venv_python_detail)" + else + report_component failed "test virtualenv" "$(venv_python_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + if check_venv_pip; then + report_component ok "test pip" "$(venv_pip_detail)" + else + report_component failed "test pip" "$(venv_pip_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + if check_venv_pytest; then + report_component ok "test pytest" "$(venv_pytest_detail)" + else + report_component failed "test pytest" "$(venv_pytest_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + if check_venv_sphinx; then + report_component ok "docs sphinx" "$(venv_sphinx_detail)" + else + report_component failed "docs sphinx" "$(venv_sphinx_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + if check_venv_docs_packages; then + report_component ok "docs packages" "$(venv_docs_packages_detail)" + else + report_component failed "docs packages" "$(venv_docs_packages_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + if check_venv_ffx; then + report_component ok "test ffx" "$(venv_ffx_detail)" + else + report_component failed "test ffx" "$(venv_ffx_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi + + if check_editable_install; then + report_component ok "editable source" "$(editable_install_detail)" + else + report_component failed "editable source" "$(editable_install_detail)" + READINESS_FAILURES=$((READINESS_FAILURES + 1)) + fi +} + +print_status_report() { + READINESS_FAILURES=0 + + print_system_status + echo + print_python_status +} + +detect_package_manager() { + if command_exists apt-get; then + printf 'apt-get\n' + return 0 + fi + if command_exists pacman; then + printf 'pacman\n' + return 0 + fi + return 1 +} + +run_root_command() { + if [ "${EUID}" -eq 0 ]; then + "$@" + elif command_exists sudo; then + sudo -n "$@" + else + return 1 + fi +} + +install_system_requirements() { + local package_manager + + if [ "${#MISSING_REQUIRED_SYSTEM[@]}" -eq 0 ]; then + return 0 + fi + + if ! package_manager="$(detect_package_manager)"; then + printf 'No supported package manager found for automatic system preparation.\n' >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + + case "${package_manager}" in + apt-get) + printf 'Installing required system dependencies via apt-get...\n' + if ! run_root_command apt-get update; then + printf 'apt-get update failed or requires interactive sudo.\n' >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + if ! run_root_command apt-get install -y git python3 python3-venv ffmpeg cpulimit; then + printf 'apt-get install failed or requires interactive sudo.\n' >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + ;; + pacman) + printf 'Installing required system dependencies via pacman...\n' + if ! run_root_command pacman -Sy --noconfirm git python ffmpeg cpulimit; then + printf 'pacman install failed or requires interactive sudo.\n' >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + ;; + esac + + return 0 +} + +ensure_test_venv() { + if ! check_venv_python; then + printf 'Creating repo test virtualenv at %s...\n' "${VENV_DIR}" + if ! python3 -m venv "${VENV_DIR}"; then + printf 'Failed to create test virtualenv at %s.\n' "${VENV_DIR}" >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + fi + + if ! check_venv_pip; then + printf 'Missing pip in %s.\n' "${VENV_DIR}" >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + + printf 'Installing FFX package with test and docs extras into %s...\n' "${VENV_DIR}" + if ! ( + cd "${ROOT_DIR}" && + "${VENV_PIP}" install --editable '.[test,docs]' + ); then + printf 'Failed to install FFX package with test and docs extras into %s.\n' "${VENV_DIR}" >&2 + INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) + return 1 + fi + + return 0 +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --check) + CHECK_ONLY=1 + ;; + --help|-h) + usage + exit 0 + ;; + *) + printf 'Unknown option: %s\n\n' "$1" >&2 + usage >&2 + exit 2 + ;; + esac + shift + done +} + +main() { + parse_args "$@" + + print_status_report + + if [ "${CHECK_ONLY}" -eq 0 ]; then + if [ "${#MISSING_REQUIRED_SYSTEM[@]}" -gt 0 ]; then + echo + install_system_requirements + fi + + if check_python_environment_ready; then + echo + report_component ok "Python package install" "repo test and docs virtualenv is already ready" + elif check_system_command "python3" && check_python_venv_support; then + echo + ensure_test_venv + fi + + echo + print_status_report + fi + + echo + if [ "${INSTALL_FAILURES}" -gt 0 ]; then + echo "One or more test preparation steps failed; see the status checks above." >&2 + exit 1 + fi + + if [ "${READINESS_FAILURES}" -gt 0 ]; then + if [ "${CHECK_ONLY}" -eq 1 ]; then + echo "The FFX test and docs environment is incomplete." >&2 + else + echo "Required test or docs components are still missing after preparation." >&2 + fi + exit 1 + fi + + if [ "${CHECK_ONLY}" -eq 1 ]; then + echo "The FFX test and docs environment is ready." + else + echo "The FFX test and docs environment is prepared." + fi +} + +main "$@"