From f02f43e4b5ad54b1eceb4b8587a011379e6f3c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 22 Feb 2016 15:02:00 +0100 Subject: [PATCH 01/87] added experimental C code generator --- pysstv/examples/codegen.py | 109 +++++++++++++++++++++++++++++++++++++ pysstv/examples/codeman.c | 28 ++++++++++ 2 files changed, 137 insertions(+) create mode 100644 pysstv/examples/codegen.py create mode 100644 pysstv/examples/codeman.c diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py new file mode 100644 index 0000000..9063175 --- /dev/null +++ b/pysstv/examples/codegen.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +class Image(object): + def __init__(self, content): + self.content = content + + def load(self): + return self + + def __getitem__(self, item): + if isinstance(item, tuple): + x, y = item + return Image('{0}[(ROW({1}) + COL({2})) * 3'.format(self.content, y, x)) + elif isinstance(item, int): + return Image('{0} + RGB({1})]'.format(self.content, item)) + else: + raise NotImplemented + + def __rmul__(self, n): + return Image('({1} * {0})'.format(self.content, float(n))) + + def __mul__(self, n): + return Image('({0} * {1})'.format(self.content, float(n))) + + def __rtruediv__(self, n): + return Image('({1} / {0})'.format(self.content, n)) + + def __truediv__(self, n): + return Image('({0} / {1})'.format(self.content, n)) + + def __radd__(self, n): + return Image('({1} + {0})'.format(self.content, n)) + + def __add__(self, n): + return Image('({0} + {1})'.format(self.content, n)) + + def __str__(self): + return self.content + +from pysstv.color import MartinM1 +import re + +ROW_RE = re.compile(r'ROW\(\d+\)') + +def main(): + sstv = MartinM1(Image('img'), 44100, 16) + n = 0 + print '#define ROW(x) x' + print '#define COL(x) x' + print '#define RGB(x) (2 - (x))' + print 'void convert(unsigned char *img, float *freqs, float *msecs) {\nint frq = 0;' + history = [] + lut = {} + same_as = {} + for freq, msec in sstv.gen_freq_bits(): + printed = 'freqs[frq] = {1}; msecs[frq++] = {2};'.format(n, freq, msec) + key = ROW_RE.sub('row', printed) + old = lut.get(key) + if old is not None: + same_as[n] = old + else: + lut[key] = n + history.append((printed, key)) + n += 1 + del lut + mgen = iter(gen_matches(same_as, history, n)) + m_start, m_len, m_end = next(mgen) + for i in xrange(same_as[m_start]): + print history[i][0] + print 'for (int row = {0}; row >= 0; row -= {1}) {{'.format( + (sstv.HEIGHT - 1) * sstv.WIDTH, sstv.WIDTH) + for i in xrange(same_as[m_start], same_as[m_start] + m_len - 1): + print ' ', history[i][1] + print '}' + print '}}\n\n#define FREQ_COUNT {0}'.format(n) + + + +def gen_matches(same_as, history, n): + cur_start = None + cur_len = None + cur_end = None + matches = [] + for i in xrange(n): + if cur_start is None: + tmp = same_as.get(i) + if tmp is not None: + cur_len = 1 + cur_start = i + cur_end = tmp + else: + tmp = same_as.get(i) + if tmp is not None and history[tmp][1] == history[cur_end + 1][1] and cur_start > cur_end: + cur_len += 1 + cur_end += 1 + else: + if tmp is not None and history[tmp][1] == history[cur_end + 1][1]: + yield cur_start, cur_len, cur_end + tmp = same_as.get(i) + if tmp is None: + cur_start = None + else: + cur_len = 1 + cur_start = i + cur_end = tmp + + +if __name__ == '__main__': + main() diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c new file mode 100644 index 0000000..fde06a5 --- /dev/null +++ b/pysstv/examples/codeman.c @@ -0,0 +1,28 @@ +#include +#include + +#include "codegen.c" + +void main() { + uint32_t offset, size; + FILE *f = fopen("320x256rgb.bmp", "r"); + fseek(f, 0x02, SEEK_SET); + fread(&size, 4, 1, f); + fseek(f, 0x0A, SEEK_SET); + fread(&offset, 4, 1, f); + fseek(f, offset, SEEK_SET); + + unsigned char img[size]; + + fread(img, size - offset, 1, f); + fclose(f); + + float freqs[FREQ_COUNT], msecs[FREQ_COUNT]; + + convert(img, freqs, msecs); + + for (int i = 0; i < FREQ_COUNT; i++) { + fwrite(&(freqs[i]), 4, 1, stdout); + fwrite(&(msecs[i]), 4, 1, stdout); + } +} From 2f6c7ca871a70142da454441e32986f937c3109e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 22 Feb 2016 17:34:21 +0100 Subject: [PATCH 02/87] codegen: use configurable SSTV subclass --- pysstv/examples/codegen.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 9063175..cb04130 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -40,10 +40,15 @@ class Image(object): from pysstv.color import MartinM1 import re +supported = [MartinM1] ROW_RE = re.compile(r'ROW\(\d+\)') -def main(): - sstv = MartinM1(Image('img'), 44100, 16) +def main(sstv_class=None): + if sstv_class is None: + sstv_class = MartinM1 + elif sstv_class not in supported: + raise NotImplementedError() + sstv = sstv_class(Image('img'), 44100, 16) n = 0 print '#define ROW(x) x' print '#define COL(x) x' From 6418a9febcbcaae2b0da5ed3da51e58955950d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 22 Feb 2016 17:34:44 +0100 Subject: [PATCH 03/87] codegen: provide generator instead of printing --- pysstv/examples/codegen.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index cb04130..817e1e3 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -50,10 +50,10 @@ def main(sstv_class=None): raise NotImplementedError() sstv = sstv_class(Image('img'), 44100, 16) n = 0 - print '#define ROW(x) x' - print '#define COL(x) x' - print '#define RGB(x) (2 - (x))' - print 'void convert(unsigned char *img, float *freqs, float *msecs) {\nint frq = 0;' + yield '#define ROW(x) x' + yield '#define COL(x) x' + yield '#define RGB(x) (2 - (x))' + yield 'void convert(unsigned char *img, float *freqs, float *msecs) {\nint frq = 0;' history = [] lut = {} same_as = {} @@ -71,13 +71,13 @@ def main(sstv_class=None): mgen = iter(gen_matches(same_as, history, n)) m_start, m_len, m_end = next(mgen) for i in xrange(same_as[m_start]): - print history[i][0] - print 'for (int row = {0}; row >= 0; row -= {1}) {{'.format( + yield history[i][0] + yield 'for (int row = {0}; row >= 0; row -= {1}) {{'.format( (sstv.HEIGHT - 1) * sstv.WIDTH, sstv.WIDTH) for i in xrange(same_as[m_start], same_as[m_start] + m_len - 1): - print ' ', history[i][1] - print '}' - print '}}\n\n#define FREQ_COUNT {0}'.format(n) + yield ' ' + history[i][1] + yield '}' + yield '}}\n\n#define FREQ_COUNT {0}'.format(n) From d7a05fc1d5e7e630b5c8c1be32564a63992e4e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 22 Feb 2016 17:35:09 +0100 Subject: [PATCH 04/87] codegen: return 0 in stub main() --- pysstv/examples/codeman.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c index fde06a5..1074261 100644 --- a/pysstv/examples/codeman.c +++ b/pysstv/examples/codeman.c @@ -3,7 +3,7 @@ #include "codegen.c" -void main() { +int main() { uint32_t offset, size; FILE *f = fopen("320x256rgb.bmp", "r"); fseek(f, 0x02, SEEK_SET); @@ -25,4 +25,6 @@ void main() { fwrite(&(freqs[i]), 4, 1, stdout); fwrite(&(msecs[i]), 4, 1, stdout); } + + return 0; } From 3c3bb34b1b12fdcfeb84bc34fd806d06b8b13d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 22 Feb 2016 17:35:25 +0100 Subject: [PATCH 05/87] codegen: added simple test suite --- pysstv/examples/codegen.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 817e1e3..68fb9cd 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -109,6 +109,31 @@ def gen_matches(same_as, history, n): cur_start = i cur_end = tmp +def test(): + from subprocess import Popen, PIPE, check_output + from os import remove, path + from PIL import Image + import struct + exe = './codegen-test-executable' + try: + for sstv_class in supported: + print 'Testing', sstv_class + gcc = Popen(['gcc', '-xc', '-o', exe, '-'], stdin=PIPE) + with open(path.join(path.dirname(__file__), 'codeman.c')) as cm: + gcc.communicate(cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class)))) + gen = check_output([exe]) + img = Image.open("320x256rgb.png") + sstv = sstv_class(img, 44100, 16) + for n, (freq, msec) in enumerate(sstv.gen_freq_bits()): + assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec) + print 'OK' + finally: + remove(exe) + if __name__ == '__main__': - main() + from sys import argv + if len(argv) > 1 and argv[1] == 'test': + test() + else: + print '\n'.join(main()) From f295a07c8309ed39f11d5855ed68ff27b7621c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 23 Feb 2016 16:17:53 +0100 Subject: [PATCH 06/87] codegen: removed unused variable --- pysstv/examples/codegen.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 68fb9cd..2e82854 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -85,7 +85,6 @@ def gen_matches(same_as, history, n): cur_start = None cur_len = None cur_end = None - matches = [] for i in xrange(n): if cur_start is None: tmp = same_as.get(i) From c185ea571b9f6368c0d3e47a0bc93f508d58ac13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 23 Feb 2016 16:21:30 +0100 Subject: [PATCH 07/87] codegen: use NotImplementedError --- pysstv/examples/codegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 2e82854..9f4eeee 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -14,7 +14,7 @@ class Image(object): elif isinstance(item, int): return Image('{0} + RGB({1})]'.format(self.content, item)) else: - raise NotImplemented + raise NotImplementedError() def __rmul__(self, n): return Image('({1} * {0})'.format(self.content, float(n))) From ff2fc58aa7385ce8942dc42824eb76ab617d9eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 23 Feb 2016 16:21:39 +0100 Subject: [PATCH 08/87] codegen: optimized gen_matches invocation --- pysstv/examples/codegen.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 9f4eeee..d17a86f 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -68,8 +68,7 @@ def main(sstv_class=None): history.append((printed, key)) n += 1 del lut - mgen = iter(gen_matches(same_as, history, n)) - m_start, m_len, m_end = next(mgen) + m_start, m_len = gen_matches(same_as, history, n) for i in xrange(same_as[m_start]): yield history[i][0] yield 'for (int row = {0}; row >= 0; row -= {1}) {{'.format( @@ -99,7 +98,7 @@ def gen_matches(same_as, history, n): cur_end += 1 else: if tmp is not None and history[tmp][1] == history[cur_end + 1][1]: - yield cur_start, cur_len, cur_end + return cur_start, cur_len tmp = same_as.get(i) if tmp is None: cur_start = None From 43d1c53514427756195837ad5e2c59e4b280bf49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 23 Feb 2016 16:36:32 +0100 Subject: [PATCH 09/87] codegen: added timing stats --- pysstv/examples/codegen.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index d17a86f..37db03e 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -111,19 +111,30 @@ def test(): from subprocess import Popen, PIPE, check_output from os import remove, path from PIL import Image + from datetime import datetime import struct exe = './codegen-test-executable' try: for sstv_class in supported: print 'Testing', sstv_class gcc = Popen(['gcc', '-xc', '-o', exe, '-'], stdin=PIPE) + start = datetime.now() with open(path.join(path.dirname(__file__), 'codeman.c')) as cm: gcc.communicate(cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class)))) + gen_elapsed = datetime.now() - start + print ' - gengcc took', gen_elapsed + start = datetime.now() gen = check_output([exe]) + native_elapsed = datetime.now() - start + print ' - native took', native_elapsed img = Image.open("320x256rgb.png") sstv = sstv_class(img, 44100, 16) + start = datetime.now() for n, (freq, msec) in enumerate(sstv.gen_freq_bits()): assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec) + python_elapsed = datetime.now() - start + print ' - python took', python_elapsed + print ' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds() print 'OK' finally: remove(exe) From ec6ff85d06737466fc4902a6b2bf4511c15f41fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 19 Apr 2016 11:01:17 +0200 Subject: [PATCH 10/87] codegen: avoid masking exceptions if exe doesn't exist yet --- pysstv/examples/codegen.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 37db03e..de83e5e 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -137,7 +137,10 @@ def test(): print ' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds() print 'OK' finally: - remove(exe) + try: + remove(exe) + except OSError: + pass if __name__ == '__main__': From dee321d59e819460314c402e026a7bc4b6992491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 19 Apr 2016 11:13:57 +0200 Subject: [PATCH 11/87] codegen: replaced BMP with RGB array and STBI --- .gitignore | 1 + pysstv/examples/codegen.py | 11 +++++++---- pysstv/examples/codeman.c | 18 +++++------------- 3 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7a3c3e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +stb_image.h diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index de83e5e..54faab8 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -52,7 +52,7 @@ def main(sstv_class=None): n = 0 yield '#define ROW(x) x' yield '#define COL(x) x' - yield '#define RGB(x) (2 - (x))' + yield '#define RGB(x) x' yield 'void convert(unsigned char *img, float *freqs, float *msecs) {\nint frq = 0;' history = [] lut = {} @@ -71,8 +71,8 @@ def main(sstv_class=None): m_start, m_len = gen_matches(same_as, history, n) for i in xrange(same_as[m_start]): yield history[i][0] - yield 'for (int row = {0}; row >= 0; row -= {1}) {{'.format( - (sstv.HEIGHT - 1) * sstv.WIDTH, sstv.WIDTH) + yield 'for (int row = 0; row < {0}; row += {1}) {{'.format( + sstv.HEIGHT * sstv.WIDTH, sstv.WIDTH) for i in xrange(same_as[m_start], same_as[m_start] + m_len - 1): yield ' ' + history[i][1] yield '}' @@ -114,10 +114,13 @@ def test(): from datetime import datetime import struct exe = './codegen-test-executable' + if not path.exists('stb_image.h'): + from urllib import urlretrieve + urlretrieve('https://raw.githubusercontent.com/nothings/stb/master/stb_image.h', 'stb_image.h') try: for sstv_class in supported: print 'Testing', sstv_class - gcc = Popen(['gcc', '-xc', '-o', exe, '-'], stdin=PIPE) + gcc = Popen(['gcc', '-xc', '-lm', '-o', exe, '-'], stdin=PIPE) start = datetime.now() with open(path.join(path.dirname(__file__), 'codeman.c')) as cm: gcc.communicate(cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class)))) diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c index 1074261..0461f76 100644 --- a/pysstv/examples/codeman.c +++ b/pysstv/examples/codeman.c @@ -1,21 +1,13 @@ -#include #include +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#include "stb_image.h" #include "codegen.c" int main() { - uint32_t offset, size; - FILE *f = fopen("320x256rgb.bmp", "r"); - fseek(f, 0x02, SEEK_SET); - fread(&size, 4, 1, f); - fseek(f, 0x0A, SEEK_SET); - fread(&offset, 4, 1, f); - fseek(f, offset, SEEK_SET); - - unsigned char img[size]; - - fread(img, size - offset, 1, f); - fclose(f); + int x, y, n; + unsigned char *img = stbi_load("320x256rgb.png", &x, &y, &n, 0); float freqs[FREQ_COUNT], msecs[FREQ_COUNT]; From d540abd852f412a3144e0e29fc9466f1841cc91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 19 Apr 2016 11:20:14 +0200 Subject: [PATCH 12/87] codegen: pass real image width as parameter --- pysstv/examples/codegen.py | 5 ++--- pysstv/examples/codeman.c | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 54faab8..a298099 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -53,7 +53,7 @@ def main(sstv_class=None): yield '#define ROW(x) x' yield '#define COL(x) x' yield '#define RGB(x) x' - yield 'void convert(unsigned char *img, float *freqs, float *msecs) {\nint frq = 0;' + yield 'void convert(unsigned char *img, float *freqs, float *msecs, const int width) {\nint frq = 0;' history = [] lut = {} same_as = {} @@ -71,8 +71,7 @@ def main(sstv_class=None): m_start, m_len = gen_matches(same_as, history, n) for i in xrange(same_as[m_start]): yield history[i][0] - yield 'for (int row = 0; row < {0}; row += {1}) {{'.format( - sstv.HEIGHT * sstv.WIDTH, sstv.WIDTH) + yield 'for (int row = 0; row < width * {0}; row += width) {{'.format(sstv.HEIGHT) for i in xrange(same_as[m_start], same_as[m_start] + m_len - 1): yield ' ' + history[i][1] yield '}' diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c index 0461f76..fce6b05 100644 --- a/pysstv/examples/codeman.c +++ b/pysstv/examples/codeman.c @@ -11,7 +11,7 @@ int main() { float freqs[FREQ_COUNT], msecs[FREQ_COUNT]; - convert(img, freqs, msecs); + convert(img, freqs, msecs, x); for (int i = 0; i < FREQ_COUNT; i++) { fwrite(&(freqs[i]), 4, 1, stdout); From 40768cb57a2ef1833f5e344b665bdd35a32979ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 19 Apr 2016 11:21:31 +0200 Subject: [PATCH 13/87] codegen: added MartinM2 to the test harness --- pysstv/examples/codegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index a298099..0447b60 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -37,10 +37,10 @@ class Image(object): def __str__(self): return self.content -from pysstv.color import MartinM1 +from pysstv.color import MartinM1, MartinM2 import re -supported = [MartinM1] +supported = [MartinM1, MartinM2] ROW_RE = re.compile(r'ROW\(\d+\)') def main(sstv_class=None): From c7fecff3e63fd9e45efe26f7590aadb60e0d0f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 19 Apr 2016 11:36:08 +0200 Subject: [PATCH 14/87] codegen: save outputs and C code in case of failed tests --- pysstv/examples/codegen.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 0447b60..2eb7985 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -122,7 +122,8 @@ def test(): gcc = Popen(['gcc', '-xc', '-lm', '-o', exe, '-'], stdin=PIPE) start = datetime.now() with open(path.join(path.dirname(__file__), 'codeman.c')) as cm: - gcc.communicate(cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class)))) + c_src = cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class))) + gcc.communicate(c_src) gen_elapsed = datetime.now() - start print ' - gengcc took', gen_elapsed start = datetime.now() @@ -132,8 +133,21 @@ def test(): img = Image.open("320x256rgb.png") sstv = sstv_class(img, 44100, 16) start = datetime.now() - for n, (freq, msec) in enumerate(sstv.gen_freq_bits()): - assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec) + try: + for n, (freq, msec) in enumerate(sstv.gen_freq_bits()): + assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec) + except AssertionError: + mode_name = sstv_class.__name__ + with file('/tmp/{0}-c.bin'.format(mode_name), 'wb') as f: + f.write(gen) + with file('/tmp/{0}-py.bin'.format(mode_name), 'wb') as f: + for n, (freq, msec) in enumerate(sstv.gen_freq_bits()): + f.write(struct.pack('ff', freq, msec)) + with file('/tmp/{0}.c'.format(mode_name), 'w') as f: + f.write(c_src) + print (" ! Outputs are different, they've been saved to " + "/tmp/{0}-{{c,py}}.bin, along with the C source code " + "in /tmp/{0}.c").format(mode_name) python_elapsed = datetime.now() - start print ' - python took', python_elapsed print ' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds() From 5d38b3b52d98627f03d04fa33d7df2923bea2014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 20 Apr 2016 14:04:57 +0200 Subject: [PATCH 15/87] codegen: avoid defining test image in multiple places --- pysstv/examples/codegen.py | 5 +++-- pysstv/examples/codeman.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 2eb7985..feeb37f 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -113,6 +113,7 @@ def test(): from datetime import datetime import struct exe = './codegen-test-executable' + img_file = '320x256rgb.png' if not path.exists('stb_image.h'): from urllib import urlretrieve urlretrieve('https://raw.githubusercontent.com/nothings/stb/master/stb_image.h', 'stb_image.h') @@ -127,10 +128,10 @@ def test(): gen_elapsed = datetime.now() - start print ' - gengcc took', gen_elapsed start = datetime.now() - gen = check_output([exe]) + gen = check_output([exe, img_file]) native_elapsed = datetime.now() - start print ' - native took', native_elapsed - img = Image.open("320x256rgb.png") + img = Image.open(img_file) sstv = sstv_class(img, 44100, 16) start = datetime.now() try: diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c index fce6b05..11ac0c0 100644 --- a/pysstv/examples/codeman.c +++ b/pysstv/examples/codeman.c @@ -5,9 +5,9 @@ #include "stb_image.h" #include "codegen.c" -int main() { +int main(int argc, char **argv) { int x, y, n; - unsigned char *img = stbi_load("320x256rgb.png", &x, &y, &n, 0); + unsigned char *img = stbi_load(argv[1], &x, &y, &n, 0); float freqs[FREQ_COUNT], msecs[FREQ_COUNT]; From ff15c3a63acbfa6364f256f05532198cdfb43155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 20 Apr 2016 14:07:35 +0200 Subject: [PATCH 16/87] codegen: removed hardcoded test image file name --- pysstv/examples/codegen.py | 7 +++---- pysstv/examples/codeman.c | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index feeb37f..31fce68 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -106,14 +106,13 @@ def gen_matches(same_as, history, n): cur_start = i cur_end = tmp -def test(): +def test(img_file): from subprocess import Popen, PIPE, check_output from os import remove, path from PIL import Image from datetime import datetime import struct exe = './codegen-test-executable' - img_file = '320x256rgb.png' if not path.exists('stb_image.h'): from urllib import urlretrieve urlretrieve('https://raw.githubusercontent.com/nothings/stb/master/stb_image.h', 'stb_image.h') @@ -162,7 +161,7 @@ def test(): if __name__ == '__main__': from sys import argv - if len(argv) > 1 and argv[1] == 'test': - test() + if len(argv) > 2 and argv[1] == 'test': + test(argv[2]) else: print '\n'.join(main()) diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c index 11ac0c0..082e449 100644 --- a/pysstv/examples/codeman.c +++ b/pysstv/examples/codeman.c @@ -1,7 +1,6 @@ #include #define STB_IMAGE_IMPLEMENTATION -#define STBI_ONLY_PNG #include "stb_image.h" #include "codegen.c" From 23a2e2bd00b53566c067e76c4fa220be756e4e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 20 Apr 2016 19:36:24 +0200 Subject: [PATCH 17/87] codegen: check for non-RGB images --- pysstv/examples/codeman.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pysstv/examples/codeman.c b/pysstv/examples/codeman.c index 082e449..a3460d5 100644 --- a/pysstv/examples/codeman.c +++ b/pysstv/examples/codeman.c @@ -7,6 +7,10 @@ int main(int argc, char **argv) { int x, y, n; unsigned char *img = stbi_load(argv[1], &x, &y, &n, 0); + if (n != 3) { + fprintf(stderr, "Only RGB images are supported\n"); + return 1; + } float freqs[FREQ_COUNT], msecs[FREQ_COUNT]; From 810f604a5a81cd0e462e887d3c0c2e120ec29920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 20 Apr 2016 19:49:43 +0200 Subject: [PATCH 18/87] codegen: added Pasokon modes to test harness --- pysstv/examples/codegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 31fce68..301f785 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -37,10 +37,10 @@ class Image(object): def __str__(self): return self.content -from pysstv.color import MartinM1, MartinM2 +from pysstv.color import MartinM1, MartinM2, PasokonP3, PasokonP5, PasokonP7 import re -supported = [MartinM1, MartinM2] +supported = [MartinM1, MartinM2, PasokonP3, PasokonP5, PasokonP7] ROW_RE = re.compile(r'ROW\(\d+\)') def main(sstv_class=None): From 56743c7afbce4db310568d67a7c167ac84c8ff39 Mon Sep 17 00:00:00 2001 From: KM4YRI Date: Tue, 3 Jan 2017 09:04:11 -0500 Subject: [PATCH 19/87] Python 3 compatibility: imap->map, izip->zip, xrange->range (#9) --- pysstv/color.py | 30 ++++++++++++++++++------------ pysstv/grayscale.py | 11 +++++++++-- pysstv/sstv.py | 30 ++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index 683a85a..e70709c 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -1,6 +1,12 @@ #!/usr/bin/env python from __future__ import division +try: # python 2/3 compatibility + xrange # will fail in python 3 +except NameError: + pass +else: + range = xrange from pysstv.sstv import byte_to_freq, FREQ_BLACK, FREQ_WHITE, FREQ_VIS_START from pysstv.grayscale import GrayscaleSSTV from itertools import chain @@ -18,7 +24,7 @@ class ColorSSTV(GrayscaleSSTV): for index in self.COLOR_SEQ: for item in self.before_channel(index): yield item - for col in xrange(self.WIDTH): + for col in range(self.WIDTH): pixel = image[col, line] freq_pixel = byte_to_freq(pixel[index]) yield freq_pixel, msec_pixel @@ -92,7 +98,7 @@ class Robot36(ColorSSTV): self.yuv = self.image.convert('YCbCr').load() def encode_line(self, line): - pixels = [self.yuv[col, line] for col in xrange(self.WIDTH)] + pixels = [self.yuv[col, line] for col in range(self.WIDTH)] channel = (line % 2) + 1 y_pixel_time = self.Y_SCAN / self.WIDTH uv_pixel_time = self.C_SCAN / self.WIDTH @@ -102,19 +108,19 @@ class Robot36(ColorSSTV): [(self.INTER_CH_FREQS[channel], self.INTER_CH_GAP), (FREQ_VIS_START, self.PORCH)], ((byte_to_freq(p[channel]), uv_pixel_time) for p in pixels)) - + class PasokonP3(ColorSSTV): """ - [ VIS code or horizontal sync here ] - Back porch - 5 time units of black (1500 Hz). - Red component - 640 pixels of 1 time unit each. - Gap - 5 time units of black. - Green component - 640 pixels of 1 time unit each. - Gap - 5 time units of black. - Blue component - 640 pixels of 1 time unit each. - Front porch - 5 time units of black. - Horizontal Sync - 25 time units of 1200 Hz. + [ VIS code or horizontal sync here ] + Back porch - 5 time units of black (1500 Hz). + Red component - 640 pixels of 1 time unit each. + Gap - 5 time units of black. + Green component - 640 pixels of 1 time unit each. + Gap - 5 time units of black. + Blue component - 640 pixels of 1 time unit each. + Front porch - 5 time units of black. + Horizontal Sync - 25 time units of 1200 Hz. """ TIMEUNIT = 1000/4800. # ms COLOR_SEQ = (RED, GREEN, BLUE) diff --git a/pysstv/grayscale.py b/pysstv/grayscale.py index 7b05b29..63ce0aa 100644 --- a/pysstv/grayscale.py +++ b/pysstv/grayscale.py @@ -1,6 +1,13 @@ #!/usr/bin/env python from __future__ import division + +try: # python 2/3 compatibility + xrange # will fail in python 3 +except NameError: + pass +else: + range = xrange from pysstv.sstv import SSTV, byte_to_freq @@ -9,7 +16,7 @@ class GrayscaleSSTV(SSTV): self.pixels = self.image.convert('LA').load() def gen_image_tuples(self): - for line in xrange(self.HEIGHT): + for line in range(self.HEIGHT): for item in self.horizontal_sync(): yield item for item in self.encode_line(line): @@ -18,7 +25,7 @@ class GrayscaleSSTV(SSTV): def encode_line(self, line): msec_pixel = self.SCAN / self.WIDTH image = self.pixels - for col in xrange(self.WIDTH): + for col in range(self.WIDTH): pixel = image[col, line] freq_pixel = byte_to_freq(pixel[0]) yield freq_pixel, msec_pixel diff --git a/pysstv/sstv.py b/pysstv/sstv.py index 29771db..e2e417e 100644 --- a/pysstv/sstv.py +++ b/pysstv/sstv.py @@ -4,7 +4,21 @@ from __future__ import division, with_statement from math import sin, pi from random import random from contextlib import closing -from itertools import imap, izip, cycle, chain +try: + import itertools.imap as map # python 2 +except ImportError: + pass # python 3 +try: + import itertools.izip as zip # python 2 +except ImportError: + pass # python 3 +from itertools import cycle, chain +try: # python 2/3 compatibility + xrange # will fail in python 3 +except NameError: + pass +else: + range = xrange from array import array import wave @@ -46,7 +60,7 @@ class SSTV(object): data = array(fmt, self.gen_samples()) if self.nchannels != 1: data = array(fmt, chain.from_iterable( - izip(*([data] * self.nchannels)))) + zip(*([data] * self.nchannels)))) with closing(wave.open(filename, 'wb')) as wav: wav.setnchannels(self.nchannels) wav.setsampwidth(self.bits // 8) @@ -64,8 +78,8 @@ class SSTV(object): amp = max_value // 2 lowest = -amp highest = amp - 1 - alias_cycle = cycle((alias * (random() - 0.5) for _ in xrange(1024))) - for value, alias_item in izip(self.gen_values(), alias_cycle): + alias_cycle = cycle((alias * (random() - 0.5) for _ in range(1024))) + for value, alias_item in zip(self.gen_values(), alias_cycle): sample = int(value * amp + alias_item) yield (lowest if sample <= lowest else sample if sample <= highest else highest) @@ -85,7 +99,7 @@ class SSTV(object): samples += spms * msec tx = int(samples) freq_factor = freq * factor - for sample in xrange(tx): + for sample in range(tx): yield sin(sample * freq_factor + offset) offset += (sample + 1) * freq_factor samples -= tx @@ -104,7 +118,7 @@ class SSTV(object): yield FREQ_SYNC, MSEC_VIS_BIT # start bit vis = self.VIS_CODE num_ones = 0 - for _ in xrange(7): + for _ in range(7): bit = vis & 1 vis >>= 1 num_ones += bit @@ -115,8 +129,8 @@ class SSTV(object): yield FREQ_SYNC, MSEC_VIS_BIT # stop bit for freq_tuple in self.gen_image_tuples(): yield freq_tuple - for fskid_byte in imap(ord, self.fskid_payload): - for _ in xrange(6): + for fskid_byte in map(ord, self.fskid_payload): + for _ in range(6): bit = fskid_byte & 1 fskid_byte >>= 1 bit_freq = FREQ_FSKID_BIT1 if bit == 1 else FREQ_FSKID_BIT0 From 7eac9636a3e9cca1dcafad7393019b2a9f0804ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 3 Jan 2017 15:08:29 +0100 Subject: [PATCH 20/87] bumped version to v0.2.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4a2e8ed..a020399 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.2.7', + version='0.2.8', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 5d3b11a1445b7ea58c3df6b0fadafee57bc7f131 Mon Sep 17 00:00:00 2001 From: KM4YRI Date: Thu, 12 Jan 2017 04:49:13 -0500 Subject: [PATCH 21/87] added Python six compatibility layer (#10) added dependency to `six` for better Python 2/3 compatibility --- pysstv/color.py | 9 ++------- pysstv/grayscale.py | 8 +------- pysstv/sstv.py | 17 +++-------------- requirements.txt | 1 + 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index e70709c..48baf4a 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -1,12 +1,6 @@ #!/usr/bin/env python - from __future__ import division -try: # python 2/3 compatibility - xrange # will fail in python 3 -except NameError: - pass -else: - range = xrange +from six.moves import range from pysstv.sstv import byte_to_freq, FREQ_BLACK, FREQ_WHITE, FREQ_VIS_START from pysstv.grayscale import GrayscaleSSTV from itertools import chain @@ -14,6 +8,7 @@ from itertools import chain RED, GREEN, BLUE = range(3) + class ColorSSTV(GrayscaleSSTV): def on_init(self): self.pixels = self.image.load() diff --git a/pysstv/grayscale.py b/pysstv/grayscale.py index 63ce0aa..2b106d8 100644 --- a/pysstv/grayscale.py +++ b/pysstv/grayscale.py @@ -1,13 +1,7 @@ #!/usr/bin/env python from __future__ import division - -try: # python 2/3 compatibility - xrange # will fail in python 3 -except NameError: - pass -else: - range = xrange +from six.moves import range from pysstv.sstv import SSTV, byte_to_freq diff --git a/pysstv/sstv.py b/pysstv/sstv.py index e2e417e..535623a 100644 --- a/pysstv/sstv.py +++ b/pysstv/sstv.py @@ -1,24 +1,13 @@ #!/usr/bin/env python from __future__ import division, with_statement +from six.moves import range +from six.moves import map +from six.moves import zip from math import sin, pi from random import random from contextlib import closing -try: - import itertools.imap as map # python 2 -except ImportError: - pass # python 3 -try: - import itertools.izip as zip # python 2 -except ImportError: - pass # python 3 from itertools import cycle, chain -try: # python 2/3 compatibility - xrange # will fail in python 3 -except NameError: - pass -else: - range = xrange from array import array import wave diff --git a/requirements.txt b/requirements.txt index 4ed5a08..58e63c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +six==1.10.0 Pillow==2.2.1 mock==1.0.1 nose==1.3.0 From 72ffc80aad93389a7120275ec0d8843dc4da0470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 10:53:44 +0100 Subject: [PATCH 22/87] Travis CI: extended Python version coverage --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6d09563..a82aff1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ language: python python: + - "2.6" - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" sudo: false install: "pip install -r requirements.txt" script: nosetests From d1777072333a09696c6c239a7071a704960f58d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 10:57:42 +0100 Subject: [PATCH 23/87] Python 2.6 still doesn't support delta in assertAlmostEqual --- .travis.yml | 1 - README.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a82aff1..5103385 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - "2.6" - "2.7" - "3.2" - "3.3" diff --git a/README.md b/README.md index 7faea81..ad4777f 100644 --- a/README.md +++ b/README.md @@ -71,5 +71,5 @@ Useful links Dependencies ------------ - - Python 2.7 (tested on 2.7.5) + - Python 2.7 (tested on 2.7.5; 2.6 might work, but test suite lacks support) - Python Imaging Library (Debian/Ubuntu package: `python-imaging`) From a43f64fd790768aaca28e4d22e4e92f76dbbac56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 11:06:21 +0100 Subject: [PATCH 24/87] modified tests for Python 2 + 3 compatibility --- pysstv/tests/test_color.py | 10 ++++++---- pysstv/tests/test_sstv.py | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pysstv/tests/test_color.py b/pysstv/tests/test_color.py index 4f50dc7..97003f1 100644 --- a/pysstv/tests/test_color.py +++ b/pysstv/tests/test_color.py @@ -17,12 +17,14 @@ class TestMartinM1(unittest.TestCase): self.lena = color.MartinM1(lena, 48000, 16) def test_gen_freq_bits(self): - expected = pickle.load(open(get_asset_filename("MartinM1_freq_bits.p"))) + with open(get_asset_filename("MartinM1_freq_bits.p"), 'rb') as f: + expected = pickle.load(f) actual = list(islice(self.s.gen_freq_bits(), 0, 1000)) self.assertEqual(expected, actual) def test_gen_freq_bits_lena(self): - expected = pickle.load(open(get_asset_filename("MartinM1_freq_bits_lena.p"))) + with open(get_asset_filename("MartinM1_freq_bits_lena.p"), 'rb') as f: + expected = pickle.load(f) actual = list(islice(self.lena.gen_freq_bits(), 0, 1000)) self.assertEqual(expected, actual) @@ -40,7 +42,7 @@ class TestMartinM1(unittest.TestCase): self.maxDiff = None line_numbers = [1, 10, 100] for line in line_numbers: - file = open(get_asset_filename("MartinM1_encode_line_lena%d.p" % line)) - expected = pickle.load(file) + with open(get_asset_filename("MartinM1_encode_line_lena%d.p" % line), 'rb') as f: + expected = pickle.load(f) actual = list(self.lena.encode_line(line)) self.assertEqual(expected, actual) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index ffcd630..1b7ac39 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -1,5 +1,6 @@ import unittest -from itertools import islice, izip +from itertools import islice +from six.moves import zip import pickle import mock from mock import MagicMock @@ -46,7 +47,7 @@ class TestSSTV(unittest.TestCase): def test_gen_values(self): gen_values = self.s.gen_values() expected = pickle.load(open(get_asset_filename("SSTV_gen_values.p"))) - for e, g in izip(expected, gen_values): + for e, g in zip(expected, gen_values): self.assertAlmostEqual(e, g, delta=0.000000001) def test_gen_samples(self): @@ -59,7 +60,7 @@ class TestSSTV(unittest.TestCase): sstv.random = MagicMock(return_value=0.4) # xkcd:221 expected = pickle.load(open(get_asset_filename("SSTV_gen_samples.p"))) actual = list(islice(gen_values, 0, 1000)) - for e, a in izip(expected, actual): + for e, a in zip(expected, actual): self.assertAlmostEqual(e, a, delta=1) def test_write_wav(self): From 8f8f431050697de756f513e54eab3a76b08d7730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 11:14:54 +0100 Subject: [PATCH 25/87] StringIO was also moved in Python 3 --- pysstv/tests/test_sstv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 1b7ac39..53da1c5 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -4,7 +4,7 @@ from six.moves import zip import pickle import mock from mock import MagicMock -from StringIO import StringIO +from six import StringIO import hashlib from pysstv import sstv From d574ab1d4901917eca6dcc9e77c649a34967eb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:15:42 +0100 Subject: [PATCH 26/87] use absolute imports in test_sstv as well --- pysstv/tests/test_sstv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 53da1c5..b4c18a8 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -9,7 +9,7 @@ import hashlib from pysstv import sstv from pysstv.sstv import SSTV -from common import get_asset_filename +from pysstv.tests.common import get_asset_filename class TestSSTV(unittest.TestCase): From bf2efdabeab16935d049ced4a4d2ccc8908838f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:25:03 +0100 Subject: [PATCH 27/87] test_sstv: handle iterables in a more portable way --- pysstv/tests/test_sstv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index b4c18a8..9991996 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -22,7 +22,7 @@ class TestSSTV(unittest.TestCase): def test_horizontal_sync(self): horizontal_sync = self.s.horizontal_sync() expected = (1200, self.s.SYNC) - actual = horizontal_sync.next() + actual = next(iter(horizontal_sync)) self.assertEqual(expected, actual) def test_gen_freq_bits(self): From ac0f89bfdf9bf64e368de2b608821248114f915d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:25:34 +0100 Subject: [PATCH 28/87] test_sstv: open further files in explicit 'rb' mode --- pysstv/tests/test_sstv.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 9991996..10162c5 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -46,7 +46,8 @@ class TestSSTV(unittest.TestCase): # FIXME: Instead of using a test fixture, 'expected' should be synthesized? def test_gen_values(self): gen_values = self.s.gen_values() - expected = pickle.load(open(get_asset_filename("SSTV_gen_values.p"))) + with open(get_asset_filename("SSTV_gen_values.p"), 'rb') as f: + expected = pickle.load(f) for e, g in zip(expected, gen_values): self.assertAlmostEqual(e, g, delta=0.000000001) @@ -58,7 +59,8 @@ class TestSSTV(unittest.TestCase): # and having different results. # https://en.wikipedia.org/wiki/Quantization_%28signal_processing%29 sstv.random = MagicMock(return_value=0.4) # xkcd:221 - expected = pickle.load(open(get_asset_filename("SSTV_gen_samples.p"))) + with open(get_asset_filename("SSTV_gen_samples.p"), 'rb') as f: + expected = pickle.load(f) actual = list(islice(gen_values, 0, 1000)) for e, a in zip(expected, actual): self.assertAlmostEqual(e, a, delta=1) From 0d52546d815a0709b6808dad3b000fdd61c19c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:26:01 +0100 Subject: [PATCH 29/87] test_sstv: select open namespace depending on Python major version --- pysstv/tests/test_sstv.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 10162c5..337a049 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -5,6 +5,7 @@ import pickle import mock from mock import MagicMock from six import StringIO +from six import PY2 import hashlib from pysstv import sstv @@ -70,7 +71,8 @@ class TestSSTV(unittest.TestCase): sio = StringIO() sio.close = MagicMock() # ignore close() so we can .getvalue() mock_open = MagicMock(return_value=sio) - with mock.patch('__builtin__.open', mock_open): + ns = '__builtin__' if PY2 else 'io' + with mock.patch('{0}.open'.format(ns), mock_open): self.s.write_wav('unittest.wav') expected = 'dd7eed880ab3360fb79ce09c469deee2' data = sio.getvalue() From 9cbb0c50ff3b19ce7bae125538f1704dcd74dcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:37:58 +0100 Subject: [PATCH 30/87] use BytesIO on Python 3 --- pysstv/tests/test_sstv.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 337a049..a47577f 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -4,7 +4,7 @@ from six.moves import zip import pickle import mock from mock import MagicMock -from six import StringIO +from six import BytesIO from six import PY2 import hashlib @@ -68,14 +68,14 @@ class TestSSTV(unittest.TestCase): def test_write_wav(self): self.maxDiff = None - sio = StringIO() - sio.close = MagicMock() # ignore close() so we can .getvalue() - mock_open = MagicMock(return_value=sio) + bio = BytesIO() + bio.close = MagicMock() # ignore close() so we can .getvalue() + mock_open = MagicMock(return_value=bio) ns = '__builtin__' if PY2 else 'io' with mock.patch('{0}.open'.format(ns), mock_open): self.s.write_wav('unittest.wav') expected = 'dd7eed880ab3360fb79ce09c469deee2' - data = sio.getvalue() + data = bio.getvalue() actual = hashlib.md5(data).hexdigest() self.assertEqual(expected, actual) From 81b45e7e4e96e5b5d009e1addff9fea6f18268a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:48:50 +0100 Subject: [PATCH 31/87] Python 3 uses builtins.open instead of io.open --- pysstv/tests/test_sstv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index a47577f..7fbb6d7 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -71,7 +71,7 @@ class TestSSTV(unittest.TestCase): bio = BytesIO() bio.close = MagicMock() # ignore close() so we can .getvalue() mock_open = MagicMock(return_value=bio) - ns = '__builtin__' if PY2 else 'io' + ns = '__builtin__' if PY2 else 'builtins' with mock.patch('{0}.open'.format(ns), mock_open): self.s.write_wav('unittest.wav') expected = 'dd7eed880ab3360fb79ce09c469deee2' From 5eb8a468e4d01d4912a8ac4d6e62e8327a819259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:54:07 +0100 Subject: [PATCH 32/87] extracted method load_pickled_asset --- pysstv/tests/common.py | 5 +++++ pysstv/tests/test_color.py | 12 ++++-------- pysstv/tests/test_sstv.py | 9 +++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pysstv/tests/common.py b/pysstv/tests/common.py index 005624f..eae997a 100644 --- a/pysstv/tests/common.py +++ b/pysstv/tests/common.py @@ -1,4 +1,9 @@ from os import path +import pickle def get_asset_filename(filename): return path.join(path.dirname(__file__), 'assets', filename) + +def load_pickled_asset(filename): + with open(get_asset_filename(filename + '.p'), 'rb') as f: + return pickle.load(f) diff --git a/pysstv/tests/test_color.py b/pysstv/tests/test_color.py index 97003f1..8d9e292 100644 --- a/pysstv/tests/test_color.py +++ b/pysstv/tests/test_color.py @@ -1,11 +1,10 @@ import unittest from itertools import islice -import pickle from PIL import Image from pysstv import color -from pysstv.tests.common import get_asset_filename +from pysstv.tests.common import get_asset_filename, load_pickled_asset class TestMartinM1(unittest.TestCase): @@ -17,14 +16,12 @@ class TestMartinM1(unittest.TestCase): self.lena = color.MartinM1(lena, 48000, 16) def test_gen_freq_bits(self): - with open(get_asset_filename("MartinM1_freq_bits.p"), 'rb') as f: - expected = pickle.load(f) + expected = load_pickled_asset("MartinM1_freq_bits") actual = list(islice(self.s.gen_freq_bits(), 0, 1000)) self.assertEqual(expected, actual) def test_gen_freq_bits_lena(self): - with open(get_asset_filename("MartinM1_freq_bits_lena.p"), 'rb') as f: - expected = pickle.load(f) + expected = load_pickled_asset("MartinM1_freq_bits_lena") actual = list(islice(self.lena.gen_freq_bits(), 0, 1000)) self.assertEqual(expected, actual) @@ -42,7 +39,6 @@ class TestMartinM1(unittest.TestCase): self.maxDiff = None line_numbers = [1, 10, 100] for line in line_numbers: - with open(get_asset_filename("MartinM1_encode_line_lena%d.p" % line), 'rb') as f: - expected = pickle.load(f) + expected = load_pickled_asset("MartinM1_encode_line_lena{0}".format(line)) actual = list(self.lena.encode_line(line)) self.assertEqual(expected, actual) diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 7fbb6d7..4a92af0 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -1,7 +1,6 @@ import unittest from itertools import islice from six.moves import zip -import pickle import mock from mock import MagicMock from six import BytesIO @@ -10,7 +9,7 @@ import hashlib from pysstv import sstv from pysstv.sstv import SSTV -from pysstv.tests.common import get_asset_filename +from pysstv.tests.common import load_pickled_asset class TestSSTV(unittest.TestCase): @@ -47,8 +46,7 @@ class TestSSTV(unittest.TestCase): # FIXME: Instead of using a test fixture, 'expected' should be synthesized? def test_gen_values(self): gen_values = self.s.gen_values() - with open(get_asset_filename("SSTV_gen_values.p"), 'rb') as f: - expected = pickle.load(f) + expected = load_pickled_asset("SSTV_gen_values") for e, g in zip(expected, gen_values): self.assertAlmostEqual(e, g, delta=0.000000001) @@ -60,8 +58,7 @@ class TestSSTV(unittest.TestCase): # and having different results. # https://en.wikipedia.org/wiki/Quantization_%28signal_processing%29 sstv.random = MagicMock(return_value=0.4) # xkcd:221 - with open(get_asset_filename("SSTV_gen_samples.p"), 'rb') as f: - expected = pickle.load(f) + expected = load_pickled_asset("SSTV_gen_samples") actual = list(islice(gen_values, 0, 1000)) for e, a in zip(expected, actual): self.assertAlmostEqual(e, a, delta=1) From a601f8e7540e23d5f10b031d0380eab89db29268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:58:11 +0100 Subject: [PATCH 33/87] updated README regarding Python 3 support (#10) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad4777f..f0888f5 100644 --- a/README.md +++ b/README.md @@ -71,5 +71,6 @@ Useful links Dependencies ------------ - - Python 2.7 (tested on 2.7.5; 2.6 might work, but test suite lacks support) + - Python 2.7 (tested on 2.7.9; 2.6 might work, but test suite lacks support) + or 3.x (tested on 3.2, 3.3, 3.4 and 3.5) - Python Imaging Library (Debian/Ubuntu package: `python-imaging`) From d51c00a04aeb04208157aaad58bdad4730d1f59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 12 Jan 2017 12:58:24 +0100 Subject: [PATCH 34/87] bumped version to v0.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a020399..264094a 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.2.8', + version='0.3', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 9ff3f73d17fed46b442a6974ba8980e0d92b3fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 24 Oct 2017 14:36:14 +0200 Subject: [PATCH 35/87] requirements.txt: bumped Pillow version to 4.3.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 58e63c4..98bb7c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ six==1.10.0 -Pillow==2.2.1 +Pillow==4.3.0 mock==1.0.1 nose==1.3.0 From da0c102090072784b15c2f42477d0bc35092f89f Mon Sep 17 00:00:00 2001 From: Blaine Murphy Date: Tue, 24 Oct 2017 08:42:17 -0400 Subject: [PATCH 36/87] Robot 36 color correction (#12) The color sequence for Robot 36 is listed as YCrCb in the SSTV Handbook: http://www.sstv-handbook.com/download/sstv_04.pdf (Table 4.3) and the Dayton Paper: https://web.archive.org/web/20120104184535/http://www.barberdsp.com/files/Dayton%20Paper.pdf * Corrected inter-channel frequency order for Robot 36 mode. * Switched Robot 36 channel order to CrCb. --- pysstv/color.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index 48baf4a..fecaf13 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -87,14 +87,14 @@ class Robot36(ColorSSTV): C_SCAN = 44 PORCH = 1.5 SYNC_PORCH = 3 - INTER_CH_FREQS = [None, FREQ_BLACK, FREQ_WHITE] + INTER_CH_FREQS = [None, FREQ_WHITE, FREQ_BLACK] def on_init(self): self.yuv = self.image.convert('YCbCr').load() def encode_line(self, line): pixels = [self.yuv[col, line] for col in range(self.WIDTH)] - channel = (line % 2) + 1 + channel = 2 - (line % 2) y_pixel_time = self.Y_SCAN / self.WIDTH uv_pixel_time = self.C_SCAN / self.WIDTH return chain( From de0dc22cb8e98bad5a39bcf72845d98c0e12ee29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 24 Oct 2017 14:45:10 +0200 Subject: [PATCH 37/87] setup.py: added Pasokon to keywords --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 264094a..0fd65dc 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( author_email='vsza@vsza.hu', url='https://github.com/dnet/pySSTV', packages=['pysstv', 'pysstv.tests', 'pysstv.examples'], - keywords='HAM SSTV slow-scan television Scottie Martin Robot', + keywords='HAM SSTV slow-scan television Scottie Martin Robot Pasokon', install_requires = ['Pillow',], license='MIT', classifiers=[ From 0c380590013ee51e2aee286ff8b86d53be125840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 24 Oct 2017 14:45:25 +0200 Subject: [PATCH 38/87] bumped version to v0.3.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0fd65dc..034e772 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.3', + version='0.3.1', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 75d9722491e85df06e0a547da008dabcd63370ad Mon Sep 17 00:00:00 2001 From: Matt Molyneaux Date: Thu, 22 Feb 2018 08:19:31 +0000 Subject: [PATCH 39/87] Add six to setup.py (#17) It's required by `pysstv.sstv` --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 034e772..8506b93 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( url='https://github.com/dnet/pySSTV', packages=['pysstv', 'pysstv.tests', 'pysstv.examples'], keywords='HAM SSTV slow-scan television Scottie Martin Robot Pasokon', - install_requires = ['Pillow',], + install_requires = ['Pillow', 'six'], license='MIT', classifiers=[ 'Development Status :: 4 - Beta', From c6504a75514077231c0afe79d050003f7826e6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 22 Feb 2018 09:21:15 +0100 Subject: [PATCH 40/87] bumped version to v0.3.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8506b93..1906242 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.3.1', + version='0.3.2', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 6d3d2143b7b0397d7f035400b824d1bdebc45828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Sat, 24 Feb 2018 21:28:46 +0100 Subject: [PATCH 41/87] build_module_map: use OrderedDict if available this results in a much nicer "usage" screen by argparse --- pysstv/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pysstv/__main__.py b/pysstv/__main__.py index 0468ae0..04a91bf 100644 --- a/pysstv/__main__.py +++ b/pysstv/__main__.py @@ -47,7 +47,11 @@ def main(): def build_module_map(): - module_map = {} + try: + from collections import OrderedDict + module_map = OrderedDict() + except ImportError: + module_map = {} for module in SSTV_MODULES: for mode in module.MODES: module_map[mode.__name__] = mode From 35de6c718e5786d315c29e74ee2aba57660a9ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Sat, 24 Feb 2018 21:40:33 +0100 Subject: [PATCH 42/87] README: added missing options --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f0888f5..61a6fb0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ Command line usage $ python -m pysstv -h usage: __main__.py [-h] [--mode {MartinM2,MartinM1,Robot24BW,ScottieS2,ScottieS1,Robot8BW,PasokonP3,PasokonP5,PasokonP7}] - [--rate RATE] [--bits BITS] + [--rate RATE] [--bits BITS] [--vox] [--fskid FSKID] + [--chan CHAN] image.png output.wav Converts an image to an SSTV modulated WAV file. @@ -32,6 +33,9 @@ Command line usage image mode (default: Martin M1) --rate RATE sampling rate (default: 48000) --bits BITS bits per sample (default: 16) + --vox add VOX tones at the beginning + --fskid FSKID add FSKID at the end + --chan CHAN number of channels (default: mono) Python interface ---------------- From b7311120070586579f7aa2feceb1f3ed29aa04e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Sat, 24 Feb 2018 22:23:55 +0100 Subject: [PATCH 43/87] added PD modes (#16) --- README.md | 4 ++-- pysstv/color.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61a6fb0..272d52a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Command line usage $ python -m pysstv -h usage: __main__.py [-h] - [--mode {MartinM2,MartinM1,Robot24BW,ScottieS2,ScottieS1,Robot8BW,PasokonP3,PasokonP5,PasokonP7}] + [--mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,Robot8BW,Robot24BW}] [--rate RATE] [--bits BITS] [--vox] [--fskid FSKID] [--chan CHAN] image.png output.wav @@ -29,7 +29,7 @@ Command line usage optional arguments: -h, --help show this help message and exit - --mode {MartinM2,MartinM1,Robot24BW,ScottieS2,ScottieS1,Robot8BW,PasokonP3,PasokonP5,PasokonP7} + --mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120D160,PD180,PD240,Robot8BW,Robot24BW} image mode (default: Martin M1) --rate RATE sampling rate (default: 48000) --bits BITS bits per sample (default: 16) diff --git a/pysstv/color.py b/pysstv/color.py index fecaf13..ef0d161 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from __future__ import division -from six.moves import range +from six.moves import range, zip from pysstv.sstv import byte_to_freq, FREQ_BLACK, FREQ_WHITE, FREQ_VIS_START from pysstv.grayscale import GrayscaleSSTV from itertools import chain @@ -149,4 +149,52 @@ class PasokonP7(PasokonP3): INTER_CH_GAP = 5 * TIMEUNIT -MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2, Robot36, PasokonP3, PasokonP5, PasokonP7) +class PD90(ColorSSTV): + VIS_CODE = 0x63 + WIDTH = 320 + HEIGHT = 256 + SYNC = 20 + PORCH = 2.08 + PIXEL = 0.532 + + def gen_image_tuples(self): + yuv = self.image.convert('YCbCr').load() + for line in range(0, self.HEIGHT, 2): + for item in self.horizontal_sync(): + yield item + yield FREQ_BLACK, self.PORCH + pixels0 = [yuv[col, line] for col in range(self.WIDTH)] + pixels1 = [yuv[col, line + 1] for col in range(self.WIDTH)] + for p in pixels0: + yield byte_to_freq(p[0]), self.PIXEL + for p0, p1 in zip(pixels0, pixels1): + yield byte_to_freq((p0[2] + p1[2]) / 2), self.PIXEL + for p0, p1 in zip(pixels0, pixels1): + yield byte_to_freq((p0[1] + p1[1]) / 2), self.PIXEL + for p in pixels1: + yield byte_to_freq(p[0]), self.PIXEL + + +class PD120(PD90): + VIS_CODE = 0x5f + WIDTH = 640 + HEIGHT = 496 + PIXEL = 0.19 + +class PD160(PD90): + VIS_CODE = 0x62 + WIDTH = 512 + HEIGHT = 400 + PIXEL = 0.382 + +class PD180(PD120): + VIS_CODE = 0x60 + PIXEL = 0.286 + +class PD240(PD120): + VIS_CODE = 0x61 + PIXEL = 0.382 + + +MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2, Robot36, + PasokonP3, PasokonP5, PasokonP7, PD90, PD120, PD160, PD180, PD240) From 03092f5549c6c38037d5fcc8a281e60f3cf2a21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Sun, 25 Feb 2018 13:17:59 +0100 Subject: [PATCH 44/87] bumped version to v0.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1906242..91d2b1c 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.3.2', + version='0.4', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 509d162a0d4352336a153b7cb7ff6fa4086e5edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 30 Apr 2018 16:24:46 +0200 Subject: [PATCH 45/87] gimp-plugin: removed dead code --- pysstv/examples/gimp-plugin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pysstv/examples/gimp-plugin.py b/pysstv/examples/gimp-plugin.py index d97683c..bb42105 100755 --- a/pysstv/examples/gimp-plugin.py +++ b/pysstv/examples/gimp-plugin.py @@ -152,9 +152,6 @@ def contrast(value): else: return 255 - value -def set_ptt_pin(port, pin, state): - getattr(port, 'set' + pin)(state) - def transmit_current_image(image, drawable, mode, vox, fskid, ptt_port, ptt_pin, ptt_state): sstv = MODULE_MAP[mode] if ptt_port is not None: From 99d8709053b74c1c9c6a72ca5c099cde9c2c0c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 30 Apr 2018 16:25:09 +0200 Subject: [PATCH 46/87] gimp-plugin: documented pyaudio dependency --- pysstv/examples/gimp-plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/examples/gimp-plugin.py b/pysstv/examples/gimp-plugin.py index bb42105..2833948 100755 --- a/pysstv/examples/gimp-plugin.py +++ b/pysstv/examples/gimp-plugin.py @@ -2,7 +2,7 @@ # -*- encoding: utf-8 -*- # copy to ~/.gimp-2.8/plug-ins/ -# dependencies: GIMP 2.8, python-imaging-tk +# dependencies: GIMP 2.8, python-imaging-tk, python-pyaudio from gimpfu import register, main, pdb, PF_BOOL, PF_STRING, PF_RADIO, CLIP_TO_IMAGE from PIL import Image, ImageTk From 4b95f8b5c32c01f718ad37b8e186f21a55a17600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 22 Jan 2019 14:41:44 +0100 Subject: [PATCH 47/87] added image resizing to the CLI fixes gh-18 --- pysstv/__main__.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/pysstv/__main__.py b/pysstv/__main__.py index 04a91bf..90f3de8 100644 --- a/pysstv/__main__.py +++ b/pysstv/__main__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from __future__ import print_function +from __future__ import print_function, division from PIL import Image from argparse import ArgumentParser from sys import stderr @@ -30,10 +30,40 @@ def main(): help='add FSKID at the end') parser.add_argument('--chan', dest='chan', type=int, help='number of channels (default: mono)') + parser.add_argument('--resize', dest='resize', action='store_true', + help='resize the image to the correct size') + parser.add_argument('--keep-aspect-ratio', dest='keep_aspect_ratio', action='store_true', + help='keep the original aspect ratio when resizing (and cut off excess pixels)') + parser.add_argument('--resample', dest='resample', default='lanczos', + choices=('nearest', 'bicubic', 'lanczos'), + help='which resampling filter to use for resizing (see Pillow documentation)') args = parser.parse_args() image = Image.open(args.img_file) mode = module_map[args.mode] - if not all(i >= m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))): + if args.resize and any(i != m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))): + resample = getattr(Image, args.resample.upper()) + if args.keep_aspect_ratio: + orig_ratio = image.width / image.height + mode_ratio = mode.WIDTH / mode.HEIGHT + crop = orig_ratio != mode_ratio + else: + crop = False + if crop: + if orig_ratio < mode_ratio: + w = mode.WIDTH + h = int(w / orig_ratio) + else: + h = mode.HEIGHT + w = int(orig_ratio * h) + else: + w = mode.WIDTH + h = mode.HEIGHT + image = image.resize((w, h), resample) + if crop: + x = (image.width - mode.WIDTH) / 2 + y = (image.height - mode.HEIGHT) / 2 + image = image.crop((x, y, mode.WIDTH + x, mode.HEIGHT + y)) + elif not all(i >= m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))): print(('Image must be at least {m.WIDTH} x {m.HEIGHT} pixels ' 'for mode {m.__name__}').format(m=mode), file=stderr) raise SystemExit(1) From e220d1964ca66378311c90c23bf9f61295e1dca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 22 Jan 2019 14:43:26 +0100 Subject: [PATCH 48/87] bumped version to v0.4.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 91d2b1c..7d8286b 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.4', + version='0.4.1', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 1b6884ee63756461a005f89de775057270371efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Fri, 12 Jul 2019 21:53:39 +0200 Subject: [PATCH 49/87] fixed RGB modes silently depending on RGB images Thanks to @Chris_J_Baird on Twitter reporting the issue: > ColorSSTV.encode_line assuming its gets a tuple from __getpixel__ > had to make the Image.open() do a convert('RGB') https://twitter.com/i/web/status/1149331458189737988 --- pysstv/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/color.py b/pysstv/color.py index ef0d161..703240c 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -11,7 +11,7 @@ RED, GREEN, BLUE = range(3) class ColorSSTV(GrayscaleSSTV): def on_init(self): - self.pixels = self.image.load() + self.pixels = self.image.convert('RGB').load() def encode_line(self, line): msec_pixel = self.SCAN / self.WIDTH From c784e9df150f2718e529863e43d0e32705327abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Fri, 12 Jul 2019 21:55:20 +0200 Subject: [PATCH 50/87] bumped version to v0.4.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7d8286b..9498805 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.4.1', + version='0.4.2', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 7b06a5bb4851cf2825f339855031953f92e08a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Fri, 12 Jul 2019 21:59:48 +0200 Subject: [PATCH 51/87] set correct content type for Markdown README --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 9498805..2107f8e 100644 --- a/setup.py +++ b/setup.py @@ -21,4 +21,5 @@ setup( 'Operating System :: OS Independent', ], long_description=open('README.md').read(), + long_description_content_type="text/markdown", ) From 9143aaec9706d2f6f828fa4f0dd7fdbdd128d388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Fri, 12 Jul 2019 22:01:40 +0200 Subject: [PATCH 52/87] bumped version to v0.4.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2107f8e..234532f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.4.2', + version='0.4.3', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 71ddabcb1d5b31028cca8921f6e2d3e76a0b5421 Mon Sep 17 00:00:00 2001 From: Edmond Belliveau Date: Mon, 1 Mar 2021 18:07:38 -0400 Subject: [PATCH 53/87] Removed tostring() in write_wav. Python 3.9+ has removed the tostring() methods from array.array and other primitives. --- pysstv/sstv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/sstv.py b/pysstv/sstv.py index 535623a..1ffe7f3 100644 --- a/pysstv/sstv.py +++ b/pysstv/sstv.py @@ -54,7 +54,7 @@ class SSTV(object): wav.setnchannels(self.nchannels) wav.setsampwidth(self.bits // 8) wav.setframerate(self.samples_per_sec) - wav.writeframes(data.tostring()) + wav.writeframes(data) def gen_samples(self): """generates discrete samples from gen_values() From 0c09c84360fef6a411f62ba9351812c56503fb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 2 Mar 2021 11:18:58 +0100 Subject: [PATCH 54/87] fixed Python 2 compatibility Python 2 returns the string representation with just calling writeframes(data) instead of writeframes(data.tostring()) /tmp/post.wav 0000 0000: 52 49 46 46 C4 AA 00 00 57 41 56 45 66 6D 74 20 RIFF.... WAVEfmt 0000 0010: 10 00 00 00 01 00 01 00 80 BB 00 00 00 77 01 00 ........ .....w.. 0000 0020: 02 00 10 00 64 61 74 61 A0 AA 00 00 61 72 72 61 ....data ....arra 0000 0030: 79 28 27 68 27 2C 20 5B 30 2C 20 38 30 36 35 2C y('h', [ 0, 8065, 0000 0040: 20 31 35 36 33 35 2C 20 32 32 32 34 32 2C 20 32 15635, 22242, 2 This change adds an escape hatch that could work for both major Python versions, except Python 3.0 and 3.1 but those should be rare and unsupported anyway. --- pysstv/sstv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pysstv/sstv.py b/pysstv/sstv.py index 1ffe7f3..e69f020 100644 --- a/pysstv/sstv.py +++ b/pysstv/sstv.py @@ -4,6 +4,7 @@ from __future__ import division, with_statement from six.moves import range from six.moves import map from six.moves import zip +from six import PY3 from math import sin, pi from random import random from contextlib import closing @@ -54,7 +55,7 @@ class SSTV(object): wav.setnchannels(self.nchannels) wav.setsampwidth(self.bits // 8) wav.setframerate(self.samples_per_sec) - wav.writeframes(data) + wav.writeframes(data if PY3 else data.tostring()) def gen_samples(self): """generates discrete samples from gen_values() From 8f9151f0c1fb13d0a74db1cf49215c0721d206ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 2 Mar 2021 11:22:39 +0100 Subject: [PATCH 55/87] Travis CI: updated Python versions --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5103385..3319ea6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: python python: - "2.7" - - "3.2" - - "3.3" - "3.4" - "3.5" + - "3.6" + - "3.7" + - "3.8" + - "3.9" sudo: false install: "pip install -r requirements.txt" script: nosetests From 9f4c43545027affcce354968bfaa6a5a464f9ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 2 Mar 2021 11:31:47 +0100 Subject: [PATCH 56/87] bumped version to v0.4.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 234532f..a4f6b4a 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.4.3', + version='0.4.4', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 323812bc590bb74a3cd1df0958f9feb333b1eff9 Mon Sep 17 00:00:00 2001 From: Andrew Simmons Date: Mon, 19 Apr 2021 10:04:53 -0400 Subject: [PATCH 57/87] Add entry_point (#23) --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index a4f6b4a..d6ee272 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,11 @@ setup( author_email='vsza@vsza.hu', url='https://github.com/dnet/pySSTV', packages=['pysstv', 'pysstv.tests', 'pysstv.examples'], + entry_points={ + 'console_scripts': [ + 'pysstv = pysstv.__main__:main', + ], + }, keywords='HAM SSTV slow-scan television Scottie Martin Robot Pasokon', install_requires = ['Pillow', 'six'], license='MIT', From 3c2e4d0a5c68c77f765348050efc9a29c8bd0775 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Mar 2021 20:23:48 +0000 Subject: [PATCH 58/87] Bump pillow from 4.3.0 to 8.1.1 Bumps [pillow](https://github.com/python-pillow/Pillow) from 4.3.0 to 8.1.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/4.3.0...8.1.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 98bb7c0..0eda817 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ six==1.10.0 -Pillow==4.3.0 +Pillow==8.1.1 mock==1.0.1 nose==1.3.0 From 10476d013a4c792b6a89bbd84f8d1dc140b83e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 19 Apr 2021 16:07:40 +0200 Subject: [PATCH 59/87] bumped version to v0.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d6ee272..7a4f114 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.4.4', + version='0.5', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 5a596df09c877196c3ccf01a8a5aa22324acae52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 19 Apr 2021 16:10:25 +0200 Subject: [PATCH 60/87] removed support for older Python versions --- .travis.yml | 3 --- README.md | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3319ea6..6d6c1d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ language: python python: - - "2.7" - - "3.4" - - "3.5" - "3.6" - "3.7" - "3.8" diff --git a/README.md b/README.md index 272d52a..dc4200c 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,5 @@ Useful links Dependencies ------------ - - Python 2.7 (tested on 2.7.9; 2.6 might work, but test suite lacks support) - or 3.x (tested on 3.2, 3.3, 3.4 and 3.5) + - Python 3.5 or later - Python Imaging Library (Debian/Ubuntu package: `python-imaging`) From 30294ee09628cfd16715d6986b1834e756290a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 19 Apr 2021 16:23:40 +0200 Subject: [PATCH 61/87] removed six --- pysstv/color.py | 1 - pysstv/grayscale.py | 1 - pysstv/sstv.py | 6 +----- pysstv/tests/test_sstv.py | 7 ++----- requirements.txt | 1 - setup.py | 2 +- 6 files changed, 4 insertions(+), 14 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index 703240c..be8096f 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -1,6 +1,5 @@ #!/usr/bin/env python from __future__ import division -from six.moves import range, zip from pysstv.sstv import byte_to_freq, FREQ_BLACK, FREQ_WHITE, FREQ_VIS_START from pysstv.grayscale import GrayscaleSSTV from itertools import chain diff --git a/pysstv/grayscale.py b/pysstv/grayscale.py index 2b106d8..75472aa 100644 --- a/pysstv/grayscale.py +++ b/pysstv/grayscale.py @@ -1,7 +1,6 @@ #!/usr/bin/env python from __future__ import division -from six.moves import range from pysstv.sstv import SSTV, byte_to_freq diff --git a/pysstv/sstv.py b/pysstv/sstv.py index e69f020..5cc5f77 100644 --- a/pysstv/sstv.py +++ b/pysstv/sstv.py @@ -1,10 +1,6 @@ #!/usr/bin/env python from __future__ import division, with_statement -from six.moves import range -from six.moves import map -from six.moves import zip -from six import PY3 from math import sin, pi from random import random from contextlib import closing @@ -55,7 +51,7 @@ class SSTV(object): wav.setnchannels(self.nchannels) wav.setsampwidth(self.bits // 8) wav.setframerate(self.samples_per_sec) - wav.writeframes(data if PY3 else data.tostring()) + wav.writeframes(data) def gen_samples(self): """generates discrete samples from gen_values() diff --git a/pysstv/tests/test_sstv.py b/pysstv/tests/test_sstv.py index 4a92af0..4f59612 100644 --- a/pysstv/tests/test_sstv.py +++ b/pysstv/tests/test_sstv.py @@ -1,10 +1,8 @@ import unittest +from io import BytesIO from itertools import islice -from six.moves import zip import mock from mock import MagicMock -from six import BytesIO -from six import PY2 import hashlib from pysstv import sstv @@ -68,8 +66,7 @@ class TestSSTV(unittest.TestCase): bio = BytesIO() bio.close = MagicMock() # ignore close() so we can .getvalue() mock_open = MagicMock(return_value=bio) - ns = '__builtin__' if PY2 else 'builtins' - with mock.patch('{0}.open'.format(ns), mock_open): + with mock.patch('builtins.open', mock_open): self.s.write_wav('unittest.wav') expected = 'dd7eed880ab3360fb79ce09c469deee2' data = bio.getvalue() diff --git a/requirements.txt b/requirements.txt index 0eda817..871fae8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -six==1.10.0 Pillow==8.1.1 mock==1.0.1 nose==1.3.0 diff --git a/setup.py b/setup.py index 7a4f114..5f7a6fa 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( ], }, keywords='HAM SSTV slow-scan television Scottie Martin Robot Pasokon', - install_requires = ['Pillow', 'six'], + install_requires = ['Pillow'], license='MIT', classifiers=[ 'Development Status :: 4 - Beta', From 2f7a11abc1a401869600c911d7551bbea86c3e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Sat, 7 Jun 2014 11:32:43 +0200 Subject: [PATCH 62/87] use yield from instead of explicit version --- pysstv/color.py | 12 ++++-------- pysstv/grayscale.py | 6 ++---- pysstv/sstv.py | 3 +-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index be8096f..3076fd9 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -16,14 +16,12 @@ class ColorSSTV(GrayscaleSSTV): msec_pixel = self.SCAN / self.WIDTH image = self.pixels for index in self.COLOR_SEQ: - for item in self.before_channel(index): - yield item + yield from self.before_channel(index) for col in range(self.WIDTH): pixel = image[col, line] freq_pixel = byte_to_freq(pixel[index]) yield freq_pixel, msec_pixel - for item in self.after_channel(index): - yield item + yield from self.after_channel(index) def before_channel(self, index): return [] @@ -65,8 +63,7 @@ class ScottieS1(MartinM1): def before_channel(self, index): if index == RED: - for item in MartinM1.horizontal_sync(self): - yield item + yield from MartinM1.horizontal_sync(self) yield FREQ_BLACK, self.INTER_CH_GAP @@ -159,8 +156,7 @@ class PD90(ColorSSTV): def gen_image_tuples(self): yuv = self.image.convert('YCbCr').load() for line in range(0, self.HEIGHT, 2): - for item in self.horizontal_sync(): - yield item + yield from self.horizontal_sync() yield FREQ_BLACK, self.PORCH pixels0 = [yuv[col, line] for col in range(self.WIDTH)] pixels1 = [yuv[col, line + 1] for col in range(self.WIDTH)] diff --git a/pysstv/grayscale.py b/pysstv/grayscale.py index 75472aa..cc225cd 100644 --- a/pysstv/grayscale.py +++ b/pysstv/grayscale.py @@ -10,10 +10,8 @@ class GrayscaleSSTV(SSTV): def gen_image_tuples(self): for line in range(self.HEIGHT): - for item in self.horizontal_sync(): - yield item - for item in self.encode_line(line): - yield item + yield from self.horizontal_sync() + yield from self.encode_line(line) def encode_line(self, line): msec_pixel = self.SCAN / self.WIDTH diff --git a/pysstv/sstv.py b/pysstv/sstv.py index 5cc5f77..0dab7c0 100644 --- a/pysstv/sstv.py +++ b/pysstv/sstv.py @@ -113,8 +113,7 @@ class SSTV(object): parity_freq = FREQ_VIS_BIT1 if num_ones % 2 == 1 else FREQ_VIS_BIT0 yield parity_freq, MSEC_VIS_BIT yield FREQ_SYNC, MSEC_VIS_BIT # stop bit - for freq_tuple in self.gen_image_tuples(): - yield freq_tuple + yield from self.gen_image_tuples() for fskid_byte in map(ord, self.fskid_payload): for _ in range(6): bit = fskid_byte & 1 From 2bf26ed4320fc6b9e2bd2aca3cbd0998a2c5ffc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Sat, 7 Jun 2014 11:53:19 +0200 Subject: [PATCH 63/87] use enum for colors --- pysstv/color.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index 3076fd9..ff99b52 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -3,9 +3,13 @@ from __future__ import division from pysstv.sstv import byte_to_freq, FREQ_BLACK, FREQ_WHITE, FREQ_VIS_START from pysstv.grayscale import GrayscaleSSTV from itertools import chain +from enum import Enum -RED, GREEN, BLUE = range(3) +class Color(Enum): + red = 0 + green = 1 + blue = 2 class ColorSSTV(GrayscaleSSTV): @@ -15,22 +19,22 @@ class ColorSSTV(GrayscaleSSTV): def encode_line(self, line): msec_pixel = self.SCAN / self.WIDTH image = self.pixels - for index in self.COLOR_SEQ: - yield from self.before_channel(index) + for color in self.COLOR_SEQ: + yield from self.before_channel(color) for col in range(self.WIDTH): pixel = image[col, line] - freq_pixel = byte_to_freq(pixel[index]) + freq_pixel = byte_to_freq(pixel[color.value]) yield freq_pixel, msec_pixel - yield from self.after_channel(index) + yield from self.after_channel(color) - def before_channel(self, index): + def before_channel(self, color): return [] after_channel = before_channel class MartinM1(ColorSSTV): - COLOR_SEQ = (GREEN, BLUE, RED) + COLOR_SEQ = (Color.green, Color.blue, Color.red) VIS_CODE = 0x2c WIDTH = 320 HEIGHT = 256 @@ -38,11 +42,11 @@ class MartinM1(ColorSSTV): SCAN = 146.432 INTER_CH_GAP = 0.572 - def before_channel(self, index): - if index == GREEN: + def before_channel(self, color): + if color is Color.green: yield FREQ_BLACK, self.INTER_CH_GAP - def after_channel(self, index): + def after_channel(self, color): yield FREQ_BLACK, self.INTER_CH_GAP @@ -61,8 +65,8 @@ class ScottieS1(MartinM1): def horizontal_sync(self): return [] - def before_channel(self, index): - if index == RED: + def before_channel(self, color): + if color is Color.red: yield from MartinM1.horizontal_sync(self) yield FREQ_BLACK, self.INTER_CH_GAP @@ -114,7 +118,7 @@ class PasokonP3(ColorSSTV): Horizontal Sync - 25 time units of 1200 Hz. """ TIMEUNIT = 1000/4800. # ms - COLOR_SEQ = (RED, GREEN, BLUE) + COLOR_SEQ = (Color.red, Color.green, Color.blue) VIS_CODE = 0x71 WIDTH = 640 HEIGHT = 480+16 @@ -122,11 +126,11 @@ class PasokonP3(ColorSSTV): SCAN = WIDTH * TIMEUNIT INTER_CH_GAP = 5 * TIMEUNIT - def before_channel(self, index): - if index == self.COLOR_SEQ[0]: + def before_channel(self, color): + if color is Color.red: yield FREQ_BLACK, self.INTER_CH_GAP - def after_channel(self, index): + def after_channel(self, color): yield FREQ_BLACK, self.INTER_CH_GAP From 39abaf1a17df874610b18825f1aa05394a23530e Mon Sep 17 00:00:00 2001 From: clemibunge Date: Sat, 15 May 2021 21:21:35 +0200 Subject: [PATCH 64/87] Add mode PD290 --- pysstv/color.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pysstv/color.py b/pysstv/color.py index ff99b52..78348cc 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -193,7 +193,13 @@ class PD180(PD120): class PD240(PD120): VIS_CODE = 0x61 PIXEL = 0.382 + +class PD290(PD240): + VIS_CODE = 0x5e + WIDTH = 800 + HEIGHT = 616 + PIXEL = 0.286 MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2, Robot36, - PasokonP3, PasokonP5, PasokonP7, PD90, PD120, PD160, PD180, PD240) + PasokonP3, PasokonP5, PasokonP7, PD90, PD120, PD160, PD180, PD240, PD290) From 89254d73ec3a5894bdea0ec93f8747f84ba8d667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 19 May 2021 08:59:31 +0200 Subject: [PATCH 65/87] removed trailing whitespace --- pysstv/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysstv/color.py b/pysstv/color.py index 78348cc..acf32b3 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -193,7 +193,7 @@ class PD180(PD120): class PD240(PD120): VIS_CODE = 0x61 PIXEL = 0.382 - + class PD290(PD240): VIS_CODE = 0x5e WIDTH = 800 From 8d93d64bc54ff7aec22e7d6c9e3c6ca435c845da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 19 May 2021 09:01:40 +0200 Subject: [PATCH 66/87] README: added PD290 to cmdline usage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc4200c..922f59d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Command line usage $ python -m pysstv -h usage: __main__.py [-h] - [--mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,Robot8BW,Robot24BW}] + [--mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,PD290,Robot8BW,Robot24BW}] [--rate RATE] [--bits BITS] [--vox] [--fskid FSKID] [--chan CHAN] image.png output.wav From af5c2dff217c372c958a1fb38a2ff18ff9204a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 19 May 2021 09:02:05 +0200 Subject: [PATCH 67/87] bumped version to v0.5.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5f7a6fa..0f78f2e 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5', + version='0.5.1', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From bb193334ffab4cfc97c51cfadfd7fce4ca6a925a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 19:02:51 +0000 Subject: [PATCH 68/87] Bump pillow from 8.1.1 to 8.2.0 Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.1.1 to 8.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.1.1...8.2.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 871fae8..36ca32b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Pillow==8.1.1 +Pillow==8.2.0 mock==1.0.1 nose==1.3.0 From 3b8607f130fd285ac70a0d1402306f7b8e73a645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Tue, 8 Jun 2021 21:13:03 +0200 Subject: [PATCH 69/87] bumped version to v0.5.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0f78f2e..6a14335 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5.1', + version='0.5.2', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From ff73d8c30934025f749c202f757bada9eeb67fa6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Sep 2021 23:47:02 +0000 Subject: [PATCH 70/87] Bump pillow from 8.2.0 to 8.3.2 Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.2.0 to 8.3.2. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.2.0...8.3.2) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 36ca32b..bae9e6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Pillow==8.2.0 +Pillow==8.3.2 mock==1.0.1 nose==1.3.0 From c978ca0515b5b8bf58a946bc3007b80c4df8e778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 4 Oct 2021 09:10:05 +0200 Subject: [PATCH 71/87] bumped version to v0.5.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6a14335..22cdf64 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5.2', + version='0.5.3', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From 011d9cbcae2876f38dc4adb399c1bcd551e83988 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 00:41:30 +0000 Subject: [PATCH 72/87] Bump pillow from 8.3.2 to 9.0.0 Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.3.2 to 9.0.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.3.2...9.0.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bae9e6a..fc8e733 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Pillow==8.3.2 +Pillow==9.0.0 mock==1.0.1 nose==1.3.0 From 65241a6b71435ba2c55a9b3155f67ecb4a276ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Thu, 13 Jan 2022 09:12:45 +0100 Subject: [PATCH 73/87] bumped version to v0.5.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 22cdf64..09da19b 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5.3', + version='0.5.4', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From f4b4ca7b19c4486327e4e19eaa2c6a3eddeecb59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:32:58 +0000 Subject: [PATCH 74/87] Bump pillow from 9.0.0 to 10.0.1 Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.0.0 to 10.0.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.0.0...10.0.1) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc8e733..1038adb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Pillow==9.0.0 +Pillow==10.0.1 mock==1.0.1 nose==1.3.0 From ab0560b71efac67d1d8a47a1b901e46a7189cf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:20:16 +0100 Subject: [PATCH 75/87] Create python-package.yml --- .github/workflows/python-package.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..14a4e65 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python package + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 456040aa6b62b19197e5cf6ddc811c0c37d12df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:22:42 +0100 Subject: [PATCH 76/87] updated Debian package name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 922f59d..3ca8362 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,4 @@ Dependencies ------------ - Python 3.5 or later - - Python Imaging Library (Debian/Ubuntu package: `python-imaging`) + - Python Imaging Library (Debian/Ubuntu package: `python3-pil`) From 5d3d6a25840ee31626b2ede1808c204056f38551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:26:56 +0100 Subject: [PATCH 77/87] ran 2to3 on examples --- pysstv/examples/codegen.py | 26 +++++++++++++------------- pysstv/examples/gimp-plugin.py | 16 ++++++++-------- pysstv/examples/pyaudio_sstv.py | 2 +- pysstv/examples/repeater.py | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index 301f785..ee0aba5 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -69,10 +69,10 @@ def main(sstv_class=None): n += 1 del lut m_start, m_len = gen_matches(same_as, history, n) - for i in xrange(same_as[m_start]): + for i in range(same_as[m_start]): yield history[i][0] yield 'for (int row = 0; row < width * {0}; row += width) {{'.format(sstv.HEIGHT) - for i in xrange(same_as[m_start], same_as[m_start] + m_len - 1): + for i in range(same_as[m_start], same_as[m_start] + m_len - 1): yield ' ' + history[i][1] yield '}' yield '}}\n\n#define FREQ_COUNT {0}'.format(n) @@ -83,7 +83,7 @@ def gen_matches(same_as, history, n): cur_start = None cur_len = None cur_end = None - for i in xrange(n): + for i in range(n): if cur_start is None: tmp = same_as.get(i) if tmp is not None: @@ -114,22 +114,22 @@ def test(img_file): import struct exe = './codegen-test-executable' if not path.exists('stb_image.h'): - from urllib import urlretrieve + from urllib.request import urlretrieve urlretrieve('https://raw.githubusercontent.com/nothings/stb/master/stb_image.h', 'stb_image.h') try: for sstv_class in supported: - print 'Testing', sstv_class + print('Testing', sstv_class) gcc = Popen(['gcc', '-xc', '-lm', '-o', exe, '-'], stdin=PIPE) start = datetime.now() with open(path.join(path.dirname(__file__), 'codeman.c')) as cm: c_src = cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class))) gcc.communicate(c_src) gen_elapsed = datetime.now() - start - print ' - gengcc took', gen_elapsed + print(' - gengcc took', gen_elapsed) start = datetime.now() gen = check_output([exe, img_file]) native_elapsed = datetime.now() - start - print ' - native took', native_elapsed + print(' - native took', native_elapsed) img = Image.open(img_file) sstv = sstv_class(img, 44100, 16) start = datetime.now() @@ -145,13 +145,13 @@ def test(img_file): f.write(struct.pack('ff', freq, msec)) with file('/tmp/{0}.c'.format(mode_name), 'w') as f: f.write(c_src) - print (" ! Outputs are different, they've been saved to " + print((" ! Outputs are different, they've been saved to " "/tmp/{0}-{{c,py}}.bin, along with the C source code " - "in /tmp/{0}.c").format(mode_name) + "in /tmp/{0}.c").format(mode_name)) python_elapsed = datetime.now() - start - print ' - python took', python_elapsed - print ' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds() - print 'OK' + print(' - python took', python_elapsed) + print(' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds()) + print('OK') finally: try: remove(exe) @@ -164,4 +164,4 @@ if __name__ == '__main__': if len(argv) > 2 and argv[1] == 'test': test(argv[2]) else: - print '\n'.join(main()) + print('\n'.join(main())) diff --git a/pysstv/examples/gimp-plugin.py b/pysstv/examples/gimp-plugin.py index 2833948..89d10dc 100755 --- a/pysstv/examples/gimp-plugin.py +++ b/pysstv/examples/gimp-plugin.py @@ -6,13 +6,13 @@ from gimpfu import register, main, pdb, PF_BOOL, PF_STRING, PF_RADIO, CLIP_TO_IMAGE from PIL import Image, ImageTk -from Tkinter import Tk, Canvas, Button, Checkbutton, IntVar, Frame, LEFT, NW +from tkinter import Tk, Canvas, Button, Checkbutton, IntVar, Frame, LEFT, NW from pysstv import __main__ as pysstv_main from pysstv.examples.pyaudio_sstv import PyAudioSSTV from pysstv.sstv import SSTV from itertools import repeat from threading import Thread -from Queue import Queue, Empty +from queue import Queue, Empty from time import sleep import gimp, os @@ -118,12 +118,12 @@ class ProgressCanvas(Canvas): self.height_ratio = 1 width, height = image.size pixels = image.load() - RED, GREEN, BLUE = range(3) + RED, GREEN, BLUE = list(range(3)) self.colors = ['#{0:02x}{1:02x}{2:02x}'.format( - contrast(sum(pixels[x, y][RED] for x in xrange(width)) / width), - contrast(sum(pixels[x, y][GREEN] for x in xrange(width)) / width), - contrast(sum(pixels[x, y][BLUE] for x in xrange(width)) / width)) - for y in xrange(height)] + contrast(sum(pixels[x, y][RED] for x in range(width)) / width), + contrast(sum(pixels[x, y][GREEN] for x in range(width)) / width), + contrast(sum(pixels[x, y][BLUE] for x in range(width)) / width)) + for y in range(height)] if height / float(width) > 1.5: width *= 2 elif width < 200: @@ -237,7 +237,7 @@ register( "*", [ (PF_RADIO, "mode", "SSTV mode", "MartinM1", - tuple((n, n) for n in sorted(MODULE_MAP.iterkeys()))), + tuple((n, n) for n in sorted(MODULE_MAP.keys()))), (PF_BOOL, "vox", "Include VOX tones", True), (PF_STRING, "fskid", "FSK ID", ""), (PF_RADIO, "ptt_port", "PTT port", None, diff --git a/pysstv/examples/pyaudio_sstv.py b/pysstv/examples/pyaudio_sstv.py index ddbeed7..bf6f9f4 100644 --- a/pysstv/examples/pyaudio_sstv.py +++ b/pysstv/examples/pyaudio_sstv.py @@ -5,7 +5,7 @@ Demonstrates playing the generated samples directly using PyAudio Tested on PyAudio 0.2.7 http://people.csail.mit.edu/hubert/pyaudio/ """ -from __future__ import division + from pysstv.sstv import SSTV from time import sleep from itertools import islice diff --git a/pysstv/examples/repeater.py b/pysstv/examples/repeater.py index f3e29b3..80354e7 100644 --- a/pysstv/examples/repeater.py +++ b/pysstv/examples/repeater.py @@ -8,9 +8,9 @@ simply copying/linking images to the directory or suing an SSTV receiver such as slowrx or QSSTV. """ -from __future__ import print_function + from pyinotify import WatchManager, Notifier, ProcessEvent, IN_CREATE -from pyaudio_sstv import PyAudioSSTV +from .pyaudio_sstv import PyAudioSSTV from pysstv.color import MartinM1, MartinM2, ScottieS1, ScottieS2 from pysstv.grayscale import Robot8BW, Robot24BW from PIL import Image @@ -44,13 +44,13 @@ class EventHandler(ProcessEvent): def get_module_for_filename(filename): basename, _ = path.splitext(path.basename(filename)) - for mode, module in MODE_MAP.iteritems(): + for mode, module in MODE_MAP.items(): if mode in basename: return module def get_module_for_image(image): size = image.size - for mode in MODE_MAP.itervalues(): + for mode in MODE_MAP.values(): if all(i >= m for i, m in zip(size, (mode.WIDTH, mode.HEIGHT))): return mode From cb00cf20ad0c9a3817c6a679fd204dda5a8d3c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:28:13 +0100 Subject: [PATCH 78/87] codegen.py: use open() instead of file() --- pysstv/examples/codegen.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysstv/examples/codegen.py b/pysstv/examples/codegen.py index ee0aba5..777b026 100644 --- a/pysstv/examples/codegen.py +++ b/pysstv/examples/codegen.py @@ -138,12 +138,12 @@ def test(img_file): assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec) except AssertionError: mode_name = sstv_class.__name__ - with file('/tmp/{0}-c.bin'.format(mode_name), 'wb') as f: + with open('/tmp/{0}-c.bin'.format(mode_name), 'wb') as f: f.write(gen) - with file('/tmp/{0}-py.bin'.format(mode_name), 'wb') as f: + with open('/tmp/{0}-py.bin'.format(mode_name), 'wb') as f: for n, (freq, msec) in enumerate(sstv.gen_freq_bits()): f.write(struct.pack('ff', freq, msec)) - with file('/tmp/{0}.c'.format(mode_name), 'w') as f: + with open('/tmp/{0}.c'.format(mode_name), 'w') as f: f.write(c_src) print((" ! Outputs are different, they've been saved to " "/tmp/{0}-{{c,py}}.bin, along with the C source code " From 75b0cd46e3d25e1ab01a2e4d2817697ebd0068d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:29:42 +0100 Subject: [PATCH 79/87] README.md: removed Travis CI build status --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 3ca8362..f770eeb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ SSTV generator in pure Python ============================= -[![Build Status](https://travis-ci.org/dnet/pySSTV.svg?branch=master)](https://travis-ci.org/dnet/pySSTV) - PySSTV generates SSTV modulated WAV files from any image that PIL can open (PNG, JPEG, GIF, and many others). These WAV files then can be played by any audio player connected to a shortwave radio for example. From 0855fc6a0c2b958c1d67173b6208604206d9ea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:30:05 +0100 Subject: [PATCH 80/87] bumped version to v0.5.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 09da19b..fac584b 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5.4', + version='0.5.5', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From c454aa3a4be812e394e723155e93604c0f5844cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:44:59 +0100 Subject: [PATCH 81/87] added pyproject.toml --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fed528d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" From b22e81e65ff1074820eb59a8d59572d32120e3f2 Mon Sep 17 00:00:00 2001 From: Rehtt Date: Mon, 6 Nov 2023 15:49:37 +0800 Subject: [PATCH 82/87] Add to scale the picture proportionally to the mode size (fill the vacant part with black pixels) (#26) --- README.md | 7 +++++++ pysstv/__main__.py | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f770eeb..04afa68 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,13 @@ Command line usage --vox add VOX tones at the beginning --fskid FSKID add FSKID at the end --chan CHAN number of channels (default: mono) + --resize resize the image to the correct size + --keep-aspect-ratio keep the original aspect ratio when resizing + (and cut off excess pixels) + --keep-aspect keep the original aspect ratio when resizing + (not cut off excess pixels) + --resample which resampling filter to use for resizing + (see Pillow documentation) Python interface ---------------- diff --git a/pysstv/__main__.py b/pysstv/__main__.py index 90f3de8..bc8f662 100644 --- a/pysstv/__main__.py +++ b/pysstv/__main__.py @@ -34,6 +34,8 @@ def main(): help='resize the image to the correct size') parser.add_argument('--keep-aspect-ratio', dest='keep_aspect_ratio', action='store_true', help='keep the original aspect ratio when resizing (and cut off excess pixels)') + parser.add_argument('--keep-aspect', dest='keep_aspect', action='store_true', + help='keep the original aspect ratio when resizing (not cut off excess pixels)') parser.add_argument('--resample', dest='resample', default='lanczos', choices=('nearest', 'bicubic', 'lanczos'), help='which resampling filter to use for resizing (see Pillow documentation)') @@ -42,14 +44,17 @@ def main(): mode = module_map[args.mode] if args.resize and any(i != m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))): resample = getattr(Image, args.resample.upper()) - if args.keep_aspect_ratio: + if args.keep_aspect_ratio or args.keep_aspect: orig_ratio = image.width / image.height mode_ratio = mode.WIDTH / mode.HEIGHT crop = orig_ratio != mode_ratio else: crop = False if crop: - if orig_ratio < mode_ratio: + t = orig_ratio < mode_ratio + if args.keep_aspect: + t = orig_ratio > mode_ratio + if t: w = mode.WIDTH h = int(w / orig_ratio) else: @@ -59,6 +64,14 @@ def main(): w = mode.WIDTH h = mode.HEIGHT image = image.resize((w, h), resample) + if args.keep_aspect: + newbg = Image.new('RGB', (mode.WIDTH, mode.HEIGHT)) + if t: + newbg.paste(image, (0, int((mode.HEIGHT/2)-(h/2)))) + else: + newbg.paste(image, (int((mode.WIDTH/2)-(w/2)), 0)) + image = newbg.copy() + crop = False if crop: x = (image.width - mode.WIDTH) / 2 y = (image.height - mode.HEIGHT) / 2 From bc74dc98d63000889c6156a07e8af248ebabe2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Mon, 6 Nov 2023 08:51:14 +0100 Subject: [PATCH 83/87] bumped version to v0.5.6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fac584b..c819538 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5.5', + version='0.5.6', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu', From b43208287f34189e2b0dd8a4c86d3d3e9aede927 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Thu, 25 Jul 2024 01:09:27 +1000 Subject: [PATCH 84/87] color: Implement Scottie DX, Wraase SC-2 120 and Wraase SC-2 180. (#37) * color: Implement Wraase SC-2 120 and 180. * color: Implement Scottie DX * color: Re-factor Wraase modes `ColorSSTV` parent class actually implements the scan-line encoding we need, however the challenge is that it seems the sync pulse requirements differ for SC2-120 and SC2-180 just slightly. I haven't figured out why, partially because there seems to be little in the way of clear (and correct!) docs as to how SC2-120 is supposed to work. * color: Fix Scottie DX timing Using actual reference timing values from N7CXI Dayton paper. --- pysstv/color.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/pysstv/color.py b/pysstv/color.py index acf32b3..b52879c 100644 --- a/pysstv/color.py +++ b/pysstv/color.py @@ -77,6 +77,12 @@ class ScottieS2(ScottieS1): WIDTH = 160 +class ScottieDX(ScottieS1): + VIS_CODE = 0x4c + # http://www.barberdsp.com/downloads/Dayton%20Paper.pdf + SCAN = 345.6000 - ScottieS1.INTER_CH_GAP + + class Robot36(ColorSSTV): VIS_CODE = 0x08 WIDTH = 320 @@ -201,5 +207,49 @@ class PD290(PD240): PIXEL = 0.286 -MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2, Robot36, - PasokonP3, PasokonP5, PasokonP7, PD90, PD120, PD160, PD180, PD240, PD290) +class WraaseSC2180(ColorSSTV): + VIS_CODE = 0x37 + WIDTH = 320 + HEIGHT = 256 + COLOR_SEQ = (Color.red, Color.green, Color.blue) + + SYNC = 5.5225 + PORCH = 0.5 + SCAN = 235.0 + + def before_channel(self, color): + if color is Color.red: + yield FREQ_BLACK, self.PORCH + else: + return [] + + def after_channel(self, color): + return [] + + +class WraaseSC2120(WraaseSC2180): + VIS_CODE = 0x3f + + # NB: there are "authoritative" sounding documents that will tell you SC-2 + # 120 uses red and blue channels that have half the line width of the + # green channel. Having spent several hours trying to nut out why SC2-120 + # images weren't decoding in anything else, I can say this is utter + # bunkum. The line width is the same for all three channels, just + # shorter. + + SCAN = 156.0 + + def before_channel(self, color): + # Not sure why, but SC2-120 decoding seems to need an extra few sync + # pulses to decode in QSSTV and slowrx. Take the extra pulse out, and + # it slants something chronic and QSSTV loses sync regularly even on + # DX mode. Put it in, and both decode reliably. Go figure. SC2-180 + # works just fine without this extra pulse at the start of each + # channel. + yield FREQ_BLACK, self.PORCH + yield from super().before_channel(color) + + +MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2, ScottieDX, Robot36, + PasokonP3, PasokonP5, PasokonP7, PD90, PD120, PD160, PD180, PD240, + PD290, WraaseSC2120, WraaseSC2180) From 02795a403bb2019cc55fdd8ba20c7c4d1639b23c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:10:47 +0200 Subject: [PATCH 85/87] Bump pillow from 10.0.1 to 10.3.0 (#36) Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.0.1 to 10.3.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/10.0.1...10.3.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1038adb..b89d905 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Pillow==10.0.1 +Pillow==10.3.0 mock==1.0.1 nose==1.3.0 From f25ecac2c5546c3ee6700847543e66e0e1c9ffd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 24 Jul 2024 17:16:19 +0200 Subject: [PATCH 86/87] README.md: updated command line usage --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 04afa68..fd21db5 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,10 @@ Command line usage $ python -m pysstv -h usage: __main__.py [-h] - [--mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,PD290,Robot8BW,Robot24BW}] + [--mode {MartinM1,MartinM2,ScottieS1,ScottieS2,ScottieDX,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,PD290,WraaseSC2120,WraaseSC2180,Robot8BW,Robot24BW}] [--rate RATE] [--bits BITS] [--vox] [--fskid FSKID] - [--chan CHAN] + [--chan CHAN] [--resize] [--keep-aspect-ratio] + [--keep-aspect] [--resample {nearest,bicubic,lanczos}] image.png output.wav Converts an image to an SSTV modulated WAV file. @@ -25,9 +26,9 @@ Command line usage image.png input image file name output.wav output WAV file name - optional arguments: + options: -h, --help show this help message and exit - --mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120D160,PD180,PD240,Robot8BW,Robot24BW} + --mode {MartinM1,MartinM2,ScottieS1,ScottieS2,ScottieDX,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,PD290,WraaseSC2120,WraaseSC2180,Robot8BW,Robot24BW} image mode (default: Martin M1) --rate RATE sampling rate (default: 48000) --bits BITS bits per sample (default: 16) @@ -35,11 +36,12 @@ Command line usage --fskid FSKID add FSKID at the end --chan CHAN number of channels (default: mono) --resize resize the image to the correct size - --keep-aspect-ratio keep the original aspect ratio when resizing + --keep-aspect-ratio keep the original aspect ratio when resizing (and cut off excess pixels) - --keep-aspect keep the original aspect ratio when resizing + --keep-aspect keep the original aspect ratio when resizing (not cut off excess pixels) - --resample which resampling filter to use for resizing + --resample {nearest,bicubic,lanczos} + which resampling filter to use for resizing (see Pillow documentation) Python interface From a4c840177c5c916bc5f2be0e4c69d09998e439c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Veres-Szentkir=C3=A1lyi?= Date: Wed, 24 Jul 2024 17:16:50 +0200 Subject: [PATCH 87/87] bumped version to v0.5.7 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c819538..72796a6 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='PySSTV', - version='0.5.6', + version='0.5.7', description='Python classes for generating Slow-scan Television transmissions', author=u'András Veres-Szentkirályi', author_email='vsza@vsza.hu',