makevideo

io.github.random00000000由 GitHub 帳號 random00000000 發布v0.6.0更新於 Oct 27, 2026

Narrated, captioned MP4 videos from Markdown or JSON. Local voice, CPU-only, no API key.

已驗證STDIO僅桌面媒體與設計AI 與機器學習

概覽

AI 產生的概覽

把 Markdown 或 JSON 腳本在本機算繪成帶旁白與字幕的 MP4 影片,語音在本地 CPU 上合成。

可以做什麼
  • 算繪影片:把 Markdown 或 JSON 規格轉成 MP4,附旁白、燒錄字幕、srt 字幕檔、封面畫格與報告。
  • 預覽與檢查:產生每個場景的縮圖拼版,並在算繪前檢查規格的版面、節奏與旁白問題。
  • 查詢用法:取得場景類型、畫面比例、主題、音色與轉場等參考資訊。
  • 設定風格:選擇橫式、直式短影音或方形比例,以及主題、轉場、生成配樂與紀錄片式配圖搜尋。
適用情境
當使用者要求解說影片、操作導覽、開發日誌、產品展示、預告片、回顧、更新說明影片或帶旁白的簡報時適用,可取代每次臨時拼湊的腳本流程。也適合沒有 GPU 與 API 金鑰的離線或沙箱環境。
執行需求
以本機 stdio 程序執行。README 說明可用 npx 搭配 Node 18+,沒有 Node 時可用 Docker 映像;ffmpeg、Skia 畫布與 Kokoro 語音模型隨套件提供或自動下載,可用 setup 指令預先下載語音模型。未宣告需要 API 金鑰或帳號。
安裝前請注意
算繪會在工作目錄寫入檔案(MP4、SRT、封面、GIF、報告),Docker 方式還會把目前目錄掛載進容器。紀錄片風格會從 Wikimedia Commons 下載圖片並快取在本機。README 提醒不要把他人影片的文字稿當成腳本重新發布。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 makevideo,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

makevideo

[GitHub release] [ci] [MCP Registry] [docs] [license: MIT] [Claude Code plugin] [CPU only] [no API key]

Fast, standard video generation for AI agents. One JSON script in, one finished MP4 out, rendered the same way every time. You write scenes, each with a line to say. makevideo voices them with one consistent neural voice, times every scene to the real audio, adds captions, and renders an MP4 with a bundled ffmpeg. It runs on the CPU only: no browser, no GPU, no API key and no cloud.

[poster]

▶ Your agent can make videos now (1:39, written, voiced and rendered by an agent from one JSON file) · ▶ Watch the 87-second showcase, rendered from examples/showcase.json on a laptop CPU, faster than it plays.

[TikTok-style demo]

Short-form too: "format": "shorts" gives 1080×1920 with big word-by-word captions. ▶ Watch the TikTok-style demo (examples/tiktok.json): 23 s of video rendered in 15 s, plus an auto-exported GIF.

More examples, all made with makevideo: ▶ What's new in 0.6: game trailers (spec) · ▶ Neon Drift game trailer (beat sheet) · ▶ What's new in 0.5: documentaries (spec) · ▶ 59-second demo (spec) · ▶ The house voice · ▶ Distribution recap · ▶ What's new in 0.4: closer to every agent (spec) · ▶ What's new in 0.3: speed (spec) · ▶ What's new in 0.2 (made from one Markdown file) · ▶ Why use makevideo instead of building from scratch (spec) · ▶ Changelog video for v0.1 (spec): changelog and recap videos are a core use case.

What it does: game trailers cut on the beat, with shots filmed from your Electron + Three.js game · narrated slides · automatic transitions (push, slide, zoom, wipe) · kinetic statements · animated charts · split media + text · write Markdown, get a video · screenshots · gameplay/video clips · GIFs in and out · code · stats · quotes · before/after · TikTok/Reels/Shorts captions · custom fonts · background music · screen recording for trailers · .srt captions · poster frames.

