Monorepo for Aesthetic.Computer aesthetic.computer
core system backend TAPE-MP4-CONVERSION-PLAN.md
3.5 kB

Tape MP4 Conversion for ATProto Sync #

Current State #

  • Tapes are uploaded as ZIP files containing:
    • frame-{n}.png - Scaled frames (6x original size)
    • timing.json - Frame durations and timestamps
    • metadata.json - Original dimensions, scale, frame count, duration
  • MongoDB tapes collection stores: { code, slug, when, bucket, user, nuked }
  • NO automatic MP4 conversion or ATProto sync yet

Desired Architecture #

Regular Tape Creation (track-tape.mjs) #

// Client uploads ZIP to storage
// Server creates MongoDB record
// NO MP4 conversion
// NO ATProto sync
{
  code: "abc",
  slug: "wand-2025.10.23.12.30.00.123",
  when: Date,
  bucket: "user-aesthetic-computer",
  user: "auth0|123",
  nuked: false
}

Sync Script (sync-atproto.mjs) #

When syncing tapes to ATProto:

  1. Download ZIP from storage
  2. Extract frames from ZIP
  3. Convert frames to MP4 using ffmpeg
  4. Upload MP4 blob to ATProto
  5. Create ATProto record with:
    • zipUrl: Direct link to download the ZIP
    • video: MP4 blob (for viewing in ATProto apps)
    • acUrl: Permalink to view on aesthetic.computer

ATProto Record Structure #

{
  $type: "computer.aesthetic.tape",
  slug: "wand-2025.10.23.12.30.00.123",
  code: "abc",
  zipUrl: "https://user-aesthetic-computer.sfo3.digitaloceanspaces.com/auth0|123/slug.zip",
  acUrl: "https://aesthetic.computer/!abc",
  when: "2025-10-23T12:30:00.123Z",
  video: {  // MP4 blob
    $type: "blob",
    ref: { $link: "bafkreiabcdef..." },
    mimeType: "video/mp4",
    size: 5242880
  },
  ref: "mongoId"
}

Implementation Tasks #

Phase 1: Basic Sync (Without MP4) #

Phase 2: MP4 Conversion (Local) #

Phase 3: Production Deployment #

Technical Notes #

ffmpeg Command #

# Create MP4 from PNG sequence with variable frame durations
ffmpeg -r 60 \
  -i frame-%d.png \
  -c:v libx264 \
  -pix_fmt yuv420p \
  -movflags +faststart \
  output.mp4

ZIP Structure #

wand-2025.10.23.12.30.00.123.zip
├── frame-0.png
├── frame-1.png
├── frame-2.png
├── ...
├── timing.json
└── metadata.json

timing.json Format #

{
  "frames": [
    { "index": 0, "duration": 16.67, "timestamp": 0 },
    { "index": 1, "duration": 16.67, "timestamp": 16.67 },
    ...
  ],
  "totalDuration": 4000
}

Future Considerations #

  • Caching: Store converted MP4s to avoid re-conversion
  • Background Jobs: Queue MP4 conversions for async processing
  • Quality Options: Allow different MP4 quality levels
  • Alternative Formats: Support WebM or other video formats
  • Frame Rate: Consider variable frame rates from timing.json