Files
effector/src/main.cpp
T
SiandClaude Opus 5.5 b9f905e04c Give Effector a version and a changelog, shown from the menu
The version is the project() line of CMakeLists.txt and nowhere else:
CMake writes it, with the text of CHANGELOG.md, into a generated
core/version.hpp, so the number and the release notes the binary shows
are the ones it was built from. effector --version (-V) prints it, and
the header-bar menu gains a section with Changelog, a window laying the
Markdown out as headings and bullets, and About Effector.

The core tests hold the newest changelog entry to the current version,
so a bump cannot go in without its notes. Releases are tagged vX.Y.Z;
this is 0.5.0, and the changelog reconstructs 0.1.0-0.4.0 from the
history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:14:43 +02:00

101 lines
3.6 KiB
C++

#include <unistd.h>
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <mutex>
#include "cli/options.hpp"
#include "core/chain.hpp"
#include "core/effect.hpp"
#include "core/image.hpp"
#include "core/registry.hpp"
#include "core/rng.hpp"
#include "core/version.hpp"
#include "gui/app.hpp"
namespace {
int renderHeadless(const effector::Options& options) {
auto stages = effector::buildChain(options);
// A headless run always renders something, so with no -e the default
// effect stands in. The interface has no such need: it opens on an empty
// chain and waits for the user to add the first stage.
if (stages.empty()) {
const auto effect = effector::effectByName(effector::defaultEffectName());
stages.push_back({effect, effect->defaults()});
}
std::vector<effector::LoadFailure> failures;
// Decoding a batch of photographs takes seconds; on a terminal keep a
// single line updated so the wait is not silent. Redirected output stays
// clean, since the line would only clutter a log.
effector::LoadProgress progress;
std::mutex progressMutex;
const bool showProgress = isatty(fileno(stderr)) != 0 && options.images.size() > 1;
if (showProgress) {
progress.onFile = [&](std::size_t done, std::size_t total, const std::string& path) {
std::lock_guard<std::mutex> lock(progressMutex);
std::cerr << "\r\033[Kreading " << done << "/" << total << " "
<< std::filesystem::path(path).filename().string() << std::flush;
};
}
auto sources = effector::loadImages(options.images, failures, showProgress ? &progress : nullptr);
if (showProgress) std::cerr << "\r\033[K" << std::flush;
for (const auto& failure : failures) {
std::cerr << "effector: skipping " << failure.path << ": " << failure.message << "\n";
}
if (sources.empty() && effector::chainNeedsSources(stages)) {
std::cerr << "effector: no usable source images\n";
return 2;
}
const std::uint64_t seed = options.seed.value_or(effector::Rng::randomSeed());
effector::Rng rng(seed);
const auto start = std::chrono::steady_clock::now();
const auto image =
effector::renderChain(stages, sources, options.width, options.height, rng);
const auto renderMillis =
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
image.save(options.output);
std::cout << options.output << " " << options.width << "x" << options.height
<< " effect=" << effector::chainSlug(stages) << " seed=" << seed << " render="
<< std::fixed << std::setprecision(1) << renderMillis << " ms\n";
return 0;
}
} // namespace
int main(int argc, char** argv) {
try {
const auto options = effector::parseOptions(argc, argv);
if (options.showHelp) {
std::cout << effector::usage(argv[0]);
return 0;
}
if (options.showVersion) {
std::cout << "effector " << effector::kVersion << "\n";
return 0;
}
if (options.listEffects) {
for (const auto& effect : effector::effects()) {
std::cout << effect->name() << "\t" << effect->title() << " - "
<< effect->description() << "\n";
}
return 0;
}
if (!options.output.empty()) return renderHeadless(options);
return effector::runGui(options, argc, argv);
} catch (const std::exception& error) {
std::cerr << "effector: " << error.what() << "\n";
return 1;
}
}