PySSTV 0.5.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andras Veres-Szentkiralyi
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: PySSTV
3
+ Version: 0.5.5
4
+ Summary: Python classes for generating Slow-scan Television transmissions
5
+ Home-page: https://github.com/dnet/pySSTV
6
+ Author: András Veres-Szentkirályi
7
+ Author-email: vsza@vsza.hu
8
+ License: MIT
9
+ Keywords: HAM SSTV slow-scan television Scottie Martin Robot Pasokon
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Topic :: Communications :: Ham Radio
12
+ Classifier: Topic :: Multimedia :: Video :: Conversion
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.txt
17
+ Requires-Dist: Pillow
18
+
19
+ SSTV generator in pure Python
20
+ =============================
21
+
22
+ PySSTV generates SSTV modulated WAV files from any image that PIL can open
23
+ (PNG, JPEG, GIF, and many others). These WAV files then can be played by any
24
+ audio player connected to a shortwave radio for example.
25
+
26
+ My main motivation was to understand the internals of SSTV in practice, so
27
+ performance is far from optimal. I tried keeping the code readable, and only
28
+ performed such optimizations that wouldn't have complicated the codebase.
29
+
30
+ Command line usage
31
+ ------------------
32
+
33
+ $ python -m pysstv -h
34
+ usage: __main__.py [-h]
35
+ [--mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120,PD160,PD180,PD240,PD290,Robot8BW,Robot24BW}]
36
+ [--rate RATE] [--bits BITS] [--vox] [--fskid FSKID]
37
+ [--chan CHAN]
38
+ image.png output.wav
39
+
40
+ Converts an image to an SSTV modulated WAV file.
41
+
42
+ positional arguments:
43
+ image.png input image file name
44
+ output.wav output WAV file name
45
+
46
+ optional arguments:
47
+ -h, --help show this help message and exit
48
+ --mode {MartinM1,MartinM2,ScottieS1,ScottieS2,Robot36,PasokonP3,PasokonP5,PasokonP7,PD90,PD120D160,PD180,PD240,Robot8BW,Robot24BW}
49
+ image mode (default: Martin M1)
50
+ --rate RATE sampling rate (default: 48000)
51
+ --bits BITS bits per sample (default: 16)
52
+ --vox add VOX tones at the beginning
53
+ --fskid FSKID add FSKID at the end
54
+ --chan CHAN number of channels (default: mono)
55
+
56
+ Python interface
57
+ ----------------
58
+
59
+ The `SSTV` class in the `sstv` module implements basic SSTV-related
60
+ functionality, and the classes of other modules such as `grayscale` and
61
+ `color` extend this. Most instances implement the following methods:
62
+
63
+ - `__init__` takes a PIL image, the samples per second, and the bits per
64
+ sample as a parameter, but doesn't perform any hard calculations
65
+ - `gen_freq_bits` generates tuples that describe a sine wave segment with
66
+ frequency in Hz and duration in ms
67
+ - `gen_values` generates samples between -1 and +1, performing sampling
68
+ according to the samples per second value given during construction
69
+ - `gen_samples` generates discrete samples, performing quantization
70
+ according to the bits per sample value given during construction
71
+ - `write_wav` writes the whole image to a Microsoft WAV file
72
+
73
+ The above methods all build upon those above them, for example `write_wav`
74
+ calls `gen_samples`, while latter calls `gen_values`, so typically, only
75
+ the first and the last, maybe the last two should be called directly, the
76
+ others are just listed here for the sake of completeness and to make the
77
+ flow easier to understand.
78
+
79
+ License
80
+ -------
81
+
82
+ The whole project is available under MIT license.
83
+
84
+ Useful links
85
+ ------------
86
+
87
+ - receive-only "counterpart": https://github.com/windytan/slowrx
88
+ - free SSTV handbook: http://www.sstv-handbook.com/
89
+ - robot 36 encoder/decoder in C: https://github.com/xdsopl/robot36/
90
+
91
+ Dependencies
92
+ ------------
93
+
94
+ - Python 3.5 or later
95
+ - Python Imaging Library (Debian/Ubuntu package: `python3-pil`)
@@ -0,0 +1,23 @@
1
+ pysstv/__init__.py,sha256=v25SUBx1XfOlGVF9AQfXaLhs3tfIrfqHMUFJuPYjFBk,85
2
+ pysstv/__main__.py,sha256=npynfJzxvB22YQkznDluVwLGqsU6qkKqelX2y5F6b5M,3726
3
+ pysstv/color.py,sha256=xR_yJR3spFVIJ6ZMgVpHlITqdMih1c7_uPYg_ifYChM,5396
4
+ pysstv/grayscale.py,sha256=sKYj7pR9mzhekiwp_16aBysUWuDe5woLaAHZsIM0i3k,897
5
+ pysstv/sstv.py,sha256=5GflGizPnthvbt6c5r4WEROniLgYqUdDTG2zyuzatLc,4354
6
+ pysstv/examples/__init__.py,sha256=eYhYvkC9jXkgZDJD1V1jffApef6mXZM18M1CusgfM_Q,73
7
+ pysstv/examples/codegen.py,sha256=BFTX3virfnrjKll99JbVVLMRk23B6iOv_bnUleTTJqc,5936
8
+ pysstv/examples/get_floats.py,sha256=NT3NE2L9xAZCM0W3cChd0bDr39YuVTpda9Rzhi5wReE,559
9
+ pysstv/examples/get_freq_bits.py,sha256=yctUehsXgshxQ_vd27c9jxzBaUMV0N6JYc98NBD9uxg,622
10
+ pysstv/examples/gimp-plugin.py,sha256=n5exX47Gklq6aKtWQfk3YnANiZV0lcHtRfrMP0gKGkY,8073
11
+ pysstv/examples/overlay.py,sha256=FSldscCI9sTFwrCe-Nl0y6cC346kx_GZ7G2uMI0VL7k,533
12
+ pysstv/examples/pyaudio_sstv.py,sha256=3gAqRTKRd-_4U2oiPvUMG5Wd8vW5olMhYs0pFWfaA1k,1408
13
+ pysstv/examples/repeater.py,sha256=W1t0yOr-YRSK9sO5HVw8l_DqTg-xj7mHaWbGxbeWZok,2183
14
+ pysstv/tests/__init__.py,sha256=liZwqN9naCqnQviykDjSLoogdvPUP14wXtvAvaeylvM,71
15
+ pysstv/tests/common.py,sha256=EaO7eJQ4YvO0b70I3WdWk79G6TX90Gbuezcvbo3-5mA,262
16
+ pysstv/tests/test_color.py,sha256=MJomuWvuF3fTroThwAcdyFiFavt8_j8b7yrKQLvl1Hk,1527
17
+ pysstv/tests/test_sstv.py,sha256=SDkzdbIMpaeWfkqzZuh7k3pHyc6-cLV8tSbxLLvraec,2807
18
+ PySSTV-0.5.5.dist-info/LICENSE.txt,sha256=3QTt5CrsmuUm5urLjdgxk2aHj9T_KreABJzDefftcQc,1069
19
+ PySSTV-0.5.5.dist-info/METADATA,sha256=1bxesFyRZY_G-L7DwHcRNZUhFuFPv16zNXvgmVLyWm8,3853
20
+ PySSTV-0.5.5.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
21
+ PySSTV-0.5.5.dist-info/entry_points.txt,sha256=o1t-uDYe9lprOoofUauxJAcSUth5ImaV2d0W2NI7Yfw,48
22
+ PySSTV-0.5.5.dist-info/top_level.txt,sha256=PrRHl9SFiJU71r-_iI4MnyK62MkbuLghNLAlxW2jMWM,7
23
+ PySSTV-0.5.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.41.3)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pysstv = pysstv.__main__:main
@@ -0,0 +1 @@
1
+ pysstv
pysstv/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env python
2
+
3
+ __all__ = ['color', 'grayscale', 'sstv', 'tests', 'examples']
pysstv/__main__.py ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python
2
+
3
+ from __future__ import print_function, division
4
+ from PIL import Image
5
+ from argparse import ArgumentParser
6
+ from sys import stderr
7
+ from pysstv import color, grayscale
8
+
9
+ SSTV_MODULES = [color, grayscale]
10
+
11
+
12
+ def main():
13
+ module_map = build_module_map()
14
+ parser = ArgumentParser(
15
+ description='Converts an image to an SSTV modulated WAV file.')
16
+ parser.add_argument('img_file', metavar='image.png',
17
+ help='input image file name')
18
+ parser.add_argument('wav_file', metavar='output.wav',
19
+ help='output WAV file name')
20
+ parser.add_argument(
21
+ '--mode', dest='mode', default='MartinM1', choices=module_map,
22
+ help='image mode (default: Martin M1)')
23
+ parser.add_argument('--rate', dest='rate', type=int, default=48000,
24
+ help='sampling rate (default: 48000)')
25
+ parser.add_argument('--bits', dest='bits', type=int, default=16,
26
+ help='bits per sample (default: 16)')
27
+ parser.add_argument('--vox', dest='vox', action='store_true',
28
+ help='add VOX tones at the beginning')
29
+ parser.add_argument('--fskid', dest='fskid',
30
+ help='add FSKID at the end')
31
+ parser.add_argument('--chan', dest='chan', type=int,
32
+ help='number of channels (default: mono)')
33
+ parser.add_argument('--resize', dest='resize', action='store_true',
34
+ help='resize the image to the correct size')
35
+ parser.add_argument('--keep-aspect-ratio', dest='keep_aspect_ratio', action='store_true',
36
+ help='keep the original aspect ratio when resizing (and cut off excess pixels)')
37
+ parser.add_argument('--resample', dest='resample', default='lanczos',
38
+ choices=('nearest', 'bicubic', 'lanczos'),
39
+ help='which resampling filter to use for resizing (see Pillow documentation)')
40
+ args = parser.parse_args()
41
+ image = Image.open(args.img_file)
42
+ mode = module_map[args.mode]
43
+ if args.resize and any(i != m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))):
44
+ resample = getattr(Image, args.resample.upper())
45
+ if args.keep_aspect_ratio:
46
+ orig_ratio = image.width / image.height
47
+ mode_ratio = mode.WIDTH / mode.HEIGHT
48
+ crop = orig_ratio != mode_ratio
49
+ else:
50
+ crop = False
51
+ if crop:
52
+ if orig_ratio < mode_ratio:
53
+ w = mode.WIDTH
54
+ h = int(w / orig_ratio)
55
+ else:
56
+ h = mode.HEIGHT
57
+ w = int(orig_ratio * h)
58
+ else:
59
+ w = mode.WIDTH
60
+ h = mode.HEIGHT
61
+ image = image.resize((w, h), resample)
62
+ if crop:
63
+ x = (image.width - mode.WIDTH) / 2
64
+ y = (image.height - mode.HEIGHT) / 2
65
+ image = image.crop((x, y, mode.WIDTH + x, mode.HEIGHT + y))
66
+ elif not all(i >= m for i, m in zip(image.size, (mode.WIDTH, mode.HEIGHT))):
67
+ print(('Image must be at least {m.WIDTH} x {m.HEIGHT} pixels '
68
+ 'for mode {m.__name__}').format(m=mode), file=stderr)
69
+ raise SystemExit(1)
70
+ s = mode(image, args.rate, args.bits)
71
+ s.vox_enabled = args.vox
72
+ if args.fskid:
73
+ s.add_fskid_text(args.fskid)
74
+ if args.chan:
75
+ s.nchannels = args.chan
76
+ s.write_wav(args.wav_file)
77
+
78
+
79
+ def build_module_map():
80
+ try:
81
+ from collections import OrderedDict
82
+ module_map = OrderedDict()
83
+ except ImportError:
84
+ module_map = {}
85
+ for module in SSTV_MODULES:
86
+ for mode in module.MODES:
87
+ module_map[mode.__name__] = mode
88
+ return module_map
89
+
90
+
91
+ if __name__ == '__main__':
92
+ main()
pysstv/color.py ADDED
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import division
3
+ from pysstv.sstv import byte_to_freq, FREQ_BLACK, FREQ_WHITE, FREQ_VIS_START
4
+ from pysstv.grayscale import GrayscaleSSTV
5
+ from itertools import chain
6
+ from enum import Enum
7
+
8
+
9
+ class Color(Enum):
10
+ red = 0
11
+ green = 1
12
+ blue = 2
13
+
14
+
15
+ class ColorSSTV(GrayscaleSSTV):
16
+ def on_init(self):
17
+ self.pixels = self.image.convert('RGB').load()
18
+
19
+ def encode_line(self, line):
20
+ msec_pixel = self.SCAN / self.WIDTH
21
+ image = self.pixels
22
+ for color in self.COLOR_SEQ:
23
+ yield from self.before_channel(color)
24
+ for col in range(self.WIDTH):
25
+ pixel = image[col, line]
26
+ freq_pixel = byte_to_freq(pixel[color.value])
27
+ yield freq_pixel, msec_pixel
28
+ yield from self.after_channel(color)
29
+
30
+ def before_channel(self, color):
31
+ return []
32
+
33
+ after_channel = before_channel
34
+
35
+
36
+ class MartinM1(ColorSSTV):
37
+ COLOR_SEQ = (Color.green, Color.blue, Color.red)
38
+ VIS_CODE = 0x2c
39
+ WIDTH = 320
40
+ HEIGHT = 256
41
+ SYNC = 4.862
42
+ SCAN = 146.432
43
+ INTER_CH_GAP = 0.572
44
+
45
+ def before_channel(self, color):
46
+ if color is Color.green:
47
+ yield FREQ_BLACK, self.INTER_CH_GAP
48
+
49
+ def after_channel(self, color):
50
+ yield FREQ_BLACK, self.INTER_CH_GAP
51
+
52
+
53
+ class MartinM2(MartinM1):
54
+ VIS_CODE = 0x28
55
+ WIDTH = 160
56
+ SCAN = 73.216
57
+
58
+
59
+ class ScottieS1(MartinM1):
60
+ VIS_CODE = 0x3c
61
+ SYNC = 9
62
+ INTER_CH_GAP = 1.5
63
+ SCAN = 138.24 - INTER_CH_GAP
64
+
65
+ def horizontal_sync(self):
66
+ return []
67
+
68
+ def before_channel(self, color):
69
+ if color is Color.red:
70
+ yield from MartinM1.horizontal_sync(self)
71
+ yield FREQ_BLACK, self.INTER_CH_GAP
72
+
73
+
74
+ class ScottieS2(ScottieS1):
75
+ VIS_CODE = 0x38
76
+ SCAN = 88.064 - ScottieS1.INTER_CH_GAP
77
+ WIDTH = 160
78
+
79
+
80
+ class Robot36(ColorSSTV):
81
+ VIS_CODE = 0x08
82
+ WIDTH = 320
83
+ HEIGHT = 240
84
+ SYNC = 9
85
+ INTER_CH_GAP = 4.5
86
+ Y_SCAN = 88
87
+ C_SCAN = 44
88
+ PORCH = 1.5
89
+ SYNC_PORCH = 3
90
+ INTER_CH_FREQS = [None, FREQ_WHITE, FREQ_BLACK]
91
+
92
+ def on_init(self):
93
+ self.yuv = self.image.convert('YCbCr').load()
94
+
95
+ def encode_line(self, line):
96
+ pixels = [self.yuv[col, line] for col in range(self.WIDTH)]
97
+ channel = 2 - (line % 2)
98
+ y_pixel_time = self.Y_SCAN / self.WIDTH
99
+ uv_pixel_time = self.C_SCAN / self.WIDTH
100
+ return chain(
101
+ [(FREQ_BLACK, self.SYNC_PORCH)],
102
+ ((byte_to_freq(p[0]), y_pixel_time) for p in pixels),
103
+ [(self.INTER_CH_FREQS[channel], self.INTER_CH_GAP),
104
+ (FREQ_VIS_START, self.PORCH)],
105
+ ((byte_to_freq(p[channel]), uv_pixel_time) for p in pixels))
106
+
107
+
108
+ class PasokonP3(ColorSSTV):
109
+ """
110
+ [ VIS code or horizontal sync here ]
111
+ Back porch - 5 time units of black (1500 Hz).
112
+ Red component - 640 pixels of 1 time unit each.
113
+ Gap - 5 time units of black.
114
+ Green component - 640 pixels of 1 time unit each.
115
+ Gap - 5 time units of black.
116
+ Blue component - 640 pixels of 1 time unit each.
117
+ Front porch - 5 time units of black.
118
+ Horizontal Sync - 25 time units of 1200 Hz.
119
+ """
120
+ TIMEUNIT = 1000/4800. # ms
121
+ COLOR_SEQ = (Color.red, Color.green, Color.blue)
122
+ VIS_CODE = 0x71
123
+ WIDTH = 640
124
+ HEIGHT = 480+16
125
+ SYNC = 25 * TIMEUNIT
126
+ SCAN = WIDTH * TIMEUNIT
127
+ INTER_CH_GAP = 5 * TIMEUNIT
128
+
129
+ def before_channel(self, color):
130
+ if color is Color.red:
131
+ yield FREQ_BLACK, self.INTER_CH_GAP
132
+
133
+ def after_channel(self, color):
134
+ yield FREQ_BLACK, self.INTER_CH_GAP
135
+
136
+
137
+ class PasokonP5(PasokonP3):
138
+ TIMEUNIT = 1000/3200. # ms
139
+ VIS_CODE = 0x72
140
+ SYNC = 25 * TIMEUNIT
141
+ SCAN = PasokonP3.WIDTH * TIMEUNIT
142
+ INTER_CH_GAP = 5 * TIMEUNIT
143
+
144
+ class PasokonP7(PasokonP3):
145
+ TIMEUNIT = 1000/2400. # ms
146
+ VIS_CODE = 0xF3
147
+ SYNC = 25 * TIMEUNIT
148
+ SCAN = PasokonP3.WIDTH * TIMEUNIT
149
+ INTER_CH_GAP = 5 * TIMEUNIT
150
+
151
+
152
+ class PD90(ColorSSTV):
153
+ VIS_CODE = 0x63
154
+ WIDTH = 320
155
+ HEIGHT = 256
156
+ SYNC = 20
157
+ PORCH = 2.08
158
+ PIXEL = 0.532
159
+
160
+ def gen_image_tuples(self):
161
+ yuv = self.image.convert('YCbCr').load()
162
+ for line in range(0, self.HEIGHT, 2):
163
+ yield from self.horizontal_sync()
164
+ yield FREQ_BLACK, self.PORCH
165
+ pixels0 = [yuv[col, line] for col in range(self.WIDTH)]
166
+ pixels1 = [yuv[col, line + 1] for col in range(self.WIDTH)]
167
+ for p in pixels0:
168
+ yield byte_to_freq(p[0]), self.PIXEL
169
+ for p0, p1 in zip(pixels0, pixels1):
170
+ yield byte_to_freq((p0[2] + p1[2]) / 2), self.PIXEL
171
+ for p0, p1 in zip(pixels0, pixels1):
172
+ yield byte_to_freq((p0[1] + p1[1]) / 2), self.PIXEL
173
+ for p in pixels1:
174
+ yield byte_to_freq(p[0]), self.PIXEL
175
+
176
+
177
+ class PD120(PD90):
178
+ VIS_CODE = 0x5f
179
+ WIDTH = 640
180
+ HEIGHT = 496
181
+ PIXEL = 0.19
182
+
183
+ class PD160(PD90):
184
+ VIS_CODE = 0x62
185
+ WIDTH = 512
186
+ HEIGHT = 400
187
+ PIXEL = 0.382
188
+
189
+ class PD180(PD120):
190
+ VIS_CODE = 0x60
191
+ PIXEL = 0.286
192
+
193
+ class PD240(PD120):
194
+ VIS_CODE = 0x61
195
+ PIXEL = 0.382
196
+
197
+ class PD290(PD240):
198
+ VIS_CODE = 0x5e
199
+ WIDTH = 800
200
+ HEIGHT = 616
201
+ PIXEL = 0.286
202
+
203
+
204
+ MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2, Robot36,
205
+ PasokonP3, PasokonP5, PasokonP7, PD90, PD120, PD160, PD180, PD240, PD290)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env python
2
+
3
+ __all__ = ['overlay', 'pyaudio_sstv', 'repeater']
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python
2
+
3
+ class Image(object):
4
+ def __init__(self, content):
5
+ self.content = content
6
+
7
+ def load(self):
8
+ return self
9
+
10
+ def __getitem__(self, item):
11
+ if isinstance(item, tuple):
12
+ x, y = item
13
+ return Image('{0}[(ROW({1}) + COL({2})) * 3'.format(self.content, y, x))
14
+ elif isinstance(item, int):
15
+ return Image('{0} + RGB({1})]'.format(self.content, item))
16
+ else:
17
+ raise NotImplementedError()
18
+
19
+ def __rmul__(self, n):
20
+ return Image('({1} * {0})'.format(self.content, float(n)))
21
+
22
+ def __mul__(self, n):
23
+ return Image('({0} * {1})'.format(self.content, float(n)))
24
+
25
+ def __rtruediv__(self, n):
26
+ return Image('({1} / {0})'.format(self.content, n))
27
+
28
+ def __truediv__(self, n):
29
+ return Image('({0} / {1})'.format(self.content, n))
30
+
31
+ def __radd__(self, n):
32
+ return Image('({1} + {0})'.format(self.content, n))
33
+
34
+ def __add__(self, n):
35
+ return Image('({0} + {1})'.format(self.content, n))
36
+
37
+ def __str__(self):
38
+ return self.content
39
+
40
+ from pysstv.color import MartinM1, MartinM2, PasokonP3, PasokonP5, PasokonP7
41
+ import re
42
+
43
+ supported = [MartinM1, MartinM2, PasokonP3, PasokonP5, PasokonP7]
44
+ ROW_RE = re.compile(r'ROW\(\d+\)')
45
+
46
+ def main(sstv_class=None):
47
+ if sstv_class is None:
48
+ sstv_class = MartinM1
49
+ elif sstv_class not in supported:
50
+ raise NotImplementedError()
51
+ sstv = sstv_class(Image('img'), 44100, 16)
52
+ n = 0
53
+ yield '#define ROW(x) x'
54
+ yield '#define COL(x) x'
55
+ yield '#define RGB(x) x'
56
+ yield 'void convert(unsigned char *img, float *freqs, float *msecs, const int width) {\nint frq = 0;'
57
+ history = []
58
+ lut = {}
59
+ same_as = {}
60
+ for freq, msec in sstv.gen_freq_bits():
61
+ printed = 'freqs[frq] = {1}; msecs[frq++] = {2};'.format(n, freq, msec)
62
+ key = ROW_RE.sub('row', printed)
63
+ old = lut.get(key)
64
+ if old is not None:
65
+ same_as[n] = old
66
+ else:
67
+ lut[key] = n
68
+ history.append((printed, key))
69
+ n += 1
70
+ del lut
71
+ m_start, m_len = gen_matches(same_as, history, n)
72
+ for i in range(same_as[m_start]):
73
+ yield history[i][0]
74
+ yield 'for (int row = 0; row < width * {0}; row += width) {{'.format(sstv.HEIGHT)
75
+ for i in range(same_as[m_start], same_as[m_start] + m_len - 1):
76
+ yield ' ' + history[i][1]
77
+ yield '}'
78
+ yield '}}\n\n#define FREQ_COUNT {0}'.format(n)
79
+
80
+
81
+
82
+ def gen_matches(same_as, history, n):
83
+ cur_start = None
84
+ cur_len = None
85
+ cur_end = None
86
+ for i in range(n):
87
+ if cur_start is None:
88
+ tmp = same_as.get(i)
89
+ if tmp is not None:
90
+ cur_len = 1
91
+ cur_start = i
92
+ cur_end = tmp
93
+ else:
94
+ tmp = same_as.get(i)
95
+ if tmp is not None and history[tmp][1] == history[cur_end + 1][1] and cur_start > cur_end:
96
+ cur_len += 1
97
+ cur_end += 1
98
+ else:
99
+ if tmp is not None and history[tmp][1] == history[cur_end + 1][1]:
100
+ return cur_start, cur_len
101
+ tmp = same_as.get(i)
102
+ if tmp is None:
103
+ cur_start = None
104
+ else:
105
+ cur_len = 1
106
+ cur_start = i
107
+ cur_end = tmp
108
+
109
+ def test(img_file):
110
+ from subprocess import Popen, PIPE, check_output
111
+ from os import remove, path
112
+ from PIL import Image
113
+ from datetime import datetime
114
+ import struct
115
+ exe = './codegen-test-executable'
116
+ if not path.exists('stb_image.h'):
117
+ from urllib.request import urlretrieve
118
+ urlretrieve('https://raw.githubusercontent.com/nothings/stb/master/stb_image.h', 'stb_image.h')
119
+ try:
120
+ for sstv_class in supported:
121
+ print('Testing', sstv_class)
122
+ gcc = Popen(['gcc', '-xc', '-lm', '-o', exe, '-'], stdin=PIPE)
123
+ start = datetime.now()
124
+ with open(path.join(path.dirname(__file__), 'codeman.c')) as cm:
125
+ c_src = cm.read().replace('#include "codegen.c"', '\n'.join(main(sstv_class)))
126
+ gcc.communicate(c_src)
127
+ gen_elapsed = datetime.now() - start
128
+ print(' - gengcc took', gen_elapsed)
129
+ start = datetime.now()
130
+ gen = check_output([exe, img_file])
131
+ native_elapsed = datetime.now() - start
132
+ print(' - native took', native_elapsed)
133
+ img = Image.open(img_file)
134
+ sstv = sstv_class(img, 44100, 16)
135
+ start = datetime.now()
136
+ try:
137
+ for n, (freq, msec) in enumerate(sstv.gen_freq_bits()):
138
+ assert gen[n * 8:(n + 1) * 8] == struct.pack('ff', freq, msec)
139
+ except AssertionError:
140
+ mode_name = sstv_class.__name__
141
+ with open('/tmp/{0}-c.bin'.format(mode_name), 'wb') as f:
142
+ f.write(gen)
143
+ with open('/tmp/{0}-py.bin'.format(mode_name), 'wb') as f:
144
+ for n, (freq, msec) in enumerate(sstv.gen_freq_bits()):
145
+ f.write(struct.pack('ff', freq, msec))
146
+ with open('/tmp/{0}.c'.format(mode_name), 'w') as f:
147
+ f.write(c_src)
148
+ print((" ! Outputs are different, they've been saved to "
149
+ "/tmp/{0}-{{c,py}}.bin, along with the C source code "
150
+ "in /tmp/{0}.c").format(mode_name))
151
+ python_elapsed = datetime.now() - start
152
+ print(' - python took', python_elapsed)
153
+ print(' - speedup:', python_elapsed.total_seconds() / native_elapsed.total_seconds())
154
+ print('OK')
155
+ finally:
156
+ try:
157
+ remove(exe)
158
+ except OSError:
159
+ pass
160
+
161
+
162
+ if __name__ == '__main__':
163
+ from sys import argv
164
+ if len(argv) > 2 and argv[1] == 'test':
165
+ test(argv[2])
166
+ else:
167
+ print('\n'.join(main()))
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ This example streams the raw floating point samples to stdout in 4-byte
5
+ single precision format, so that it can be processed outside PySSTV.
6
+
7
+ Usage example: get_floats.py | play -r 44100 -t f32 -c 1 --norm -
8
+ """
9
+
10
+ from PIL import Image
11
+ from pysstv.grayscale import Robot8BW
12
+ import struct, sys
13
+
14
+ def main():
15
+ img = Image.open("160x120bw.png")
16
+ sstv = Robot8BW(img, 44100, 16)
17
+ sstv.vox_enabled = True
18
+ for value in sstv.gen_values():
19
+ sys.stdout.write(struct.pack('f', value))
20
+
21
+ if __name__ == '__main__':
22
+ main()
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ This example streams the raw floating point (freq, msec) tuples to stdout
5
+ in 4-byte single precision format (8 bytes per tuple), so that it can be
6
+ processed outside PySSTV.
7
+
8
+ Usage example using unixsstv/gen_values:
9
+ get_freq_bits.py | gen_values 44100 | play -r 44100 -t f32 -c 1 --norm -
10
+ """
11
+
12
+ from PIL import Image
13
+ from pysstv.color import MartinM1
14
+ import struct, sys
15
+
16
+ def main():
17
+ img = Image.open("320x256rgb.png")
18
+ sstv = MartinM1(img, 44100, 16)
19
+ for freq, msec in sstv.gen_freq_bits():
20
+ sys.stdout.write(struct.pack('ff', freq, msec))
21
+
22
+ if __name__ == '__main__':
23
+ main()