OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / transcode.test.js

82 lines · javascript ref:
// The video rungs, offline. No ffmpeg, no spawn, no hang.
//
// ⛔ THE REAL ROUND TRIP LIVES IN `test/transcode.spawn.js`, run by `npm run test:media`. A test that
// actually spawns ffmpeg passes and then holds brittle's runner open for ever — verified: the whole
// 346-test suite went from finishing in seconds to being killed at seven minutes. A suite nobody can
// run is worse than one that mocks a boundary, so the boundary is injected and the spawning test is
// opt-in. It has been run and it works: 1280x720 in, a genuine 480-tall even-width file out.
import test from 'brittle'
import process from '#node:process'
import { probe, encodeLevel, available } from '../lib/transcode.js'

const ok = (out) => async () => ({ code: 0, out, err: '' })
const fails = async () => ({ code: 1, out: '', err: 'boom' })

test('probe reads the real stream dimensions out of ffprobe json', async (t) => {
  const run = ok(JSON.stringify({ streams: [{ width: 1920, height: 1080 }], format: { duration: '12.5' } }))
  t.alike(await probe('/x.mp4', { run }), { width: 1920, height: 1080, duration: 12.5 })
})

test('⛔ probe answers null rather than throwing on anything unusable', async (t) => {
  t.is(await probe('/x.mp4', { run: fails }), null, 'a non-zero exit')
  t.is(await probe('/x.mp4', { run: ok('not json') }), null, 'output that is not json')
  t.is(await probe('/x.mp4', { run: ok('{}') }), null, 'json with no stream')
  t.is(await available({ run: fails }), false, '⛔ and a node without an encoder is a state, not a crash')
})

test('★ encodeLevel asks for an EVEN width and the level’s real height', async (t) => {
  let args = null
  const run = async (cmd, a) => { args = a; return { code: 0, out: '', err: '' } }
  const r = await encodeLevel('/src.mp4', { name: '480p', edge: 480, bitrate: '1000k', maxrate: '1200k', profile: 'baseline' },
    { run, stat: () => 4242 })

  const vf = args[args.indexOf('-vf') + 1]
  t.is(vf, 'scale=-2:480', '⛔ -2 keeps the aspect AND rounds even — h264 refuses an odd width, and')
  t.is(args[args.indexOf('-profile:v') + 1], 'baseline', 'the rung carries its own profile')
  t.is(args[args.indexOf('-b:v') + 1], '1000k', 'and its own bitrate')
  t.ok(args.includes('+faststart'), 'faststart, so a player can begin before the file has arrived')
  t.is(r.size, 4242)
  t.is(r.height, 480)
})

test('encodeLevel returns null when ffmpeg fails or writes nothing', async (t) => {
  t.is(await encodeLevel('/src.mp4', { name: '480p', edge: 480, bitrate: '1k', maxrate: '1k', profile: 'baseline' },
    { run: fails, stat: () => 10 }), null, 'a non-zero exit')
  t.is(await encodeLevel('/src.mp4', { name: '480p', edge: 480, bitrate: '1k', maxrate: '1k', profile: 'baseline' },
    { run: ok(''), stat: () => 0 }), null, '⛔ and a zero-byte file is not a level')
})

// ── the binaries are ENV-OVERRIDABLE, because a phone has no `ffmpeg` on any PATH ────────────────
//
// ⛔ WHY THIS EXISTS. `rpc.js` calls `available()`, `probe(path)` and `encodeLevel(path, level)`
// with NO options, so every caller gets the `'ffmpeg'`/`'ffprobe'` defaults. On a server that is
// right — the binary is on PATH. An Android app has no PATH: a bundled binary lives at an absolute
// path the app learns at runtime (its extracted native-lib dir), so without an override there is no
// way to reach it and the node can never derive a rung, however many binaries the APK ships.
//
// ⚠ DEFAULTS ARE UNCHANGED, deliberately. The three hosts running this daemon must see no
// difference: absent env, this is byte-for-byte the previous behaviour.
test('the ffmpeg binaries can be overridden by env, and default unchanged when it is absent', async (t) => {
  const seen = []
  const run = async (cmd) => { seen.push(cmd); return { code: 0, out: '', err: '' } }

  // ⚠ '' not `delete`: bare-process's env is a proxy whose deleteProperty trap refuses. Empty is
  // falsy, which is the same branch an unset var takes — so this tests the real fallback path.
  process.env.FAVELA_FFMPEG = ''
  process.env.FAVELA_FFPROBE = ''
  await available({ run })
  t.alike(seen.splice(0), ['ffmpeg', 'ffprobe'], 'absent env -> the previous defaults, unchanged')

  process.env.FAVELA_FFMPEG = '/data/app/lib/arm64/libffmpeg.so'
  process.env.FAVELA_FFPROBE = '/data/app/lib/arm64/libffprobe.so'
  await available({ run })
  t.alike(seen.splice(0), ['/data/app/lib/arm64/libffmpeg.so', '/data/app/lib/arm64/libffprobe.so'],
    'env names the bundled binaries — the only way an APK can reach its own ffmpeg')

  await probe('/tmp/x.mp4', { run })
  t.is(seen.splice(0)[0], '/data/app/lib/arm64/libffprobe.so', 'probe honours it too')

  process.env.FAVELA_FFMPEG = ''
  process.env.FAVELA_FFPROBE = ''
})