pySSTV/pysstv/examples/gimp-plugin.py

196 wiersze
5.9 KiB
Python
Czysty Zwykły widok Historia

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# copy to ~/.gimp-2.8/plug-ins/
# dependencies: GIMP 2.8, python-imaging-tk
2013-11-03 21:29:21 +00:00
from gimpfu import register, main, pdb, PF_BOOL, PF_STRING, PF_RADIO
from tempfile import mkstemp
from PIL import Image, ImageTk
2013-11-04 22:48:42 +00:00
from Tkinter import Tk, Canvas, Button, Checkbutton, IntVar, Frame, LEFT, NW
2013-11-03 21:29:21 +00:00
from pysstv import __main__ as pysstv_main
from pysstv.examples.pyaudio_sstv import PyAudioSSTV
2013-11-04 15:37:43 +00:00
from pysstv.sstv import SSTV
from itertools import repeat
2013-11-04 14:08:24 +00:00
from threading import Thread
2013-11-05 20:25:43 +00:00
from Queue import Queue, Empty
import os
2013-11-03 21:29:21 +00:00
MODULE_MAP = pysstv_main.build_module_map()
2013-11-04 14:08:24 +00:00
class AudioThread(Thread):
2013-11-04 14:29:54 +00:00
def __init__(self, sstv, parent):
2013-11-04 14:08:24 +00:00
Thread.__init__(self)
self.pas = PyAudioSSTV(sstv)
2013-11-04 14:29:54 +00:00
self.parent = parent
2013-11-04 14:08:24 +00:00
def run(self):
self.pas.execute()
2013-11-04 14:29:54 +00:00
self.parent.audio_thread_ended()
2013-11-04 14:08:24 +00:00
def stop(self):
if self.pas is not None:
self.pas.sampler = []
self.pas = None
2013-11-04 14:08:24 +00:00
2013-11-04 15:37:43 +00:00
class Sine1750(SSTV):
encode_line = None
def gen_freq_bits(self):
return repeat((1750, 1000))
class Transmitter(object):
2013-11-04 22:48:42 +00:00
def __init__(self, sstv, root, progress):
def encode_line_hooked(line):
2013-11-04 22:48:42 +00:00
progress.update_image(line)
return self.original_encode_line(line)
2013-11-04 22:48:42 +00:00
self.progress = progress
self.sstv = sstv
self.original_encode_line = sstv.encode_line
sstv.encode_line = encode_line_hooked
self.root = root
2013-11-04 14:29:54 +00:00
self.tx_enabled = IntVar()
2013-11-04 14:08:24 +00:00
self.audio_thread = None
2013-11-05 18:50:03 +00:00
self.stopping = False
2013-11-04 14:29:54 +00:00
def start_stop_tx(self):
if self.tx_enabled.get():
2013-11-05 18:50:03 +00:00
self.stopping = False
2013-11-04 14:29:54 +00:00
self.audio_thread = AudioThread(self.sstv, self)
self.audio_thread.start()
2013-11-05 18:37:47 +00:00
else:
self.stop()
if self.progress is not None:
self.progress.update_image()
2013-11-05 18:37:47 +00:00
def stop(self):
if self.audio_thread is not None:
2013-11-05 18:50:03 +00:00
self.stopping = True
2013-11-05 18:37:47 +00:00
self.audio_thread.stop()
2013-11-04 14:29:54 +00:00
def audio_thread_ended(self):
2013-11-05 18:50:03 +00:00
if not self.stopping:
self.tx_enabled.set(0)
2013-11-04 14:29:54 +00:00
def close(self):
self.root.destroy()
2013-11-04 14:08:24 +00:00
class CanvasUpdater(Thread):
def __init__(self, progress):
Thread.__init__(self)
self.progress = progress
self.queue = Queue()
2013-11-05 20:25:43 +00:00
self.should_run = True
def stop(self):
self.should_run = False
def update_image(self, line=None):
self.queue.put(line)
def run(self):
2013-11-05 20:25:43 +00:00
while self.should_run:
try:
self.progress.update_image(self.queue.get(timeout=0.5))
except Empty:
pass
2013-11-04 22:48:42 +00:00
class ProgressCanvas(Canvas):
def __init__(self, master, image):
self.height_ratio = 1
2013-11-04 22:48:42 +00:00
width, height = image.size
pixels = image.load()
RED, GREEN, BLUE = range(3)
self.colors = ['#{0:02x}{1:02x}{2:02x}'.format(
255 - sum(pixels[x, y][RED] for x in xrange(width)) / width,
255 - sum(pixels[x, y][GREEN] for x in xrange(width)) / width,
255 - sum(pixels[x, y][BLUE] for x in xrange(width)) / width)
for y in xrange(height)]
if height / float(width) > 1.5:
width *= 2
elif width < 200:
width *= 2
height *= 2
self.height_ratio = 2
if (width, height) != image.size:
image = image.resize((width, height))
2013-11-04 22:48:42 +00:00
Canvas.__init__(self, master, width=width, height=height)
self.tk_img = ImageTk.PhotoImage(image)
self.update_image()
def update_image(self, line=None):
image = self.tk_img
self.create_image(0, 0, anchor=NW, image=image)
if line is not None:
fill = self.colors[line]
line *= self.height_ratio
self.create_line(0, line, image.width(), line, fill=fill)
2013-11-04 22:48:42 +00:00
2013-11-03 21:29:21 +00:00
def transmit_current_image(image, drawable, mode, vox, fskid):
sstv = MODULE_MAP[mode]
png_fn = generate_png_filename()
try:
pdb.gimp_file_save(image, drawable, png_fn, png_fn)
pil_img = match_image_with_sstv_mode(Image.open(png_fn), sstv)
root = Tk()
s = sstv(pil_img, 44100, 16)
s.vox_enabled = vox
if fskid:
s.add_fskid_text(fskid)
2013-11-04 22:48:42 +00:00
pc = ProgressCanvas(root, pil_img)
pc.pack()
cu = CanvasUpdater(pc)
cu.start()
tm = Transmitter(s, root, cu)
2013-11-04 22:48:42 +00:00
tm1750 = Transmitter(Sine1750(None, 44100, 16), None, None)
buttons = Frame(root)
2013-11-04 22:01:41 +00:00
for text, tram in (('TX', tm), ('1750 Hz', tm1750)):
Checkbutton(buttons, text=text, indicatoron=False, padx=5, pady=5,
variable=tram.tx_enabled, command=tram.start_stop_tx).pack(side=LEFT)
Button(buttons, text="Close", command=tm.close).pack(side=LEFT)
buttons.pack()
root.mainloop()
2013-11-05 20:32:40 +00:00
for obj in (tm, tm1750, cu):
obj.stop()
finally:
os.remove(png_fn)
def generate_png_filename():
handle, png_fn = mkstemp(suffix='.png', prefix='pysstv-gimp-')
os.fdopen(handle).close()
return png_fn
def match_image_with_sstv_mode(image, mode):
mode_size = mode.WIDTH, mode.HEIGHT
if image.size != mode_size:
image = image.resize(mode_size, Image.ANTIALIAS)
if 'grayscale' in mode.__module__:
image = image.convert('LA').convert('RGB')
return image
register(
"pysstv_for_gimp",
"PySSTV for GIMP",
"Transmits the current image using PySSTV",
"Andras Veres-Szentkiralyi",
"Andras Veres-Szentkiralyi",
"November 2013",
"<Image>/PySSTV/Transmit...",
"*",
2013-11-03 21:29:21 +00:00
[
(PF_RADIO, "mode", "SSTV mode", "MartinM1",
tuple((n, n) for n in sorted(MODULE_MAP.iterkeys()))),
(PF_BOOL, "vox", "Include VOX tones", True),
(PF_STRING, "fskid", "FSK ID", ""),
],
[],
transmit_current_image
)
main()