Build1 publisher3 min readPublished
A boolean set at upload time decides what your video migration costs
A dev.to post ships a dependency-free Node script that asks three portability questions of every media ID your own database already stores. The most expensive answer, source retention, was fixed at ingest.
The Engineer · Build desk

What happened
- A dev.to post publishes a provider-agnostic Node script that reads a video library and writes a local CSV, one row per media ID, and recommends running it once a quarter.
- It asks three questions per asset: whether the provider still holds the original upload, whether the playback URL is a standard HLS manifest, and how many of your own tables carry the provider's IDs.
- The worked example targets FastPix because its source-retention flag is explicit in the API, and the post says Mux, Cloudflare Stream or api.video need only a different adapter behind the same interface.
- The script runs on Node 20.x or newer with no dependencies beyond the standard library, issues only GET requests, and takes its media IDs from a file rather than a list-everything call.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Assets whose original was never retained can only be moved by re-encoding the transcoded renditions, and the permanent quality loss is paid by whoever owns the catalogue, not by the provider that dropped the source.
- decision Provider selection can now include the exit, putting the retention flag and the playback format beside the pricing page and the upload benchmark the post says most teams stop at.
- capability With one method, audit(mediaId), as the whole interface, evaluating a second video platform costs one class and a set of credentials instead of a trial migration.
- constraint The ID-sprawl figure comes out of your own schema, so no provider can quote it and the migration estimate is bounded by write paths only your team can enumerate.
Call `audit(mediaId)` and one request goes out: a GET to `/on-demand/{mediaId}` on `https://api.fastpix.com/v1`, with HTTP Basic auth where the Access Token ID is the username and the Secret Key is the password [8][9]. The response's `data` comes back as an object [9]. A code comment notes that older FastPix docs showed an array there, so a parser written against those breaks on this call [9].
The adapter reads `data.playbackIds?.[0]?.id`, builds `https://stream.fastpix.com/{id}.m3u8` from it, and sets `standardHls: Boolean(playbackUrl.endsWith('.m3u8'))` [10][11]. It is testing a suffix it appended itself two lines earlier [10][11]. The column answers whether a playback ID exists, and says nothing about whether the provider serves a standard manifest [12]. So it is true for every asset that has a playback ID and false only when the URL came back empty [12].
`sourceRetained` is `data.sourceAccess ?? null`, and `sourceAccess` is decided at ingest on the create call; if it was not enabled then, the original is not retrievable now [13]. Because the typedef declares `boolean|null`, three values can reach the CSV: true, false, and null where the field was absent [7][14]. A plan that reads null as false will under-count the assets it has to re-encode [14].
The post recommends running the script once a quarter [1]. Four runs a year is one every 91 days [19]. Since retention is fixed at upload, that interval is also the largest amount of ingest that can arrive with the wrong default before a row says so [19]. "If it defaulted off across your whole library, this script will tell you today rather than during a migration in 2028," the post writes [16]. Today is the right answer for a library that already exists; for new uploads, the check belongs beside the create call.
Media IDs come from a file your database generates, one per line, not from a list-everything API call [17]. The post defends this design decision at length: "An asset sitting in the provider's dashboard that nothing in your product references is not a migration problem. A row in your videos table is." [18] So the row count in the CSV is the size of your exposure.
Two of the three questions are answered by the adapter's return shape [21]. The third, ID sprawl, is a query against your own schema, and the published listing stops inside `src/ids.js` before that code [20]. The rest transfers cheaply: Node 20.x or newer, nothing beyond the standard library, and GET requests only, so the script reads and writes a local CSV without touching anything in the provider [5][6]. The interface is a single method, so a second provider costs one class and a set of credentials [7][4]. FastPix is the worked example because its retention flag is explicit in the API [4]. For a provider that does not return one, the adapter has nothing to read and the column stays null [7]. "Nobody checks the exit until they're already trying to leave, at which point the answer is expensive and non-negotiable," the post says [15].
What to watch
- Whether the finished post publishes the ID-sprawl counter, and whether it counts tables, columns or rows.
- Adapters for Mux, Cloudflare Stream and api.video, and whether each exposes source retention over a plain GET.
- Whether FastPix's docs are corrected on the data object-versus-array shape that breaks parsers written against the older version.