bash
npm i github:random00000000/makevideonpx makevideo init            # writes video.json  (or: init --style documentary | shorts | explainer)npx makevideo video.json      # → video.mp4 + video.srt + video.report.json + video.jpgnpx makevideo video.json --fast   # same, about 3x faster on a first render
js
import { video } from 'makevideo';
await video({  title: 'Patch notes 1.2',  scenes: [    { type: 'title',   title: 'Patch 1.2', subtitle: 'Siege update', say: 'Here is what is new in patch one point two.' },    { type: 'clip',    src: 'siege.mp4', title: 'Rams breach gates', say: 'Rams can now breach wooden gates.' },    { type: 'bullets', title: 'Also new', items: ['Faster pathing', 'New HUD'], say: 'Also new. Pathing is faster. And the HUD is redesigned.' },    { type: 'decisions', items: ['Ship Friday?'], say: 'One decision for you. Do we ship on Friday?' },  ],});

Why

Agents get asked for videos all the time, and every agent builds them differently. Each video ends up with a new pipeline, a different voice, no narration unless someone asks for it, and a render that can take ten minutes. makevideo makes that one standard:

ad-hoc agent videosmakevideo
Formata new script every timeone JSON spec (schema)
Voicerandom TTS, sometimes noneKokoro-82M, same voice every time (af_heart)
Narrationmust be requestedon by default: every scene has say
Timingguessedmeasured from the generated audio, per sentence
Captionsrarelyburned in, plus an .srt sidecar
Speedminutesfaster than real time on a CPU (see Performance)
Verificationwatch the whole thingmakevideo preview makes a contact sheet in about 1 s

Premium by default

The goal is that an agent's first attempt looks finished. Without any extra config you get:

  • Transitions that blend real frames. Every scene flows from the previous scene's actual last frame. auto picks push, zoom, wipe or fade based on what is cut to what. Override with "transition" globally or per scene.
  • Typography that ships with the package. Inter, Montserrat and JetBrains Mono (OFL) are bundled, so a bare cloud Linux box renders exactly like your laptop.
  • Depth: key-light gradient, vignette and soft panel shadows on every theme.
  • Speech cleanup: write naturally. 42%, v1.2, $3M, 4.6x, 35 s, e.g. and -> are all spoken correctly, and captions keep what you wrote.
  • Lint: check flags walls of text, too many bullets, unsynced lists, overlong statements, dense scenes in portrait and repetitive pacing, and says how to fix each one.

YouTube documentaries (history, science, dinosaurs)

One switch, style, sets the whole look and sound: documentary (history channels), history (adds a war-drum score), or nature (science and dinosaurs). Then write the script and say which images you want:

md
---title: Tyrannosaurus rex, the tyrant lizard kingstyle: naturethumbnailText: The real T. rex---
## Tyrannosaurus rex![Tyrannosaurus rex skeleton museum]()The tyrant lizard king> Sixty six million years ago, the largest predator in its world...
## Chapter: Built to kill![Tyrannosaurus rex skull]()> Chapter two. Built to kill.
## Timeline- 68 Mya: T. rex appears- 66 Mya: Asteroid impact> Its story fits on one line. It appears sixty eight million years ago. It vanishes when the asteroid hits.

