Remotion's compositor and media-parser carry the highest activity risk — two to fix first

A Hotspots analysis of remotion-dev/remotion at f4cebd8 finds two high-priority functions — get_frame in the Rust compositor and an anonymous dispatcher in media-parser — combining extreme cyclomatic complexity with commits as recently as yesterday.

Stephen Collins ·
oss typescript refactoring code-health
Activity Risk19.22Low
Hottest Functionget_frame

Antipatterns Detected

complex_branching5exit_heavy5god_function5long_function5deeply_nested4

Key Points

What is a god function and why does it matter in remotion?

A god function is a single function that has accumulated so many responsibilities that it controls a disproportionately large portion of the system's behavior — in structural terms, it calls a large number of distinct functions and contains enough branching logic that it cannot easily be understood or changed in isolation. In remotion, the anonymous function in `emit-available-info.ts` exemplifies this: it calls into 26 distinct functions and contains 118 independent execution paths. That breadth means a change to any one of those 26 callees may require a coordinated edit inside this function, and a bug introduced here can affect a wide range of downstream behaviors. God functions are also test-coverage burdens — 118 cyclomatic complexity implies at least 118 paths that need coverage to verify the function's behavior exhaustively.

How do I reduce cyclomatic complexity in TypeScript?

The most direct technique is extract-method refactoring: identify cohesive groups of branches — all the paths that handle one case or one field type — and pull them into named helper functions. This reduces the CC of the original function by the number of decision points you move out, and it makes each extracted piece independently testable. A cyclomatic complexity above 15 is a reasonable threshold to begin splitting; above 30 it warrants immediate attention; the anonymous function in `emit-available-info.ts` at CC 118 is well past that point. For functions that branch primarily on type or property presence — which the name `emit-available-info` suggests — replacing a long conditional chain with a dispatch table or a record of handler functions is often the highest-leverage single refactoring, and it commonly cuts CC by more than half in one pass.

Is remotion actively maintained?

The data indicates active development: the two highest-risk functions — `get_frame` and the anonymous dispatcher in `emit-available-info.ts` — were each touched twice in the last 30 days and were last modified just one day before this analysis. Both combine high structural complexity with recent commit activity, meaning they are both structurally complex and currently being changed. The web-renderer drawing functions `resolveLength` and `parseValue` also received commits in the same window, suggesting parallel work across subsystems. Active development and structural complexity are not mutually exclusive — the high-risk functions reflect accumulated structural debt in a codebase that is clearly still evolving rapidly.

How do I reproduce this analysis?

The Hotspots CLI is available at github.com/hotspots-dev/hotspots. This analysis was run against remotion-dev/remotion at commit `f4cebd8`. To reproduce it, check out that commit with `git checkout f4cebd8` in a local clone of the repository, then run `hotspots analyze . --mode snapshot --explain-patterns --force`. The same command works on any local git repository without additional configuration.

What does activity-weighted risk mean?

Activity-weighted risk multiplies a function's structural complexity — derived from cyclomatic complexity, nesting depth, and fan-out — by a signal reflecting how frequently the function has been changed recently. The result prioritizes functions that are both hard to understand and actively being modified, because those are the ones most likely to introduce regressions right now. A function with cyclomatic complexity 80 that hasn't been touched in two years scores lower than one with CC 20 being committed to every week, because the dormant function's complexity is a future concern rather than an immediate one. In remotion's case, `get_frame` scores 19.22 not just because its CC is 53, but because it was modified twice in the last 30 days — the combination is what makes it the highest-priority refactoring target rather than a backlog item.

At commit f4cebd8, the two functions that demand immediate attention in remotion-dev/remotion are both structurally complex and were touched within the last 24 hours. get_frame in packages/compositor/rust/opened_stream.rs carries an activity-weighted risk score of 19.22, driven by a cyclomatic complexity of 53 and two commits in the last 30 days, the most recent just one day ago. The anonymous dispatcher in packages/media-parser/src/emit-available-info.ts scores 18.88 with a cyclomatic complexity of 118 — the highest single value in this dataset — and shares the same recent-touch cadence. I would start with get_frame because it sits at the boundary between Rust and the rest of the stack, but the anonymous function in media-parser is structurally the more extreme case.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
get_framepackages/compositor/rust/opened_stream.rs19.253720
<anonymous>packages/media-parser/src/emit-available-info.ts18.9118626
useGenerationApipackages/template-prompt-to-motion-graphics/src/hooks/useGenerationApi.ts18.112921
open_streampackages/compositor/rust/opened_stream.rs17.021617
POSTpackages/template-prompt-to-motion-graphics/src/app/api/generate/route.ts16.942431

Large Repo Analysis

remotion is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

get_frame — packages/compositor/rust/opened_stream.rs

