ffmpeg-skill 2.8.4 → 2.8.5

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.
package/docs/contract.md CHANGED
@@ -21,7 +21,7 @@ The contract is derived from the code that runs, not maintained beside it:
21
21
  | Field | Meaning | Changes when |
22
22
  |---|---|---|
23
23
  | `contract_version` | shape of this document (`1.0`) | a key is renamed, removed or changes meaning |
24
- | `skill.version` | the npm / package.json version (`2.8.4`) | any release |
24
+ | `skill.version` | the npm / package.json version (`2.8.5`) | any release |
25
25
 
26
26
  A release that adds a tool or a flag keeps `contract_version`; a breaking change to the
27
27
  ToolSpec shape bumps it. Consumers pin on `contract_version` and read `skill.version`
@@ -119,7 +119,7 @@ JSON would have been rewritten for that. The flat keys, typed per tool in `outpu
119
119
  "contract_version": "1.0",
120
120
  "deprecated": [],
121
121
  "removed": [{"what": "...", "since": "1.10.0", "replacement": "...", "removed_in": "2.0.0", "where": "cli | json | mcp | behaviour"}],
122
- "skill": {"id": "ffmpeg-skill", "version": "2.8.4", "execution_mode": "local", "kind": "execution",
122
+ "skill": {"id": "ffmpeg-skill", "version": "2.8.5", "execution_mode": "local", "kind": "execution",
123
123
  "entrypoints": {"cli": "...", "mcp": "...", "contract": "...", "doctor": "..."},
124
124
  "not_provided": ["AI reasoning", "decisions", "production plans", "project IR", "approvals", "network access", "transcription engine"]},
125
125
  "requirements": {"python": ">=3.9 (standard library only)", "ffmpeg": ">=5.0", "ffprobe": ">=5.0"},
@@ -379,7 +379,9 @@ and no detector can name it, a Whisper engine that can run takes it (whisper.cpp
379
379
  `-l auto`: its own default is English). Parakeet then runs only on English *assumed* -- when no
380
380
  Whisper engine is installed, or every one failed, or `--model` names an English-only Parakeet
381
381
  model -- and the result's top-level `notes` says the transcript is wrong if the speech is not
382
- English; a multilingual (v3) Parakeet model assumes nothing and gets no note. The result's
382
+ English; a multilingual (v3) Parakeet model assumes nothing and gets no note. Every whisper.cpp
383
+ command that writes a transcript is also passed `-mc 0` (no previous-text context, so one
384
+ hallucinated line is not repeated to the end of the file); its language detection is not. The result's
383
385
  `transcription` says which engine, model and routing decision produced the cues (in every
384
386
  `--mode`, `mux` included).
385
387
  Every engine transcribes on the machine running the skill, and the audio is never uploaded. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffmpeg-skill",
3
- "version": "2.8.4",
3
+ "version": "2.8.5",
4
4
  "description": "Agent Skill that gives coding agents (Claude Code, Cursor, Codex) a local video editor: 42 FFmpeg tools with a machine-readable contract, contract-derived MCP server, FFmpeg capability detection, probe-first / verify-last workflow. Cut, join, silence removal, fit, captions and karaoke, overlays, motion graphics, HDR to SDR, LUTs, audio clean-up and typed dynamics, sync with drift correction, multicam, loudness, delivery checks, project rendering, batch. No API keys, no cloud, no dependencies.",
5
5
  "keywords": [
6
6
  "ffmpeg",
@@ -1023,7 +1023,8 @@ top-level `notes` then says so: that transcript is wrong for non-English speech,
1023
1023
  `--engine parakeet-mlx|parakeet.cpp|whisper.cpp|faster-whisper|openai-whisper` forces one (a
1024
1024
  missing one is `kind: missing_tool`; an installed one that fails is `kind: input`, `reason:
1025
1025
  engine_failed`, with its own error line in `detail`). Without a language whisper.cpp is passed
1026
- `-l auto` (its own default is English), else the named or detected language. `--model` is the
1026
+ `-l auto` (its own default is English), else the named or detected language, and `-mc 0` (no
1027
+ previous-text context, so one hallucinated line is not repeated to the end of the file; #318). `--model` is the
1027
1028
  whisper model; a Parakeet engine takes it only when it names a Parakeet model
1028
1029
  (`mlx-community/parakeet-*` or a `.gguf`), else `PARAKEET_MODEL` / `PARAKEET_CPP_MODEL`, else the
1029
1030
  English `tdt-0.6b-v2`. A non-English `--language` on an English-only Parakeet model is refused; a
@@ -28,6 +28,11 @@ WHISPER_ENGINES = ("whisper.cpp", "faster-whisper", "openai-whisper")
28
28
  ASR_ENGINES = PARAKEET_ENGINES + WHISPER_ENGINES
29
29
  ENGINE_CHOICES = ("auto",) + ASR_ENGINES
30
30
  ASR_ENGINE_ENV = "FFMPEG_SKILL_ASR_ENGINE"
31
+ # whisper.cpp conditions every window on the text before it, and once a hallucinated line is in that
32
+ # context it can repeat it to the end of the file (#318: one sentence ~300 times, ~7 minutes of speech
33
+ # lost). -mc / --max-context 0 keeps no previous text. Given to the commands that write a transcript,
34
+ # not to language detection (-dl), which writes none.
35
+ WHISPER_CPP_NO_CONTEXT = ["-mc", "0"]
31
36
  PARAKEET_MLX_DEFAULT_MODEL = "mlx-community/parakeet-tdt-0.6b-v2"
32
37
  # Every engine transcribes on this machine: the audio is never uploaded. Three of them fetch
33
38
  # their model weights the first time they run, which the hint says, so "offline" is never
@@ -342,7 +347,7 @@ def _transcribe_in(tmpdir: str, video: str, out_srt: str, language: Optional[str
342
347
  model_path = _whisper_cpp_model(model)
343
348
  base = os.path.join(tmpdir, "out")
344
349
  # whisper.cpp's own default is -l en: without "auto" it decodes any speech as English
345
- cmd = [cli, "-m", model_path, "-f", wav, "-osrt", "-of", base, "-l", lang or "auto"]
350
+ cmd = [cli, "-m", model_path, "-f", wav, "-osrt", "-of", base, "-l", lang or "auto", *WHISPER_CPP_NO_CONTEXT]
346
351
  proc = _asr_run(cmd, subprocess, "whisper.cpp")
347
352
  if proc.returncode == 0 and os.path.exists(base + ".srt"):
348
353
  info(f"transcribed with whisper.cpp ({os.path.basename(cli)}, model {os.path.basename(model_path)})")
@@ -995,7 +1000,8 @@ def transcribe_words_result(video: str, language: "Optional[str]" = None, model:
995
1000
  model_path = _whisper_cpp_model(model)
996
1001
  base = os.path.join(tmpdir, "out")
997
1002
  # whisper.cpp's own default is -l en: without "auto" it decodes any speech as English
998
- cmd = [cli, "-m", model_path, "-f", wav, "--output-json-full", "-of", base, "-l", lang or "auto"]
1003
+ cmd = [cli, "-m", model_path, "-f", wav, "--output-json-full", "-of", base, "-l", lang or "auto",
1004
+ *WHISPER_CPP_NO_CONTEXT]
999
1005
  proc = _asr_run(cmd, _subprocess, "whisper.cpp")
1000
1006
  if proc.returncode == 0 and os.path.exists(base + ".json"):
1001
1007
  words = _words_from_whisper_cpp_json(base + ".json")