Skip to content

Commit d6b87d7

Browse files
v1.0.17 (#214)
### new in v1.0.17 GPIO pins can now be used to send "keyboard commands" ### new in v1.0.16 send previous/next chapter commands to omxplayer (o/i on keyboard)
1 parent ed98e85 commit d6b87d7

File tree

8 files changed

+68
-28
lines changed

8 files changed

+68
-28
lines changed

Adafruit_Video_Looper/hello_video.py

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ def play(self, movie, loop=None, **kwargs):
4545
def pause(self):
4646
#todo add pause to HelloVideoPlayer
4747
print("pausing is not supported in HelloVideoPlayer")
48+
49+
def sendKey(self, key: str):
50+
print("sendKey not available for hello_video")
4851

4952
def is_playing(self):
5053
"""Return true if the video player is running, false otherwise."""

Adafruit_Video_Looper/image_player.py

+3
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ def play(self, image, loop=None, **kwargs):
8181

8282
def pause(self):
8383
self._isPaused = not self._isPaused
84+
85+
def sendKey(self, key: str):
86+
print("sendKey not available for image_player")
8487

8588
def is_playing(self):
8689
"""Here we need to compare for how long the image was displayed"""

Adafruit_Video_Looper/omxplayer.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,11 @@ def play(self, movie, loop=None, vol=0):
8080
close_fds=True)
8181

8282
def pause(self):
83+
self.sendKey("p")
84+
85+
def sendKey(self, key: str):
8386
if self.is_playing():
84-
self._process.stdin.write('p'.encode())
87+
self._process.stdin.write(key.encode())
8588
self._process.stdin.flush()
8689

8790
def is_playing(self):

Adafruit_Video_Looper/usb_drive_copymode.py

+17-17
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def _load_config(self, config):
6262
.translate(str.maketrans('','', ' \t\r\n.')) \
6363
.split(','))
6464

65-
def copy_files(self, paths):
66-
self.clear_screen()
65+
def _copy_files(self, paths):
66+
self._clear_screen()
6767

6868
copy_mode = self._copy_mode
6969
copy_mode_info = "(from config)"
@@ -89,7 +89,7 @@ def copy_files(self, paths):
8989
copy_mode_info = "(from config)"
9090

9191
#inform about copymode
92-
self.draw_info_text("Mode: " + copy_mode + " " + copy_mode_info)
92+
self._draw_info_text("Mode: " + copy_mode + " " + copy_mode_info)
9393

9494
if copy_mode == "replace":
9595
# iterate over target path for deleting:
@@ -101,18 +101,18 @@ def copy_files(self, paths):
101101
for x in os.listdir(path):
102102
if x[0] is not '.' and re.search('\.({0})$'.format(self._extensions), x, flags=re.IGNORECASE):
103103
#copy file
104-
self.copy_with_progress('{0}/{1}'.format(path.rstrip('/'), x), '{0}/{1}'.format(self._target_path.rstrip('/'), x))
104+
self._copy_with_progress('{0}/{1}'.format(path.rstrip('/'), x), '{0}/{1}'.format(self._target_path.rstrip('/'), x))
105105

106106
#copy loader image
107107
if self._copyloader:
108108
loader_file_path = '{0}/{1}'.format(path.rstrip('/'), 'loader.png')
109109
if os.path.exists(loader_file_path):
110-
self.clear_screen()
111-
self.draw_info_text("Copying splashscreen file...")
110+
self._clear_screen()
111+
self._draw_info_text("Copying splashscreen file...")
112112
time.sleep(2)
113-
self.copy_with_progress(loader_file_path,'/home/pi/loader.png')
113+
self._copy_with_progress(loader_file_path,'/home/pi/loader.png')
114114

115-
def draw_copy_progress(self, copied, total):
115+
def _draw_copy_progress(self, copied, total):
116116
perc = 100 * copied / total
117117
assert (isinstance(perc, float))
118118
assert (0. <= perc <= 100.)
@@ -132,7 +132,7 @@ def draw_copy_progress(self, copied, total):
132132

133133
pygame.display.update(self.borderrect)
134134

135-
def draw_info_text(self, message):
135+
def _draw_info_text(self, message):
136136
label1 = self._font.render(message, True, self._fontcolor, self._bgcolor)
137137
l1w, l1h = label1.get_size()
138138
self._screen.blit(label1, (self.screenwidth / 2 - l1w / 2, self.screenheight / 2 - l1h - self.pheight/2 - 3*self.borderthickness))
@@ -143,7 +143,7 @@ def draw_progress_text(self, progress):
143143
l1w, l1h = label1.get_size()
144144
self._screen.blit(label1, (self.screenwidth / 2 - l1w / 2, self.screenheight / 2 - l1h / 2 + self.borderthickness))
145145

146-
def clear_screen(self, full=True):
146+
def _clear_screen(self, full=True):
147147
if full:
148148
self._screen.fill(self._bgcolor)
149149
pygame.display.update()
@@ -155,7 +155,7 @@ def clear_screen(self, full=True):
155155
def check_file_exists(self,file):
156156
return (glob.glob(file + ".*") + glob.glob(file)) != []
157157

