-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathliveview.py
115 lines (93 loc) · 3.15 KB
/
liveview.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import sys
from io import BytesIO
from threading import Thread
from queue import Queue
import tkinter as tk
from PIL import Image, ImageTk
JPEG_START_CODE = b'\xff\xd8'
JPEG_END_CODE = b'\xff\xd9'
class FrameReader():
"""
Object that reads jpegs from an mjpeg input stream, puts them in a queue
and then displays them on a tk panel. Will not stop until the input does.
"""
def __init__(self, panel, instream):
self.panel = panel
self.instream = instream
self.framecount = 0
self.frame_queue = Queue()
self._started = False
def start_threads(self):
"""
Starts one thread to read input frames from the stream, and one to
update the panel with those frames.
"""
if self._started:
return
thread_frames = Thread(target=self.read_jpegs, args=())
thread_frames.daemon = True
thread_image = Thread(target=self.update_image, args=())
thread_image.daemon = True
thread_frames.start()
thread_image.start()
self._started = True
def read_jpegs(self):
"""
For as long as there is input, reads jpegs and adds them to the queue.
"""
frame = self.read_jpeg()
while frame:
frame = self.read_jpeg()
self.frame_queue.put(frame)
self.frame_queue.put(None) # signal end of frames
def read_jpeg(self):
"""
Reads a single jpeg from the stream.
Adapted from https://github.com/aqiank/gphoto2-liveview-example/blob/master/main.c
"""
frame_buffer = bytearray(JPEG_START_CODE)
# Discard data until the start of a frame
code = self.instream.read(2)
while code and code != JPEG_START_CODE:
code = self.instream.read(2)
while code != JPEG_END_CODE:
code = self.instream.read(2)
frame_buffer += code
if not code:
# Stream has been cut off
return None # Signal end
return BytesIO(frame_buffer)
def update_image(self):
"""
Continually takes frames from the queue, and adds them to the
given panel.
"""
frame = self.frame_queue.get()
while frame:
img = Image.open(frame)
img = ImageTk.PhotoImage(img)
self.panel.configure(image=img)
self.panel.image = img
frame = self.frame_queue.get()
def main():
"""
Creates a panel and starts the framereader
"""
# For our purposes, this is where the mjpeg stream will come from
instream = sys.stdin.buffer
# Setup tk panel with corresponding reader
root = tk.Tk()
panel = tk.Label(root)
reader = FrameReader(panel, instream)
# Read a frame and pack the panel in order to have the right window dimensions
frame = reader.read_jpeg()
img = Image.open(frame)
img = ImageTk.PhotoImage(img)
panel.configure(image=img)
panel.image = img
panel.pack(side="bottom", fill="both", expand="yes")
# Start the reader and tk's main loop
reader.start_threads()
tk.mainloop()
if __name__ == "__main__":
main()