Audio upload and compression database flow

Last updated: 10 September 2026

1. Result

A new audio file is uploaded directly to the shared evequality bucket, queued through a durable MySQL outbox, compressed by the audio Lambda with FFmpeg, verified, and atomically published.

The final compressed audio key is stored in media_versions.object_key. media_assets.active_version_id selects the live version. Publication also writes the full CDN URL to the legacy audio.audio column.

Normal audio uses Lambda. If the source is larger than the configured Lambda limit or the Lambda process times out, the same job is safely rerouted to the scale-to-zero EC2 heavy worker.

This describes the implemented repository state, not proof that the Terraform resources are already deployed.

2. Main AWS and database flow

Rendering diagram…

3. Database identity

Each legacy audio row has two independent media assets:

media_assets.media_typeRoleLegacy compatibility target
audiocontentaudio.audio
imagethumbnailaudio.audioThumbnail

For an audio content row with audio.id = 45, the stable identity is:

1legacy_table = audio 2legacy_id = 45 3role = content

media_assets.external_code contains audio.audioCode. Replacements create new source revisions and versions under the same content asset.

4. Stage-by-stage database changes

Stage A — upload authorization

The admin UI posts asset ID, filename, size, MIME and checksum contract to POST /admin/api/media/uploads.

Allowed source extensions are mp3, m4a, wav, flac and ogg. Laravel checks the administrator's audios.update permission and inserts:

TableFields written
media_uploadsasset/admin/revision identity, bucket, temporary object_key, original filename, expected bytes/MIME/checksum, optional multipart state, status = authorized, expiry
admin_audit_eventsmedia_upload.authorized

Example temporary key:

1incoming/audio/{media_asset_ulid}/revision-{number}/{upload_ulid}.wav

Files at or above the default 100 MiB multipart threshold upload in parts. The browser uploads bytes directly to S3; Laravel supplies signed requests but does not proxy the file.

Stage B — confirmation and job creation

After upload, the browser confirms the upload. Laravel checks S3 byte count, content type, checksum and real signature. One transaction then changes or inserts:

TableTransition or write
media_uploadsauthorized -> uploaded -> confirmed; records confirmation and S3 version data
media_assetsadvances source_revision
media_jobsinserts media_type = audio, profile_version = aac-v1, processor = audio_lambda, status = pending, generation 1, default 3 attempts
outbox_messagesinserts media.job.requested for the audio queue with status = pending
admin_audit_eventsmedia_upload.confirmed with job ID

The job is idempotent for asset + source revision + profile, so repeated confirmation does not create duplicate jobs.

Stage C — outbox and audio SQS

The one-minute outbox dispatcher changes:

1outbox_messages: pending -> dispatching -> dispatched 2media_jobs: pending -> dispatched

The audio queue is a Standard encrypted SQS queue with 660-second visibility, 20-second long polling, four-day message retention and redrive to a 14-day DLQ after three receives. The event-source mapping invokes only the audio Lambda, with batch size one and partial-batch failure reporting.

Stage D — Lambda lease and processing attempt

The audio Lambda is an ARM64 container configured with:

SettingValue
Reserved concurrency1
Memory2,048 MiB
Ephemeral storage2,048 MiB
Function timeout600 seconds
Default application audio timeout600 seconds
Default Lambda source-size limit250 MiB

Before conversion, the Lambda claims a durable lease:

TableTransition or write
media_job_attemptsinserts processor = audio_lambda, attempt number, status = claimed, lease token/owner/expiry and timestamps
media_jobsincrements attempts; dispatched -> processing
media_job_attemptsclaimed -> processing

The pipeline requires local free space of at least three times the expected source size, with a 100 MB minimum.

Stage E — FFmpeg compression and verification

The processor:

  1. Downloads the exact confirmed S3 object/version.
  2. Uses FFprobe to require a positive source duration.
  3. Runs FFmpeg with AAC at 128 kbit/s, removes video streams and creates fast-start M4A.
  4. Uses FFprobe to require AAC and compare output duration to source within one second or one percent.
  5. Fully decodes the output to a null sink to detect corruption.
  6. Uploads the result as audio.m4a.

An example final key is:

1audio/wb10/subject/service/unit/chapter/audio-name-code/audio.m4a