158-
def copyfile(self, src, dst, *, follow_symlinks=True):
158+
def _copyfile(self, src, dst, *, follow_symlinks=True):
159159
"""Copy data from src to dst.
160160
161161
If follow_symlinks is not set and src is a symbolic link, a new
@@ -182,10 +182,10 @@ def copyfile(self, src, dst, *, follow_symlinks=True):
182182
size = os.stat(src).st_size
183183
with open(src, 'rb') as fsrc:
184184
with open(dst, 'wb') as fdst:
185-
self.copyfileobj(fsrc, fdst, callback=self.draw_copy_progress, total=size)
185+
self._copyfileobj(fsrc, fdst, callback=self._draw_copy_progress, total=size)
186186
return dst
187187

188-
def copyfileobj(self, fsrc, fdst, callback, total, length=16 * 1024):
188+
def _copyfileobj(self, fsrc, fdst, callback, total, length=16 * 1024):
189189
copied = 0
190190
while True:
191191
buf = fsrc.read(length)
@@ -195,14 +195,14 @@ def copyfileobj(self, fsrc, fdst, callback, total, length=16 * 1024):
195195
copied += len(buf)
196196
callback(copied, total=total)
197197

198-
def copy_with_progress(self, src, dst, *, follow_symlinks=True):
198+
def _copy_with_progress(self, src, dst, *, follow_symlinks=True):
199199
if os.path.isdir(dst):
200200
dst = os.path.join(dst, os.path.basename(src))
201201

202202
# clear screen before copying
203-
self.clear_screen(False)
203+
self._clear_screen(False)
204204

205-
self.copyfile(src, dst, follow_symlinks=follow_symlinks)
205+
self._copyfile(src, dst, follow_symlinks=follow_symlinks)
206206
# shutil.copymode(src, dst)
207207
return dst
208208

@@ -212,7 +212,7 @@ def search_paths(self):
212212
"""
213213
if(self._mounter.has_nodes()):
214214
self._mounter.mount_all()
215-
self.copy_files(glob.glob(self._mount_path + '*'))
215+
self._copy_files(glob.glob(self._mount_path + '*'))
216216

217217
return [self._target_path]
218218

Adafruit_Video_Looper/video_looper.py

+20-7
Original file line numberDiff line numberDiff line change
@@ -457,15 +457,27 @@ def _handle_keyboard_shortcuts(self):
457457
self._playlist.seek(-1)
458458
self._player.stop(3)
459459
self._playbackStopped = False
460+
if event.key == pygame.K_o:
461+
self._print("o was pressed. next chapter...")
462+
self._player.sendKey("o")
463+
if event.key == pygame.K_i:
464+
self._print("i was pressed. previous chapter...")
465+
self._player.sendKey("i")
460466

461467
def _handle_gpio_control(self, pin):
462468
if self._pinMap == None:
463469
return
470+
464471
action = self._pinMap[str(pin)]
465-
self._print("pin {} triggered: {}".format(pin, action))
466-
self._playlist.set_next(action)
467-
self._player.stop(3)
468-
self._playbackStopped = False
472+
473+
self._print(f'pin {pin} triggered: {action}')
474+
475+
if action in ['K_ESCAPE', 'K_k', 'K_s', 'K_SPACE', 'K_p', 'K_b', 'K_o', 'K_i']:
476+
pygame.event.post(pygame.event.Event(pygame.KEYDOWN, key=getattr(pygame, action, None)))
477+
else:
478+
self._playlist.set_next(action)
479+
self._player.stop(3)
480+
self._playbackStopped = False
469481

470482
def _gpio_setup(self):
471483
if self._pinMap == None:
@@ -552,18 +564,19 @@ def quit(self, shutdown=False):
552564
"""Shut down the program"""
553565
self._print("quitting Video Looper")
554566

567+
if shutdown:
568+
os.system("sudo shutdown now")
569+
555570
self._playbackStopped = True
556571
self._running = False
557572
pygame.event.post(pygame.event.Event(pygame.QUIT))
558573

559574
if self._player is not None:
560575
self._player.stop()
576+
561577
if self._pinMap:
562578
GPIO.cleanup()
563579

564-
if shutdown:
565-
os.system("sudo shutdown now")
566-
567580

568581
def signal_quit(self, signal, frame):
569582
"""Shut down the program, meant to by called by signal handler."""

README.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ For a detailed tutorial visit: <https://learn.adafruit.com/raspberry-pi-video-lo
1414
There are also pre-compiled images available from <https://videolooper.de> (but they might not always contain the latest version of pi_video_looper)
1515

1616
## Changelog
17+
#### new in v1.0.17
18+
- GPIO pins can now be used to send "keyboard commands"
19+
20+
#### new in v1.0.16
21+
- send previous/next chapter commands to omxplayer (o/i on keyboard)
22+
1723
#### new in v1.0.15
1824
- one shot playback: option to enable stopping playback after each file (usefull in combination with gpio triggers)
1925

