summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVasile Vilvoiu <vasi@vilvoiu.ro>2021-07-15 22:53:30 +0300
committerVasile Vilvoiu <vasi@vilvoiu.ro>2021-07-15 22:53:30 +0300
commit82c81858c65c80fb667e73ffdcc4ff69007cfa17 (patch)
tree455bdf7a85f99c770f97d05d53c8ce340c3f7fa0
parent3938ba31f633d48bb6f4471a3f49ab696a453aee (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.110
-rw-r--r--man/specgram.1.html17
-rw-r--r--man/specgram.1.pdfbin36426 -> 37118 bytes
-rw-r--r--src/configuration.cpp13
-rw-r--r--src/configuration.hpp2
-rw-r--r--src/specgram.cpp93
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
+&quot;<b>-</b>&quot; 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>&minus;l,
&minus;&minus;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 &quot;<b>-</b>&quot; is provided, data will
+be read indefinitely from stdin.</p>
<p style="margin-left:11%;"><b>&minus;r</b>,
<b>&minus;&minus;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
index 7c6de03..4b9db2d 100644
--- a/man/specgram.1.pdf
+++ b/man/specgram.1.pdf
Binary files differ
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");
}
}