From 89762c1e1680adbec008620544f2415416d00667 Mon Sep 17 00:00:00 2001 From: sz Date: Fri, 27 May 2022 19:02:03 -0500 Subject: [PATCH 1/9] An idea --- src/exe/cimbar/cimbar.cpp | 104 ++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/src/exe/cimbar/cimbar.cpp b/src/exe/cimbar/cimbar.cpp index 8f8c91e1..1c98353d 100644 --- a/src/exe/cimbar/cimbar.cpp +++ b/src/exe/cimbar/cimbar.cpp @@ -19,11 +19,84 @@ using std::string; using std::vector; -int decode(const vector& infiles, const std::function& decode, bool no_deskew, bool undistort, int preprocess, bool color_correct) +namespace { + class stdinerator + { + public: + stdinerator(bool done=false) + : _done(done) + { + read_once(); + } + + void mark_done() + { + _done = true; + } + + void read_once() + { + if (!_done) + std::getline(std::cin, _current); + } + + std::string operator*() const + { + return _current; + } + + stdinerator& operator++() + { + read_once(); + return *this; + } + + bool operator==(const stdinerator& rhs) const + { + return _done and rhs._done; + } + + bool operator!=(const stdinerator& rhs) const + { + return !operator==(rhs); + } + + static stdinerator begin() + { + return stdinerator(); + } + + static stdinerator end() + { + return stdinerator(true); + } + + protected: + bool _done = false; + std::string _current; + }; + + struct StdineratorFactory + { + stdinerator begin() const + { + return stdinerator::begin(); + } + + stdinerator end() const + { + return stdinerator::end(); + } + }; +} + +template +int decode(const FileIterable& infiles, const std::function& decode, bool no_deskew, bool undistort, int preprocess, bool color_correct) { int err = 0; for (const string& inf : infiles) { + std::cout << "file is " << inf << std::endl; bool shouldPreprocess = (preprocess == 1); cv::UMat img = cv::imread(inf).getUMat(cv::ACCESS_RW); cv::cvtColor(img, img, cv::COLOR_BGR2RGB); @@ -73,25 +146,26 @@ int main(int argc, char** argv) unsigned compressionLevel = cimbar::Config::compression_level(); unsigned ecc = cimbar::Config::ecc_bytes(); options.add_options() - ("i,in", "Encoded pngs/jpgs/etc (for decode), or file to encode", cxxopts::value>()) - ("o,out", "Output file prefix (encoding) or directory (decoding).", cxxopts::value()) - ("c,color-bits", "Color bits. [0-3]", cxxopts::value()->default_value(turbo::str::str(colorBits))) - ("e,ecc", "ECC level", cxxopts::value()->default_value(turbo::str::str(ecc))) - ("f,fountain", "Attempt fountain encode/decode", cxxopts::value()) - ("z,compression", "Compression level. 0 == no compression.", cxxopts::value()->default_value(turbo::str::str(compressionLevel))) - ("color-correct", "Toggle decoding color correction. 1 == on. 0 == off.", cxxopts::value()->default_value("1")) - ("encode", "Run the encoder!", cxxopts::value()) - ("no-deskew", "Skip the deskew step -- treat input image as already extracted.", cxxopts::value()) - ("undistort", "Attempt undistort step -- useful if image distortion is significant.", cxxopts::value()) - ("preprocess", "Run sharpen filter on the input image. 1 == on. 0 == off. -1 == guess.", cxxopts::value()->default_value("-1")) - ("h,help", "Print usage") + ("i,in", "Encoded pngs/jpgs/etc (for decode), or file to encode", cxxopts::value>()) + ("o,out", "Output file prefix (encoding) or directory (decoding).", cxxopts::value()) + ("c,color-bits", "Color bits. [0-3]", cxxopts::value()->default_value(turbo::str::str(colorBits))) + ("e,ecc", "ECC level", cxxopts::value()->default_value(turbo::str::str(ecc))) + ("f,fountain", "Attempt fountain encode/decode", cxxopts::value()) + ("z,compression", "Compression level. 0 == no compression.", cxxopts::value()->default_value(turbo::str::str(compressionLevel))) + ("color-correct", "Toggle decoding color correction. 1 == on. 0 == off.", cxxopts::value()->default_value("1")) + ("encode", "Run the encoder!", cxxopts::value()) + ("no-deskew", "Skip the deskew step -- treat input image as already extracted.", cxxopts::value()) + ("undistort", "Attempt undistort step -- useful if image distortion is significant.", cxxopts::value()) + ("preprocess", "Run sharpen filter on the input image. 1 == on. 0 == off. -1 == guess.", cxxopts::value()->default_value("-1")) + ("h,help", "Print usage") ; options.show_positional_help(); options.parse_positional({"in"}); options.positional_help(""); auto result = options.parse(argc, argv); - if (result.count("help") or !result.count("out") or !result.count("in")) + bool hasInputs = result.count("in") or result.count("stdin"); + if (result.count("help") or !result.count("out") or !hasInputs) { std::cout << options.help() << std::endl; exit(0); @@ -139,7 +213,7 @@ int main(int argc, char** argv) // else -- default case, all bells and whistles fountain_decoder_sink> sink(outpath, chunkSize); - return decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); + return decode(StdineratorFactory(), fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); } // else From 074d813a751828bca5f1434a81451e73ca7e7b2c Mon Sep 17 00:00:00 2001 From: sz Date: Fri, 27 May 2022 19:23:36 -0500 Subject: [PATCH 2/9] Make fountain encoding the default. Add StdinLineReader for decodes. --- src/exe/cimbar/cimbar.cpp | 64 +++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/exe/cimbar/cimbar.cpp b/src/exe/cimbar/cimbar.cpp index 1c98353d..211c3317 100644 --- a/src/exe/cimbar/cimbar.cpp +++ b/src/exe/cimbar/cimbar.cpp @@ -76,7 +76,7 @@ namespace { std::string _current; }; - struct StdineratorFactory + struct StdinLineReader { stdinerator begin() const { @@ -96,7 +96,7 @@ int decode(const FileIterable& infiles, const std::function>()) ("o,out", "Output file prefix (encoding) or directory (decoding).", cxxopts::value()) + ("stdin", "Read input filenames from stdin.", cxxopts::value()) ("c,color-bits", "Color bits. [0-3]", cxxopts::value()->default_value(turbo::str::str(colorBits))) ("e,ecc", "ECC level", cxxopts::value()->default_value(turbo::str::str(ecc))) - ("f,fountain", "Attempt fountain encode/decode", cxxopts::value()) ("z,compression", "Compression level. 0 == no compression.", cxxopts::value()->default_value(turbo::str::str(compressionLevel))) ("color-correct", "Toggle decoding color correction. 1 == on. 0 == off.", cxxopts::value()->default_value("1")) ("encode", "Run the encoder!", cxxopts::value()) ("no-deskew", "Skip the deskew step -- treat input image as already extracted.", cxxopts::value()) + ("no-fountain", "Disable fountain encode/decode. Will also disable compression.", cxxopts::value()) ("undistort", "Attempt undistort step -- useful if image distortion is significant.", cxxopts::value()) ("preprocess", "Run sharpen filter on the input image. 1 == on. 0 == off. -1 == guess.", cxxopts::value()->default_value("-1")) ("h,help", "Print usage") @@ -164,18 +165,21 @@ int main(int argc, char** argv) options.positional_help(""); auto result = options.parse(argc, argv); - bool hasInputs = result.count("in") or result.count("stdin"); + bool useStdin = result.count("stdin"); + bool hasInputs = result.count("in") or useStdin; if (result.count("help") or !result.count("out") or !hasInputs) { - std::cout << options.help() << std::endl; + std::cerr << options.help() << std::endl; exit(0); } - vector infiles = result["in"].as>(); + vector infiles; + if (result.count("in")) + infiles = result["in"].as>(); string outpath = result["out"].as(); bool encode = result.count("encode"); - bool fountain = result.count("fountain"); + bool no_fountain = result.count("no-fountain"); colorBits = std::min(3, result["color-bits"].as()); compressionLevel = result["compression"].as(); @@ -186,10 +190,10 @@ int main(int argc, char** argv) Encoder en(ecc, cimbar::Config::symbol_bits(), colorBits); for (const string& f : infiles) { - if (fountain) - en.encode_fountain(f, outpath, compressionLevel); - else + if (no_fountain) en.encode(f, outpath); + else + en.encode_fountain(f, outpath, compressionLevel); } return 0; } @@ -202,24 +206,32 @@ int main(int argc, char** argv) Decoder d(ecc, colorBits); - if (fountain) + if (no_fountain) { - unsigned chunkSize = cimbar::Config::fountain_chunk_size(ecc); - if (compressionLevel <= 0) - { - fountain_decoder_sink sink(outpath, chunkSize); - return decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); - } + // simpler encoding, just the basics + ECC. No compression, fountain codes, etc. + std::ofstream f(outpath); + std::function fun = [&f, &d] (cv::UMat m, bool pre, bool cc) { + return d.decode(m, f, pre, cc); + }; + if (useStdin) + return decode(StdinLineReader(), fun, no_deskew, undistort, preprocess, color_correct); + else + return decode(infiles, fun, no_deskew, undistort, preprocess, color_correct); + } - // else -- default case, all bells and whistles - fountain_decoder_sink> sink(outpath, chunkSize); - return decode(StdineratorFactory(), fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); + // else, the good stuff + unsigned chunkSize = cimbar::Config::fountain_chunk_size(ecc); + if (compressionLevel <= 0) + { + fountain_decoder_sink sink(outpath, chunkSize); + return decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); } - // else - std::ofstream f(outpath); - std::function fun = [&f, &d] (cv::UMat m, bool pre, bool cc) { - return d.decode(m, f, pre, cc); - }; - return decode(infiles, fun, no_deskew, undistort, preprocess, color_correct); + // else -- default case, all bells and whistles + fountain_decoder_sink> sink(outpath, chunkSize); + + if (useStdin) + return decode(StdinLineReader(), fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); + else + return decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); } From aad249d4d9b0fd6963dc6a757208ace15b515d0e Mon Sep 17 00:00:00 2001 From: sz Date: Fri, 27 May 2022 19:42:07 -0500 Subject: [PATCH 3/9] Read filenames from stdin iff no inputs are provided, + ... ... output files to cwd if no output path is provided. --- src/exe/cimbar/cimbar.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/exe/cimbar/cimbar.cpp b/src/exe/cimbar/cimbar.cpp index 211c3317..310b5a50 100644 --- a/src/exe/cimbar/cimbar.cpp +++ b/src/exe/cimbar/cimbar.cpp @@ -12,6 +12,7 @@ #include "cxxopts/cxxopts.hpp" +#include #include #include #include @@ -96,7 +97,6 @@ int decode(const FileIterable& infiles, const std::function>()) ("o,out", "Output file prefix (encoding) or directory (decoding).", cxxopts::value()) - ("stdin", "Read input filenames from stdin.", cxxopts::value()) ("c,color-bits", "Color bits. [0-3]", cxxopts::value()->default_value(turbo::str::str(colorBits))) ("e,ecc", "ECC level", cxxopts::value()->default_value(turbo::str::str(ecc))) ("z,compression", "Compression level. 0 == no compression.", cxxopts::value()->default_value(turbo::str::str(compressionLevel))) @@ -165,18 +164,23 @@ int main(int argc, char** argv) options.positional_help(""); auto result = options.parse(argc, argv); - bool useStdin = result.count("stdin"); - bool hasInputs = result.count("in") or useStdin; - if (result.count("help") or !result.count("out") or !hasInputs) + if (result.count("help")) { - std::cerr << options.help() << std::endl; - exit(0); + std::cerr << options.help() << std::endl; + exit(0); } + string outpath = std::filesystem::current_path(); + if (result.count("out")) + outpath = result["out"].as(); + std::cerr << "Output files will appear in " << outpath << std::endl; + + bool useStdin = !result.count("in"); vector infiles; - if (result.count("in")) + if (useStdin) + std::cerr << "Enter input filenames:" << std::endl; + else infiles = result["in"].as>(); - string outpath = result["out"].as(); bool encode = result.count("encode"); bool no_fountain = result.count("no-fountain"); From 21cc6a79a032d31d7c02f8523ef92606993ab384 Mon Sep 17 00:00:00 2001 From: sz Date: Fri, 27 May 2022 19:56:24 -0500 Subject: [PATCH 4/9] :thinking: Not sure about dragging gcc7 along like this, but I'm already doing it for the unit tests. Will have to pull the plug sooner or later (maybe sooner) --- src/exe/cimbar/CMakeLists.txt | 1 + src/exe/cimbar/cimbar.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/exe/cimbar/CMakeLists.txt b/src/exe/cimbar/CMakeLists.txt index c1b3d1de..d15b563e 100644 --- a/src/exe/cimbar/CMakeLists.txt +++ b/src/exe/cimbar/CMakeLists.txt @@ -20,6 +20,7 @@ target_link_libraries(cimbar wirehair zstd ${OPENCV_LIBS} + ${CPPFILESYSTEM} ) add_custom_command( diff --git a/src/exe/cimbar/cimbar.cpp b/src/exe/cimbar/cimbar.cpp index 310b5a50..f03bd5df 100644 --- a/src/exe/cimbar/cimbar.cpp +++ b/src/exe/cimbar/cimbar.cpp @@ -12,7 +12,7 @@ #include "cxxopts/cxxopts.hpp" -#include +#include #include #include #include @@ -170,7 +170,7 @@ int main(int argc, char** argv) exit(0); } - string outpath = std::filesystem::current_path(); + string outpath = std::experimental::filesystem::current_path(); if (result.count("out")) outpath = result["out"].as(); std::cerr << "Output files will appear in " << outpath << std::endl; From 892579ae76120aa24ff55d833a54101a6cf70dc7 Mon Sep 17 00:00:00 2001 From: sz Date: Fri, 27 May 2022 23:44:04 -0500 Subject: [PATCH 5/9] Have the encoder be stdin-aware too --- src/exe/cimbar/cimbar.cpp | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/exe/cimbar/cimbar.cpp b/src/exe/cimbar/cimbar.cpp index f03bd5df..fe12ff0e 100644 --- a/src/exe/cimbar/cimbar.cpp +++ b/src/exe/cimbar/cimbar.cpp @@ -91,8 +91,22 @@ namespace { }; } -template -int decode(const FileIterable& infiles, const std::function& decode, bool no_deskew, bool undistort, int preprocess, bool color_correct) +template +int encode(const FilenameIterable& infiles, const std::string& outpath, int ecc, int color_bits, int compression_level, bool no_fountain) +{ + Encoder en(ecc, cimbar::Config::symbol_bits(), color_bits); + for (const string& f : infiles) + { + if (no_fountain) + en.encode(f, outpath); + else + en.encode_fountain(f, outpath, compression_level); + } + return 0; +} + +template +int decode(const FilenameIterable& infiles, const std::function& decode, bool no_deskew, bool undistort, int preprocess, bool color_correct) { int err = 0; for (const string& inf : infiles) @@ -182,24 +196,19 @@ int main(int argc, char** argv) else infiles = result["in"].as>(); - bool encode = result.count("encode"); + bool encodeFlag = result.count("encode"); bool no_fountain = result.count("no-fountain"); colorBits = std::min(3, result["color-bits"].as()); compressionLevel = result["compression"].as(); ecc = result["ecc"].as(); - if (encode) + if (encodeFlag) { - Encoder en(ecc, cimbar::Config::symbol_bits(), colorBits); - for (const string& f : infiles) - { - if (no_fountain) - en.encode(f, outpath); - else - en.encode_fountain(f, outpath, compressionLevel); - } - return 0; + if (useStdin) + return encode(StdinLineReader(), outpath, ecc, colorBits, compressionLevel, no_fountain); + else + return encode(infiles, outpath, ecc, colorBits, compressionLevel, no_fountain); } // else, decode From d646ec725d4168b26cac59b6fd083fbae825f0bf Mon Sep 17 00:00:00 2001 From: sz Date: Sun, 5 Jun 2022 19:27:30 -0500 Subject: [PATCH 6/9] Have fountain_decoder_sink optionally print its work... Also, have the StdinLineReader know how to deal with a closed stream. --- src/exe/cimbar/cimbar.cpp | 21 +++++++++++++++++---- src/lib/fountain/fountain_decoder_sink.h | 11 ++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/exe/cimbar/cimbar.cpp b/src/exe/cimbar/cimbar.cpp index fe12ff0e..a2827695 100644 --- a/src/exe/cimbar/cimbar.cpp +++ b/src/exe/cimbar/cimbar.cpp @@ -12,6 +12,7 @@ #include "cxxopts/cxxopts.hpp" +#include #include #include #include @@ -37,8 +38,16 @@ namespace { void read_once() { - if (!_done) - std::getline(std::cin, _current); + if (_done) + return; + + if (::feof(::stdin)) + { + mark_done(); + return; + } + + std::getline(std::cin, _current); } std::string operator*() const @@ -97,6 +106,8 @@ int encode(const FilenameIterable& infiles, const std::string& outpath, int ecc, Encoder en(ecc, cimbar::Config::symbol_bits(), color_bits); for (const string& f : infiles) { + if (f.empty()) + continue; if (no_fountain) en.encode(f, outpath); else @@ -111,6 +122,8 @@ int decode(const FilenameIterable& infiles, const std::function sink(outpath, chunkSize); + fountain_decoder_sink sink(outpath, chunkSize, true); return decode(infiles, fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); } // else -- default case, all bells and whistles - fountain_decoder_sink> sink(outpath, chunkSize); + fountain_decoder_sink> sink(outpath, chunkSize, true); if (useStdin) return decode(StdinLineReader(), fountain_decode_fun(sink, d), no_deskew, undistort, preprocess, color_correct); diff --git a/src/lib/fountain/fountain_decoder_sink.h b/src/lib/fountain/fountain_decoder_sink.h index 3fcb10dc..118a65fd 100644 --- a/src/lib/fountain/fountain_decoder_sink.h +++ b/src/lib/fountain/fountain_decoder_sink.h @@ -5,6 +5,7 @@ #include "FountainMetadata.h" #include "serialize/format.h" +#include #include #include #include @@ -15,9 +16,10 @@ template class fountain_decoder_sink { public: - fountain_decoder_sink(std::string data_dir, unsigned chunk_size) - : _dataDir(data_dir) - , _chunkSize(chunk_size) + fountain_decoder_sink(std::string data_dir, unsigned chunk_size, bool log_writes=false) + : _dataDir(data_dir) + , _chunkSize(chunk_size) + , _logWrites(log_writes) { } @@ -36,6 +38,8 @@ class fountain_decoder_sink std::string file_path = fmt::format("{}/{}", _dataDir, get_filename(md)); OUTSTREAM f(file_path); f.write((char*)data.data(), data.size()); + if (_logWrites) + printf("%s\n", file_path.c_str()); return true; } @@ -143,4 +147,5 @@ class fountain_decoder_sink std::unordered_map _streams; // track the uint32_t combo of (encode_id,size) to avoid redundant work std::set _done; + bool _logWrites; }; From 2ff867d7db7dee08451614b26829c3cd6b008d49 Mon Sep 17 00:00:00 2001 From: sz Date: Sun, 5 Jun 2022 19:53:51 -0500 Subject: [PATCH 7/9] Test cli? --- .github/workflows/ci.yml | 4 ++ test/py/__init__.py | 0 test/py/helpers.py | 16 +++++++ test/py/test_cimbar_cli.py | 88 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 test/py/__init__.py create mode 100644 test/py/helpers.py create mode 100644 test/py/test_cimbar_cli.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04301b75..b29fb7fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,10 @@ jobs: working-directory: ${{runner.workspace}}/build run: make test CTEST_OUTPUT_ON_FAILURE=TRUE + - name: Usage test + working-directory: ${{runner.workspace}}/libcimbar/test/py + run: python3 -m unittest + cppcheck: runs-on: ubuntu-18.04 diff --git a/test/py/__init__.py b/test/py/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/py/helpers.py b/test/py/helpers.py new file mode 100644 index 00000000..759f124f --- /dev/null +++ b/test/py/helpers.py @@ -0,0 +1,16 @@ +from os.path import join, realpath, dirname +from tempfile import TemporaryDirectory + +CIMBAR_SRC = realpath(join(dirname(realpath(__file__)), '..', '..')) +BIN_DIR = join(CIMBAR_SRC, 'dist', 'bin') + + +class TestDirMixin(): + def setUp(self): + self.working_dir = TemporaryDirectory() + super().setUp() + + def tearDown(self): + super().tearDown() + with self.working_dir: + pass \ No newline at end of file diff --git a/test/py/test_cimbar_cli.py b/test/py/test_cimbar_cli.py new file mode 100644 index 00000000..e8ad65e4 --- /dev/null +++ b/test/py/test_cimbar_cli.py @@ -0,0 +1,88 @@ +import subprocess +from os.path import join as path_join, getsize +from unittest import TestCase +from unittest.mock import patch + +from helpers import TestDirMixin, CIMBAR_SRC, BIN_DIR + + +CIMBAR_EXE = path_join(BIN_DIR, 'cimbar') + + +def _get_command(*args): + cmd = [CIMBAR_EXE] + for arg in args: + if isinstance(arg, str): + cmd += arg.split(' ') + else: + cmd += arg + return cmd + +# look in dist/bin/ for executables +# run them, confirm a few boring command results against samples(?) +# at the very least, confirm a roundtrip and cli options.... + + +class CimbarCliTest(TestDirMixin, TestCase): + def test_roundtrip_oneshot(self): + # encode + infile = path_join(CIMBAR_SRC, 'LICENSE') + outprefix = path_join(self.working_dir.name, 'img') + cmd = _get_command('--encode -i', infile, '-o', outprefix) + + res = subprocess.run(cmd, capture_output=True) + self.assertEqual(0, res.returncode) + + encoded_img = f'{outprefix}_0.png' + self.assertTrue(getsize(encoded_img) > 30000) + + # decode + cmd = _get_command('-i', encoded_img, '-o', self.working_dir.name) + res = subprocess.run(cmd, capture_output=True) + self.assertEqual(0, res.returncode) + + # filename in stdout + decoded_file = res.stdout.strip().decode('utf-8') + self.assertTrue(self.working_dir.name in decoded_file) + + with open(infile) as r: + expected = r.read() + with open(decoded_file) as r: + actual = r.read() + + self.assertEqual(expected, actual) + + def test_roundtrip_stdin(self): + # encode + infile = path_join(CIMBAR_SRC, 'LICENSE') + outprefix = path_join(self.working_dir.name, 'img') + cmd = _get_command('--encode -o', outprefix) + + res = subprocess.run( + cmd, input=infile.encode('utf-8'), capture_output=True + ) + + self.assertEqual(0, res.returncode) + + encoded_img = f'{outprefix}_0.png' + self.assertTrue(getsize(encoded_img) > 30000) + + # decode defaults to cwd -- which should be self.working_dir + cmd = _get_command() + res = subprocess.run( + cmd, input=encoded_img.encode('utf-8'), capture_output=True, + cwd=self.working_dir.name, + ) + self.assertEqual(0, res.returncode) + + # filename in stdout + decoded_file = res.stdout.strip().decode('utf-8') + self.assertTrue(self.working_dir.name in decoded_file) + + with open(infile) as r: + expected = r.read() + with open(decoded_file) as r: + actual = r.read() + + self.assertEqual(expected, actual) + From 21f993277e574a1c9bb7910176fc7db364d4f870 Mon Sep 17 00:00:00 2001 From: sz Date: Sun, 5 Jun 2022 20:29:40 -0500 Subject: [PATCH 8/9] A bit silly, but: py3.6 compatibility for now --- test/py/test_cimbar_cli.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/py/test_cimbar_cli.py b/test/py/test_cimbar_cli.py index e8ad65e4..f809f05a 100644 --- a/test/py/test_cimbar_cli.py +++ b/test/py/test_cimbar_cli.py @@ -1,4 +1,5 @@ import subprocess +from subprocess import PIPE from os.path import join as path_join, getsize from unittest import TestCase from unittest.mock import patch @@ -30,7 +31,7 @@ def test_roundtrip_oneshot(self): outprefix = path_join(self.working_dir.name, 'img') cmd = _get_command('--encode -i', infile, '-o', outprefix) - res = subprocess.run(cmd, capture_output=True) + res = subprocess.run(cmd, stdout=PIPE) self.assertEqual(0, res.returncode) encoded_img = f'{outprefix}_0.png' @@ -38,7 +39,7 @@ def test_roundtrip_oneshot(self): # decode cmd = _get_command('-i', encoded_img, '-o', self.working_dir.name) - res = subprocess.run(cmd, capture_output=True) + res = subprocess.run(cmd, stdout=PIPE) self.assertEqual(0, res.returncode) # filename in stdout @@ -59,7 +60,7 @@ def test_roundtrip_stdin(self): cmd = _get_command('--encode -o', outprefix) res = subprocess.run( - cmd, input=infile.encode('utf-8'), capture_output=True + cmd, input=infile.encode('utf-8'), stdout=PIPE ) self.assertEqual(0, res.returncode) @@ -70,7 +71,7 @@ def test_roundtrip_stdin(self): # decode defaults to cwd -- which should be self.working_dir cmd = _get_command() res = subprocess.run( - cmd, input=encoded_img.encode('utf-8'), capture_output=True, + cmd, input=encoded_img.encode('utf-8'), stdout=PIPE, cwd=self.working_dir.name, ) self.assertEqual(0, res.returncode) From d17dcf310b0316d3935449e0bb17b84d4f49441c Mon Sep 17 00:00:00 2001 From: sz Date: Sun, 5 Jun 2022 20:32:42 -0500 Subject: [PATCH 9/9] -f is now the default --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2dc9e664..bfb06101 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,17 @@ Encode: * large input files may fill up your disk with pngs! ``` -./cimbar --encode -i inputfile.txt -o outputprefix -f +./cimbar --encode -i inputfile.txt -o outputprefix ``` Decode (extracts file into output directory): ``` -./cimbar outputprefix*.png -o /tmp -f +./cimbar outputprefix*.png -o /tmp +``` + +Decode a series of encoded images from stdin: +``` +echo outputprefix*.png | ./cimbar -o /tmp ``` Encode and animate to window: