diff options
| author | Vasile Vilvoiu <vasi@vilvoiu.ro> | 2021-07-15 22:53:30 +0300 |
|---|---|---|
| committer | Vasile Vilvoiu <vasi@vilvoiu.ro> | 2021-07-15 22:53:30 +0300 |
| commit | 82c81858c65c80fb667e73ffdcc4ff69007cfa17 (patch) | |
| tree | 455bdf7a85f99c770f97d05d53c8ce340c3f7fa0 | |
| parent | 3938ba31f633d48bb6f4471a3f49ab696a453aee (diff) | |
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.
| -rw-r--r-- | man/specgram.1 | 10 | ||||
| -rw-r--r-- | man/specgram.1.html | 17 | ||||
| -rw-r--r-- | man/specgram.1.pdf | bin | 36426 -> 37118 bytes | |||
| -rw-r--r-- | src/configuration.cpp | 13 | ||||
| -rw-r--r-- | src/configuration.hpp | 2 | ||||
| -rw-r--r-- | src/specgram.cpp | 93 |
6 files changed, 123 insertions, 12 deletions
diff --git a/man/specgram.1 b/man/specgram.1 index 8df7812..971af9d 100644 --- a/man/specgram.1 +++ b/man/specgram.1 @@ -56,6 +56,8 @@ See \fBEXAMPLES\fR for common use cases. .BR \fIoutfile\fR Optional output image file. Check \fISFML\fR documentation for supported file types, but PNG files are recommended. +If "\fB-\fR" is provided then the resulting image is written to stdout in PNG format. + Either \fIoutfile\fR must be specified, \fB\-l, \-\-live\fR must be set, or both. .TP @@ -75,7 +77,7 @@ Input file name. If option is provided, \fIINFILE\fR is handled as a raw dump of values (i.e. input file format is not considered). The program will stop when EOF is encountered. -If option is not provided, data will be read indefinitely from stdin. +If option is not provided or "\fB-\fR" is provided, data will be read indefinitely from stdin. .TP .BR \-r ", " \-\-rate =\fIRATE\fR @@ -325,6 +327,12 @@ Render a crisp output with a transparent background, so it can be embedded in a .IP \fBspecgram\fR -qe --bg-color=00000000 -i \fIinfile\fR \fIoutfile.png\fR +.LP +Generating from a file to stdout and displaying the output with \fBimagemagick\fR: + +.IP +\fBspecgram\fR -i \fIinfile\fR - | \fBdisplay\fR + .SH BUGS Frequency bounds (\fB\-x, \-\-fmin\fR and \fB\-y, \-\-fmax\fR) may exceed FFT window frequency limits when resampling is enabled (i.e. default behaviour), but may not do so when resampling is disabled (\fB\-q, \-\-no_resampling\fR). diff --git a/man/specgram.1.html b/man/specgram.1.html index 1559b4c..41cce87 100644 --- a/man/specgram.1.html +++ b/man/specgram.1.html @@ -1,5 +1,5 @@ <!-- Creator : groff version 1.22.4 --> -<!-- CreationDate: Thu Jul 15 15:26:09 2021 --> +<!-- CreationDate: Thu Jul 15 19:45:50 2021 --> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> @@ -128,6 +128,10 @@ forcefully quit.</p> Check <i>SFML</i> documentation for supported file types, but PNG files are recommended.</p> +<p style="margin-left:22%; margin-top: 1em">If +"<b>-</b>" is provided then the resulting image is +written to stdout in PNG format.</p> + <p style="margin-left:22%; margin-top: 1em">Either <i>outfile</i> must be specified, <b>−l, −−live</b> must be set, or both.</p> @@ -151,7 +155,8 @@ provided, <i>INFILE</i> is handled as a raw dump of values stop when EOF is encountered.</p> <p style="margin-left:22%; margin-top: 1em">If option is -not provided, data will be read indefinitely from stdin.</p> +not provided or "<b>-</b>" is provided, data will +be read indefinitely from stdin.</p> <p style="margin-left:11%;"><b>−r</b>, <b>−−rate</b>=<i>RATE</i></p> @@ -536,6 +541,14 @@ in a document:</p> <p style="margin-left:22%; margin-top: 1em"><b>specgram</b> -qe --bg-color=00000000 -i <i>infile outfile.png</i></p> +<p style="margin-left:11%; margin-top: 1em">Generating from +a file to stdout and displaying the output with +<b>imagemagick</b>:</p> + + +<p style="margin-left:22%; margin-top: 1em"><b>specgram</b> +-i <i>infile</i> - | <b>display</b></p> + <h2>BUGS <a name="BUGS"></a> </h2> diff --git a/man/specgram.1.pdf b/man/specgram.1.pdf Binary files differindex 7c6de03..4b9db2d 100644 --- a/man/specgram.1.pdf +++ b/man/specgram.1.pdf 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<std::string> input_filename_; std::optional<std::string> 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 <spdlog/spdlog.h> +#include <spdlog/sinks/stdout_color_sinks.h> #include <iostream> #include <iomanip> #include <fstream> #include <csignal> #include <list> +#include <random> +#include <cstdio> /* 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) { @@ -59,17 +67,77 @@ void print_real_window(const std::string& name, const RealWindow& window) } /* + * 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<int> distribution { 'a', 'z' }; + + std::string output(length, '\0'); + for(auto& ch : output) { + ch = static_cast<char>(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<InputReader> 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<SyncInputReader>(input_stream, input->GetDataTypeSize() * conf.GetBlockSize()); } else { + spdlog::info("Input: STDIN"); input_stream = &std::cin; reader = std::make_unique<AsyncInputReader>(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<const uint8_t *>(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"); } } |
