diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..a9fc084 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,61 @@ +name: Deploy Docs + +on: + push: + branches: + - main + paths: + - "site-docs/**" + - "zensical.toml" + - "requirements-docs.txt" + - ".github/workflows/deploy-docs.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Configure Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - name: Set up Python + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 + with: + python-version: "3.11" + + - name: Install docs dependencies + run: python -m pip install -r requirements-docs.txt + + - name: Build site + run: zensical build --strict + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: site/ + + deploy: + needs: build + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/.gitignore b/.gitignore index 1314a9f..a2734c4 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ __pycache__/ .DS_Store .idea/ .vscode/ + +# Generated Zensical site output +/site/ diff --git a/README.md b/README.md index 54a6b3d..adbfba5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ **ffmpegfyMe** is a Dockerized Python CLI tool for downloading streaming media (e.g. `.m3u8`) using `ffmpeg`. It offers interactive or batch downloading, progress tracking, retry logic, and state persistence. +[Documentation](https://fnayou.github.io/ffmpegfyMe/) + --- ## Features diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..0d62b74 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1 @@ +zensical==0.0.72 diff --git a/site-docs/authentication.md b/site-docs/authentication.md new file mode 100644 index 0000000..bc6214b --- /dev/null +++ b/site-docs/authentication.md @@ -0,0 +1,48 @@ +# Authentication + +Protected sources may require HTTP header or cookie auth. + +## Environment references + +Auth values use exact environment references such as `${API_TOKEN}`. + +```json +{ + "headers": { + "Authorization": "${API_TOKEN}" + }, + "cookies": "${SESSION_COOKIE}" +} +``` + +A prefix such as `Bearer ${API_TOKEN}` is invalid. References must be exact. + +Header values map header names to references. + +`cookies` takes one reference. + +## Configuration + +Compose loads a git-ignored `.env` file and injects values via `env_file`. + +A direct local Python run needs the variables exported in the process environment. + +## Secrets + +- No real secrets in examples. +- Use placeholder values only. +- Credentials do not belong in the manifest; only `${ENV_NAME}` placeholders. + +## Privacy + +- The CLI redacts HTTP(S) URLs and resolved header/cookie values from displayed + FFmpeg output. +- Progress state stores item status/output and an identity key, not raw manifest + URLs or credentials. + +## Caveats + +- Signed URLs remain in plaintext in the manifest file. Protect that file and avoid sharing or committing URLs that contain credentials. +- Local processes may inspect credential-bearing FFmpeg arguments while FFmpeg runs. + +See [Reliability](reliability.md) for state and retries. diff --git a/site-docs/community.md b/site-docs/community.md new file mode 100644 index 0000000..46602a5 --- /dev/null +++ b/site-docs/community.md @@ -0,0 +1,21 @@ +# Community + +## Participate + +- Report issues via GitHub Issues. +- Review the community health files. + +## Community and license files + +- [Code of Conduct](https://github.com/fnayou/ffmpegfyMe/blob/main/CODE_OF_CONDUCT.md) +- [Contributing](https://github.com/fnayou/ffmpegfyMe/blob/main/CONTRIBUTING.md) +- [Security](https://github.com/fnayou/ffmpegfyMe/blob/main/SECURITY.md) +- [Support](https://github.com/fnayou/ffmpegfyMe/blob/main/SUPPORT.md) +- [License](https://github.com/fnayou/ffmpegfyMe/blob/main/LICENSE) + +## Project links + +- Repository: +- Issues: + +Public documentation lives in `site-docs/`. Repository-internal guidance is not published. diff --git a/site-docs/getting-started.md b/site-docs/getting-started.md new file mode 100644 index 0000000..81bd938 --- /dev/null +++ b/site-docs/getting-started.md @@ -0,0 +1,44 @@ +# Getting Started + +## Prerequisites + +- Python 3.11+ +- FFmpeg installed and available on `PATH` +- Docker Engine and Docker Compose (optional, for container use) + +## Docker Compose + +The project provides a Compose service named `ffmpegfy-me`. + +```bash +docker compose build +docker compose run --rm ffmpegfy-me +``` + +The service mounts `in/`, `out/`, and `progress/`, and optionally loads a +git-ignored `.env` file for credentials. + +## Direct local use + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +python ffmpegfyMe.py +``` + +FFmpeg must be on `PATH`. + +## Input, output, and progress + +- Place one or more JSON manifest files in `in/`. The app lists `in/*.json` and prompts you to choose one. +- The app prompts for an output directory. Its default is `out//`, where `` is the selected file name without `.json`. +- Per-item progress is stored in `progress/_state.json`. + +## Interactive prompts + +ffmpegfyMe has no command-line argument parser and no command flags. + +Use the interactive prompts to configure batches and overwrite behavior. + +See [Manifest](manifest.md) for the input format. diff --git a/site-docs/index.md b/site-docs/index.md new file mode 100644 index 0000000..a93db5d --- /dev/null +++ b/site-docs/index.md @@ -0,0 +1,37 @@ +# ffmpegfyMe + +**ffmpegfyMe** is an interactive or batch Python wrapper around +[FFmpeg](https://ffmpeg.org/) for downloading HTTP and HTTPS media streams, +commonly HLS playlists, and saving them into local media files. + +## What it does + +- Downloads HTTP/HTTPS media streams, commonly HLS (`.m3u8`) playlists. +- Copies the stream into an `.mp4` or `.mkv` container using FFmpeg stream copy + (`-c copy`). +- Supports interactive or batch mode, progress tracking, retries, and saved state. + +## Why use it + +- One tool wraps the heavy lifting: playlist handling, retries, progress, and state. +- Stream copy means no transcoding step. +- Works with Docker or a local Python environment. + +## Workflow at a glance + +1. Add one or more JSON manifests to `in/`. +2. Run ffmpegfyMe and choose a manifest when prompted. +3. Choose an output directory or accept the `out//` default. + +See [Getting Started](getting-started.md) for setup and +[Manifest](manifest.md) for the input format. + +## MP4/MKV and stream copy + +ffmpegfyMe does **not** transcode. FFmpeg uses stream copy (`-c copy`). + +Success depends on the source streams being compatible with the chosen container. +An `.mp4` or `.mkv` output is required; unsupported codec or container combinations +fail and are reported per item. + +See [Troubleshooting](troubleshooting.md) for common failures. diff --git a/site-docs/manifest.md b/site-docs/manifest.md new file mode 100644 index 0000000..abd224e --- /dev/null +++ b/site-docs/manifest.md @@ -0,0 +1,51 @@ +# Manifest + +The manifest is a JSON list of items. Each item requires `url` and `output`. + +## Fields + +| Field | Required | Notes | +|---|---|---| +| `url` | yes | HTTP(S) media URL, commonly an `.m3u8` playlist | +| `output` | yes | Output filename, not a path; must end in `.mp4` or `.mkv` | +| `id` | no | Optional stable identifier, subject to the format and length limit below | +| `headers` | no | Optional header names mapped to environment references | +| `cookies` | no | Optional single environment reference | + +## Rules + +- Unknown fields are rejected. +- Duplicate IDs are rejected. +- Duplicate output destinations are rejected (case-insensitive). +- Only HTTP and HTTPS URLs are allowed. +- Userinfo in URLs is rejected. +- Unsafe output paths are rejected. +- Output extensions other than MP4/MKV are rejected. +- Output is a filename, not a path. + +## Processing limits + +- Input URLs must use HTTP or HTTPS. Playlist segments may use HTTP, HTTPS, TCP, TLS, or crypto protocols. +- Subtitle and data streams are not copied. Media streams are copied without transcoding. +- Stable IDs must match `[A-Za-z0-9][A-Za-z0-9._-]*` and be no longer than 128 characters. +- Header names must be valid HTTP token names and unique without regard to case. + +## Stable IDs + +An explicit `id` must be safe and public. + +Without an explicit `id`, identity is derived from the URL and output. + +## Example + +```json +[ + { + "url": "https://cdn.example.com/video1.m3u8", + "output": "video1.mp4", + "id": "video-1" + } +] +``` + +Use placeholder URLs and identifiers only. diff --git a/site-docs/reliability.md b/site-docs/reliability.md new file mode 100644 index 0000000..1ce421f --- /dev/null +++ b/site-docs/reliability.md @@ -0,0 +1,35 @@ +# Reliability + +## Retries + +The app makes an initial attempt plus up to two retries. Backoff waits are 1.5–2.5 seconds before the first retry and 5–7 seconds before the second. + +Inactivity timeouts and other FFmpeg download failures use this retry policy. Authentication configuration errors, FFmpeg process-start errors, and failures to move a completed output into place are not retried. + +## Inactivity timeout + +- Default inactivity timeout: 60 seconds. +- Any FFmpeg output resets the timer. +- Set with `FFMPEG_INACTIVITY_TIMEOUT`. +- Must be a finite positive number. + +Invalid, non-finite, zero, and negative values produce a clear error before batch processing. + +## No total-download cap + +There is no total-download time limit by default. + +## Progress and state + +- Per-item progress lives under `progress/`. +- Legacy `last_index` state is detected and requires an explicit reset choice. + +A successful item is skipped only when its saved state says success and the output +file still exists. + +Existing outputs are never silently overwritten. + +- Manual mode asks before overwrite. +- Automatic mode records a conflict/failure and continues. + +See [Troubleshooting](troubleshooting.md). diff --git a/site-docs/stylesheets/catppuccin.css b/site-docs/stylesheets/catppuccin.css new file mode 100644 index 0000000..20c823d --- /dev/null +++ b/site-docs/stylesheets/catppuccin.css @@ -0,0 +1,52 @@ +/* Catppuccin Macchiato for Zensical (Material) */ +[data-md-color-scheme="macchiato"] { + color-scheme: dark; + + --md-default-fg-color: #cad3f5; + --md-default-fg-color--light: #b8c0e0; + --md-default-fg-color--lighter: #a5adcb; + --md-default-fg-color--lightest: #6e738d; + --md-default-bg-color: #24273a; + --md-default-bg-color--light: #24273a; + --md-default-bg-color--lighter: #1e2030; + --md-default-bg-color--lightest: #181926; + --md-typeset-color: #cad3f5; + --md-typeset-a-color: #8aadf4; + + --md-code-fg-color: #cad3f5; + --md-code-bg-color: #1e2030; + --md-code-bg-color--light: #1e2030; + --md-code-bg-color--lighter: #181926; + --md-code-hl-color: #8aadf4; + --md-code-hl-color--light: rgba(138, 173, 244, 0.1); + --md-code-hl-comment-color: #6e738d; + --md-code-hl-constant-color: #f5a97f; + --md-code-hl-function-color: #8aadf4; + --md-code-hl-generic-color: #b8c0e0; + --md-code-hl-keyword-color: #c6a0f6; + --md-code-hl-name-color: #cad3f5; + --md-code-hl-number-color: #f5a97f; + --md-code-hl-operator-color: #91d7e3; + --md-code-hl-punctuation-color: #b8c0e0; + --md-code-hl-special-color: #f5bde6; + --md-code-hl-string-color: #a6da95; + --md-code-hl-variable-color: #ee99a0; + + --md-primary-fg-color: #8aadf4; + --md-primary-bg-color: #24273a; + --md-primary-bg-color--light: #24273a; + --md-accent-fg-color: #8aadf4; + --md-accent-bg-color: #24273a; + --md-accent-bg-color--light: #24273a; + --md-accent-fg-color--transparent: rgba(138, 173, 244, 0.1); + + --md-admonition-bg-color: #363a4f; + --md-admonition-fg-color: #cad3f5; + --md-typeset-mark-color: #5b6078; + --md-typeset-kbd-color: #cad3f5; + --md-typeset-kbd-border-color: #6e738d; + --md-typeset-kbd-accent-color: #363a4f; + + --md-footer-bg-color: #1e2030; + --md-footer-bg-color--dark: #181926; +} diff --git a/site-docs/troubleshooting.md b/site-docs/troubleshooting.md new file mode 100644 index 0000000..9deb021 --- /dev/null +++ b/site-docs/troubleshooting.md @@ -0,0 +1,39 @@ +# Troubleshooting + +## Missing FFmpeg + +Check FFmpeg is installed and on `PATH`. + +```bash +ffmpeg -version +``` + +## Invalid manifest, URL, or output + +Check JSON validity, required fields, HTTP(S) URLs, output extension, and filename-only output rule. + +## Missing auth environment values + +For Compose, check that required `${ENV_NAME}` values are present in `.env`. For direct local Python runs, export each variable in the shell before starting the app; local Python use does not load `.env`. + +## Output conflicts + +Existing outputs are never silently overwritten. + +Manual mode asks before overwrite. Automatic mode records a conflict/failure and continues. + +## Inactivity timeouts + +Default is 60 seconds. Any FFmpeg output resets it. + +Set `FFMPEG_INACTIVITY_TIMEOUT` to a finite positive number. + +## Unsupported codecs/container combinations + +ffmpegfyMe does not transcode. Stream copy (`-c copy`) requires source and container compatibility. + +## Saved or legacy progress + +Check `progress/` state. Legacy `last_index` state requires an explicit reset choice. + +See [Reliability](reliability.md). diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 0000000..0bf7998 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,28 @@ +[project] +site_name = "ffmpegfyMe" +site_url = "https://fnayou.github.io/ffmpegfyMe/" +site_description = "Interactive or batch Python wrapper around FFmpeg for downloading HTTP and HTTPS media streams, commonly HLS playlists, and copying them into MP4 or MKV containers." +docs_dir = "site-docs" +site_dir = "site" +repo_url = "https://github.com/fnayou/ffmpegfyMe" + +nav = [ + { "Home" = "index.md" }, + { "Getting Started" = "getting-started.md" }, + { "Manifest" = "manifest.md" }, + { "Authentication" = "authentication.md" }, + { "Reliability" = "reliability.md" }, + { "Troubleshooting" = "troubleshooting.md" }, + { "Community" = "community.md" }, +] + +extra_css = ["stylesheets/catppuccin.css"] + +[project.theme] +language = "en" +palette = [ + { scheme = "macchiato", primary = "custom", accent = "custom" }, +] + +[project.theme.icon] +repo = "fontawesome/brands/github"