---
phase: 42-wallpapers-social-format-conversion-voice
plan: 03
subsystem: api
tags: [sharp, ffmpeg-static, xlsx, csv-parse, file-type, convert-renderer, multipart, magic-byte, content-jobs, typescript]

# Dependency graph
requires:
  - phase: 42-wallpapers-social-format-conversion-voice
    plan: 01
    provides: ConvertBundle type, convert case in renderContent switch, converter-capabilities service
  - phase: 40-content-job-infra
    provides: contentJobStore, contentJobRunner, async job pattern
provides:
  - server/src/services/renderers/convert-renderer.ts — full format conversion router (sharp/ffmpeg/xlsx/AI-bridge)
  - server/src/routes/convert.ts — POST /api/companies/:companyId/convert (multipart, 202)
  - server/src/routes/convert.ts — GET /api/system/converters (capability map)
  - convert route mounted in app.ts
affects:
  - 42-06 (UI wiring — ConvertPanel calls these endpoints)

# Tech tracking
tech-stack:
  added: []
  patterns:
    - ffmpeg-static stdin/stdout pipe pattern for audio/video conversion
    - csv-parse/sync for synchronous CSV parsing with column headers
    - xlsx.read/write for XLSX ↔ JSON ↔ CSV round-trips
    - fileTypeFromBuffer magic-byte MIME validation with text-based extension allowlist
    - multer memoryStorage + runSingleFileUpload wrapper (follows chat-files.ts pattern)

key-files:
  created:
    - server/src/routes/convert.ts
  modified:
    - server/src/services/renderers/convert-renderer.ts
    - server/src/app.ts

key-decisions:
  - "ffmpegPath cast as unknown as string required — ffmpeg-static typings return string|null but binary is always present; matches documented Pitfall 3 from research"
  - "TEXT_BASED_EXTENSIONS allowlist prevents false 422 rejections for CSV/JSON/SVG which have no magic bytes"
  - "sourceMime resolved from extension map rather than multer claim to avoid browser MIME inconsistencies"
  - "StorageService passed through to convertRoutes for future asset storage but not used in job dispatch path (job runner handles storage)"

# Metrics
duration: 3min
completed: 2026-04-04
---

# Phase 42 Plan 03: Format Conversion Renderer and Multipart Route

**Full convert-renderer.ts routing sharp/ffmpeg/xlsx/AI-bridge by format pair, multipart upload route with magic-byte MIME validation returning 202, and GET /api/system/converters capability endpoint**

## Performance

- **Duration:** 3 min
- **Started:** 2026-04-04T22:12:21Z
- **Completed:** 2026-04-04T22:15:16Z
- **Tasks:** 2
- **Files modified:** 3 (1 replaced, 1 created, 1 modified)

## Accomplishments

- Replaced stub convert-renderer.ts with full implementation routing four format categories:
  - Image pairs (PNG/JPG/WebP/GIF/SVG) → sharp with density:300 for SVG input
  - Audio/video pairs → ffmpeg-static via stdin/stdout pipe (`ffmpegPath as unknown as string`)
  - Data pairs (CSV/JSON/XLSX) → csv-parse/sync + xlsx for all six conversion combinations
  - All other pairs → AI-bridge via puterChatComplete with system prompt
- Created convert.ts route with multer memoryStorage, fileTypeFromBuffer magic-byte validation, and 202 job dispatch
- Implemented MIME mismatch detection: returns 422 with `{ error, actualMime, claimedMime }` for misnamed binary files
- Text-based formats (CSV, JSON, SVG, TXT, HTML, MD, XML) allowed through without magic-byte check (they return null)
- Wired GET /api/system/converters endpoint returning converterCapabilitiesService result
- Mounted convertRoutes in app.ts after contentJobRoutes

## Task Commits

Each task was committed atomically:

1. **Task 1: Implement convert renderer with format routing** - `d5f7586d` (feat)
2. **Task 2: Create multipart convert route with MIME validation and wire to app.ts** - `84f97a43` (feat)

## Files Created/Modified

- `server/src/services/renderers/convert-renderer.ts` - Full implementation: isImageFormat/isAVFormat/isDataFormat helpers, convertImage/convertAV/convertData/convertViaAiBridge functions, renderConvert entry point
- `server/src/routes/convert.ts` - Multipart upload route: multer, fileTypeFromBuffer MIME validation, content job dispatch, converter capability endpoint
- `server/src/app.ts` - Import and mount convertRoutes after contentJobRoutes

## Decisions Made

- `ffmpegPath as unknown as string` cast used in spawn — ffmpeg-static typings declare `string | null` but the binary is always present when the package is installed; matches Pitfall 3 documented in 42-RESEARCH.md
- `TEXT_BASED_EXTENSIONS` allowlist prevents false 422 rejection for text-based formats whose `fileTypeFromBuffer` returns null (no magic bytes)
- Source MIME resolved from extension map rather than multer's `file.mimetype` to avoid browser-reported MIME inconsistencies (e.g. Chrome reports `text/csv` but Safari may report `application/csv`)

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

None — all conversion paths are fully implemented.

## Self-Check: PASSED

- `server/src/services/renderers/convert-renderer.ts` — FOUND
- `server/src/routes/convert.ts` — FOUND
- Task 1 commit `d5f7586d` — FOUND
- Task 2 commit `84f97a43` — FOUND
- `tsc --noEmit` — zero errors
