kopia lustrzana https://github.com/dnet/pySSTV
pep8ified run.py
rodzic
3cf902ad89
commit
81f25ae4a9
93
color.py
93
color.py
|
@ -4,71 +4,72 @@ from __future__ import division
|
|||
from sstv import byte_to_freq, FREQ_BLACK
|
||||
from grayscale import GrayscaleSSTV
|
||||
|
||||
|
||||
class ColorSSTV(GrayscaleSSTV):
|
||||
RED, GREEN, BLUE = range(3)
|
||||
RED, GREEN, BLUE = range(3)
|
||||
|
||||
def encode_line(self, line):
|
||||
cs = self.COLOR_SEQ
|
||||
msec_pixel = self.SCAN / self.WIDTH
|
||||
image = self.image
|
||||
for index in cs:
|
||||
for item in self.before_channel(index):
|
||||
yield item
|
||||
for col in xrange(self.WIDTH):
|
||||
pixel = image.getpixel((col, line))
|
||||
freq_pixel = byte_to_freq(pixel[index])
|
||||
yield freq_pixel, msec_pixel
|
||||
for item in self.after_channel(index):
|
||||
yield item
|
||||
def encode_line(self, line):
|
||||
cs = self.COLOR_SEQ
|
||||
msec_pixel = self.SCAN / self.WIDTH
|
||||
image = self.image
|
||||
for index in cs:
|
||||
for item in self.before_channel(index):
|
||||
yield item
|
||||
for col in xrange(self.WIDTH):
|
||||
pixel = image.getpixel((col, line))
|
||||
freq_pixel = byte_to_freq(pixel[index])
|
||||
yield freq_pixel, msec_pixel
|
||||
for item in self.after_channel(index):
|
||||
yield item
|
||||
|
||||
def before_channel(self, index):
|
||||
return []
|
||||
def before_channel(self, index):
|
||||
return []
|
||||
|
||||
after_channel = before_channel
|
||||
after_channel = before_channel
|
||||
|
||||
|
||||
class MartinM1(ColorSSTV):
|
||||
COLOR_SEQ = (ColorSSTV.GREEN, ColorSSTV.BLUE, ColorSSTV.RED)
|
||||
VIS_CODE = 0x2c
|
||||
WIDTH = 320
|
||||
HEIGHT = 256
|
||||
SYNC = 4.862
|
||||
SCAN = 146.432
|
||||
INTER_CH_GAP = 0.572
|
||||
COLOR_SEQ = (ColorSSTV.GREEN, ColorSSTV.BLUE, ColorSSTV.RED)
|
||||
VIS_CODE = 0x2c
|
||||
WIDTH = 320
|
||||
HEIGHT = 256
|
||||
SYNC = 4.862
|
||||
SCAN = 146.432
|
||||
INTER_CH_GAP = 0.572
|
||||
|
||||
def before_channel(self, index):
|
||||
if index == ColorSSTV.GREEN:
|
||||
yield FREQ_BLACK, self.INTER_CH_GAP
|
||||
def before_channel(self, index):
|
||||
if index == ColorSSTV.GREEN:
|
||||
yield FREQ_BLACK, self.INTER_CH_GAP
|
||||
|
||||
def after_channel(self, index):
|
||||
yield FREQ_BLACK, self.INTER_CH_GAP
|
||||
def after_channel(self, index):
|
||||
yield FREQ_BLACK, self.INTER_CH_GAP
|
||||
|
||||
|
||||
class MartinM2(MartinM1):
|
||||
VIS_CODE = 0x28
|
||||
WIDTH = 160
|
||||
SCAN = 73.216
|
||||
VIS_CODE = 0x28
|
||||
WIDTH = 160
|
||||
SCAN = 73.216
|
||||
|
||||
|
||||
class ScottieS1(MartinM1):
|
||||
VIS_CODE = 0x3c
|
||||
SYNC = 9
|
||||
INTER_CH_GAP = 1.5
|
||||
SCAN = 138.24 - INTER_CH_GAP
|
||||
VIS_CODE = 0x3c
|
||||
SYNC = 9
|
||||
INTER_CH_GAP = 1.5
|
||||
SCAN = 138.24 - INTER_CH_GAP
|
||||
|
||||
def horizontal_sync(self):
|
||||
return []
|
||||
def horizontal_sync(self):
|
||||
return []
|
||||
|
||||
def before_channel(self, index):
|
||||
if index == ColorSSTV.RED:
|
||||
for item in MartinM1.horizontal_sync(self):
|
||||
yield item
|
||||
yield FREQ_BLACK, self.INTER_CH_GAP
|
||||
def before_channel(self, index):
|
||||
if index == ColorSSTV.RED:
|
||||
for item in MartinM1.horizontal_sync(self):
|
||||
yield item
|
||||
yield FREQ_BLACK, self.INTER_CH_GAP
|
||||
|
||||
|
||||
class ScottieS2(ScottieS1):
|
||||
VIS_CODE = 0x38
|
||||
SCAN = 88.064 - ScottieS1.INTER_CH_GAP
|
||||
WIDTH = 160
|
||||
VIS_CODE = 0x38
|
||||
SCAN = 88.064 - ScottieS1.INTER_CH_GAP
|
||||
WIDTH = 160
|
||||
|
||||
MODES = (MartinM1, MartinM2, ScottieS1, ScottieS2)
|
||||
|
|
54
grayscale.py
54
grayscale.py
|
@ -3,38 +3,40 @@
|
|||
from __future__ import division
|
||||
from sstv import SSTV, byte_to_freq
|
||||
|
||||
class GrayscaleSSTV(SSTV):
|
||||
def gen_freq_bits(self):
|
||||
for item in SSTV.gen_freq_bits(self):
|
||||
yield item
|
||||
for line in xrange(self.HEIGHT):
|
||||
for item in self.horizontal_sync():
|
||||
yield item
|
||||
for item in self.encode_line(line):
|
||||
yield item
|
||||
|
||||
def encode_line(self, line):
|
||||
msec_pixel = self.SCAN / self.WIDTH
|
||||
image = self.image
|
||||
for col in xrange(self.WIDTH):
|
||||
pixel = image.getpixel((col, line))
|
||||
freq_pixel = byte_to_freq(sum(pixel) / len(pixel))
|
||||
yield freq_pixel, msec_pixel
|
||||
class GrayscaleSSTV(SSTV):
|
||||
|
||||
def gen_freq_bits(self):
|
||||
for item in SSTV.gen_freq_bits(self):
|
||||
yield item
|
||||
for line in xrange(self.HEIGHT):
|
||||
for item in self.horizontal_sync():
|
||||
yield item
|
||||
for item in self.encode_line(line):
|
||||
yield item
|
||||
|
||||
def encode_line(self, line):
|
||||
msec_pixel = self.SCAN / self.WIDTH
|
||||
image = self.image
|
||||
for col in xrange(self.WIDTH):
|
||||
pixel = image.getpixel((col, line))
|
||||
freq_pixel = byte_to_freq(sum(pixel) / len(pixel))
|
||||
yield freq_pixel, msec_pixel
|
||||
|
||||
|
||||
class Robot8BW(GrayscaleSSTV):
|
||||
VIS_CODE = 0x02
|
||||
WIDTH = 160
|
||||
HEIGHT = 120
|
||||
SYNC = 7
|
||||
SCAN = 60
|
||||
VIS_CODE = 0x02
|
||||
WIDTH = 160
|
||||
HEIGHT = 120
|
||||
SYNC = 7
|
||||
SCAN = 60
|
||||
|
||||
|
||||
class Robot24BW(GrayscaleSSTV):
|
||||
VIS_CODE = 0x0A
|
||||
WIDTH = 320
|
||||
HEIGHT = 240
|
||||
SYNC = 12
|
||||
SCAN = 93
|
||||
VIS_CODE = 0x0A
|
||||
WIDTH = 320
|
||||
HEIGHT = 240
|
||||
SYNC = 12
|
||||
SCAN = 93
|
||||
|
||||
MODES = (Robot8BW, Robot24BW)
|
||||
|
|
62
run.py
62
run.py
|
@ -4,41 +4,45 @@ from __future__ import print_function
|
|||
from PIL import Image
|
||||
from argparse import ArgumentParser
|
||||
from sys import stderr
|
||||
import color, grayscale
|
||||
import color
|
||||
import 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)
|
||||
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
|
||||
module_map = {}
|
||||
for module in SSTV_MODULES:
|
||||
for mode in module.MODES:
|
||||
module_map[mode.__name__] = mode
|
||||
return module_map
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
|
136
sstv.py
136
sstv.py
|
@ -4,7 +4,8 @@ from __future__ import division, with_statement
|
|||
from math import sin, pi, floor
|
||||
from random import random
|
||||
from contextlib import closing
|
||||
import struct, wave
|
||||
import struct
|
||||
import wave
|
||||
|
||||
FREQ_VIS_BIT1 = 1100
|
||||
FREQ_SYNC = 1200
|
||||
|
@ -18,73 +19,84 @@ MSEC_VIS_START = 300
|
|||
MSEC_VIS_SYNC = 10
|
||||
MSEC_VIS_BIT = 30
|
||||
|
||||
|
||||
class SSTV(object):
|
||||
def __init__(self, image, samples_per_sec, bits):
|
||||
self.image = image
|
||||
self.samples_per_sec = samples_per_sec
|
||||
self.bits = bits
|
||||
|
||||
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
|
||||
def write_wav(self, filename):
|
||||
"""writes the whole image to a Microsoft WAV file"""
|
||||
fmt = '<' + self.BITS_TO_STRUCT[self.bits]
|
||||
data = ''.join(struct.pack(fmt, b) for b in self.gen_samples())
|
||||
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 __init__(self, image, samples_per_sec, bits):
|
||||
self.image = image
|
||||
self.samples_per_sec = samples_per_sec
|
||||
self.bits = bits
|
||||
|
||||
def gen_samples(self):
|
||||
"""generates discrete samples from gen_values(), performing quantization according to the bits per sample value given during construction
|
||||
"""
|
||||
max_value = 2 ** self.bits
|
||||
alias = 1 / max_value
|
||||
amp = max_value / 2
|
||||
lowest = -amp
|
||||
highest = amp - 1
|
||||
for value in self.gen_values():
|
||||
sample = int(round(value * amp + alias * (random() - 0.5)))
|
||||
yield max(min(highest, sample), lowest)
|
||||
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
|
||||
|
||||
def gen_values(self):
|
||||
"""generates samples between -1 and +1 from gen_freq_bits(), performing sampling according to the samples per second value given during construction
|
||||
"""
|
||||
spms = self.samples_per_sec / 1000
|
||||
param = 0
|
||||
samples = 0
|
||||
for freq, msec in self.gen_freq_bits():
|
||||
offset = param
|
||||
samples += spms * msec
|
||||
tx = floor(samples)
|
||||
for sample in xrange(int(tx)):
|
||||
t = sample / self.samples_per_sec
|
||||
param = t * freq * 2 * pi + offset
|
||||
yield sin(param)
|
||||
samples -= tx
|
||||
def write_wav(self, filename):
|
||||
"""writes the whole image to a Microsoft WAV file"""
|
||||
fmt = '<' + self.BITS_TO_STRUCT[self.bits]
|
||||
data = ''.join(struct.pack(fmt, b) for b in self.gen_samples())
|
||||
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_freq_bits(self):
|
||||
"""generates tuples (freq, msec) that describe a sine wave segment with frequency in Hz and duration in ms
|
||||
"""
|
||||
yield FREQ_VIS_START, MSEC_VIS_START
|
||||
yield FREQ_SYNC, MSEC_VIS_SYNC
|
||||
yield FREQ_VIS_START, MSEC_VIS_START
|
||||
yield FREQ_SYNC, MSEC_VIS_BIT # start bit
|
||||
vis = self.VIS_CODE
|
||||
num_ones = 0
|
||||
for _ in xrange(7):
|
||||
bit = vis & 1
|
||||
vis >>= 1
|
||||
num_ones += bit
|
||||
bit_freq = FREQ_VIS_BIT1 if bit == 1 else FREQ_VIS_BIT0
|
||||
yield bit_freq, MSEC_VIS_BIT
|
||||
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
|
||||
def gen_samples(self):
|
||||
"""generates discrete samples from gen_values()
|
||||
|
||||
def horizontal_sync(self):
|
||||
yield FREQ_SYNC, self.SYNC
|
||||
performs quantization according to
|
||||
the bits per sample value given during construction
|
||||
"""
|
||||
max_value = 2 ** self.bits
|
||||
alias = 1 / max_value
|
||||
amp = max_value / 2
|
||||
lowest = -amp
|
||||
highest = amp - 1
|
||||
for value in self.gen_values():
|
||||
sample = int(round(value * amp + alias * (random() - 0.5)))
|
||||
yield max(min(highest, sample), lowest)
|
||||
|
||||
def gen_values(self):
|
||||
"""generates samples between -1 and +1 from gen_freq_bits()
|
||||
|
||||
performs sampling according to
|
||||
the samples per second value given during construction
|
||||
"""
|
||||
spms = self.samples_per_sec / 1000
|
||||
param = 0
|
||||
samples = 0
|
||||
for freq, msec in self.gen_freq_bits():
|
||||
offset = param
|
||||
samples += spms * msec
|
||||
tx = floor(samples)
|
||||
for sample in xrange(int(tx)):
|
||||
t = sample / self.samples_per_sec
|
||||
param = t * freq * 2 * pi + offset
|
||||
yield sin(param)
|
||||
samples -= tx
|
||||
|
||||
def gen_freq_bits(self):
|
||||
"""generates tuples (freq, msec) that describe a sine wave segment
|
||||
|
||||
frequency "freq" in Hz and duration "msec" in ms
|
||||
"""
|
||||
yield FREQ_VIS_START, MSEC_VIS_START
|
||||
yield FREQ_SYNC, MSEC_VIS_SYNC
|
||||
yield FREQ_VIS_START, MSEC_VIS_START
|
||||
yield FREQ_SYNC, MSEC_VIS_BIT # start bit
|
||||
vis = self.VIS_CODE
|
||||
num_ones = 0
|
||||
for _ in xrange(7):
|
||||
bit = vis & 1
|
||||
vis >>= 1
|
||||
num_ones += bit
|
||||
bit_freq = FREQ_VIS_BIT1 if bit == 1 else FREQ_VIS_BIT0
|
||||
yield bit_freq, MSEC_VIS_BIT
|
||||
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
|
||||
|
||||
def horizontal_sync(self):
|
||||
yield FREQ_SYNC, self.SYNC
|
||||
|
||||
|
||||
def byte_to_freq(value):
|
||||
return FREQ_BLACK + FREQ_RANGE * value / 255
|
||||
return FREQ_BLACK + FREQ_RANGE * value / 255
|
||||
|
|
Ładowanie…
Reference in New Issue