@@ -119,7 +125,7 @@ There are also pre-compiled images available from <https://videolooper.de> (but
119125
`cd pi_video_looper`
120126
`sudo ./install.sh`
121127

122-
Default player is omxplayer. Use the `no_hello_video` flag to install without the hello_video player (a lot faster to install):
128+
Default player is omxplayer. Use the `no_hello_video` flag to install without the hello_video player (a bit faster to install):
123129
`sudo ./install.sh no_hello_video`
124130

125131
## How to update
@@ -172,6 +178,8 @@ The following keyboard commands are active by default (can be disabled in the [v
172178
* "s" - Stop/Start - stops or starts playback of current file
173179
* "p" - Power off - stop playback and shutdown RPi
174180
* " " - (space bar) - Pause/Resume the omxplayer and imageplayer
181+
* "o" - next chapter (only omxplayer)
182+
* "i" - previous chapter (only omxplayer)
175183

176184
#### GPIO control:
177185
To enable GPIO control you need to set a GPIO pin mapping via the `gpio_pin_map` in the `control` section of the video_looper.ini.
@@ -181,14 +189,18 @@ action can be one of the following:
181189
* a filename as a string to play
182190
* an absoulte index number (starting with 0)
183191
* a string in the form of `+X` or `-X` (with X being an integer) for a relative jump
192+
* a keyboard command (see above) in the form of a pygame key constant (see list: https://www.pygame.org/docs/ref/key.html)
184193

185194
Here are some examples that can be set:
186195
* `"11" : 1` -> pin 11 will start the second file in the playlist
187196
* `"13" : "-2"` -> pin 13 will jump back two files
188197
* `"15" : "video.mp4"` -> pin 15 will start the file "video.mp4" (if it exists)
189198
* `"16" : "+1"` -> pin 16 will start next file
199+
* `"18" : "K_SPACE"` -> pin 18 will send space command (= pause)
200+
* `"21" : "K_p"` -> pin 21 will send "p" keyboard command (= shutdown)
190201

191202
Note: to be used as an absolute index the action needs to be an integer not a string
203+
Note 2: "keyboard_control" needs to be enabled in the ini for gpio to utilise keyboard commands
192204

193205
## Troubleshooting:
194206
* nothing happening (screen flashes once) when in copymode and new drive is plugged in?

assets/video_looper.ini

+7-1
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,15 @@ keyboard_control = true
125125
# See: https://www.raspberrypi.com/documentation/computers/raspberry-pi.html for info about the pin numbers
126126
# the pins are pulled high so you need to connect your switch to the selected pin and Ground (e.g. pin 9) - there is some debouncing done in software
127127
# The accepted settings are like this: "pinnumber" : videoindex or "pinnumber" : "filename" or "pinnumber" : "-1" or "pinnumber" : "+1"
128+
# its also possible to send "keyboard commands" like shutdown or pause (see readme for available keyboard commands)
129+
# the format follows the pygame key list (https://www.pygame.org/docs/ref/key.html) see example below. "keyboard_control" needs to be enabled for this to work
128130
# to enable GPIO set a gpio_pin_map like in the example below
129131
gpio_pin_map =
130-
#gpio_pin_map = "11" : 1, "13": 4, "15": "test.mp4", "16": "+2", "18": "-1"
132+
#gpio_pin_map = "11" : 1, "13": 4, "15": "test.mp4", "16": "+2", "18": "-1", "19": "K_SPACE", "21": "K_p"
131133
# Example - Pin 11 should start 2nd video in the playlist, Pin 13 should start 5th video, pin 15 should play file with name "test.mp4",
132134
# pin 16 should jump 2 videos ahead and pin 18 should jump one video back
135+
# pin 19 sends the "spacebar" to the looper, pausing the current video
136+
# pin 21 sends the "p" key and thus triggers the shutdown of the Raspberry Pi
133137

134138

135139
# USB drive file reader configuration follows.
@@ -255,6 +259,8 @@ title_duration = 10
255259
# video FIFO buffers are kept low to reduce clipping ends of movie at loop.
256260
# Run 'omxplayer -h' to have the full list of parameters or see
257261
# https://github.com/popcornmix/omxplayer#synopsis for all available options
262+
# on Raspberry Pi 4 and 5 you can choose the HDMI output with --display 7 or --display 2
263+
# without specifing --display both hdmi ports have the same output
258264
extra_args = --no-osd --audio_fifo 0.01 --video_fifo 0.01 --align center --font-size 55
259265

260266

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from setuptools import setup, find_packages
22

33
setup(name = 'Adafruit_Video_Looper',
4-
version = '1.0.14',
4+
version = '1.0.17',
55
author = 'Tony DiCola',
66
author_email = '[email protected]',
77
description = 'Application to turn your Raspberry Pi into a dedicated looping video playback device, good for art installations, information displays, or just playing cat videos all day.',

0 commit comments

Comments
 (0)