Drop a video, give it a prompt, and have an LLM analyze it. Results land in your Notion page so Claude Code can pick up where you left off. Uses Apify MCP Connectors to write to Notion on your behalf — no Notion API key required.
New input allowLongVideos (default false) lifts the 10-minute duration cap
for users who accept the higher, harder-to-estimate cost. Off, nothing changes.
New Advanced toggle: "Process videos longer than 10 minutes (costs more)".
The schema copy is explicit that cost scales with length and is hard to
estimate up front, and points at maxChargeUsd — which still works: the
post-ingest worst-case estimate uses the real duration, so a cap aborts
before the LLM call exactly as before.
File pipeline: videos over 10 minutes no longer hard-fail when the toggle
is on; instead the run logs and shows a long-video cost warning in the status
line (including whether a cost cap is set). The refusal message when the
toggle is off now names the toggle.
YouTube visual path: a visual prompt on a >10-min video downloads real
frames when the toggle is on, instead of falling back to transcript +
thumbnail. Fallback messages now mention the toggle.
Downloader timeout: sub-actor download timeout goes 6 → 20 minutes on
long-video runs so 1-hour downloads aren't killed mid-flight.
Whisper 25 MB guard: audio over Whisper's API file limit (~69 minutes at
our 48 kbps mono encoding) skips transcription with a clear
softErrors.whisper message instead of burning a doomed API upload. The
constant is decimal 25,000,000 bytes so the "~69 minutes" copy is exact and
the guard is conservative (review catch: 25 MiB would shift the boundary to
~73 min and could let a doomed upload through).
Early budget gate (review blocker fix): the pre-LLM cost gate used to fire
after Whisper had billed — on long videos, the cost that scales. The file
pipeline now checks the cap right after reading the video's duration, before
frame extraction and transcription, assuming the transcript hits its
truncation cap. Cap-selection and publisher-fee math are shared with the
refined gate via new resolve_charge_cap() / publisher_fees_estimate_usd()
helpers, and the refined gate's abort message now names any Whisper spend
that already happened.
allowLongVideos joins the idempotency key (review blocker fix): the
fallback copy tells users to flip the flag and re-run; without the flag in
the key that exact re-run replayed the stale transcript-only result.
Existing idempotency entries miss once and recompute; the analysis cache
still absorbs the LLM cost where the resolved mode matches.
Whisper empty-result fix (pre-existing, amplified by long videos): a
silent video no longer sets whisper_used (which added a phantom per-minute
term to cost estimates) or clobbers a preserved caption transcript; it now
soft-errors "no audio track".
Limits that do not lift: 1 GB file size, 12-frame cap, 20,000-character
transcript truncation. README documents all of this under Long videos.
Deferred from review, with reasoning: (a) the downloader's one infra retry
keeps the same 20-min timeout on long runs (worst case ~40 min parent wait) —
acceptable for an explicit opt-in, revisit if real runs hit it; (b) passing a
cost cap down to the sub-actor call — needs SDK signature verification first;
(c) surfacing transcript truncation in softErrors — output-schema addition,
separate change.
2.8.9 — YouTube ingest fixes (2026-08-03)
Fixes the three bugs found in the 2026-08-03 demo-review runs (fCaYYYdXwRagdgqiE,
TuRJhQp0HXRnbsugw). No input schema changes; one new analysisMode value.
YouTube transcripts extract again.streamers/youtube-scraper returns
captions as one complete raw SRT per subtitles entry ({language, srt}), not
the {text, start} segment shape the extractor expected — so runs recorded
transcriptChars: 0 while a 50 KB SRT sat in the response. Added an SRT
flattener (strips indices, timestamps, and the rolling-caption duplicates
auto-generated tracks emit). The demo video now yields 12,050 transcript chars.
No more silent caption failures.softErrors.youtubeTranscript used to stay
null unless BOTH transcript and title were missing. It now reports the
metadata-present-but-no-captions case, with the recovery step in the message.
Thumbnail-only runs stop pretending. A YouTube run with no usable transcript
was labeled transcript+thumbnail and the LLM was told to lean on a transcript
that wasn't there — which produced confident fabricated reviews. Those runs are
now labeled analysisMode: "thumbnail" (new value, documented in the README),
and the prompt tells the model it has one still image, forbids inventing
content, and redirects it to what the thumbnail and metadata do show.
Poisoned-cache guard. Pre-fix cached analyses were computed without the
transcript this build now extracts; replaying them would resurface the
fabricated answers. A cache hit is refused when the cached run had no
transcript but the current run does, and thumbnail runs refuse mode-less
pre-2.7 cache entries.
Downloader retry.download_via_actor retries once after a 10 s pause on
infra failures (launch timeouts), with a log line for the wait. Deterministic
failures are not retried. This does not fix a persistently-down downloader —
a second fallback is a known follow-up, not in this build.
13 new tests (SRT edge cases pinned to the verbatim scraper payload, retry
control flow, and an analysisMode contract test that fails if the code and
README enum drift).
2.8.8 — crash reliability (2026-06-24)
Build reproducibility and memory rightsizing. No input/output schema changes.
Reproducible builds (lockfile). Added requirements.lock, a fully pinned,
hash-verified resolution compiled from requirements.txt. The Docker build now
installs from it with pip install --require-hashes -r requirements.lock instead
of from the loose ranges. This pins apify-shared (currently 2.2.0) — the
transitive dependency behind the v2.x crash — so a rebuild can't silently pull a
changed version. requirements.txt keeps the human-readable ranges and is the
source the lock is compiled from. Regenerate the lock after editing
requirements.txt with:
uv pip compile requirements.txt -o requirements.lock --generate-hashes --no-header --python-version 3.14
(--python-version 3.14 matches the apify/actor-python:3.14 base image;
pre-releases are excluded by default). The lock in this commit was generated this
way on a connected machine, not hand-written.
Memory rightsizing. Lowered .actor/actor.json from min 2048 / max 4096 MB to
min 1024 / max 2048 MB. Peak observed Python RSS was ~114 MB; the 2 GB max keeps
headroom for the ffmpeg subprocess (whose RSS is not in that 114 MB number) while
halving the per-GB actor-start charge.
Follow-up (not yet done): run an on-platform headroom test on a long /
high-resolution video to confirm 2 GB is enough for ffmpeg before considering a
further drop to 1024 MB max. Watch the run for OOM kills / ffmpeg exit 137. Do
not lower the max below 2048 MB until that test passes.
v2.8 — 2026-06-12
Error-handling hardening. No input/output schema changes; existing Tasks keep working.
Added a top-level exception net in main(). An unhandled error now fails the
run with a failed_internal dataset row (errorCategory: internal_error,
errorRecoverable: false) and a human status message, instead of exiting with
a bare exit_code 1 and no explanation.
Split the LLM-call handler. Known RuntimeErrors from the LLM paths
(transport, 4xx/5xx, out-of-credit, rate limit) stay user-friendly and
recoverable. Any other exception is treated as an internal error (permanent),
so a bug isn't mislabeled as a retryable LLM condition and looped on by agents.
Both failure messages state "No LLM analysis charge was incurred" where that's
provably true, and keep raw exception types out of the lead of the message.
First release built from the GitHub repo (private) rather than uploaded source files.
Earlier
For 2.7 and earlier, see the Apify Console build history and the project notes.