forked from TomWhitwell/SlowMovie
-
Notifications
You must be signed in to change notification settings - Fork 1
/
slowmovie.py
executable file
·242 lines (193 loc) · 7.64 KB
/
slowmovie.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/python3
# -*- coding:utf-8 -*-
# *************************
# ** Before running this **
# ** code ensure you've **
# ** turned on SPI on **
# ** your Raspberry Pi **
# ** & installed the **
# ** Waveshare library **
# *************************
import os, time, sys, random, signal
from PIL import Image, ImageEnhance
import ffmpeg
from fractions import Fraction
try:
import configargparse as argparse
except ImportError:
import argparse
# Ensure this is the correct import for your particular screen
from waveshare_epd import epd7in5_V2 as epd_driver
# Defaults
frameIncrement = 4
timeInterval = 120
contrast = 1.0
fileTypes = [".mp4", ".mkv"]
def exithandler(signum, frame):
try:
epd_driver.epdconfig.module_exit()
finally:
sys.exit()
signal.signal(signal.SIGTERM, exithandler)
signal.signal(signal.SIGINT, exithandler)
def clamp(n, smallest, largest):
return max(smallest, min(n, largest))
def generate_frame(in_filename, out_filename, time):
(
ffmpeg
.input(in_filename, ss=time)
.filter("scale", "iw*sar", "ih")
.filter("scale", width, height, force_original_aspect_ratio=1)
.filter("pad", width, height, -1, -1)
.output(out_filename, vframes=1)
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
def check_mp4(value):
if not os.path.isfile(value):
raise argparse.ArgumentTypeError("File '%s' does not exist" % value)
if not supported_filetype(value):
raise argparse.ArgumentTypeError("'%s' is not a supported file type" % value)
return value
def check_dir(value):
if os.path.isdir(value):
return value
else:
raise argparse.ArgumentTypeError("Directory '%s' does not exist" % value)
def supported_filetype(file):
_, ext = os.path.splitext(file)
return ext.lower() in fileTypes
def video_info(file):
videoInfo = ffmpeg.probe(file)
frameCount = int(videoInfo["streams"][0]["nb_frames"])
framerate = videoInfo["streams"][0]["avg_frame_rate"]
framerate = float(Fraction(framerate))
frametime = 1000 / framerate
return frameCount, framerate, frametime
os.chdir(os.path.dirname(os.path.realpath(__file__)))
if "configargparse" in sys.modules:
parser = argparse.ArgumentParser(default_config_files=["slowmovie.conf"])
else:
parser = argparse.ArgumentParser()
parser.add_argument("-D", "--dir", default = "Videos", type = check_dir, help = "Select video directory")
parser.add_argument("-r", "--random", action = "store_true", help = "Display random frames")
parser.add_argument("-R", "--random-file", action = "store_true", help = "Play files in random order")
parser.add_argument("-f", "--file", type = check_mp4, help = "Specify an MP4 file to play")
parser.add_argument("-d", "--delay", default = timeInterval, type = int, help = "Time between updates, in seconds")
parser.add_argument("-i", "--increment", default = frameIncrement, type = int, help = "Number of frames to advance on update")
parser.add_argument("-s", "--start", type = int, help = "Start at a specific frame")
parser.add_argument("-c", "--contrast", default=contrast, type=float, help = "Adjust image contrast (default: 1.0)")
parser.add_argument("-a", "--adjust-delay", action = "store_true", help = "Reduce delay by the amount of time taken to display a frame.")
parser.add_argument("-l", "--loop", action = "store_true", help = "Loop single video.")
args = parser.parse_args()
viddir = args.dir
logdir = "logs"
if not os.path.isdir(logdir):
os.mkdir(logdir)
if not os.path.isdir(viddir):
os.mkdir(viddir)
# First we try the file argument...
currentVideo = args.file
# ...then a random video, if selected...
if not currentVideo and args.random_file:
videos = list(filter(supported_filetype, os.listdir(viddir)))
if videos:
currentVideo = os.path.join(viddir, random.choice(videos))
# ...then the last played file...
if not currentVideo and os.path.isfile("nowPlaying"):
with open("nowPlaying") as file:
lastVideo = file.readline().strip()
if os.path.isfile(lastVideo):
currentVideo = lastVideo
else:
os.remove("nowPlaying")
# ...then we look in the videos folder.
if not currentVideo:
videos = os.listdir(viddir)
for file in videos:
if supported_filetype(file):
currentVideo = os.path.join(viddir, file)
break
# If none of the above worked, exit.
if not currentVideo:
print("No videos found")
sys.exit()
print("Update interval: " + str(args.delay))
if not args.random:
print("Frame increment: " + str(args.increment))
with open("nowPlaying", "w") as file:
file.write(os.path.abspath(currentVideo))
videoFilename = os.path.basename(currentVideo)
if not args.loop:
viddir = os.path.dirname(currentVideo)
videos = list(filter(supported_filetype, os.listdir(viddir)))
fileIndex = videos.index(videoFilename)
logfile = os.path.join(logdir, videoFilename + ".progress")
epd = epd_driver.EPD()
width = epd.width
height = epd.height
frameCount, framerate, frametime = video_info(currentVideo)
if not args.random:
if args.start:
currentFrame = clamp(args.start, 0, frameCount)
print("Starting at frame " + str(currentFrame))
elif (os.path.isfile(logfile)):
# Read current frame from logfile
with open(logfile) as log:
try:
currentFrame = int(log.readline())
currentFrame = clamp(currentFrame, 0, frameCount)
except ValueError:
currentFrame = 0
else:
currentFrame = 0
lastVideo = None
while 1:
if lastVideo != currentVideo:
print(f"Playing '{videoFilename}'")
lastVideo = currentVideo
timeStart = time.perf_counter()
epd.init()
if args.random:
currentFrame = random.randint(0, frameCount)
msTimecode = "%dms" % (currentFrame * frametime)
# Use ffmpeg to extract a frame from the movie, letterbox/pillarbox it and save it as frame.bmp
generate_frame(currentVideo, "/dev/shm/frame.bmp", msTimecode)
# Open frame.bmp in PIL
pil_im = Image.open("/dev/shm/frame.bmp")
if args.contrast != 1:
enhancer = ImageEnhance.Contrast(pil_im)
pil_im = enhancer.enhance(args.contrast)
# Dither the image into a 1 bit bitmap
#pil_im = pil_im.convert(mode = "1", dither = Image.FLOYDSTEINBERG)
# display the image
#print(f"Displaying frame {currentFrame} of '{videoFilename}'")
epd.display(epd.getbuffer(pil_im))
if not args.random:
currentFrame += args.increment
if currentFrame > frameCount:
# end of video
if not args.loop:
if args.random_file:
currentVideo = os.path.join(viddir, random.choice(videos))
else:
# go to next video in folder
fileIndex += 1
if fileIndex >= len(videos):
# last video in folder; go to first
fileIndex = 0
videoFilename = videos[fileIndex]
currentVideo = os.path.join(viddir, videoFilename)
with open("nowPlaying", "w") as file:
file.write(os.path.abspath(currentVideo))
logfile = os.path.join(logdir, videoFilename + ".progress")
frameCount, framerate, frametime = video_info(currentVideo)
currentFrame = 0
with open(logfile, "w") as log:
log.write(str(currentFrame))
epd.sleep()
timeDiff = time.perf_counter() - timeStart
if args.adjust_delay:
time.sleep(max(args.delay - timeDiff, 0))
else:
time.sleep(args.delay)