使用 fframes 製作影片
fframes 是一個 Rust 函式庫,可從程式碼渲染影片。影片是一個 Rust 結構體,帶有 render_frame(frame) -> Svgr 方法:對於每個影格,它回傳一個 SVG 樹,使用 svgr! 巨集(帶有 {rust 表達式} 的 SVG 標記)建構。場景分割時間軸,timeline! 和彈簧(spring)負責動畫數值,音訊地圖放置聲音,而 ffmpeg 則進行編碼。
每個專案都有一個命令列(來自 fframes::cli)。用它來處理所有事情:渲染影片、開啟即時預覽視窗,以及以檔案形式檢視影格、聯絡表和音量數值。在設計之前請閱讀 references/design.md,在編寫程式碼時請閱讀 references/api.md,音效相關請閱讀 references/audio.md,3D 鏡頭(fframes::three_d)請閱讀 references/3d.md。
當比對提供的影片時,請先遵循 references/design.md 中的參考分析指南。其剪輯、節奏和動作優先於下方設計預設值;快速的剪輯和故意的黑畫面可能是所要求結果的一部分。
在開始任何其他事情之前的兩條規則
在編寫程式碼之前先規劃
不要直接開啟模板並開始製作動畫。首先:
- 確定簡報內容:誰在看它以及在哪個平台觀看(平台決定格式和長度)、觀看者必須記住的一件事、調性,以及現有的素材(標誌、產品鏡頭、影片片段、音樂、旁白)。向使用者詢問缺少且重要的部分。切勿編造產品事實、數字或引言。
- 當影片配合音樂或語音剪輯時,請先選擇音效。其節奏、拍子和樂句決定了所有其他元素的時間安排。
- 首先規劃動態設計,將動態設計與聲音峰值同步,以形成一個獨特的流程:元素將從何處出現,它們將如何轉換。嘗試為每個場景創建一個獨特的動態設計,使其看起來獨特且精緻。影片不應該看起來像幻燈片。
- 嘗試推理觀看最終影片的人的觀感,並相應地調整您所做的每個設計元素:流程、節奏、拍子、動態和顏色。
不要有「模板痕跡」(Slop)
模板痕跡是一種由預設選擇組成的影片:它可能只更改了文字就能套用在任何產品上,而觀眾在一秒鐘內就能認出它。除非使用者要求,否則請勿提供:
- 模板遺留物:起始版面配置、顏色、字體或裝飾、佔位文字、假標誌、虛構數字。
- 節拍轟炸:整個影格在每一個拍子上縮放、搖晃或閃爍。將小東西(一條線條、一個單詞、一個火花)同步到節奏上,並且只在少數剪輯點才改變整個影格。
- 萬用動畫:每個元素都以相同的緩動曲線和持續時間向上淡入 40 像素。
- 靜態場景:一個東西出現,保持不動,然後淡出,而其他所有東西都不動。
- 通用文案:「推出」、「釋放」、「無縫」、「次世代」、三個詞的標語(「快速。簡單。強大。」)。用一般人會說的方式來撰寫螢幕上的文字。
- 一般性的文字:文字數量應有限且精準。
相反地,為每個元素賦予一個與主題相關的理由,並移除那些沒有理由的元素。將精力花在細節和動畫上,而不是文字上:次要動畫、交錯入場、繪製的線條、彼此反應的物體、跨剪輯攜帶的元素。在向使用者展示任何東西之前,先查看一個序列並問問自己,如果只更改文字,這個影片是否仍能套用在不同的產品上。如果可以,那就改變想法,而不是改變顏色。
1. 安裝
從 https://rustup.rs 安裝 Rust,然後安裝建構 ffmpeg 所需的系統函式庫:
# macOS
brew install pkg-config ffmpeg x264 x265 opus nasm ninja
# Debian / Ubuntu
sudo apt-get install -y yasm nasm ffmpeg libx264-dev libx265-dev libopus-dev libclang-dev clang ninja-build libvpx-dev libasound2-dev
# Arch
sudo pacman -S ninja yasm nasm ffmpeg x264 x265 opus clang
Windows 會連結預先建置的 FFmpeg 9 共享建置版本(設定 FFMPEG_DIR 指向從 BtbN/FFmpeg-Builds 解壓縮的 ffmpeg-n9.0-latest-win64-gpl-shared 建置版本,將其 bin 資料夾加入 PATH,使用 winget install LLVM.LLVM 安裝 LLVM,並設定 LIBCLANG_PATH)。詳細資訊請參閱 fframes README。
2. 建立專案
curl -fsSL https://raw.githubusercontent.com/dmtrKovalenko/fframes/main/scripts/new-video.sh | bash -s -- my-video --yes
# 或一次性安裝產生器並直接使用:
cargo install --locked cargo-fframes
cargo fframes new my-video --format landscape --fps 30 --yes
選項:--template single-scene|multi-scene、--format landscape|portrait|square|uhd、--fps、--title、--backend、--dir。此專案依賴於與已安裝 cargo-fframes 匹配的 fframes 版本;使用 --git 則會改用儲存庫的 main 分支。務必傳遞 --yes 以避免等待輸入。
模板:
single-scene(預設):單一場景。它適用於所有格式;從這裡開始製作單鏡頭短片、標題卡和直式影片。multi-scene:兩個場景。從這裡開始製作包含多個場景的任何內容;它只接受landscape和uhd格式。
模板內容是展示 API 的佔位程式碼。請將其版面配置、顏色、字體和裝飾替換為針對使用者要求影片所設計的內容。
使用 Skia GPU 後端(預設)。 cargo fframes new 會選擇 Metal(macOS)或 Vulkan(Linux、Windows)上的 Skia。其渲染速度比 CPU 後端快約 10 倍,並提供即時的 preview 視窗。在 macOS 和 Linux 上,首次建置會下載預先建置的 Skia 和 ffmpeg,並且只編譯 Rust 依賴項(約一分鐘;其他目標,或同時使用 metal 和 vulkan,則會從原始碼編譯 Skia,大約需要 20 分鐘)。立即啟動它並在建置過程中撰寫影片:
cd my-video && cargo build --release # 在背景執行,首次建置是最慢的
--backend cpu 不需要 Skia,但沒有預覽視窗;僅在沒有 GPU 時才選擇它。
專案渲染為產生的結構:
my-video/
src/lib.rs # 影片(來自模板)
src/main.rs # 命令列
media/ # 編譯進二進位檔的字型、圖片和音訊(一個起始字型)
README.md # 下方的命令
下方的每個命令都是 cargo run --release -- <command>。定義一個簡短的 shell 函式(變數如 R="cargo run --release --" 在 zsh(macOS shell)中無法正確分割單詞):
R() { cargo run --release -- "$@"; }
3. 開發迴圈
使用此技能時完全不要撰寫測試。 不要建立單元測試、整合測試、快照測試或視覺回歸測試、測試模組或測試框架。使用 inspect、渲染的影格、聯絡表、音訊檢查和原生預覽來驗證影片。
預覽,不要渲染。 如果影片使用 GPU 渲染後端且此機器上有 GPU,請執行原生預覽(或向使用者顯示命令),而不是在每次更改後再次渲染影片。一旦變更準備好審查:
- 在可複製的 shell 程式碼區塊中顯示完整的命令,使用實際的專案路徑和套件名稱;不要要求使用者定義
$R輔助函式。例如:cd /absolute/path/to/fframes && cargo run --release -p my-video -- preview 0s。 - 明確提供為使用者執行該預覽的選項。如果他們已授權開啟或重新整理預覽,則顯示命令並在該授權範圍內繼續。
在請求的變更之後,將命令的開始時間設定為變更區段當下或之前的特定秒數,並提供足夠的引導時間以查看其入場或轉換。例如,preview 8.5s 讓使用者無需重播開頭即可審查大約 9 秒處的變更。說明他們將看到哪個區段。當使用者請求最終匯出時,渲染完整影片;如果該請求已經明確,則不需要再確認。
在每次變更之後:
$R timeline列出場景及其影格和秒數範圍,以及每個音軌。在此先檢查結構和節奏,然後再檢查像素。$R inspect每 0.25 秒檢查一個影格,加上每個場景的第一個和最後一個影格。它會回報缺失的圖片、字型或字形、被畫布邊緣截斷的文字、無效的 SVG(零尺寸的矩形、錯誤的半徑)、損壞的變換和中斷(panic),每個都帶有其時間和場景。遇到錯誤時會以代碼 2 退出。修正它回報的所有問題。僅出現在場景前幾個影格的警告通常是入場動畫;使用 strip 檢查它。$R strip <scene or range> -n 12寫入一個聯絡表(strip.png):在一個帶有標籤的圖片中均勻間隔的影格。使用您的圖片工具開啟它。這是判斷版面配置、節奏和動作最快的方式。$R strip 2s,5s,9s將選定的時間並排放置。$R frame Intro@end,Outro@50%將全尺寸 PNG 寫入frames/以進行細節檢查(排版、對齊、對比度),並列印在這些影格中發現的問題。在銳利的縮放裁切上判斷小細節(元素之間的接觸、邊緣、小形狀),切勿在縮小的影格上判斷:$R frame 3s --crop 1100,520,240,120(影片像素,放大到大約 960 像素寬)。--ids會勾勒出每個具有id的元素並標示其名稱,以找出哪個形狀是哪個。$R onion "[email protected]@1s" -n 6將影格混合成一張圖片(onion.png)以顯示動作的路徑和間距:緩動、超調、交錯。$R preview Intro為使用者開啟一個帶有聲音的即時視窗(空白鍵播放/暫停、h/l 前後快進一秒、j/k 前後跳一個影格、q 離開)。它會阻塞直到關閉,因此在為使用者執行時請在背景啟動它。如上所述顯示命令並提供啟動它的選項;在編輯後,使用接近變更處的特定秒數。
當需要編碼樣本時,$R render Intro --draft 會以一半解析度編碼一個場景。$R render 會為請求的最終匯出寫入完整影片。
在展示完成的影片之前,執行 $R inspect --pops:它會以小尺寸渲染每個影格,並警告關於突然出現(pop)的部分,即畫面中在兩個靜止影格之間於一個影格內變化的部分,例如一個元素出現、消失或跳到另一個元素前面,而不是移動到那裡。每個警告都會給出 frame ... --crop ... 命令來查看它;相同區域的重複(計數器、閃爍的燈光)會被分組為一個警告。
規則:
- 在說任何東西看起來不錯之前,請先查看 PNG 圖片。
- 優先使用
strip而不是多次frame呼叫;當只需要構圖時,加上--scale 0.5。 - 在解析輸出時加上
--json:結果會輸出到標準輸出(stdout),進度則輸出到標準錯誤(stderr)。 - 保持使用
--release:偵錯建置版本的渲染速度會慢很多倍。
指定時間
120(影格)、3.2s、500ms、1:05.5、50%、start、end;按結構體名稱指定場景,Intro(也會匹配 IntroScene,不區分大小寫)、#3(場景索引)、Intro[1](該類型場景的第二個);在場景內部 [email protected]、Intro@12、Intro@50%、Intro@end。範圍:a..b(結尾不包含)、a..、..b、all,或使用場景名稱代表整個場景。用逗號分隔多個時間點。
所有命令
| 命令 | 用途 |
|---|---|
timeline | 查看場景、持續時間、音軌及其混音設定 |
inspect [RANGE] [--every 0.1s | --all-frames] [--fail-on warning] [--pops] | 在不渲染像素的情況下發現問題;--pops 也找出突然出現或消失的元素 |
strip [RANGE | TIMES] -n N [--columns 4] [--width 480] [--crop x,y,w,h] | 在一張圖片中審查流程和動作 |
frame TIMES [-o dir] [--svg] [--crop x,y,w,h] [--zoom N] [--ids] | 選定影格的全尺寸 PNG(以及佈局後的 SVG)、銳利裁切、標籤元素 |
onion RANGE -n N | 查看動作的軌跡和緩動曲線 |
svg TIME | 將給定影格的最終 SVG 以文字形式讀取(位置、文字、顏色) |
preview [TIME] [--paused] [--mute] | 為使用者提供帶有聲音的即時視窗 |
render [RANGE] [-o out.mp4] [--draft] | 編碼影片或其一部分(音訊也相應裁剪) |
audio analyze [RANGE] [--waveform w.png] | 音量(LUFS)、真實峰值、削波、靜音、按場景顯示 |
audio at TIMES | 在特定時刻播放哪些聲音,以及它們在檔案中的位置和音量 |
audio render [RANGE] -o a.wav | 將混音輸出為 WAV 檔案 |
瀏覽器編輯器
fframes 也有一個帶有時間軸和拖曳功能的網路編輯器,當有人想要以互動方式調整影片時非常有用。它執行編譯為 WebAssembly 的影片,需要 Node.js 和 wasm-pack;設定位於 fframes 儲存庫中 hello-world 範例的 editor/ 資料夾(https://github.com/dmtrKovalenko/fframes/tree/main/examples/hello-world)。當使用者要求 GUI 編輯器時提及它。用於觀看請使用 preview,用於您自己的審查請使用 CLI。
4. 撰寫影片
impl Video for MyVideo<'_> {
const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080;
fn duration(&self) -> Duration<'_> { Duration::Auto } // 場景時長總和
fn audio(&self) -> AudioMap<'_> { AudioMap::none() } // 參閱 references/audio.md
fn define_scenes(&self) -> Scenes<'_> { Scenes::from(vec![&self.intro as &dyn Scene, &self.main]) }
fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> {
fframes::svgr!(<svg xmlns="http://www.w3.org/2000/svg" width={Self::WIDTH} height={Self::HEIGHT}>
<rect width={Self::WIDTH} height={Self::HEIGHT} fill={BACKGROUND} />
{ctx.render_scenes(&frame)}
</svg>)
}
}
- 每個想法對應一個場景,每個場景 2-6 秒。在場景內部,
frame.seconds()從場景開始計時。 - 預設情況下,在
svgr!屬性中直接使用frame.animate(fframes::timeline!(...))來處理具有固定值的動畫。svgr!將這些動畫提升為快取的靜態變數。在時間軸中內聯定義緩動曲線,包括Easing::Spring { mass: 1.0, stiffness: 180.0, damping: 20.0 };命名常數是可選的。對於彈簧動畫,請省略其結束時間,以便其自然停止。
…
[內容因長度而被截斷]