mirror of
https://github.com/dnet/pySSTV.git
synced 2025-12-06 07:12:00 +01:00
fix merge conflict
This commit is contained in:
commit
8f8889a4ac
22
LICENSE.txt
Normal file
22
LICENSE.txt
Normal 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
71
README.md
Normal 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`)
|
||||
2
color.py
2
color.py
|
|
@ -70,3 +70,5 @@ class ScottieS2(ScottieS1):
|
|||
VIS_CODE = 0x38
|
||||
SCAN = 88.064 - ScottieS1.INTER_CH_GAP
|
||||
WIDTH = 160
|
||||
|
||||
MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2)
|
||||
|
|
|
|||
|
|
@ -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
44
run.py
Normal 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
31
sstv.py
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Reference in a new issue