For later replacements, the directory can include versions/r{revision}. The exact hierarchy is derived from legacy board, class, subject, service, unit and chapter records.

The candidate verifier checks that the S3 object exists, has a valid M4A/AAC signature, has positive duration and reports the AAC codec.

Stage F — atomic publication

Publication is allowed only when the lease, generation, source revision and asset state are still authoritative.

TableFields or transition
media_jobsprocessing -> publish_pending -> published
media_versionsinserts format = m4a, processor = audio_lambda or heavy_worker, final object_key, prefix, bytes, codec/duration/channels/sample-rate metadata, verification_status = verified, verify/publish timestamps
media_assetssets active_version_id
audiosets audio.audio to {EVE_MEDIA_DOMAIN}/{object_key}
media_job_attemptsprocessing -> succeeded, completion timestamp
publication_auditsrecords the previous and new versions plus job/attempt/generation/actor

Example:

1media_versions.object_key = audio/wb10/.../audio-name-code/audio.m4a 2audio.audio = https://evevideoquality.rayandmartin.in/audio/wb10/.../audio-name-code/audio.m4a

The modern table deliberately stores a relative S3 key. The legacy column receives a full CDN URL for compatibility.

Important media_versions fields

FieldMeaning
object_keyPrimary relative S3 key ending in audio.m4a
object_prefixDirectory containing the published audio object
metadata.object_keysVerified output inventory; currently one M4A key
file_size_bytesPublished M4A size
media_job_attempt_idProducing Lambda/EC2 attempt relation, not an audio URL
inventory_object_keyCurrently null; the inventory is embedded in metadata.object_keys

5. Audio job status timeline

Rendering diagram…

If upload confirmation fails its S3 or signature checks, media_uploads changes from uploaded back to authorized, clears confirmation_started_at, and may be confirmed again before expires_at.

6. Heavy-worker fallback

Fallback is automatic and keeps the same media_jobs.id:

  1. If media_uploads.expected_bytes exceeds the default 250 MiB Lambda threshold, or an audio process times out, the Lambda attempt becomes failed with error_code = heavy_fallback.
  2. media_jobs.processor changes from audio_lambda to heavy_worker.
  3. media_jobs.generation increases, status returns to pending, retry budget is extended and fallback_reason records source_size_limit or lambda_timeout.
  4. A new outbox_messages row targets the video/heavy queue.
  5. The conversion ASG starts an EC2 worker if necessary.
  6. The EC2 worker runs the same AudioProcessor with the heavy-media timeout, then publishes normally.

Old Lambda/SQS messages contain the old generation and therefore cannot publish after rerouting.

7. Table ownership summary

TableAudio responsibility
audioLegacy catalogue and full compatibility URLs
media_assetsStable content or thumbnail identity and active version pointer
media_uploadsTemporary original source and upload integrity contract
media_jobsProcessor, status, generation, retries and fallback reason
media_job_attemptsLambda/EC2 lease and outcome per attempt
outbox_messagesReliable dispatch to audio or heavy SQS
media_versionsVersion history and final compressed key
publication_auditsActivation and rollback history
admin_audit_eventsAdmin upload actions

8. Failure, DLQ and cleanup

  • A retryable processing error marks the attempt failed, records a safe error code/context and returns the job to dispatched with exponential delay.
  • The Lambda response marks retryable SQS records as failed; successful or stale-generation records are acknowledged by omission.
  • The job becomes failed when its attempt budget is exhausted. SQS moves a message to the audio DLQ after three receives.
  • Candidate output uploaded by a failed attempt is deleted best-effort.
  • Expired processing leases are marked lease_expired by the one-minute recovery command.
  • A manual retry increases the job generation and creates a fresh outbox row.
  • Rollback re-verifies a previously published version before changing active_version_id and the legacy URL.
  • The temporary incoming/audio/... source is deleted only after the exact revision is verified, published, active and at least the configured retention age, default 24 hours.
  • Source cleanup records source_delete_started_at, source_deleted_at or source_delete_error_code in media_uploads.

9. Audio thumbnail

The audio image is not produced by FFmpeg. It is a separate media_assets row with media_type = image and role = thumbnail. The administrator uploads it through the image and thumbnail upload flow. Successful image publication updates audio.audioThumbnail.