What you get, with no other settings:

  • Image check: preview lists which file each search picked, and flags with "?" any pick whose title doesn't match the query (for example a different species). Agents can refine the search before rendering.
  • Real images, licensed for publishing. ![description]() (or "search": "..." in JSON) searches Wikimedia Commons. It keeps only publish-safe licenses (CC0, public domain, CC BY, CC BY-SA), never reuses an image within a video, caches downloads, and shortens over-specific searches until something matches. Each image gets a small credit line, and the full attributions go into the description.
  • Cinematic motion: slow Ken Burns on every still, varied by scene (in, out, left, right, up, down; set pan / focus to override), plus chapter cards (## Chapter: ...) and timelines.
  • A score: an original royalty-free music bed is synthesized for the video (music: documentary | epic | wonder | tension) and ducked under the narration.
  • Broadcast loudness: the final mix is normalized to −14 LUFS, YouTube's playback target.
  • A publish package: video.thumbnail.jpg (1280×720), and video.description.txt with YouTube chapter timestamps (0:00 first, each at least 10 s apart) and image credits.

▶ The T. rex example (script): 2 min 37 s, rendered from one Markdown file.

From a transcript: treat another creator's transcript as research, not as a script. Have the agent write its own narration. Republishing someone else's script is copyright infringement, and YouTube demonetizes it as "reused content".

Text on screen vs. narration (the cohesion rules)

The engine enforces these, and following them in the script makes videos land:

  • The screen shows keywords; the voice says sentences. Titles, bullets and labels are short ("Faster builds"); narration is the full thought ("Builds are now twice as fast").
  • Big statements use the narrator's own words. Statement words appear one by one as they are spoken, so put the same words in say. check warns when they don't match.
  • Captions never repeat the screen. Any caption whose words are already displayed is hidden on screen automatically (the .srt keeps every line). Use captions: "all" to turn this off.

Write Markdown, get a video

md
---theme: midnightbrand: My Game---# Patch 1.2The siege update> Here is what changed in patch one point two.
## Rams breach gates![](captures/ram.mp4)- Wooden gates break in 3 hits- Stone gates still hold> Rams now breach wooden gates. Three hits and they are down. Stone gates still hold.
## Load time<!-- highlight: Now -->- Before: 12 s- Now: 3 s
## Decisions- Ship Friday?
bash
npx makevideo patch-notes.md

Each #/## heading starts a scene, and its body decides the type: a list gives bullets/steps/decisions, label: number items give a chart, an image or video gives image/clip (or split, when there is text too), a code fence gives code, **42%** label gives a stat, and a paragraph gives a statement. Lines starting with > are the narration. When a scene has none, its visible text is read aloud, because narration is the default. <!-- key: value --> sets scene options. See examples/whats-new-0.2.md; the 0.2 release video is that one file.

Use it from Claude Code (plugin)

/plugin marketplace add random00000000/makevideo/plugin install makevideo@makevideo

Then just say "make a video about …". Claude writes the spec, previews it, renders it, and hands you the MP4.

The plugin bundles both the skill and the MCP server.

Use it from any MCP client

Claude Desktop, Cursor, VS Code, Codex, Windsurf and other MCP clients:

json
{ "mcpServers": { "makevideo": { "command": "npx", "args": ["-y", "github:random00000000/makevideo", "mcp"] } } }

It's listed in the official MCP Registry as io.github.random00000000/makevideo, as a Docker image (ghcr.io/random00000000/makevideo). Without Node, run docker run -i --rm -v "$PWD:/work" -w /work ghcr.io/random00000000/makevideo mcp.

The tools are make_video (Markdown or JSON in, MP4 out, with progress notifications), preview_video (returns the contact sheet as an image the model can look at), check_video and video_reference. See the MCP guide.

Teach every agent in a repo

bash
npx -y github:random00000000/makevideo agents --all     # AGENTS.md, CLAUDE.md, Cursor rules, Copilot instructionsnpx -y github:random00000000/makevideo agents --global  # this machine: Claude Code skill + Codex AGENTS.md

This writes a short, idempotent "use makevideo for videos" block, so every agent that works in the project reaches for the same tool.

Guides: agents · CPU text-to-video · Markdown to video · TikTok/Shorts · game trailers · MCP · vs Remotion/MoviePy/ffmpeg

For AI agents (start here)

If you are an LLM agent and the user asked for a video, explainer, walkthrough, devlog, demo, trailer, recap, patch notes or presentation with voice-over, use this library:

  1. Write a spec: a scenes array where every scene has a type and a say string (the narration).
  2. npx makevideo check spec.json --json validates it and estimates the length (instant).
  3. npx makevideo preview spec.json: open the PNG contact sheet and check the layout (about 1 s, no audio).
  4. npx makevideo spec.json --json renders and prints { output, srt, report, poster, duration, seconds }.
  5. Hand the user the .mp4. End informational videos with a decisions scene listing what only the user can decide.

Rules of thumb: about 1–3 sentences per scene. Write say the way it should be spoken: spell out numbers and symbols ("forty two percent", not "42%") and use pronounce for names. Put on-screen text in title/items; it does not have to match the narration word for word. Reuse the same voice and theme across a project so all its videos match.

There is also AGENTS.md, llms.txt, a JSON Schema at schema.json, and a drop-in Claude Code skill at skills/makevideo/SKILL.md. Copy that folder to ~/.claude/skills/ and Claude will reach for makevideo whenever you ask for a video.

Spec

jsonc
{  "title": "My video",              // used for default output name  "output": "out/my-video.mp4",     // default: <title-slug>.mp4 (relative to spec file)  "format": "landscape",            // landscape 1920x1080 | portrait/shorts 1080x1920 | square | 720p  "style": "nature",                // documentary | history | nature: whole look + score + YouTube package  "theme": "midnight",              // midnight | forge | paper | terminal | documentary | nature | { ...overrides }  "voice": "af_heart",              // the default house voice (warm female); "fast" = ~10x real-time Piper voice (male); false = silent  "speed": 1,                       // speech rate  "brand": "My Project",            // small label top-right  "captions": true,                 // burned-in captions (an .srt is always written)  "progressBar": true,              // chapter bar along the bottom  "music": "documentary",           // generated score: documentary | epic | wonder | tension, or a file; ducked under the voice  "musicVolume": 0.12,  "pronounce": { "Hacksilver": "Hack silver" },  // TTS-only substitutions (merged over built-ins for JSON, MP4, GPU, ...); captions keep originals  "quality": "standard",            // draft | standard | high  "transition": "auto",             // auto | push | slide | zoom | wipe | fade | dip | cut (also per scene)  "captionStyle": "classic",        // classic (subtitle box) | bold (TikTok word-by-word, default for portrait) | none  "font": "fonts/Anton.ttf",        // display font: a .ttf/.otf path or an installed family name  "gif": { "width": 480, "fps": 12, "duration": 8 },  // also export an animated GIF (true = defaults)  "scenes": [ /* see below */ ]}

Every scene accepts say (narration) and an optional duration (minimum seconds; required if there is no say). Scenes are as long as their narration plus a short pad.

typefieldsnotes
titletitle, subtitle?, kicker?opening card; outro is an alias for closers
bulletstitle?, items[], highlight?items appear in sync with sentences: N items + 1 intro sentence works best
stepstitle?, items[]numbered bullets
decisionstitle?, items[]numbered, for "what the user must decide"
imagesrc, title?, subtitle?, fit?, zoom?slow push-in; fit: "contain" keeps the whole image with a blurred backdrop
clipsrc, start?, title?, subtitle?, fit?any mp4/webm/mov; cropped to fill, looped if short; original audio dropped
codecode, title?, highlight? ([3, [5,8]])syntax-tinted, line numbers
statvalue, label?numbers count up ("42%", "$1.2", "3x")
quotetext, by?
comparetitle?, left: {title, items}, right: {title, items}stacks vertically in portrait
statementtext, kicker?, align?huge kinetic type; words pop in with the voice; *stars* mark accent words
chartbars: [{label, value}] (or {label: value}), title?, unit?, prefix?, highlight?, note?bars grow as the voice reaches them; values count up
chaptertitle, number?, subtitle?, src or searchfull-bleed image chapter card with a serif title (## Chapter: ... in Markdown)
timelineevents: [{when, label}], title?events light up as the voice reaches them (## Timeline + - When: what)
splitsrc (image or video), title?, items? or text?, side? (left/right)media beside synced points; video plays inside the frame

Scene type is inferred when omitted (src + items/text → split, src → image/clip, bars → chart, items → bullets, code → code, value → stat, text → statement).

CLI

makevideo <spec.json|spec.js>      render (mp4 + srt + json report + jpg poster)makevideo check <spec>             validate, estimate duration (instant)makevideo preview <spec>           contact-sheet PNG of every scene (about 1 s)makevideo init [file] [--style trailer]   starter spec / trailer beat sheetmakevideo shoot <trailer.md> [--all] [--only "Label"]   film every shot from your Electron gamemakevideo beats <song.mp3>          tempo, beats and sections of your trackmakevideo record <out.mp4> --seconds 10 [--window "Game"]   record screen/window for clip scenesmakevideo agents [--all|--global]   tell agents to use makevideo for videosmakevideo mcp                       MCP server (stdio)makevideo setup                     pre-download voice modelsmakevideo voices | themes | types | transitions | formats  -o <file>  --draft  --format <f>  --voice <id>  --theme <name>  --json

A .js spec can export default { ... } (or an async function returning the spec), which is handy for generating scenes from data.

API

js
import { video, check, preview, VOICES, THEMES, SCENE_TYPES } from 'makevideo';
const r = await video(spec, { baseDir, onProgress: (e) => console.log(e.stage, e.message) });// r = { output, srt, report, poster, duration, seconds, scenes: [{ index, type, title, start, duration, say }] }
check(spec)      // { ok, problems[], estimatedSeconds }await preview(spec) // { output: '...preview.png' }

Errors are written so an agent can act on them. They name the scene and include a working example:

makevideo: invalid spec  - scenes[2] (clip) is missing "src". Example: {"type":"clip","src":"gameplay.mp4","start":2,...}  - scenes[4] has no "say" (narration) and no "duration". Narration is the default in makevideo: ...

Game trailers: shot list → filmed shots → cut on the beat

[Neon Drift trailer]

▶ Watch the Neon Drift trailer (36 s). The sample Electron + Three.js game filmed all of its own shots from one beat sheet, and every cut lands on the music.

A trailer is a shot list, the shots, and an edit on the music. makevideo does all three:

bash
npx makevideo init --style trailer      # trailer.md: a beat sheet (shots, text slams, logo, Wishlist card)npx makevideo preview trailer.md        # storyboard on the beat grid + trailer lint (about 5 s)npx makevideo shoot trailer.md          # launches your Electron game and films every shotnpx makevideo trailer.md                # cuts it on the beat with a score, text, logo and end card

1. The shot list is a beat sheet. Each ## block is one beat of the edit, measured in music beats. A shot block says what to film. A lone line of text is a slam card. # is the logo. "Wishlist on Steam" is the end card.

md
---style: trailertitle: Neon Driftbpm: 110---
## Dogfight<!-- section: drop, beats: 4 -->```shotsetup: |  game.reset(); game.wave(20);camera: orbit hero 9 2 -40 40```FLY FAST
## UntilTHEN THEY CAME
# NEON DRIFTA neon arena shooter
## Wishlist on SteamComing 2027

2. The game films itself. You add two lines to the game:

js
// Electron main processrequire('makevideo/electron').attach(win);// renderer (your Three.js code)window.makevideo = { renderer, scene, camera };

makevideo shoot starts the game, runs each shot's setup code, and moves the camera (orbit, push, dolly, fly, follow, hold, around any named object). It then records the canvas at 1920×1080 on virtual time, frame by frame. Footage is perfectly smooth at 30/60 fps even when your machine renders the game at 20 fps, and the HUD is never in the shot. Shots that need the game's own audio can use mode: realtime.

Instant replay: while you play, press F8 to save the last 15 seconds to makevideo-clips/, for the moments you can't script.

3. The edit is built on the music.

  • Every scene lasts a whole number of beats, and every cut lands within half a frame of its beat.
  • Cuts that land on a hit flash.
  • The generated score follows your sections (intro → build → drop → breakdown → finale → outro), with braams, drums and risers. You can also bring your own track: makevideo beats song.mp3 finds the tempo, beats and sections, and bpm + musicOffset make the sync exact.
  • Clips play at their speed (or ramp: 1>0.3), and start: auto picks a clip's most active window.
  • Each clip's game audio sits under the music, and the master is normalized to −14 LUFS.

Trailer lint applies rules from how good Steam trailers are cut:

  • gameplay before 5 s
  • logo after gameplay, never before it
  • 30–90 s long
  • end on a call to action
  • text slams of 5 words or fewer
  • no three text cards in a row
  • clips that would loop
  • shots that run past the music

Screen recordings

When the game isn't Electron, or you just want raw footage:

bash
npx makevideo record captures/siege.mp4 --seconds 12 --window "Hacksilver"   # one window (Windows)npx makevideo record captures/menu.mp4 --seconds 8                           # whole screen (Win/macOS/Linux)

Then use ![](captures/siege.mp4) in the beat sheet.

Performance

makevideo is built to run on a CPU, because most agent sandboxes and cloud runners have no GPU.

Need it fast? Add --fast (or "voice": "fast"). That switches to a Piper voice that speaks at about 10× real time on 2 CPU threads. Kokoro, the default, is about 2× real time using every core. The fast voice sounds less natural, so it's best for drafts, long videos and fast iteration.

bash
npx makevideo video.md --fast   # fastest: Piper voice + quick encodenpx makevideo setup             # once per machine/image: pre-download voice models

Measured with the same 74 s, 8-scene video (examples/whats-new-0.2.md) on an i7-10750H laptop (6 cores, no GPU), with the machine about 35% busy with other work. Both versions ran back to back:

v0.2.0v0.3.0
first render (no cached speech)119.9 s81.0 s
first render with --fast voicen/a36.3 s
re-render (speech cached)41.0 s28.4 s

How it gets there:

  • Frame holding. Slides are still most of the time. Only frames that can change are drawn. Each scene type knows exactly how long its entrance lasts, so a typical scene has about 30% "live" frames instead of 50%. Held frames are repeated inside ffmpeg after colour conversion, where they cost almost nothing.
  • Render processes. Scenes are drawn and encoded in a pool of worker processes (one canvas and one ffmpeg each), so drawing uses many cores instead of Node's one main thread. The workers start while speech is still being generated.
  • Tuned speech. Kokoro runs as 1–3 processes with ONNX threads pinned to cores/2/N, because processes that each grab every core fight each other. Idle ONNX threads sleep instead of spinning, leaving the CPU to the renderer.
  • Overlapped pipeline. Each scene renders as soon as its own narration exists. The poster, GIF and final mux run side by side.
  • Caching. Every sentence's audio is cached by (engine, voice, speed, text) in ~/.cache/makevideo (override with MAKEVIDEO_CACHE). Re-renders after a layout tweak skip the voice entirely.
  • Measured, not guessed. Each of these came from a profile. Some ideas lost: film grain (pattern fills cost 80 ms per frame), transparent content layers (slower in Skia), and more x264 threads (2 per scene beats 4).

Knobs: --fast, quality: "draft", format: "720p", MAKEVIDEO_THREADS (x264 threads per scene, default 2), video(spec, { concurrency, ttsWorkers, ttsThreads }), and MAKEVIDEO_DEBUG=1 for per-scene profiles.

How it works

spec ─► validate ─► split narration into sentences ─► Kokoro TTS pool (cached)                                     │ per scene, as soon as its audio is ready                                     ▼            measure audio → scene duration, caption cues, reveal times                                     ▼       Skia canvas draws only frames that change ─► ffmpeg (loop holds, x264)                                     ▼        concat segments + narration WAV (+ music) ─► .mp4  .srt  .json  .jpg

Requirements

Node 18+. Everything else installs from npm: ffmpeg-static ships ffmpeg, @napi-rs/canvas ships prebuilt Skia, and kokoro-js runs the voice locally through ONNX Runtime. CI renders test videos on Linux, macOS and Windows (Node 20 and 22) on every push.

Credits & license

MIT. The voice is Kokoro-82M (Apache-2.0) via kokoro-js. Video is encoded with FFmpeg (via ffmpeg-static).

Keywords: AI video generator, text to video, make a video, narrated video, fast video generation, voiceover, TTS video, explainer video generator, programmatic video, Node.js video library, ffmpeg video from JSON, slideshow with narration, auto captions, subtitles, SRT, agent tools, Claude Code video, Codex video, LLM video generation, devlog video, patch notes video, gameplay trailer narration, Remotion alternative without a browser.

來源:README.md,提交 a3a21b3

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

4
  1. v0.6.0最新Oct 16, 2026
  2. v0.5.2Oct 15, 2026
  3. v0.5.1Oct 15, 2026
  4. v0.4.1Oct 14, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分