Add a video export quality choice: Small file, Standard or High

A Quality dropdown under Video on Setup, --video-quality on the command
line, and "quality" in a project's video settings (written only when it is
not Standard). One table, videoQualities(), holds the ffmpeg settings per
codec; Standard is what every video was written with before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-26 19:41:00 +02:00
co-authored by Claude Opus 5.5
parent 1b30a079e6
commit 6df4bcb079
10 changed files with 160 additions and 10 deletions
+25
View File
@@ -2082,6 +2082,16 @@ int main() {
const std::string base = "{\"chain\": [{\"effect\": \"blur\"}], \"video\": {\"duration\": ";
check(!effector::projectFromJson(base + "2, \"preset\": \"cinema\"}}").video.preset.size(),
"an unknown preset reads as custom");
check(effector::projectToJson(project).find("\"quality\"") == std::string::npos,
"the standard quality is not written");
project.video.quality = "small";
check(effector::projectFromJson(effector::projectToJson(project)).video.quality == "small",
"another quality is kept");
check(effector::projectFromJson(base + "2, \"quality\": \"lossless\"}}").video.quality.empty(),
"an unknown quality reads as the standard one");
check(effector::videoQualityOrDefault("").key == effector::kDefaultVideoQuality &&
effector::videoQualityByKey(effector::kDefaultVideoQuality) != nullptr,
"an empty quality is the standard one, which the table holds");
bool rejected = false;
try {
effector::projectFromJson(base + "2, \"fps\": 500}}");
@@ -2106,6 +2116,9 @@ int main() {
check(!parsedOptions({"--fps", "0"}) && !parsedOptions({"--duration", "abc"}) &&
!parsedOptions({"--duration", "400"}) && !parsedOptions({"--video-preset", "vhs"}),
"a bad rate, length or preset is refused");
const auto quality = parsedOptions({"--duration", "2", "--video-quality", "high"});
check(quality && quality->video.quality == "high", "--video-quality picks the encoding");
check(!parsedOptions({"--video-quality", "best"}), "and refuses one it does not know");
check(!parsedOptions({"-o", "x.png"})->video.enabled, "without them the output is a still");
bool wrongName = false;
@@ -2128,6 +2141,18 @@ int main() {
}
check(std::filesystem::exists(file) && std::filesystem::file_size(file) > 0,
"ffmpeg writes an .mp4 of odd size");
// Every quality, through every codec.
bool everyQuality = true;
for (const auto& quality : effector::videoQualities()) {
for (const char* extension : {".mp4", ".webm", ".gif"}) {
const auto encoded = (dir / (quality.key + extension)).string();
effector::VideoWriter writer(encoded, 16, 16, 12, quality.key);
for (int i = 0; i < 2; ++i) writer.write(gradient(16, 16, 60 * i));
writer.finish();
everyQuality = everyQuality && std::filesystem::file_size(encoded) > 0;
}
}
check(everyQuality, "every quality writes .mp4, .webm and .gif");
const auto dropped = (dir / "dropped.webm").string();
{
effector::VideoWriter writer(dropped, 16, 16, 12);