fix merge conflict

This commit is contained in:
Joël Franusic 2013-06-17 12:44:40 -07:00
commit 8f8889a4ac
6 changed files with 150 additions and 26 deletions

22
LICENSE.txt Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2013 Andras Veres-Szentkiralyi
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

71
README.md Normal file
View file

@ -0,0 +1,71 @@
SSTV generator in pure Python
=============================
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.
My main motivation was to understand the internals of SSTV in practice, so
performance is far from optimal. I tried keeping the code readable, and only
performed such optimizations that wouldn't have complicated the codebase.
Command line usage
------------------
usage: run.py [-h]
[--mode {MartinM2,MartinM1,Robot24BW,ScottieS2,ScottieS1,Robot8BW}]
[--rate RATE] [--bits BITS]
image.png output.wav
Converts an image to an SSTV modulated WAV file.
positional arguments:
image.png input image file name
output.wav output WAV file name
optional arguments:
-h, --help show this help message and exit
--mode {MartinM2,MartinM1,Robot24BW,ScottieS2,ScottieS1,Robot8BW}
image mode (default: Martin M1)
--rate RATE sampling rate (default: 48000)
--bits BITS bits per sample (default: 16)
Python interface
----------------
The `SSTV` class in the `sstv` module implements basic SSTV-related
functionality, and the classes of other modules such as `grayscale` and
`color` extend this. Most instances implement the following methods:
- `__init__` takes a PIL image, the samples per second, and the bits per
sample as a parameter, but doesn't perform any hard calculations
- `gen_freq_bits` generates tuples that describe a sine wave segment with
frequency in Hz and duration in ms
- `gen_values` generates samples between -1 and +1, performing sampling
according to the samples per second value given during construction
- `gen_samples` generates discrete samples, performing quantization
according to the bits per sample value given during construction
- `write_wav` writes the whole image to a Microsoft WAV file
The above methods all build upon those above them, for example `write_wav`
calls `gen_samples`, while latter calls `gen_values`, so typically, only
the first and the last, maybe the last two should be called directly, the
others are just listed here for the sake of completeness and to make the
flow easier to understand.
License
-------
The whole project is available under MIT license.
Useful links
------------
- receive-only "counterpart": https://github.com/windytan/slowrx
- free SSTV handbook: http://www.sstv-handbook.com/
Dependencies
------------
- Python 2.7 (tested on 2.7.5)
- Python Imaging Library (Debian/Ubuntu package: `python-imaging`)

View file

@ -70,3 +70,5 @@ class ScottieS2(ScottieS1):
VIS_CODE = 0x38
SCAN = 88.064 - ScottieS1.INTER_CH_GAP
WIDTH = 160
MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2)

View file

@ -26,8 +26,8 @@ class Robot8BW(GrayscaleSSTV):
VIS_CODE = 0x02
WIDTH = 160
HEIGHT = 120
SYNC = 10
SCAN = 56
SYNC = 7
SCAN = 60
class Robot24BW(GrayscaleSSTV):
@ -36,3 +36,5 @@ class Robot24BW(GrayscaleSSTV):
HEIGHT = 240
SYNC = 12
SCAN = 93
MODES = (Robot8BW, Robot24BW)

44
run.py Normal file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python
from __future__ import print_function
from PIL import Image
from argparse import ArgumentParser
from sys import stderr
import color, grayscale
SSTV_MODULES = [color, grayscale]
def main():
module_map = build_module_map()
parser = ArgumentParser(
description='Converts an image to an SSTV modulated WAV file.')
parser.add_argument('img_file', metavar='image.png',
help='input image file name')
parser.add_argument('wav_file', metavar='output.wav',
help='output WAV file name')
parser.add_argument('--mode', dest='mode', default='MartinM1', choices=module_map,
help='image mode (default: Martin M1)')
parser.add_argument('--rate', dest='rate', type=int, default=48000,
help='sampling rate (default: 48000)')
parser.add_argument('--bits', dest='bits', type=int, default=16,
help='bits per sample (default: 16)')
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))):
print(('Image must be at least {m.WIDTH} x {m.HEIGHT} pixels '
'for mode {m.__name__}').format(m=mode), file=stderr)
raise SystemExit(1)
s = mode(image, args.rate, args.bits)
s.write_wav(args.wav_file)
def build_module_map():
module_map = {}
for module in SSTV_MODULES:
for mode in module.MODES:
module_map[mode.__name__] = mode
return module_map
if __name__ == '__main__':
main()

31
sstv.py
View file

@ -3,7 +3,8 @@
from __future__ import division, with_statement
from math import sin, pi, floor
from random import random
import struct
from contextlib import closing
import struct, wave
FREQ_VIS_BIT1 = 1100
FREQ_SYNC = 1200
@ -25,23 +26,13 @@ class SSTV(object):
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
def write_wav(self, filename):
bytes_per_sec = self.bits // 8
fmt = '<' + self.BITS_TO_STRUCT[self.bits]
data = ''.join(struct.pack(fmt, b) for b in self.gen_samples())
payload = ''.join((
'WAVE',
'fmt ',
struct.pack('<IHHIIHH', 16, 1, 1, self.samples_per_sec,
self.samples_per_sec * bytes_per_sec, bytes_per_sec,
self.bits),
'data',
struct.pack('<I', len(data))
))
header = 'RIFF' + struct.pack('<I', len(payload) + len(data))
with file(filename, 'wb') as wav:
wav.write(header)
wav.write(payload)
wav.write(data)
with closing(wave.open(filename, 'wb')) as wav:
wav.setnchannels(1)
wav.setsampwidth(self.bits // 8)
wav.setframerate(self.samples_per_sec)
wav.writeframes(data)
def gen_samples(self):
"""generates bits from gen_values"""
@ -93,11 +84,3 @@ class SSTV(object):
def byte_to_freq(value):
return FREQ_BLACK + FREQ_RANGE * value / 255
if __name__ == '__main__':
from PIL import Image
from color import MartinM1
image = Image.open('320x256.png')
s = MartinM1(image, 48000, 16)
s.write_wav('test.wav')