尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證The UI for the Media Manipulator web app
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗The free, no‑signup web app for converting, compressing, editing, transcribing, and cleaning up image, video, and audio files — including Content Studio, a multi‑track video editor that runs right in your browser.
This repository is the frontend for media-manipulator.com: a Next.js (App Router) application that renders the marketing/SEO surface as static HTML and hosts the interactive conversion and editing tools as client‑side islands. Heavy media processing happens on a dedicated backend; this app orchestrates uploads, job polling, and downloads, and presents everything behind a fast, crawlable, ad‑safe UI.
TOOL_PAGES in content/toolPages.ts: 112 pages, of which 14 are thin alias pages for duplicate‑intent slugs that canonicalise to their primary, content/toolAliases.ts) across image, video, audio, AI, and privacy/metadata categories, each with a unique landing page. The 24 allowlisted and pending pages meet the MM84 copy bar (content/toolCopy.test.ts).i18next / react-i18next with feature‑sharded locale bundles.npm run audit:ads) blocks regressions.| Category | Examples | Notes |
|---|---|---|
| Image | Convert (JPG/PNG/WebP/AVIF/GIF/SVG/ICO), compress, resize, crop, remove background, image→PDF | Server‑side ImageMagick; AI background removal on a GPU server |
| Video | Convert (MP4/MOV/WebM/MKV/AVI…), compress, trim/cut, crop, rotate, →GIF, extract/remove audio, transcode to HLS/DASH | FFmpeg pipeline; large files use presigned S3 uploads |
| Audio | Convert (MP3/WAV/AAC…), waveform generator, isolate vocals | AI stem separation on a GPU server |
| AI | Transcribe video/audio, SRT generator, caption translator, AI frame interpolation | Models run on a local GPU server we operate |
| AI Video Restoration | Restore/upscale a ≤15s snippet with up to six models (Real‑ESRGAN, SwinIR, HAT, BasicVSR++, RVRT, VRT) and download one comparison package | /tools/ai-video-restoration; snippet selector + multi‑model pipeline on our GPU server |
| Privacy | Remove EXIF/GPS/IPTC/XMP metadata | Strips location & device data before sharing |
| Content Studio | Multi‑track editor → MP4 | See Content Studio |
Plus content sections: Tutorials (getting‑started guides + AI frame interpolation + Content Studio) and a Blog (compression, optimization, and audio‑quality guides).
The frontend is a thin, fast orchestration layer. It never does heavy media work itself — it requests uploads, tracks jobs, and serves results, while the backend API does the actual transcoding/AI.
flowchart LR
U([User]) -->|HTML / RSC| FE["Next.js App Router<br/>(this repo, on Vercel)"]
FE -->|"static pages, metadata, JSON-LD"| U
FE -->|"upload / poll / download (REST)"| API["Media API<br/>api.media-manipulator.com"]
FE -->|"large files (PUT)"| S3[("Object storage<br/>presigned S3")]
API --> S3
API -->|"FFmpeg / ImageMagick"| WORK["Processing workers"]
API -->|"transcribe, interpolate,<br/>bg-removal, vocal isolation"| GPU["Local GPU server"]
FE -. "consent-gated" .-> AN["Analytics<br/>first-party (MM Analytics) · slim GA4 cross-check"]
Rendering model
"use client") power anything interactive: file inputs, drag‑and‑drop, the converter forms, and the Content Studio editor. Browser‑only/editor surfaces are loaded with next/dynamic({ ssr: false }) so they never run during prerender — yet the SEO copy around them is always server‑rendered.lib/metadata.ts) and server‑rendered JSON‑LD (components/seo/json-ld.tsx) — never mutated on the client.sequenceDiagram
actor User
participant UI as Web UI
participant API as Media API
User->>UI: Drop a file + pick options
UI->>API: POST /upload (multipart) + options
API-->>UI: { jobId }
loop every 2s until done
UI->>API: GET /job/{jobId}
API-->>UI: { status, progress }
end
UI->>API: GET /download/{jobId}
API-->>UI: Converted file (Blob)
UI-->>User: Preview + download
sequenceDiagram
actor User
participant UI as Web UI
participant API as Media API
participant S3 as Object storage
User->>UI: Drop a video + pick options
Note over UI: phase: requesting-url
UI->>API: POST /video-upload/presign
API-->>UI: { uploadUrl, key }
Note over UI: phase: uploading-to-s3
UI->>S3: PUT file (direct upload)
Note over UI: phase: finalizing
UI->>API: POST /video-upload/complete (key + options)
API-->>UI: { jobId }
loop every 2s
UI->>API: GET /job/{jobId}
API-->>UI: { status, progress }
end
UI->>API: GET /download/{jobId}
UI-->>User: Result
The same upload→poll→download lifecycle powers convert, transcribe, and transcode (HLS/DASH) workflows — they differ only in the options payload and how the result is rendered (file download, transcript view, or streaming manifest).
flowchart TD
drop([File dropped / selected]) --> detect{Detect media kind}
detect -->|image| img[Image conversion form]
detect -->|video| vmode{Workflow}
detect -->|audio| amode{Workflow}
detect -->|pdf| pdf[PDF tools]
vmode -->|convert| vconv[Video conversion form]
vmode -->|transcribe| vtr[Transcribe form]
vmode -->|transcode| vtc[HLS/DASH transcode form]
amode -->|convert| aconv[Audio conversion form]
amode -->|transcribe| atr[Transcribe form]
Content Studio (/tools/content-studio) is the flagship tool: a browser‑based, multi‑track video editor.
flowchart LR
new([Create project<br/>resolution + fps]) --> bin[Import media<br/>video / image / audio]
bin --> tl[Multi-track timeline]
tl --> edit[Trim · split · ripple-delete]
edit --> layer[Layer overlays · titles · audio mix]
layer --> prev[Live preview<br/>canvas compositing]
prev --> exp[[Export → MP4<br/>H.264 / AAC]]
This app is built to pass Google AdSense review cleanly:
NEXT_PUBLIC_ADSENSE_ENABLED=true and NODE_ENV=production and the page/placement/slot pass the guards in lib/adsenseConfig.ts./tools/<slug> pages, placed after substantial copy and far from action buttons. No header/footer/sidebar/anchor/post‑conversion ads, no side rails, no sticky units.data-ad-slot.components/mock-ad.tsx) are local‑preview‑only and never contact Google.content/reviewAllowlist.ts) drives which tools are indexed, listed, sitemapped, and ad‑eligible. The homepage and review pages never link into non‑allowlisted tools.components/ads/adsense-loader.tsx loads Google's script from the layout on ad‑eligible pages (isAdEligiblePath), after the Consent Mode defaults (all ad signals denied); units show to anonymous and free visitors only.npm run audit:ads fails the build on any regression (placeholder leakage, ad script outside the config/loader, disallowed placements, macOS artifacts, the loader or a unit on ineligible pages, an ads‑on build with no loader + slot on the tool pages, the home/tool pages linking to non‑allowlisted tools, /tools or home linking a non‑allowlisted, non‑pending tool without rel="nofollow" (MM84), or any page linking an alias slug from content/toolAliases.ts). It reads .next/server/app/en.html + en/**/*.html and fails if a build exists but no English HTML is found.app/sitemap.ts) + crawler‑friendly app/robots.ts, with canonical origin https://www.media-manipulator.com..
├── app/ # Next.js App Router (routes, layout, sitemap, robots)
│ ├── tools/ # /tools, /tools/[slug], /tools/content-studio
│ ├── blog/ · tutorials/ # content routes (server-rendered)
│ ├── layout.tsx # root layout, GA consent bootstrap, fonts
│ ├── providers.tsx # client providers + route analytics + shell
│ ├── sitemap.ts · robots.ts
│ └── globals.css # Tailwind v4 + theme tokens + custom CSS
├── components/ # UI components
│ ├── content-studio/ # the multi-track editor
│ ├── tools/ # server-rendered tool landing page + panel island
│ ├── seo/ # JSON-LD renderer
│ ├── ui/ # Radix-based primitives
│ ├── ad-banner.tsx · mock-ad.tsx
│ └── top-nav.tsx · footer.tsx · ...
├── views/ # Page-level client views (home, about, blog, tutorials…)
├── content/ # toolPages.ts (tool data), reviewAllowlist.ts, keywordMap.ts
├── lib/ # hooks + logic: upload/convert/transcode, firebase (lazy),
│ # analytics, adsenseConfig, adSlots, seo, metadata, studio…
├── i18n/ # i18next setup + sharded en-US locale bundles
├── schemas/ # zod schemas for conversion options
├── scripts/ # audit-adsense.mjs, clean-appledouble.mjs
├── docs/ # adsense review plan + slot inventory
└── public/ # fonts, icons, ads.txt, og image
Prerequisites: Node.js ≥ 20 and npm.
# 1. Install dependencies
npm ci
# 2. Configure environment
cp .env.example .env.local # then fill in values (see below)
# 3. Run the dev server
npm run dev # http://localhost:3000
The app builds and runs without Firebase credentials — Firebase initializes lazily and only in the browser, so public pages prerender fine with blank
NEXT_PUBLIC_FB_*values. Accounts/auth simply stay disabled until configured.
All browser‑exposed variables use the NEXT_PUBLIC_ prefix. See .env.example for the full template.
| Variable | Purpose | Default if unset |
|---|---|---|
NEXT_PUBLIC_API_BASE_URL | Media API base URL | https://api.media-manipulator.com/api |
NEXT_PUBLIC_ANALYTICS_BASE_URL | First‑party analytics endpoint | https://analytics.media-manipulator.com |
NEXT_PUBLIC_ANALYTICS_WRITE_KEY | Required. Public site key for /v1/capture; must match a WRITE_KEYS entry on the analytics service. Not a secret — it ships in the bundle. | — (capture returns 401) |
NEXT_PUBLIC_GA_MEASUREMENT_ID | GA4 measurement ID. Slim cross‑check only (3 events). | G-6J910CMHRY |
NEXT_PUBLIC_FB_* | Firebase web config (auth only) | — (auth disabled if blank) |
NEXT_PUBLIC_ADSENSE_ENABLED | Enable real AdSense (prod only) | false |
NEXT_PUBLIC_MOCK_ADS_ENABLED | Show first‑party mock ads (dev only) | false |
Set only as branch-scoped Vercel Preview variables on a translate/<prefix> branch, never on Production. scripts/vercel-translator-env.sh sets them for all 17 branches from your shell (run --dry-run first; it reads the secrets from your environment and never prints them). The design is in ../docs/design/TRANSLATOR_WORKBENCH.md; the setup checklist is MM55.
| Variable | Scope | Purpose |
|---|---|---|
NEXT_PUBLIC_DEPLOY_ROLE | build | translator turns on translator mode (hardening; app/api/translator/* answer 404 without it) |
NEXT_PUBLIC_TRANSLATOR_LOCALE | build | The edited locale, e.g. de-DE (node scripts/list-locales.mjs --codes) |
TRANSLATOR_EMAILS | server | Comma-separated allowlist (trimmed, case-insensitive; empty = nobody) |
GITHUB_APP_ID | server | The "Media Manipulator Translator" GitHub App id |
GITHUB_APP_INSTALLATION_ID | server | Its installation on this repo |
GITHUB_APP_PRIVATE_KEY | server, sensitive | The App's private key PEM, base64-encoded |
GITHUB_REPO | server | mrrobotisreal/media_manipulator_ui |
GITHUB_BASE_BRANCH | server | main (PR base and conflict reference) |
NEXT_PUBLIC_FB_PROJECT_ID | build | Already set for auth; also the issuer/audience for ID-token checks |
GitHub Actions secrets for .github/workflows/sync-translate-branches.yml: VERCEL_TOKEN (a Vercel access token), VERCEL_PROJECT_ID, and VERCEL_TEAM_ID when the project belongs to a team. The workflow creates each translate/<prefix> Preview deployment through POST /v13/deployments, because Vercel does not build branches pushed by a GitHub Actions bot and Deploy Hooks are capped at 5 per project. Missing secrets are a notice, not a failure. TRANSLATE_SYNC_TOKEN is optional: a fine-grained token with Contents + Workflows write, used only if GitHub refuses a GITHUB_TOKEN push of commits that change workflow files.
Translator routes (Node runtime, Cache-Control: no-store, 404 off-role): GET /api/translator/me, GET /api/translator/base, POST /api/translator/submit[?dryRun=1]. The contract is in lib/translator/submitContract.ts and lib/translator/server/handlers.ts.
| Script | What it does |
|---|---|
npm run dev | Start the Next dev server |
npm run build | Production build (static generation) |
npm run start | Serve the production build |
npm run lint | ESLint |
npm run typecheck | tsc --noEmit |
npm run audit:ads | AdSense‑review safety audit (run after build for link checks) |
npm run clean:macos-artifacts | Remove stray .DS_Store / ._* files |
Pre‑push checklist: npm run typecheck && npm run lint && npm run build && npm run audit:ads — all should pass.
Deployed on Vercel (zero‑config Next.js). Set the NEXT_PUBLIC_* variables in the Vercel project settings; for the AdSense review keep NEXT_PUBLIC_ADSENSE_ENABLED=false and NEXT_PUBLIC_MOCK_ADS_ENABLED=false. ads.txt, the sitemap, and robots are served automatically.
Translations live in i18n/locales/<locale>/<namespace>/*.json, sharded by feature (interface, forms, panels, pages, components, accessibility, error). useLocalization() wraps react-i18next and adds locale‑aware date/number/file‑size/duration formatters. Resources are bundled inline so server‑prerendered HTML contains real translated text (good for SEO). Add a language by adding its row to i18n/locales.ts, copying i18n/locales/en-us/ to i18n/locales/<code-lowercased>/ (without the English-only interface/metadata.json) and running npm run i18n:gen. Only English is bundled into client JS; other locales reach the client through the [lang] layout. node scripts/list-locales.mjs prints the non-English prefixes for scripts and workflows.
Uploads are processed on our own servers and are designed to be automatically deleted within 24 hours. AI features run on a GPU server we operate; files are not shared with third‑party AI providers. No account is required to use the tools. Analytics are first-party (our own MM Analytics service) with a slim GA4 cross-check, and are consent‑gated — Google Consent Mode v2 mirrors our own consent state rather than the other way round. Mixpanel and Firebase Analytics were removed in Part 1.
This project is released under a proprietary license — see LICENSE. The source is published for demonstration and portfolio review; commercial use, redistribution, and hosting are not permitted without prior written permission.
Author: Mitchell Wintrow Email: [email protected] Website: https://www.media-manipulator.com
© 2026 Media Manipulator, a CreaTV Ltd. product. All rights reserved.