# Drop a PDF (or a .url/.txt file with a link to a PDF) in _incoming/ and commit it. This job turns it into a finished audiobook folder and commits that back. # No bot is involved: every step is a deterministic script in _pipeline/. name: audiobook on: push: branches: [main, 'test/**'] # test/** lets the pipeline be proven on a branch without touching main paths: ['_incoming/*.pdf', '_incoming/*.PDF', '_incoming/*.url', '_incoming/*.URL', '_incoming/*.txt'] # a PDF or a link file landing in _incoming/ starts a run workflow_dispatch: inputs: pdf: description: 'PDF or .url/.txt link-file path inside the repo (empty = everything waiting in _incoming/; a dry run with nothing waiting uses the Clark book as a regression sample)' required: false default: '' voice_endpoint: description: 'Testing only: use this voice URL instead of the default (for example to prove a failed voice run commits nothing)' required: false default: '' dry_run: description: 'true = text stages only, voice reachability check, push-permission test, commit nothing' type: boolean default: true permissions: contents: write concurrency: group: audiobook cancel-in-progress: false # one book at a time; a second drop waits jobs: build: runs-on: ubuntu-latest timeout-minutes: 720 # long books render for hours # The commit-back commit carries [skip ci] and removes the PDF from _incoming/, so it cannot start another run. if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} env: AUDIOBOOK_VOICE_ENDPOINT: ${{ (github.event_name == 'workflow_dispatch' && inputs.voice_endpoint) || vars.AUDIOBOOK_VOICE_ENDPOINT || 'http://192.168.1.202:8090/tts' }} AUDIOBOOK_WORK_DIR: /tmp/audiobook-work AUDIOBOOK_REQUIRE_OCR: '1' BRANCH: ${{ github.ref_name }} DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || 'false' }} PDF_INPUT: ${{ github.event_name == 'workflow_dispatch' && inputs.pdf || '' }} steps: - name: Check out the repository timeout-minutes: 15 # a stalled fetch fails the run instead of hanging it for hours uses: actions/checkout@v4 with: fetch-depth: 100 # enough history to rebase onto a main that moved during a long render filter: blob:none # do not download every book's audio and PDFs on every run sparse-checkout: | /_pipeline/ /_incoming/ /.gitea/ sparse-checkout-cone-mode: false - name: Install tools (fresh container every run) run: bash _pipeline/ci/install.sh - name: Unit tests (dry runs only) if: ${{ env.DRY_RUN == 'true' }} run: | .venv/bin/pip install -q --disable-pip-version-check reportlab pillow .venv/bin/python -W ignore -m unittest discover -s _pipeline/tests 2>&1 | grep -v -E '^\s+(data|read) =' | tail -40 test "${PIPESTATUS[0]}" -eq 0 - name: Voice server reachable from this job run: | echo "voice endpoint: $AUDIOBOOK_VOICE_ENDPOINT" base="${AUDIOBOOK_VOICE_ENDPOINT%/tts}" curl -sS --max-time 20 "$base/api/model-info" && echo || echo "WARNING: voice server not reachable from this job (the Process step will stop with a clear message if it is needed)" - name: Process run: bash _pipeline/ci/run.sh