mostlyright.finance.transcripts.capture
Earnings webcast capture fleet.
Provider-aware capture adapters that drive the public guest path and cold-fetch
the open media asset, with no auth, DRM, or captcha circumvention. Q4 (widest
large-cap coverage) is the shipped replay adapter: it captures the static
post-call media asset into a transient AudioArtifact for transcription.
CaptureNotAvailableError is re-exported from mostlyright.core.exceptions so
capture call sites import the ABC, the artifact, and the control-signal error
from one place.
class mostlyright.finance.transcripts.capture.AudioArtifact(audio_path, ticker, call_id, source_media_url, is_transient=True)
Section titled “class mostlyright.finance.transcripts.capture.AudioArtifact(audio_path, ticker, call_id, source_media_url, is_transient=True)”Bases: object
A captured-audio handle whose backing file is transient.
audio_path points at a temp file extracted from the captured webcast
media. is_transient is True by design: the file must be deleted
after transcription and is never persisted or served. cleanup() unlinks
it; the artifact is also a context manager, so with adapter.capture(...) as a guarantees the audio is gone on exit even if transcription raises.
source_media_url / call_id / ticker carry the capture lineage for
audit (which asset produced these bytes) without retaining the bytes.
audio_path: str
Section titled “audio_path: str”call_id: str
Section titled “call_id: str”cleanup()
Section titled “cleanup()”Delete the transient audio file (idempotent — safe to call twice).
FileNotFoundError (an idempotent double-call, or an already-cleaned
artifact) is suppressed silently. Any other OSError, such as
PermissionError on a read-only mount, is suppressed but logged at
error level: it must not propagate out of __exit__, which would
mask the transcription error the with block was raising and that the
caller must see. It still means copyrighted earnings audio survived on
disk, so the log line names the path an operator must delete manually.
- Return type:
None
is_transient: bool
Section titled “is_transient: bool”source_media_url: str
Section titled “source_media_url: str”ticker: str
Section titled “ticker: str”class mostlyright.finance.transcripts.capture.CaptureAdapter
Section titled “class mostlyright.finance.transcripts.capture.CaptureAdapter”Bases: ABC
Provider-aware webcast capture contract.
Concrete adapters implement detect() (does this adapter handle the
URL?) and capture() (event → transient AudioArtifact).
Providers with a fetchable live stream can also override live().
abstractmethod capture(event, **kwargs)
Section titled “abstractmethod capture(event, **kwargs)”Capture event to a transient AudioArtifact.
An implementation must stay on the public path (no auth, DRM, or captcha
circumvention) and must return an artifact whose backing file is
transient. Raise
CaptureNotAvailableError when the
provider template exposes no cold-fetchable HTTP media (for example a
dial-in bridge) rather than emitting empty audio.
- Return type:
AudioArtifact - Parameters:
abstractmethod detect(url)
Section titled “abstractmethod detect(url)”Return True iff this adapter handles url’s webcast provider.
live(manifest_url, session=None)
Section titled “live(manifest_url, session=None)”Follow the provider’s live edge, yielding (pcm_frame, spoken_at).
Reserved live-during-call entry point. A concrete adapter with a fetchable
live HLS endpoint overrides this to open a rolling-window live capturer
against manifest_url and stream PCM frames into the streaming STT.
Audio stays a transient frame stream: the generator yields decoded PCM to
the consumer and never writes audio to disk or serves it.
The ABC default raises LiveCaptureUnsupported. A provider with no
live endpoint (dial-in bridge, SABR/YouTube, replay-only such as Lumi) has
no live path, and the runner routes it to the authoritative post-call batch
ledger instead. session carries any header-gated context
(Origin/Referer/User-Agent/token) a provider needs; Q4 over IVS needs none.
exception mostlyright.finance.transcripts.capture.CaptureNotAvailableError(message=”, , error_code=None, source=None, request_id=None)
Section titled “exception mostlyright.finance.transcripts.capture.CaptureNotAvailableError(message=”, , error_code=None, source=None, request_id=None)”Bases: EarningsError
A webcast has no cold-fetchable HTTP media asset to capture.
Raised by a capture adapter when the provider template is not an HTTP-media
webcast — for example Q4’s analyst template, which is a telephone dial-in
(meeting ID plus passcode, with no media or CDN host ever requested). The
caller should route to dial-in capture or skip the webcast, rather than emit
empty audio. This is a normal control signal, not a corruption, and
subclasses EarningsError so callers can filter it cleanly.
- Parameters:
- Return type: None
default_error_code: str
Section titled “default_error_code: str”Subclass override — the stable string enum surfaced via error_code.
error_code: str
Section titled “error_code: str”message: str
Section titled “message: str”request_id: str | None
Section titled “request_id: str | None”source: str | None
Section titled “source: str | None”exception mostlyright.finance.transcripts.capture.LiveCaptureUnsupported(message=”, , error_code=None, source=None, request_id=None)
Section titled “exception mostlyright.finance.transcripts.capture.LiveCaptureUnsupported(message=”, , error_code=None, source=None, request_id=None)”Bases: EarningsError
A capture adapter has no supported live-during-call endpoint.
Engine-local exception: it subclasses the core EarningsError so
callers filter it with the rest of the earnings-pipeline errors, but it is
deliberately not added to packages/core — the live capture path is
engine-only and must never touch the parity-critical modules (research(),
merge/*, _live/_sources.py). Raised by CaptureAdapter.live()
(the ABC default) for providers whose template has no fetchable live HLS
endpoint: a telephone dial-in bridge, an unverified SABR/YouTube stream, or a
replay-only provider such as Lumi. The runner routes such a provider to the
post-call batch path and never opens a live session.
- Parameters:
- Return type: None
default_error_code: str
Section titled “default_error_code: str”Subclass override — the stable string enum surfaced via error_code.
class mostlyright.finance.transcripts.capture.Q4CaptureAdapter
Section titled “class mostlyright.finance.transcripts.capture.Q4CaptureAdapter”Bases: CaptureAdapter
Q4 browser-webcast replay capture over the public guest path.
capture(event, , client=None, transport=None, tmp_dir=None, **_)
Section titled “capture(event, , client=None, transport=None, tmp_dir=None, **_)”Cold-fetch the Q4 static MP4 and extract transient audio.
event carries ticker / call_id / media_url (the sniffed
static.events.q4inc.com/.../*.mp4 currentSrc). When media_url is
absent the event is the analyst dial-in template (no HTTP media) —
CaptureNotAvailableError is raised.
client may be an injected httpx.Client (a guest-flow session for
later gated providers). transport is the explicit transport the cold
media fetch rides on; tests pass an httpx.MockTransport directly.
The cold media fetch is always issued through a fresh User-Agent-only
client built here (from transport when given). It deliberately does not
inherit the injected client’s cookies or default headers, so no cookie,
token, or Authorization header can leak onto the asset GET — the guest form
gates the page, not the asset. When only client is given with no
explicit transport, the media fetch reuses that client’s transport via
httpx.Client._transport so a mock host still serves the request,
but starts from a clean header set and empty cookie jar.
media_url is validated (https plus a Q4 media host) before any fetch;
an unvalidated cold GET would be an SSRF sink. The media fetch uses
generous timeouts rather than httpx’s flat 5s default, which would abort a
full MP4 stream at the read deadline. A borrowed transport — from an
injected client or the explicit transport param — is never closed
here; only a transport this method created itself is, because closing a
borrowed shared transport would break the caller’s injected client.
A 2xx/206 carrying a zero-byte body (an expired or edited replay that
answers the range but serves no media) raises
CaptureNotAvailableError instead of writing an empty MP4 that
flows to STT: the byte count is tracked during the stream loop and an empty
result fails loud.
The fetch is also bounded on size and total time. media_url is external
event data and the SSRF allowlist bounds only the host, not the object
size, so an asset over _MAX_MEDIA_BYTES is rejected up front from
its declared Content-Range/Content-Length and, as a backstop,
aborted mid-stream once the written bytes exceed the cap. A slow-drip
source that never trips the per-operation read timeout is aborted at the
_MEDIA_TOTAL_TIMEOUT wall-clock deadline. All abort paths unlink
the partial MP4 via the outer finally, so an oversized or slow asset
can never fill the ingest host’s temp disk or flow to STT.
- Return type:
AudioArtifact - Parameters:
detect(url)
Section titled “detect(url)”Return True when url is a Q4 webcast/asset host.
Modules
Section titled “Modules”| Module | Description |
|---|---|
base | Capture-adapter ABC + the transient audio artifact. |
q4 | Q4 browser-webcast capture adapter. |