get_frame retrieves a decoded video frame from an open media stream inside remotion’s Rust compositor. With a cyclomatic complexity of 53, it contains at least 53 independent execution paths — each one a required test case and a surface for a conditional to drift out of sync with the others. A nesting depth of 7 means the deepest branches are seven control structures deep, well past the point where a reader can hold the full state space in their head. Fan-out of 20 makes this a structural centre of gravity: 20 distinct functions are called from here, so a change to the interface or semantics of any of them may require a coordinated edit inside get_frame as well.

What makes this a live concern rather than accumulated debt is the activity signal: two commits in the last 30 days, with the most recent landing just one day ago. That is not a dormant function with theoretical blast-radius risk — it is a function being actively changed right now while carrying structural complexity that makes each edit harder to reason about. My recommendation is to begin extracting the seek/decode decision logic and the error-path branches into smaller, named functions. Even splitting the happy path from the error-exit paths would reduce CC materially and make each remaining branch independently testable.

<anonymous> — packages/media-parser/src/emit-available-info.ts

An anonymous function at the top level of emit-available-info.ts is the structural outlier in this entire dataset. Its cyclomatic complexity of 118 is more than double that of get_frame, placing it firmly in the extreme range. Based on the file name, this function is likely a dispatcher or callback that fires whenever new parsed media metadata becomes available — checking which fields have been populated and emitting or routing them accordingly. A design like that accumulates branches naturally: one conditional per supported metadata field or track type, compounding over time as the media-parser gains support for new container formats and codec properties.

With a fan-out of 26, it calls into 26 distinct functions, making it the broadest coupling point in the top hotspots. Six levels of nesting at ND 6, combined with CC 118 and 26 callees, means that reasoning about a single change requires mentally tracing through a large and branchy call graph simultaneously. Like get_frame, it received two commits in the last 30 days and was last modified one day ago. I would prioritize introducing a discriminated-union type or a data-driven dispatch table to replace the conditional chain: that technique commonly reduces CC by an order of magnitude in functions that branch primarily on type or field presence, and it makes each emission path individually testable. Because the function is anonymous, naming it explicitly is the zero-cost first step — it makes stack traces legible and makes coverage reports meaningful.

useGenerationApi — packages/template-prompt-to-motion-graphics/src/hooks/useGenerationApi.ts

useGenerationApi is a React hook in the AI video generation template. Its CC of 12 is modest, but ND 9 is the deepest nesting value in the top five — nine levels of control structure inside a hook that likely manages async API state: loading, error, retry, cancellation, and response parsing all layered on top of each other. Fan-out into 21 callees suggests it’s also coordinating several concerns that could be isolated.

Recommendation: The nesting is the primary issue here, not the branching count. A series of guard clauses at the top — return early on error, on loading, on missing prerequisites — would flatten the structure without restructuring the logic. If the hook is managing multiple independent async concerns, useReducer is often a cleaner model than nested useState chains.

open_stream — packages/compositor/rust/opened_stream.rs

open_stream lives in the same Rust file as get_frame and is almost certainly its counterpart: the function that opens and initialises the media stream before frames can be retrieved. At CC 21 and ND 6, its structural profile is meaningfully simpler than get_frame, but it shares the same file and the same activity window. Two functions in one file — both in the top five by activity-weighted risk — suggests opened_stream.rs as a whole is the current focal point of compositor development. Refactoring get_frame first is the right priority, but open_stream will likely need the same extract-and-name treatment as the file evolves.

POST — packages/template-prompt-to-motion-graphics/src/app/api/generate/route.ts

The POST handler for the AI generation API route carries CC 42 and fan-out into 31 distinct callees — a wide surface for a single HTTP handler. ND 4 is shallow, so the complexity comes from branching breadth rather than nesting depth: validation branches, provider selection, error handling, and response shaping are likely all inline in one function. At risk score 16.9, this sits at the edge of the top five rather than the centre of it, but a handler that calls 31 functions is doing too much in one place. Extracting validation and the provider-dispatch logic into named helpers would reduce both CC and FO and make the handler’s linear flow legible.

Key Takeaways

  • Start with get_frame (packages/compositor/rust/opened_stream.rs): activity-weighted risk 19.22, CC 53, touched within the last day. Extract the seek/decode decision and error-exit branches into named helper functions before the next change lands.
  • Name and decompose the anonymous dispatcher in packages/media-parser/src/emit-available-info.ts: CC 118 with a fan-out of 26 makes this the single highest structural complexity point in the dataset. Giving it an explicit name costs nothing; replacing its conditional chain with a data-driven dispatch table is the refactoring that will actually bring CC down.
  • opened_stream.rs as a whole is the hottest file: both get_frame and open_stream rank in the top five from a single Rust file — treat the file, not just the functions, as the refactoring unit.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching5
exit_heavy5
god_function5
long_function5
deeply_nested4

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

Reproduce This Analysis

git clone https://github.com/remotion-dev/remotion
cd remotion
git checkout f4cebd8179b4fa66cb7476bca30bd4a206306d89
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

macOS
$ brew install Stephen-Collins-tech/tap/hotspots
Linux / cargo
$ cargo install hotspots-cli
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Related Analyses