Files
2021-04-24 15:18:11 +02:00

246 lines
7.6 KiB
Python

import cv2
import numpy as np
import os
import sys
import json
import math
import time
import argparse
from enum import Enum
class Config:
QUIT_KEY = ord("q")
CONTINUE_KEY = 83 #right arrow
BACK_KEY = 81 #left arrow
REWIND_KEY = ord("r")
PLAYPAUSE_KEY = 32 #spacebar
class State(Enum):
PLAYING = 0
PAUSED = 1
WAIT = 2
END = 3
def __str__(self):
if self.value == 0: return "Playing"
if self.value == 1: return "Paused"
if self.value == 2: return "Wait"
if self.value == 3: return "End"
return "..."
def now():
return round(time.time() * 1000)
def fix_time(x):
return x if x > 0 else 1
class Presentation:
def __init__(self, config):
self.slides = config["slides"]
self.files = config["files"]
self.reset()
self.load_files()
self.add_last_slide()
def reset(self):
self.current_animation = 0
self.current_slide_i = 0
def add_last_slide(self):
last_slide_end = self.slides[-1]["end_animation"]
last_animation = len(self.files) - 1
self.slides.append(dict(
start_animation = last_slide_end,
end_animation = last_animation,
type = "last",
number = len(self.slides) + 1
))
def load_files(self):
self.caps = list()
for f in self.files:
self.caps.append(cv2.VideoCapture(f))
def next(self):
self.current_slide_i += 1
self.current_animation = self.current_slide["start_animation"]
def prev(self):
self.current_slide_i = max(0, self.current_slide_i - 1)
self.current_animation = self.current_slide["start_animation"]
def rewind(self):
self.current_animation = self.current_slide["start_animation"]
@property
def current_slide(self):
return self.slides[self.current_slide_i]
@property
def current_cap(self):
return self.caps[self.current_animation]
@property
def fps(self):
return self.current_cap.get(cv2.CAP_PROP_FPS)
def get_frame_and_state(self):
ret, frame = self.current_cap.read()
state = State.PLAYING
if ret:
self.lastframe = frame
else:
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
if self.current_slide["end_animation"] == self.current_animation + 1:
if self.current_slide["type"] == "slide":
state = State.WAIT
elif self.current_slide["type"] == "loop":
self.current_animation = self.current_slide["start_animation"]
elif self.current_slide["type"] == "last":
return self.lastframe, State.END
elif self.current_slide["type"] == "last" and self.current_slide["end_animation"] == self.current_animation:
return self.lastframe, State.END
else:
self.current_animation += 1
return self.lastframe, state
class Display:
def __init__(self, presentations, start_paused=False):
self.presentations = presentations
self.start_paused = start_paused
self.state = State.PLAYING
self.lastframe = None
self.current_presentation_i = 0
self.lag = 0
self.last_time = now()
@property
def current_presentation(self):
return self.presentations[self.current_presentation_i]
def run(self):
while True:
if self.state == State.PLAYING:
self.lastframe, self.state = self.current_presentation.get_frame_and_state()
if self.start_paused:
self.state = State.PAUSED
self.start_paused = False
if self.state == State.END:
if self.current_presentation_i == len(self.presentations) - 1:
self.quit()
else:
self.current_presentation_i += 1
self.state = State.PLAYING
self.show_video()
self.show_info()
self.handle_key()
def show_video(self):
self.lag = now() - self.last_time
self.last_time = now()
cv2.imshow("Video", self.lastframe)
def show_info(self):
info = np.zeros((130, 420), np.uint8)
font_args = (cv2.FONT_HERSHEY_SIMPLEX, 0.7, 255)
grid_x = [30, 230]
grid_y = [30, 70, 110]
cv2.putText(
info,
f"Animation: {self.current_presentation.current_animation}",
(grid_x[0], grid_y[0]),
*font_args
)
cv2.putText(
info,
f"State: {self.state}",
(grid_x[1], grid_y[0]),
*font_args
)
cv2.putText(
info,
f"Slide {self.current_presentation.current_slide['number']}/{len(self.current_presentation.slides)}",
(grid_x[0], grid_y[1]),
*font_args
)
cv2.putText(
info,
f"Slide Type: {self.current_presentation.current_slide['type']}",
(grid_x[1], grid_y[1]),
*font_args
)
cv2.putText(
info,
f"Scene {self.current_presentation_i + 1}/{len(self.presentations)}",
((grid_x[0]+grid_x[1])//2, grid_y[2]),
*font_args
)
cv2.imshow("Info", info)
def handle_key(self):
sleep_time = math.ceil(1000/self.current_presentation.fps)
key = cv2.waitKey(fix_time(sleep_time - self.lag)) & 0xFF
if key == Config.QUIT_KEY:
self.quit()
elif self.state == State.PLAYING and key == Config.PLAYPAUSE_KEY:
self.state = State.PAUSED
elif self.state == State.PAUSED and key == Config.PLAYPAUSE_KEY:
self.state = State.PLAYING
elif self.state == State.WAIT and (key == Config.CONTINUE_KEY or key == Config.PLAYPAUSE_KEY):
self.current_presentation.next()
self.state = State.PLAYING
elif self.state == State.PLAYING and key == Config.CONTINUE_KEY:
self.current_presentation.next()
elif key == Config.BACK_KEY:
if self.current_presentation.current_slide_i == 0:
self.current_presentation_i = max(0, self.current_presentation_i - 1)
self.current_presentation.reset()
self.state = State.PLAYING
else:
self.current_presentation.prev()
self.state = State.PLAYING
elif key == Config.REWIND_KEY:
self.current_presentation.rewind()
self.state = State.PLAYING
def quit(self):
cv2.destroyAllWindows()
sys.exit()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scenes", metavar="scenes", type=str, nargs="+", help="Scenes to present")
parser.add_argument("--folder", type=str, default="./presentation", help="Presentation files folder")
parser.add_argument("--start-paused", action="store_true", help="Start paused")
args = parser.parse_args()
presentations = list()
for scene in args.scenes:
config_file = os.path.join(args.folder, f"{scene}.json")
if not os.path.exists(config_file):
raise Exception(f"File {config_file} does not exist, check the scene name and make sure to use Slide as your scene base class")
config = json.load(open(config_file))
presentations.append(Presentation(config))
display = Display(presentations, start_paused=args.start_paused)
display.run()
if __name__ == "__main__":
main()