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>
101 lines
3.6 KiB
C++
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;
|
|
}
|
|
}
|