Skip to content

Commit 2ab5c85

Browse files
authored
add play video capability to picframe (#456)
Preconditions: - sudo apt install vlc (if not already installed) - use_sdl2: True (video playing requires SDL2 to display the video player) New config item in configuration.yaml: - video_fit_display: False # default=False # True => scale video to match display size (video may have distortion) # False => scale video without distortion (video may have black bars) Restrictions: - videos are not played under MacOs
1 parent bfbd545 commit 2ab5c85

File tree

8 files changed

+569
-137
lines changed

8 files changed

+569
-137
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010
strategy:
1111
matrix:
12-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
12+
python-version: ["3.11" , "3.12", "3.13"]
1313

1414
steps:
1515
- uses: actions/checkout@v3

.pylintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[TYPECHECK]
2+
generated-members=cv2.*

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ classifiers=[
3030
"Programming Language :: Python :: 3",
3131
"Programming Language :: Python :: 3.11",
3232
"Programming Language :: Python :: 3.12",
33+
"Programming Language :: Python :: 3.13",
3334
"Topic :: Multimedia :: Graphics :: Viewers",
3435
]
3536
dynamic = ["version"]
@@ -43,7 +44,8 @@ dependencies = [
4344
"numpy",
4445
"ninepatch>=0.2.0",
4546
"pi_heif>=0.8.0",
46-
"mpv"
47+
"python-vlc",
48+
"opencv-python"
4749
]
4850

4951
[project.urls]

src/picframe/config/configuration_example.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ viewer:
1717
text_opacity: 1.0 # default=1.0 (0.0-1.0), alpha value of text overlay
1818
fit: False # default=False, True => scale image so all visible and leave 'gaps'
1919
# False => crop image so no 'gaps'
20+
video_fit_display: False # default=False, True => scale video to match display size (video may have distortion)
21+
# False => scale video without distortion (video may have black bars)
2022
kenburns: False # default=False, will set fit->False and blur_edges->False
2123
display_x: 0 # offset from left of screen (can be negative)
2224
display_y: 0 # offset from top of screen (can be negative)

src/picframe/controller.py

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,26 +73,37 @@ def paused(self):
7373
@paused.setter
7474
def paused(self, val: bool):
7575
self.__paused = val
76+
if self.__viewer.is_video_playing():
77+
self.__viewer.pause_video(val)
7678
pic = self.__model.get_current_pics()[0] # only refresh left text
7779
self.__viewer.reset_name_tm(pic, val, side=0, pair=self.__model.get_current_pics()[1] is not None)
7880
if self.__mqtt_config['use_mqtt']:
7981
self.publish_state()
8082

8183
def next(self):
82-
self.__next_tm = 0
84+
if self.__viewer.is_video_playing():
85+
self.__viewer.stop_video()
86+
else:
87+
self.__next_tm = 0
8388
self.__viewer.reset_name_tm()
8489
self.__force_navigate = True
8590

8691
def back(self):
92+
if self.__viewer.is_video_playing():
93+
self.__viewer.stop_video()
94+
else:
95+
self.__next_tm = 0
8796
self.__model.set_next_file_to_previous_file()
88-
self.__next_tm = 0
8997
self.__viewer.reset_name_tm()
9098
self.__force_navigate = True
9199

92100
def delete(self):
101+
if self.__viewer.is_video_playing():
102+
self.__viewer.stop_video()
103+
else:
104+
self.__next_tm = 0
93105
self.__model.delete_file()
94106
self.next() # TODO check needed to avoid skipping one as record has been deleted from model.__file_list
95-
self.__next_tm = 0
96107

97108
def set_show_text(self, txt_key=None, val="ON"):
98109
if val is True: # allow to be called with boolean from httpserver
@@ -118,7 +129,10 @@ def subdirectory(self):
118129
def subdirectory(self, dir):
119130
self.__model.subdirectory = dir
120131
self.__model.force_reload()
121-
self.__next_tm = 0
132+
if self.__viewer.is_video_playing():
133+
self.__viewer.stop_video()
134+
else:
135+
self.__next_tm = 0
122136

123137
@property
124138
def date_from(self):
@@ -136,7 +150,10 @@ def date_from(self, val):
136150
# remove from where_clause
137151
self.__model.set_where_clause('date_from')
138152
self.__model.force_reload()
139-
self.__next_tm = 0
153+
if self.__viewer.is_video_playing():
154+
self.__viewer.stop_video()
155+
else:
156+
self.__next_tm = 0
140157

141158
@property
142159
def date_to(self):
@@ -153,7 +170,10 @@ def date_to(self, val):
153170
else:
154171
self.__model.set_where_clause('date_to') # remove from where_clause
155172
self.__model.force_reload()
156-
self.__next_tm = 0
173+
if self.__viewer.is_video_playing():
174+
self.__viewer.stop_video()
175+
else:
176+
self.__next_tm = 0
157177

158178
@property
159179
def display_is_on(self):
@@ -182,7 +202,10 @@ def shuffle(self):
182202
def shuffle(self, val: bool):
183203
self.__model.shuffle = val
184204
self.__model.force_reload()
185-
self.__next_tm = 0
205+
if self.__viewer.is_video_playing():
206+
self.__viewer.stop_video()
207+
else:
208+
self.__next_tm = 0
186209
if self.__mqtt_config['use_mqtt']:
187210
self.publish_state()
188211

@@ -193,7 +216,10 @@ def fade_time(self):
193216
@fade_time.setter
194217
def fade_time(self, time):
195218
self.__model.fade_time = float(time)
196-
self.__next_tm = 0
219+
if self.__viewer.is_video_playing():
220+
self.__viewer.stop_video()
221+
else:
222+
self.__next_tm = 0
197223

198224
@property
199225
def time_delay(self):
@@ -206,7 +232,10 @@ def time_delay(self, time):
206232
if time < 5.0:
207233
time = 5.0
208234
self.__model.time_delay = time
209-
self.__next_tm = 0
235+
if self.__viewer.is_video_playing():
236+
self.__viewer.stop_video()
237+
else:
238+
self.__next_tm = 0
210239

211240
@property
212241
def brightness(self):
@@ -225,7 +254,10 @@ def matting_images(self):
225254
@matting_images.setter
226255
def matting_images(self, val):
227256
self.__viewer.set_matting_images(float(val))
228-
self.__next_tm = 0
257+
if self.__viewer.is_video_playing():
258+
self.__viewer.stop_video()
259+
else:
260+
self.__next_tm = 0
229261

230262
@property
231263
def location_filter(self):
@@ -234,7 +266,10 @@ def location_filter(self):
234266
@location_filter.setter
235267
def location_filter(self, val):
236268
self.__model.location_filter = val
237-
self.__next_tm = 0
269+
if self.__viewer.is_video_playing():
270+
self.__viewer.stop_video()
271+
else:
272+
self.__next_tm = 0
238273

239274
@property
240275
def tags_filter(self):
@@ -243,7 +278,10 @@ def tags_filter(self):
243278
@tags_filter.setter
244279
def tags_filter(self, val):
245280
self.__model.tags_filter = val
246-
self.__next_tm = 0
281+
if self.__viewer.is_video_playing():
282+
self.__viewer.stop_video()
283+
else:
284+
self.__next_tm = 0
247285

248286
def text_is_on(self, txt_key):
249287
return self.__viewer.text_is_on(txt_key)
@@ -290,16 +328,15 @@ def loop(self): # TODO exit loop gracefully and call image_cache.stop()
290328
image_attr[key] = pics[0].__dict__[field_name] # TODO nicer using namedtuple for Pic
291329
if self.__mqtt_config['use_mqtt']:
292330
self.publish_state(pics[0].fname, image_attr)
293-
video_extended = False
294331
self.__model.pause_looping = self.__viewer.is_in_transition()
295-
(loop_running, skip_image, video_time) = self.__viewer.slideshow_is_running(pics, time_delay, fade_time, self.__paused)
332+
(loop_running, skip_image, video_playing) = self.__viewer.slideshow_is_running(pics, time_delay, fade_time, self.__paused)
296333
if not loop_running:
297334
break
298-
if skip_image:
335+
if skip_image or (video_extended and not video_playing):
299336
self.__next_tm = 0
300-
if video_time is not None and not video_extended:
337+
video_extended = False
338+
if video_playing:
301339
video_extended = True
302-
self.__next_tm += (video_time - time_delay)
303340
self.__interface_peripherals.check_input()
304341

305342
def start(self):

src/picframe/model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
'text_bkg_hgt': 0.25,
2626
'text_opacity': 1.0,
2727
'fit': False,
28+
'video_fit_display': True,
2829
'kenburns': False,
2930
'display_x': 0,
3031
'display_y': 0,

0 commit comments

Comments
 (0)