ffai-cli 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ffai_cli-0.2.0/LICENSE +21 -0
- ffai_cli-0.2.0/PKG-INFO +620 -0
- ffai_cli-0.2.0/README.md +585 -0
- ffai_cli-0.2.0/ffai/__init__.py +5 -0
- ffai_cli-0.2.0/ffai/__main__.py +5 -0
- ffai_cli-0.2.0/ffai/cli.py +97 -0
- ffai_cli-0.2.0/ffai/command_builder.py +287 -0
- ffai_cli-0.2.0/ffai/config.py +251 -0
- ffai_cli-0.2.0/ffai/errors.py +31 -0
- ffai_cli-0.2.0/ffai/executor.py +255 -0
- ffai_cli-0.2.0/ffai/i18n.py +400 -0
- ffai_cli-0.2.0/ffai/llm.py +181 -0
- ffai_cli-0.2.0/ffai/models.py +454 -0
- ffai_cli-0.2.0/ffai/pandoc_builder.py +254 -0
- ffai_cli-0.2.0/ffai/parser.py +112 -0
- ffai_cli-0.2.0/ffai/prompt.py +296 -0
- ffai_cli-0.2.0/ffai/providers.py +122 -0
- ffai_cli-0.2.0/ffai/repl.py +933 -0
- ffai_cli-0.2.0/ffai/state.py +291 -0
- ffai_cli-0.2.0/ffai/tools.py +177 -0
- ffai_cli-0.2.0/ffai/utils.py +91 -0
- ffai_cli-0.2.0/ffai_cli.egg-info/PKG-INFO +620 -0
- ffai_cli-0.2.0/ffai_cli.egg-info/SOURCES.txt +39 -0
- ffai_cli-0.2.0/ffai_cli.egg-info/dependency_links.txt +1 -0
- ffai_cli-0.2.0/ffai_cli.egg-info/entry_points.txt +2 -0
- ffai_cli-0.2.0/ffai_cli.egg-info/requires.txt +9 -0
- ffai_cli-0.2.0/ffai_cli.egg-info/top_level.txt +1 -0
- ffai_cli-0.2.0/pyproject.toml +63 -0
- ffai_cli-0.2.0/setup.cfg +4 -0
- ffai_cli-0.2.0/tests/test_cli.py +236 -0
- ffai_cli-0.2.0/tests/test_command_builder.py +284 -0
- ffai_cli-0.2.0/tests/test_config.py +123 -0
- ffai_cli-0.2.0/tests/test_executor.py +157 -0
- ffai_cli-0.2.0/tests/test_i18n.py +70 -0
- ffai_cli-0.2.0/tests/test_integration.py +197 -0
- ffai_cli-0.2.0/tests/test_llm.py +339 -0
- ffai_cli-0.2.0/tests/test_pandoc_builder.py +344 -0
- ffai_cli-0.2.0/tests/test_parser.py +121 -0
- ffai_cli-0.2.0/tests/test_providers.py +69 -0
- ffai_cli-0.2.0/tests/test_state.py +326 -0
- ffai_cli-0.2.0/tests/test_tools.py +65 -0
ffai_cli-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ffai contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ffai_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ffai-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Natural-language, stateful media and document format conversion assistant (FFmpeg + Pandoc)
|
|
5
|
+
Author: ffai contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Lixuannan/ffai
|
|
8
|
+
Project-URL: Repository, https://github.com/Lixuannan/ffai
|
|
9
|
+
Project-URL: Issues, https://github.com/Lixuannan/ffai/issues
|
|
10
|
+
Keywords: ffmpeg,pandoc,cli,llm,video,document,conversion,openai
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Multimedia :: Video
|
|
21
|
+
Classifier: Topic :: Text Processing
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: openai<3,>=1.40.0
|
|
27
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
28
|
+
Requires-Dist: python-dotenv<2,>=1.0
|
|
29
|
+
Requires-Dist: rich<15,>=13.7
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest<10,>=8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov<8,>=5.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff<1,>=0.6; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# ffai
|
|
37
|
+
|
|
38
|
+
English | [简体中文](README.zh-CN.md)
|
|
39
|
+
|
|
40
|
+
`ffai` is an interactive, stateful natural-language format conversion assistant.
|
|
41
|
+
Describe a media operation or a document conversion, a language model updates a
|
|
42
|
+
structured task, and ffai builds the actual command locally with **FFmpeg**
|
|
43
|
+
(media files) or **Pandoc** (documents, ebooks, markup, and PDF). Nothing is
|
|
44
|
+
executed until you review the complete command and confirm it.
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
ffai v0.2.0
|
|
48
|
+
Natural-language interface for media and document conversion
|
|
49
|
+
|
|
50
|
+
ffai> add notes.md
|
|
51
|
+
✓ Added input: /home/you/notes.md
|
|
52
|
+
Detected Pandoc from the input file type; the tool was selected automatically.
|
|
53
|
+
ffai> setoutput notes.docx
|
|
54
|
+
✓ Output set: /home/you/notes.docx
|
|
55
|
+
ffai> convert to a Word document with a table of contents
|
|
56
|
+
╭─ ffai ──────────────────────────────────────────────────────────╮
|
|
57
|
+
│ Enabling a table of contents for the Word output. │
|
|
58
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
59
|
+
ffai> preview
|
|
60
|
+
╭─ Preview not executed ──────────────────────────────────────────╮
|
|
61
|
+
│ pandoc --toc -o /home/you/notes.docx /home/you/notes.md │
|
|
62
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
63
|
+
ffai> exec
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- Two external tools behind one REPL: FFmpeg for media and Pandoc for documents.
|
|
69
|
+
- Extension-based tool routing when you add files, plus an explicit
|
|
70
|
+
`tool <ffmpeg|pandoc>` command to switch at any time.
|
|
71
|
+
- Each tool keeps its own task, so switching preserves the other tool's work;
|
|
72
|
+
`reset` clears only the active task.
|
|
73
|
+
- Incremental natural-language edits; existing fields are preserved unless the
|
|
74
|
+
request changes them.
|
|
75
|
+
- Structured LLM output validated by Pydantic. A response is a tool-scoped
|
|
76
|
+
patch, and the model never emits a shell command.
|
|
77
|
+
- Safe command construction from an argv list, with no `shell=True`.
|
|
78
|
+
- FFmpeg: multi-input support with explicit stream maps and `filter_complex`.
|
|
79
|
+
- Pandoc: table of contents, section numbering, metadata, templates, reference
|
|
80
|
+
documents, citeproc with bibliographies, PDF engines, syntax highlighting,
|
|
81
|
+
and embedded resources.
|
|
82
|
+
- Rich task panels, syntax-highlighted command previews, confirmation prompts,
|
|
83
|
+
and live FFmpeg progress.
|
|
84
|
+
- Provider presets for OpenAI, OpenRouter, DeepSeek, GLM, Kimi, and Qwen, plus
|
|
85
|
+
a custom OpenAI-compatible endpoint.
|
|
86
|
+
- First-run API setup wizard; `.env` settings are written with owner-only
|
|
87
|
+
permissions. The `config` command displays or updates the active settings.
|
|
88
|
+
- English and Simplified Chinese UI with locale detection and an explicit
|
|
89
|
+
language override. Natural-language replies follow the user's language.
|
|
90
|
+
|
|
91
|
+
## Requirements
|
|
92
|
+
|
|
93
|
+
- Python 3.11 or newer
|
|
94
|
+
- FFmpeg and/or Pandoc on `PATH`. Both are supported; install at least the tool
|
|
95
|
+
you intend to use.
|
|
96
|
+
- An OpenAI-compatible API key and model. ffai requires this configuration
|
|
97
|
+
before the REPL starts.
|
|
98
|
+
- `ffprobe` on `PATH` for an estimated FFmpeg progress percentage (optional;
|
|
99
|
+
execution and live elapsed time still work without it)
|
|
100
|
+
- A LaTeX or Typst engine only for Pandoc PDF output (for example Tectonic or a
|
|
101
|
+
TeX Live distribution). Non-PDF document conversions do not need one.
|
|
102
|
+
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
105
|
+
Install from PyPI:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install ffai-cli
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For an isolated command install:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pipx install ffai-cli
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The distribution is named `ffai-cli` on PyPI; the Python package and the
|
|
118
|
+
`ffai` command keep the shorter name.
|
|
119
|
+
|
|
120
|
+
Verify the entry point:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
ffai --version
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### From source
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git clone https://github.com/Lixuannan/ffai.git
|
|
130
|
+
cd ffai
|
|
131
|
+
python -m venv .venv
|
|
132
|
+
source .venv/bin/activate
|
|
133
|
+
pip install -e .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### FFmpeg
|
|
137
|
+
|
|
138
|
+
Install FFmpeg with the package manager you use, or download a build from
|
|
139
|
+
https://ffmpeg.org/download.html:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# macOS
|
|
143
|
+
brew install ffmpeg
|
|
144
|
+
|
|
145
|
+
# Debian / Ubuntu
|
|
146
|
+
sudo apt install ffmpeg
|
|
147
|
+
|
|
148
|
+
# Windows (Chocolatey)
|
|
149
|
+
choco install ffmpeg
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Pandoc
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# macOS
|
|
156
|
+
brew install pandoc
|
|
157
|
+
|
|
158
|
+
# Debian / Ubuntu
|
|
159
|
+
sudo apt install pandoc
|
|
160
|
+
|
|
161
|
+
# Windows (Chocolatey)
|
|
162
|
+
choco install pandoc
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
You can also download an installer for your platform from
|
|
166
|
+
https://pandoc.org/installing.html. Verify with `pandoc --version`.
|
|
167
|
+
|
|
168
|
+
### PDF engine for Pandoc
|
|
169
|
+
|
|
170
|
+
Pandoc writes PDF by invoking an external engine. Install one if you want PDF
|
|
171
|
+
output:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Tectonic (standalone, downloads packages on demand)
|
|
175
|
+
brew install tectonic
|
|
176
|
+
# or
|
|
177
|
+
cargo install tectonic
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
A TeX Live installation that provides `xelatex`, `lualatex`, `pdflatex`, or
|
|
181
|
+
`latexmk` also works. A Typst installation can be selected as the PDF engine
|
|
182
|
+
instead of a LaTeX engine. ffai restricts `pdf_engine` to the engines supported
|
|
183
|
+
by the command builder (see
|
|
184
|
+
[Command construction and safety](#command-construction-and-safety)).
|
|
185
|
+
|
|
186
|
+
## Quick start
|
|
187
|
+
|
|
188
|
+
### FFmpeg: crop and encode a video
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
ffai> add input.mp4
|
|
192
|
+
✓ Added input: /media/input.mp4
|
|
193
|
+
ffai> setoutput final.mp4
|
|
194
|
+
✓ Output set: /media/final.mp4
|
|
195
|
+
ffai> crop the video to 1920x1080, keeping the center framing
|
|
196
|
+
╭─ ffai ──────────────────────────────────────────────────────────╮
|
|
197
|
+
│ Updating the video filter chain to scale and center-crop. │
|
|
198
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
199
|
+
ffai> use H.265 with CRF 23
|
|
200
|
+
...
|
|
201
|
+
ffai> preview
|
|
202
|
+
╭─ Preview not executed ──────────────────────────────────────────╮
|
|
203
|
+
│ ffmpeg -hide_banner -nostdin -y -i /media/input.mp4 \ │
|
|
204
|
+
│ -vf 'scale=1920:1080:force_original_aspect_ratio=increase' │
|
|
205
|
+
│ -c:v libx265 -crf 23 /media/final.mp4 │
|
|
206
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
207
|
+
ffai> exec
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`exec` asks for confirmation before FFmpeg runs.
|
|
211
|
+
|
|
212
|
+
### Pandoc: Markdown to Word with a table of contents
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
ffai> add notes.md
|
|
216
|
+
✓ Added input: /home/you/notes.md
|
|
217
|
+
Detected Pandoc from the input file type; the tool was selected automatically.
|
|
218
|
+
ffai> setoutput notes.docx
|
|
219
|
+
✓ Output set: /home/you/notes.docx
|
|
220
|
+
ffai> convert to a Word document with a table of contents
|
|
221
|
+
ffai> preview
|
|
222
|
+
╭─ Preview not executed ──────────────────────────────────────────╮
|
|
223
|
+
│ pandoc --toc -o /home/you/notes.docx /home/you/notes.md │
|
|
224
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
225
|
+
ffai> exec
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Pandoc: Markdown to PDF with xelatex
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
ffai> add report.md
|
|
232
|
+
✓ Added input: /home/you/report.md
|
|
233
|
+
ffai> setoutput report.pdf
|
|
234
|
+
✓ Output set: /home/you/report.pdf
|
|
235
|
+
ffai> export to PDF with a table of contents using xelatex
|
|
236
|
+
ffai> preview
|
|
237
|
+
╭─ Preview not executed ──────────────────────────────────────────╮
|
|
238
|
+
│ pandoc --toc --pdf-engine xelatex \ │
|
|
239
|
+
│ -o /home/you/report.pdf /home/you/report.md │
|
|
240
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
241
|
+
ffai> exec
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Pandoc infers the input and output formats from the file extensions; ffai adds
|
|
245
|
+
`-f`/`-t` only when you set an explicit format. PDF output requires the selected
|
|
246
|
+
PDF engine to be installed.
|
|
247
|
+
|
|
248
|
+
### Switching tools manually
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
ffai> tool pandoc
|
|
252
|
+
✓ Conversion tool set to Pandoc.
|
|
253
|
+
ffai> tool ffmpeg
|
|
254
|
+
✓ Conversion tool set to FFmpeg.
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Switching never discards the other tool's task. `show`, `preview`, `exec`, and
|
|
258
|
+
`reset` act on the active tool.
|
|
259
|
+
|
|
260
|
+
## Built-in commands
|
|
261
|
+
|
|
262
|
+
| Command | Action |
|
|
263
|
+
| --- | --- |
|
|
264
|
+
| `add <file> [file ...]` | Add existing, readable input files to the active task. Duplicates are ignored. When the active task is still empty, recognized extensions can select the tool. |
|
|
265
|
+
| `setoutput <file>` | Set an output whose parent directory exists. The output cannot overwrite an input. |
|
|
266
|
+
| `tool <ffmpeg\|pandoc>` | Switch the active tool. Each tool keeps its own inputs, output, and fields. |
|
|
267
|
+
| `show` | Display the active task, including inputs, output, and the tool-specific fields. |
|
|
268
|
+
| `preview` | Build and display the complete command without executing it. |
|
|
269
|
+
| `exec` | Display the command and ask for confirmation before running it. |
|
|
270
|
+
| `reset` | Clear the active tool's task only; the other tool's task is kept. |
|
|
271
|
+
| `config`, `config show` | Display the active API configuration with the key masked. |
|
|
272
|
+
| `config set` | Interactively update API settings and save them to `.env`. |
|
|
273
|
+
| `help` | Show command help. |
|
|
274
|
+
| `exit`, `quit` | Exit the REPL. |
|
|
275
|
+
|
|
276
|
+
Paths use shell-like quoting, so spaces in filenames work:
|
|
277
|
+
|
|
278
|
+
```text
|
|
279
|
+
add "my input video.mp4"
|
|
280
|
+
setoutput "renders/final cut.mp4"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Exact Chinese aliases are also accepted: `预览`, `显示`, `执行`, `重置`,
|
|
284
|
+
`配置`, `工具`, `帮助`, `退出`.
|
|
285
|
+
|
|
286
|
+
Any unrecognized input is sent to the LLM. For example:
|
|
287
|
+
|
|
288
|
+
```text
|
|
289
|
+
convert to 1080p
|
|
290
|
+
use H.265 at CRF 23
|
|
291
|
+
audio AAC at 192k
|
|
292
|
+
crop to 1920x1080, center composition
|
|
293
|
+
remove all video filters
|
|
294
|
+
convert the document to EPUB
|
|
295
|
+
add a table of contents and number the sections
|
|
296
|
+
export to PDF with xelatex
|
|
297
|
+
use the template letter.template
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Only the task's input files and output path are controlled by `add` and
|
|
301
|
+
`setoutput`; the LLM cannot change them. Auxiliary Pandoc paths such as
|
|
302
|
+
`template`, `reference_doc`, `bibliography`, `csl`, and `resource_path` can be
|
|
303
|
+
set through natural language and are validated against the local filesystem.
|
|
304
|
+
|
|
305
|
+
## How tool routing works
|
|
306
|
+
|
|
307
|
+
- Adding files to an empty task (no inputs and no output yet) inspects their
|
|
308
|
+
extensions. If all recognized extensions belong to one tool, that tool becomes
|
|
309
|
+
active. The REPL announces the detected tool.
|
|
310
|
+
- If the extensions are mixed across tools, unknown, or the active task already
|
|
311
|
+
has inputs or an output, the current tool is kept.
|
|
312
|
+
- `tool ffmpeg` and `tool pandoc` switch manually at any time. `add` only
|
|
313
|
+
auto-selects a tool while the active task is still empty (no inputs and no
|
|
314
|
+
output); after that it keeps the current tool.
|
|
315
|
+
- Each tool has its own task. `show`, `preview`, and `exec` use the active task;
|
|
316
|
+
the other tool's task is preserved until you reset it.
|
|
317
|
+
|
|
318
|
+
Representative extension routing (the complete sets live in `ffai/tools.py`):
|
|
319
|
+
|
|
320
|
+
| Tool | Example extensions |
|
|
321
|
+
| --- | --- |
|
|
322
|
+
| FFmpeg | `.mp4`, `.mkv`, `.mov`, `.avi`, `.webm`, `.ts`, `.mp3`, `.m4a`, `.wav`, `.flac`, `.ogg`, `.opus`, `.jpg`, `.png`, `.gif`, `.webp` |
|
|
323
|
+
| Pandoc | `.md`, `.markdown`, `.txt`, `.docx`, `.odt`, `.html`, `.epub`, `.tex`, `.rst`, `.org`, `.adoc`, `.ipynb`, `.pptx` |
|
|
324
|
+
|
|
325
|
+
Unknown extensions keep the current tool. Use `tool` when a file type is not
|
|
326
|
+
recognized.
|
|
327
|
+
|
|
328
|
+
## Pandoc task fields
|
|
329
|
+
|
|
330
|
+
Natural language fills these fields. `from_format` and `to_format` are only
|
|
331
|
+
emitted when set; otherwise Pandoc infers the formats from file extensions.
|
|
332
|
+
|
|
333
|
+
| Field | Meaning |
|
|
334
|
+
| --- | --- |
|
|
335
|
+
| `from_format`, `to_format` | Explicit Pandoc reader/writer format names, for example `gfm` or `docx`. |
|
|
336
|
+
| `standalone` | Produce a standalone document. |
|
|
337
|
+
| `toc`, `toc_depth` | Add a table of contents, optionally with a depth from 1 to 6. |
|
|
338
|
+
| `number_sections` | Number section headings. |
|
|
339
|
+
| `shift_heading_level_by` | Shift heading levels by an offset from -10 to 10. |
|
|
340
|
+
| `metadata` | Repeatable `key=value` metadata entries. |
|
|
341
|
+
| `template` | Local template file used for the output. |
|
|
342
|
+
| `reference_doc` | Local reference document used for styling. |
|
|
343
|
+
| `pdf_engine` | PDF engine; restricted to the allowlist below. |
|
|
344
|
+
| `highlight_style` | Syntax-highlighting theme; restricted to the allowlist below. |
|
|
345
|
+
| `embed_resources` | Embed images and other resources into the output. |
|
|
346
|
+
| `citeproc` | Run Pandoc's citation processor. |
|
|
347
|
+
| `bibliography` | One or more local bibliography files. |
|
|
348
|
+
| `csl` | Local CSL citation style file. |
|
|
349
|
+
| `resource_path` | One or more local directories searched for resources. |
|
|
350
|
+
| `extra_options` | Allowlisted Pandoc options with a fixed number of values. |
|
|
351
|
+
| `description` | Short summary of the conversion. |
|
|
352
|
+
|
|
353
|
+
`pdf_engine` accepts `pdflatex`, `xelatex`, `lualatex`, `latexmk`, `tectonic`,
|
|
354
|
+
`typst`, `context`, `wkhtmltopdf`, `weasyprint`, `prince`, or `pdfroff`.
|
|
355
|
+
`highlight_style` accepts `pygments`, `tango`, `espresso`, `zenburn`, `kate`,
|
|
356
|
+
`monochrome`, `breezedark`, or `haddock`.
|
|
357
|
+
|
|
358
|
+
## Supported formats
|
|
359
|
+
|
|
360
|
+
- **FFmpeg** handles the media containers, codecs, filters, and image formats
|
|
361
|
+
supported by your FFmpeg build. Common examples include MP4, MKV, MOV, AVI,
|
|
362
|
+
WebM, MP3, AAC, WAV, FLAC, Ogg/Opus, GIF, and JPEG/PNG/WebP images.
|
|
363
|
+
- **Pandoc** reads and writes the document formats supported by your Pandoc
|
|
364
|
+
version, including Markdown variants, HTML, EPUB, DOCX, ODT, RTF, LaTeX,
|
|
365
|
+
reStructuredText, Org, AsciiDoc, MediaWiki, Textile, Jupyter notebooks,
|
|
366
|
+
OPML, and PPTX output. PDF output is produced through an external PDF engine.
|
|
367
|
+
|
|
368
|
+
In both cases the installed tool is the authority on format availability; ffai
|
|
369
|
+
passes through what the task expresses and rejects values that violate its
|
|
370
|
+
validation rules.
|
|
371
|
+
|
|
372
|
+
## Configuration
|
|
373
|
+
|
|
374
|
+
API configuration is mandatory. On first launch, ffai requires a complete
|
|
375
|
+
configuration and opens an interactive setup wizard before showing the
|
|
376
|
+
`ffai>` prompt. Non-interactive launches without configuration exit with code
|
|
377
|
+
2. Configuration can also be provided through `.env` or the process
|
|
378
|
+
environment:
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
cp .env.example .env
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
The three settings are (`.env` and the process environment are both read;
|
|
385
|
+
process environment values take precedence over `.env`):
|
|
386
|
+
|
|
387
|
+
| Variable | Required for LLM | Meaning |
|
|
388
|
+
| --- | --- | --- |
|
|
389
|
+
| `OPENAI_API_KEY` | yes | API key |
|
|
390
|
+
| `OPENAI_MODEL` | yes | Chat model identifier |
|
|
391
|
+
| `OPENAI_BASE_URL` | no | OpenAI-compatible endpoint; omit for the OpenAI default |
|
|
392
|
+
|
|
393
|
+
### Provider presets
|
|
394
|
+
|
|
395
|
+
The setup wizard and `config set` offer these presets. The provider's base URL
|
|
396
|
+
is filled in automatically; the model is suggested but can be edited because
|
|
397
|
+
availability varies by account and region. Every provider stores its key in
|
|
398
|
+
`OPENAI_API_KEY`.
|
|
399
|
+
|
|
400
|
+
| Provider | Base URL | Suggested model | Documentation |
|
|
401
|
+
| --- | --- | --- | --- |
|
|
402
|
+
| OpenAI | `https://api.openai.com/v1` | `gpt-5.4-mini` | [OpenAI GPT-5.4 mini](https://developers.openai.com/api/docs/models/gpt-5.4-mini) |
|
|
403
|
+
| OpenRouter | `https://openrouter.ai/api/v1` | `openai/gpt-5.4-mini` | [OpenRouter quickstart](https://openrouter.ai/docs/quickstart) |
|
|
404
|
+
| DeepSeek | `https://api.deepseek.com` | `deepseek-flash` | [DeepSeek API docs](https://api-docs.deepseek.com/) |
|
|
405
|
+
| GLM (Zhipu) | `https://open.bigmodel.cn/api/paas/v4` | `glm-5.3` | [GLM-5.3 docs](https://docs.bigmodel.cn/cn/guide/models/text/glm-5.3) |
|
|
406
|
+
| Kimi (Moonshot) | `https://api.moonshot.cn/v1` | `kimi-k3.1-pro` | [Kimi API docs](https://platform.kimi.com/docs/get-api-key) |
|
|
407
|
+
| Qwen (DashScope) | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen3.8-flash` | [DashScope base URLs](https://www.alibabacloud.com/help/zh/model-studio/base-url) |
|
|
408
|
+
| Custom | Enter manually | Enter manually | Any OpenAI-compatible endpoint |
|
|
409
|
+
|
|
410
|
+
Select a provider by its list number or ID (`openai`, `openrouter`, `deepseek`,
|
|
411
|
+
`glm`, `kimi`, `qwen`, `custom`). OpenRouter model IDs are vendor-prefixed.
|
|
412
|
+
The Qwen preset uses the China-region endpoint; its API key must match the
|
|
413
|
+
selected region, so use a regional compatible-mode URL for other regions.
|
|
414
|
+
|
|
415
|
+
OpenAI environment example:
|
|
416
|
+
|
|
417
|
+
```dotenv
|
|
418
|
+
OPENAI_API_KEY=sk-your-key
|
|
419
|
+
OPENAI_MODEL=gpt-5.4-mini
|
|
420
|
+
# OPENAI_BASE_URL=https://api.openai.com/v1
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
DeepSeek environment example:
|
|
424
|
+
|
|
425
|
+
```dotenv
|
|
426
|
+
OPENAI_API_KEY=sk-your-deepseek-key
|
|
427
|
+
OPENAI_BASE_URL=https://api.deepseek.com
|
|
428
|
+
OPENAI_MODEL=deepseek-flash
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
The placeholder values from `.env.example` are treated as unset, so copying
|
|
432
|
+
the example file still triggers setup. The wizard saves settings to `.env` in
|
|
433
|
+
the current directory with mode `0600`. It never echoes the API key and
|
|
434
|
+
displays only a masked form. Use the REPL command `config` to show the active
|
|
435
|
+
configuration and `config set` to change it. When `config set` prompts, Enter
|
|
436
|
+
keeps a current value; choose another provider by number or ID to switch.
|
|
437
|
+
Ctrl+C cancels without writing anything.
|
|
438
|
+
|
|
439
|
+
Start ffai from the project directory (or any directory with the same exported
|
|
440
|
+
variables).
|
|
441
|
+
|
|
442
|
+
### UI language
|
|
443
|
+
|
|
444
|
+
The interface auto-detects Chinese from `LC_ALL`, `LC_MESSAGES`, or `LANG`;
|
|
445
|
+
otherwise it uses English. Override the choice with `FFAI_LANG=en` /
|
|
446
|
+
`FFAI_LANG=zh` (process environment or `.env`) or the `--lang en` / `--lang zh`
|
|
447
|
+
flag. LLM replies follow the language used in your request.
|
|
448
|
+
|
|
449
|
+
## Architecture
|
|
450
|
+
|
|
451
|
+
```text
|
|
452
|
+
user input
|
|
453
|
+
│
|
|
454
|
+
├── built-in command ───────────────► SessionState (local validation)
|
|
455
|
+
│
|
|
456
|
+
└── natural language
|
|
457
|
+
│
|
|
458
|
+
▼
|
|
459
|
+
OpenAI-compatible LLM
|
|
460
|
+
│ JSON: intent + tool-scoped patch (ffmpeg | pandoc)
|
|
461
|
+
▼
|
|
462
|
+
Pydantic validation ──► SessionState.apply_patch
|
|
463
|
+
│
|
|
464
|
+
▼
|
|
465
|
+
tool builder (command_builder | pandoc_builder) argv list, no shell
|
|
466
|
+
│
|
|
467
|
+
▼
|
|
468
|
+
preview ──► user confirmation ──► executor (subprocess, live FFmpeg progress)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
The task and patch schemas are defined in `ffai/models.py`. Every patch
|
|
472
|
+
targets exactly one tool:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
class ToolPatch(BaseModel):
|
|
476
|
+
ffmpeg: FFmpegPatch | None = None
|
|
477
|
+
pandoc: PandocPatch | None = None
|
|
478
|
+
# Exactly one tool; omitted fields are unchanged, explicit null clears.
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The model returns one of three intents:
|
|
482
|
+
|
|
483
|
+
- `apply`: apply a non-empty patch for exactly one tool (an empty tool payload
|
|
484
|
+
only selects that tool).
|
|
485
|
+
- `clarify`: ask one focused question; no task mutation.
|
|
486
|
+
- `noop`: explain without mutating state.
|
|
487
|
+
|
|
488
|
+
The full model result also includes `explanation` and an optional `question`.
|
|
489
|
+
`inputs` and `output` are not patchable. Each tool keeps its own task in
|
|
490
|
+
`SessionState`, so patches and switches never merge fields across tools.
|
|
491
|
+
|
|
492
|
+
### Modules
|
|
493
|
+
|
|
494
|
+
| Module | Responsibility |
|
|
495
|
+
| --- | --- |
|
|
496
|
+
| `ffai/cli.py` | Argparse entry point and top-level error handling |
|
|
497
|
+
| `ffai/repl.py` | REPL loop, dispatch, Rich presentation, confirmation |
|
|
498
|
+
| `ffai/parser.py` | Built-in command parsing versus natural language |
|
|
499
|
+
| `ffai/models.py` | Tool-scoped task, patch, and LLM decision schemas |
|
|
500
|
+
| `ffai/tools.py` | Tool registry, binaries, and extension-based routing |
|
|
501
|
+
| `ffai/state.py` | Per-tool session state and filesystem-validated changes |
|
|
502
|
+
| `ffai/prompt.py` | Two-tool system prompt and current-state context |
|
|
503
|
+
| `ffai/llm.py` | OpenAI-compatible client, JSON extraction, validation retry |
|
|
504
|
+
| `ffai/command_builder.py` | FFmpeg task-to-argv conversion and extra-option allowlist |
|
|
505
|
+
| `ffai/pandoc_builder.py` | Pandoc task-to-argv conversion, path checks, option allowlist |
|
|
506
|
+
| `ffai/executor.py` | Generic subprocess execution, progress parsing, interrupts |
|
|
507
|
+
| `ffai/config.py` | `.env`/environment configuration and safe persistence |
|
|
508
|
+
| `ffai/utils.py` | Command rendering, duration probing, environment scrubbing |
|
|
509
|
+
| `ffai/i18n.py` | English and Simplified Chinese UI strings and language detection |
|
|
510
|
+
|
|
511
|
+
## Command construction and safety
|
|
512
|
+
|
|
513
|
+
Shared guarantees:
|
|
514
|
+
|
|
515
|
+
- The LLM output is limited to the `ToolPatch` schema. Unknown fields are
|
|
516
|
+
rejected, and no model-generated shell or Python code is interpreted.
|
|
517
|
+
- Commands are invoked with an argument vector and `shell=False`.
|
|
518
|
+
- Inputs and outputs are normalized local paths and checked for existence,
|
|
519
|
+
readability, parent-directory validity, and accidental overwrite of an input.
|
|
520
|
+
- Execution always displays the complete argv-rendered command and asks for
|
|
521
|
+
confirmation. Existing outputs are flagged before confirmation.
|
|
522
|
+
- The tool runs with stdin closed and with an environment scrubbed of variables
|
|
523
|
+
whose names contain `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `PASSWD`, or
|
|
524
|
+
`CREDENTIAL`.
|
|
525
|
+
- Ctrl+C terminates the tool's process group, escalating from SIGTERM to SIGKILL
|
|
526
|
+
after a timeout, and reports the run as interrupted.
|
|
527
|
+
- A successful exit is verified against the expected output file; a zero exit
|
|
528
|
+
with a missing or empty output is reported as a failure.
|
|
529
|
+
|
|
530
|
+
FFmpeg-specific rules:
|
|
531
|
+
|
|
532
|
+
- Multiple inputs require explicit `maps` or `filter_complex`; inputs are never
|
|
533
|
+
implicitly concatenated.
|
|
534
|
+
- `filter_complex` cannot be combined with the simple video/audio filter chains.
|
|
535
|
+
- Stream-copy mode rejects filters, CRF, preset, or bitrates that would be
|
|
536
|
+
ignored or invalid.
|
|
537
|
+
- `extra_options` accepts only a conservative allowlist of common output
|
|
538
|
+
options with known arity. Options that set codecs, filters, maps, overwrite
|
|
539
|
+
behavior, or side files are rejected. The allowlist is defined in
|
|
540
|
+
`ffai/command_builder.py` and embedded in the LLM prompt.
|
|
541
|
+
|
|
542
|
+
Pandoc-specific rules:
|
|
543
|
+
|
|
544
|
+
- `--filter`, `--lua-filter`, `--pdf-engine-opt`, and `--data-dir` are not
|
|
545
|
+
expressible: no Pandoc option can execute an arbitrary program or add an
|
|
546
|
+
arbitrary engine argument.
|
|
547
|
+
- `pdf_engine` is restricted to the allowlist shown above; arbitrary executable
|
|
548
|
+
paths are rejected.
|
|
549
|
+
- `highlight_style` is restricted to the documented allowlist.
|
|
550
|
+
- `template`, `reference_doc`, `bibliography`, and `csl` must point at existing,
|
|
551
|
+
readable local files; `resource_path` entries must be existing local
|
|
552
|
+
directories.
|
|
553
|
+
- `extra_options` accepts only a small allowlist of Pandoc options with fixed
|
|
554
|
+
arity. Shell fragments and options outside the allowlist are rejected.
|
|
555
|
+
- `from_format` and `to_format` are only passed as `-f`/`-t` when explicitly
|
|
556
|
+
set, and are validated as single format-name tokens.
|
|
557
|
+
- `metadata` entries are passed as one `-M key=value` argv pair each.
|
|
558
|
+
|
|
559
|
+
It remains your responsibility to confirm the visible command and output path.
|
|
560
|
+
Both FFmpeg and Pandoc are powerful tools; the confirmation screen is the final
|
|
561
|
+
authorization step.
|
|
562
|
+
|
|
563
|
+
## Troubleshooting
|
|
564
|
+
|
|
565
|
+
| Symptom | Fix |
|
|
566
|
+
| --- | --- |
|
|
567
|
+
| `FFmpeg was not found on PATH` | Install FFmpeg (see [Installation](#installation)), open a new terminal, and check `ffmpeg -version`. |
|
|
568
|
+
| `Pandoc was not found on PATH` | Install Pandoc (see [Installation](#installation)), open a new terminal, and check `pandoc --version`. |
|
|
569
|
+
| PDF conversion fails with a missing engine such as `pdflatex` | Install a PDF engine (Tectonic, TeX Live, or Typst) and make sure it is on `PATH`. Select the engine you installed in your request, for example "export to PDF with xelatex" or "use tectonic". |
|
|
570
|
+
| Pandoc exits with an unknown format error | Pass a format that your Pandoc version supports; check `pandoc --list-input-formats` and `pandoc --list-output-formats`. |
|
|
571
|
+
| No progress percentage for FFmpeg | Install `ffprobe`; without it the REPL shows only elapsed time. Pandoc runs do not report a percentage. |
|
|
572
|
+
| A tool is reported missing at startup | ffai warns about each missing tool; install the ones you need. The other tool remains usable. |
|
|
573
|
+
|
|
574
|
+
## Development
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
python -m venv .venv
|
|
578
|
+
source .venv/bin/activate
|
|
579
|
+
pip install -e '.[dev]'
|
|
580
|
+
ruff check .
|
|
581
|
+
pytest
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
The parser, state transitions, command builders, configuration, and LLM
|
|
585
|
+
response parsing are testable without a network connection. The LLM boundary is
|
|
586
|
+
the only non-deterministic external dependency in the core path. Real executor
|
|
587
|
+
tests run when the corresponding tool is available.
|
|
588
|
+
|
|
589
|
+
### Publishing
|
|
590
|
+
|
|
591
|
+
Releases go to PyPI from the `publish.yml` GitHub Actions workflow using
|
|
592
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so no API token
|
|
593
|
+
is stored in the repository. One-time setup on PyPI: add a pending publisher for
|
|
594
|
+
`ffai-cli` with owner `Lixuannan`, repository `ffai`, workflow `publish.yml`, and
|
|
595
|
+
environment `pypi`. For each release, bump `version` in `pyproject.toml` and
|
|
596
|
+
`__version__` in `ffai/__init__.py`, then publish a GitHub Release to trigger
|
|
597
|
+
the upload. PyPI never allows reusing a version number.
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
python -m build
|
|
601
|
+
twine check dist/*
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
## Limitations
|
|
605
|
+
|
|
606
|
+
- One output file per tool task; no batch or persistent session storage.
|
|
607
|
+
- Local file inputs only.
|
|
608
|
+
- Only the extra-option allowlists are accepted. The FFmpeg and Pandoc
|
|
609
|
+
allowlists are defined in `ffai/command_builder.py` and `ffai/pandoc_builder.py`
|
|
610
|
+
and embedded in the LLM prompt; unlisted options are rejected instead of
|
|
611
|
+
passed through.
|
|
612
|
+
- Pandoc PDF output requires a separately installed PDF engine.
|
|
613
|
+
- Filter expressions and option values are syntactically checked only at the
|
|
614
|
+
boundaries ffai knows about. The external tool remains the authority on graph
|
|
615
|
+
validity, codec availability, and format support.
|
|
616
|
+
- Estimated progress requires `ffprobe` and applies to FFmpeg only.
|
|
617
|
+
|
|
618
|
+
## License
|
|
619
|
+
|
|
620
|
+
MIT.
|