From 82c81858c65c80fb667e73ffdcc4ff69007cfa17 Mon Sep 17 00:00:00 2001 From: Vasile Vilvoiu Date: Thu, 15 Jul 2021 22:53:30 +0300 Subject: Support "-" for input/output files as stdin/stdout. Support dumping output file to stdout. Switch logger to stderr, make it multithreaded. Log input/output files. Closes #8, closes #10, closes #13. --- src/configuration.cpp | 13 +++++-- src/configuration.hpp | 2 ++ src/specgram.cpp | 93 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 99 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/configuration.cpp b/src/configuration.cpp index eeb4da4..abfecfa 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -18,6 +18,7 @@ Configuration::Configuration() { this->input_filename_ = {}; this->output_filename_ = {}; + this->dump_to_stdout_ = false; this->block_size_ = 256; this->rate_ = 44100; @@ -207,14 +208,20 @@ Configuration::FromArgs(int argc, char **argv) /* check and store command line arguments */ if (outfile) { - conf.output_filename_ = args::get(outfile); + if (args::get(outfile) != "-") { /* "-" denotes stdout */ + conf.output_filename_ = args::get(outfile); + } else { + conf.dump_to_stdout_ = true; + } } else if (!live) { - std::cerr << "Either specify file or '--live', otherwise nothing to do." << std::endl; + std::cerr << "Either specify output file name or '--live', otherwise nothing to do." << std::endl; return std::make_tuple(conf, 1, true); } if (infile) { - conf.input_filename_ = args::get(infile); + if (args::get(infile) != "-") { /* "-" denotes stdin */ + conf.input_filename_ = args::get(infile); + } } if (block_size) { if (args::get(block_size) <= 0) { diff --git a/src/configuration.hpp b/src/configuration.hpp index d7627ee..db3be73 100644 --- a/src/configuration.hpp +++ b/src/configuration.hpp @@ -21,6 +21,7 @@ class Configuration { private: std::optional input_filename_; std::optional output_filename_; + bool dump_to_stdout_; std::size_t block_size_; double rate_; @@ -76,6 +77,7 @@ public: Configuration GetForLive() const; const auto & GetInputFilename() const { return input_filename_; } const auto & GetOutputFilename() const { return output_filename_; } + const auto MustDumpToStdout() const { return dump_to_stdout_; } /* input getters */ auto GetBlockSize() const { return block_size_; } diff --git a/src/specgram.cpp b/src/specgram.cpp index f4a03e1..6e209af 100644 --- a/src/specgram.cpp +++ b/src/specgram.cpp @@ -14,15 +14,21 @@ #include "live.hpp" #include +#include #include #include #include #include #include +#include +#include /* main loop exit condition */ volatile bool main_loop_running = true; +/* temporary output file name */ +std::string temp_file_name = ""; + /* * SIGINT handler */ @@ -39,7 +45,8 @@ sigint_handler(int) /* * printing functions */ -void print_complex_window(const std::string& name, const ComplexWindow& window) +void +print_complex_window(const std::string& name, const ComplexWindow& window) { std::cout << name << ": ["; for (const auto& v : window) { @@ -49,7 +56,8 @@ void print_complex_window(const std::string& name, const ComplexWindow& window) std::cout << "]" << std::endl; } -void print_real_window(const std::string& name, const RealWindow& window) +void +print_real_window(const std::string& name, const RealWindow& window) { std::cout << name << ": ["; for (const auto& v : window) { @@ -58,18 +66,78 @@ void print_real_window(const std::string& name, const RealWindow& window) std::cout << "]" << std::endl; } +/* + * generate a random string (see https://stackoverflow.com/a/50556436) + */ +std::string +generate_random_string(std::size_t length) +{ + std::mt19937 generator { std::random_device{}() }; + std::uniform_int_distribution distribution { 'a', 'z' }; + + std::string output(length, '\0'); + for(auto& ch : output) { + ch = static_cast(distribution(generator) & 0xff); + } + + return output; +} + +/* + * dump image to stdout (in PNG format). This writes a temp file in /dev/shm since SFML cannot save to memory. + */ +void +dump_to_stdout(const sf::Image& image) +{ + static constexpr size_t TEMP_BUFFER_SIZE = 1024; + static constexpr size_t TEMP_FILENAME_LENGTH = 32; + + /* save */ + temp_file_name = "/dev/shm/" + generate_random_string(TEMP_FILENAME_LENGTH) + ".png"; + spdlog::info("Temporary file: {}", temp_file_name); + image.saveToFile(temp_file_name); + + /* from now on we have a leakable resource (the file); if using STDIN for input, we're here from a SIGINT, + * and we expect a SIGPIPE soon; install a handler that will clean up */ + std::signal(SIGPIPE, [](int) { std::remove(temp_file_name.c_str()); /* no logger, no checks */ std::exit(0); }); + + /* dump */ + std::ifstream file(temp_file_name, std::ios::in | std::ios::binary); + if (file.fail()) { + throw std::runtime_error("cannot read temp file " + temp_file_name); + } + while (!file.eof()) { + char buffer[TEMP_BUFFER_SIZE]; + file.read(buffer, TEMP_BUFFER_SIZE); + std::size_t read_count = file.gcount(); + std::cout.write(buffer, read_count); + } + file.close(); + + /* clean up */ + if (std::remove(temp_file_name.c_str()) != 0) { + spdlog::warn("Failed to delete temp file {}", temp_file_name); + } +} + /* * entry point */ int main(int argc, char** argv) { + /* set spdlog to STDERR and make it multithreaded */ + spdlog::set_default_logger(spdlog::stderr_color_mt("stderr")); + /* parse command line arguments into global settings */ auto [conf, conf_rc, conf_must_exit] = Configuration::FromArgs(argc, argv); if (conf_must_exit) { return conf_rc; } + /* decide whether we have output or not */ + bool have_output = conf.GetOutputFilename().has_value() || conf.MustDumpToStdout(); + /* create window function */ auto win_function = WindowFunction::FromType(conf.GetWindowFunction(), conf.GetFFTWidth()); @@ -108,6 +176,7 @@ main(int argc, char** argv) std::istream *input_stream = nullptr; std::unique_ptr reader = nullptr; if (conf.GetInputFilename().has_value()) { + spdlog::info("Input: {}", *conf.GetInputFilename()); input_stream = new std::ifstream(*conf.GetInputFilename(), std::ios::in | std::ios::binary); assert(input_stream != nullptr); if (!input_stream->good()) { @@ -117,6 +186,7 @@ main(int argc, char** argv) reader = std::make_unique(input_stream, input->GetDataTypeSize() * conf.GetBlockSize()); } else { + spdlog::info("Input: STDIN"); input_stream = &std::cin; reader = std::make_unique(input_stream, input->GetDataTypeSize() * conf.GetBlockSize()); @@ -229,7 +299,7 @@ main(int argc, char** argv) } /* add to history */ - if (conf.GetOutputFilename().has_value()) { + if (have_output) { history.push_back(colorized); } @@ -250,11 +320,12 @@ main(int argc, char** argv) } /* save file */ - if (conf.GetOutputFilename().has_value()) { + if (have_output) { Renderer file_renderer(conf, *color_map, *value_map, history.size()); file_renderer.RenderFFTArea(history); auto image = file_renderer.GetCanvas().copyToImage(); + /* rotate, if needed */ if (conf.IsHorizontal()) { std::size_t w = image.getSize().x; std::size_t h = image.getSize().y; @@ -276,9 +347,19 @@ main(int argc, char** argv) sf::Image rimage; rimage.create(h, w, reinterpret_cast(optr)); delete[] optr; - rimage.saveToFile(*conf.GetOutputFilename()); - } else { + + image = rimage; + } + + /* dump to file or stdout */ + if (conf.GetOutputFilename().has_value()) { + spdlog::info("Output: {}", *conf.GetOutputFilename()); image.saveToFile(*conf.GetOutputFilename()); + } else if (conf.MustDumpToStdout()) { + spdlog::info("Output: STDOUT"); + dump_to_stdout(image); + } else { + throw std::runtime_error("don't know what to do with output"); } } -- cgit v1.2.3