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
3. Database identity
Each legacy audio row has two independent media assets:
media_assets.media_type | Role | Legacy compatibility target |
|---|---|---|
audio | content | audio.audio |
image | thumbnail | audio.audioThumbnail |
For an audio content row with audio.id = 45, the stable identity is:
1legacy_table = audio
2legacy_id = 45
3role = contentmedia_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:
| Table | Fields written |
|---|---|
media_uploads | asset/admin/revision identity, bucket, temporary object_key, original filename, expected bytes/MIME/checksum, optional multipart state, status = authorized, expiry |
admin_audit_events | media_upload.authorized |
Example temporary key:
1incoming/audio/{media_asset_ulid}/revision-{number}/{upload_ulid}.wavFiles 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:
| Table | Transition or write |
|---|---|
media_uploads | authorized -> uploaded -> confirmed; records confirmation and S3 version data |
media_assets | advances source_revision |
media_jobs | inserts media_type = audio, profile_version = aac-v1, processor = audio_lambda, status = pending, generation 1, default 3 attempts |
outbox_messages | inserts media.job.requested for the audio queue with status = pending |
admin_audit_events | media_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 -> dispatchedThe 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:
| Setting | Value |
|---|---|
| Reserved concurrency | 1 |
| Memory | 2,048 MiB |
| Ephemeral storage | 2,048 MiB |
| Function timeout | 600 seconds |
| Default application audio timeout | 600 seconds |
| Default Lambda source-size limit | 250 MiB |
Before conversion, the Lambda claims a durable lease:
| Table | Transition or write |
|---|---|
media_job_attempts | inserts processor = audio_lambda, attempt number, status = claimed, lease token/owner/expiry and timestamps |
media_jobs | increments attempts; dispatched -> processing |
media_job_attempts | claimed -> 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:
- Downloads the exact confirmed S3 object/version.
- Uses FFprobe to require a positive source duration.
- Runs FFmpeg with AAC at 128 kbit/s, removes video streams and creates fast-start M4A.
- Uses FFprobe to require AAC and compare output duration to source within one second or one percent.
- Fully decodes the output to a null sink to detect corruption.
- Uploads the result as
audio.m4a.
An example final key is:
1audio/wb10/subject/service/unit/chapter/audio-name-code/audio.m4aFor 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.
| Table | Fields or transition |
|---|---|
media_jobs | processing -> publish_pending -> published |
media_versions | inserts 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_assets | sets active_version_id |
audio | sets audio.audio to {EVE_MEDIA_DOMAIN}/{object_key} |
media_job_attempts | processing -> succeeded, completion timestamp |
publication_audits | records 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.m4aThe modern table deliberately stores a relative S3 key. The legacy column receives a full CDN URL for compatibility.
Important media_versions fields
| Field | Meaning |
|---|---|
object_key | Primary relative S3 key ending in audio.m4a |
object_prefix | Directory containing the published audio object |
metadata.object_keys | Verified output inventory; currently one M4A key |
file_size_bytes | Published M4A size |
media_job_attempt_id | Producing Lambda/EC2 attempt relation, not an audio URL |
inventory_object_key | Currently null; the inventory is embedded in metadata.object_keys |
5. Audio job status timeline
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:
- If
media_uploads.expected_bytesexceeds the default 250 MiB Lambda threshold, or an audio process times out, the Lambda attempt becomesfailedwitherror_code = heavy_fallback. media_jobs.processorchanges fromaudio_lambdatoheavy_worker.media_jobs.generationincreases, status returns topending, retry budget is extended andfallback_reasonrecordssource_size_limitorlambda_timeout.- A new
outbox_messagesrow targets the video/heavy queue. - The conversion ASG starts an EC2 worker if necessary.
- The EC2 worker runs the same
AudioProcessorwith 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
| Table | Audio responsibility |
|---|---|
audio | Legacy catalogue and full compatibility URLs |
media_assets | Stable content or thumbnail identity and active version pointer |
media_uploads | Temporary original source and upload integrity contract |
media_jobs | Processor, status, generation, retries and fallback reason |
media_job_attempts | Lambda/EC2 lease and outcome per attempt |
outbox_messages | Reliable dispatch to audio or heavy SQS |
media_versions | Version history and final compressed key |
publication_audits | Activation and rollback history |
admin_audit_events | Admin 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 todispatchedwith exponential delay. - The Lambda response marks retryable SQS records as failed; successful or stale-generation records are acknowledged by omission.
- The job becomes
failedwhen 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_expiredby 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_idand 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_atorsource_delete_error_codeinmedia_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.