From 90227f52c0d6540c1181a596cc4f6059a7d992e6 Mon Sep 17 00:00:00 2001 From: Antoine Lima Date: Mon, 22 Oct 2018 21:33:50 +0200 Subject: [PATCH] PiCam replay --- .spyproject/codestyle.ini | 6 +++ .spyproject/encoding.ini | 6 +++ .spyproject/vcs.ini | 7 ++++ .spyproject/workspace.ini | 10 +++++ com.py | 17 ++++---- modules/game.py | 81 ++++++++++++++++++++------------------- replay.py | 48 +++++++++++++++++------ 7 files changed, 116 insertions(+), 59 deletions(-) create mode 100644 .spyproject/codestyle.ini create mode 100644 .spyproject/encoding.ini create mode 100644 .spyproject/vcs.ini create mode 100644 .spyproject/workspace.ini diff --git a/.spyproject/codestyle.ini b/.spyproject/codestyle.ini new file mode 100644 index 0000000..b827aa5 --- /dev/null +++ b/.spyproject/codestyle.ini @@ -0,0 +1,6 @@ +[codestyle] +indentation = True + +[main] +version = 0.1.0 + diff --git a/.spyproject/encoding.ini b/.spyproject/encoding.ini new file mode 100644 index 0000000..d1c1d17 --- /dev/null +++ b/.spyproject/encoding.ini @@ -0,0 +1,6 @@ +[encoding] +text_encoding = utf-8 + +[main] +version = 0.1.0 + diff --git a/.spyproject/vcs.ini b/.spyproject/vcs.ini new file mode 100644 index 0000000..30bb7fa --- /dev/null +++ b/.spyproject/vcs.ini @@ -0,0 +1,7 @@ +[vcs] +use_version_control = False +version_control_system = + +[main] +version = 0.1.0 + diff --git a/.spyproject/workspace.ini b/.spyproject/workspace.ini new file mode 100644 index 0000000..700d2b4 --- /dev/null +++ b/.spyproject/workspace.ini @@ -0,0 +1,10 @@ +[workspace] +restore_data_on_startup = True +save_data_on_exit = True +save_history = True +save_non_project_files = False + +[main] +version = 0.1.0 +recent_files = ['/home/antoine/prog/PR/source-rasp/main.py', '/home/antoine/prog/PR/source-rasp/modules/game.py', '/home/antoine/prog/PR/content/settings.json', '/home/antoine/prog/PR/source-rasp/replay.py', '/home/antoine/prog/PR/source-rasp/com.py'] + diff --git a/com.py b/com.py index 9c3f4bd..57bd491 100644 --- a/com.py +++ b/com.py @@ -7,33 +7,32 @@ Created on Wed Apr 18 18:34:40 2018 """ import logging -import autopy import serial -from os.path import dirname, abspath, join, isfile as exists -from autopy.key import tap as PressKey, Code as KeyCode +from os.path import isfile as exists +from autopy.key import tap as PressKey, Code as KeyCode from threading import Thread from player import Side class InputThread(Thread): keyButtonBindings = [KeyCode.ESCAPE, KeyCode.UP_ARROW, KeyCode.LEFT_ARROW, KeyCode.RIGHT_ARROW, KeyCode.DOWN_ARROW, KeyCode.RETURN] - + def __init__(self, dispatcher, side): Thread.__init__(self) self.side = side self.dispatcher = dispatcher self.continueRunning = True self.path = '/dev/ttyUSB0' if self.side==Side.Left else '/dev/ttyUSB1' - + if exists(self.path): self.arduino = serial.Serial(self.path, 9600, timeout=1) else: raise RuntimeError('No arduino connected on the {} side'.format(self.side.name.lower())) - + def run(self): while self.arduino.isOpen(): msg = self.arduino.readline()[:-1] - + if msg: parsedMessage = self.parseMsg(msg) if 'butn' in parsedMessage: @@ -46,14 +45,14 @@ class InputThread(Thread): def stop(self): self.continueRunning = False self.arduino.close() - + def sendKeyStroke(self, msg): if 'butn' in msg: button = int(msg['butn']) print({button: ['ESCAPE', 'UP_ARROW', 'LEFT_ARROW', 'RIGHT_ARROW', 'DOWN_ARROW', 'RETURN'][button]}) key = InputThread.keyButtonBindings[button] PressKey(key, []) - + def parseMsg(self, msg): parts = msg.split(':') return {parts[0]: parts[1], 'source': self.side} diff --git a/modules/game.py b/modules/game.py index 6a23846..9515ecc 100644 --- a/modules/game.py +++ b/modules/game.py @@ -18,6 +18,7 @@ from PyQt5.QtMultimediaWidgets import QVideoWidget from player import Side, PlayerGuest from replay import Replay from module import Module +from settings import Settings import modules from ui.game_ui import Ui_Form as GameWidget @@ -25,25 +26,25 @@ class GameOverChecker(): def __init__(self, conditionType, limit): self.conditionType = conditionType self.limit = limit - + def check(self, time, scores): ''' Checks if a game is over and return the winner if that's the case Returns the winning side or Side.Undef otherwise - + Takes the game time is seconds and a list containing the two scores ''' - - # Gets the index of the highest scoring player + + # Gets the index of the highest scoring player bestPlayer = max(scores, key=scores.get) - + if self.conditionType=='score' and scores[bestPlayer]>=self.limit: return bestPlayer elif self.conditionType=='time' and time>self.limit: return bestPlayer else: return Side.Undef - + class GameModule(Module): def __init__(self, parent=None): super().__init__(parent, GameWidget()) @@ -55,19 +56,20 @@ class GameModule(Module): # Button connections self.ui.btnScore1.clicked.connect(lambda: self.goal(Side.Left)) self.ui.btnScore2.clicked.connect(lambda: self.goal(Side.Right)) - - self.replayer = Replay() - + + self.replayer = Replay(Side.Left) + def load(self): logging.debug('Loading GameModule') self.gameStartTime = QTime.currentTime() self.timerUpdateChrono.start(1000) self.ui.lcdChrono.display(QTime(0,0).toString("hh:mm:ss")) - + self.showingReplay = False + self.replayer.start_recording() self.gameoverChecker = GameOverChecker('score', 10) - + if all([len(val)==0 for val in self.players.values()]): self.players[Side.Left ].append(PlayerGuest) self.players[Side.Right].append(PlayerGuest) @@ -79,17 +81,18 @@ class GameModule(Module): logging.debug('Unloading GameModule') del self.gameStartTime self.timerUpdateChrono.stop() - + self.replayer.stop_recording() + def other(self, **kwargs): logging.debug('Other GameModule') - + for key, val in kwargs.items(): if key=='goal' and 'source' in kwargs: self.goal(kwargs['source']) - + elif key=='players': self.players = val - + def resizeEvent(self, event): # 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin) btnDiameter = self.mainwin.width()*0.4 @@ -98,7 +101,7 @@ class GameModule(Module): self.ui.btnScore2.setMinimumSize(btnDiameter, btnDiameter) self.ui.btnScore1.setMask(region) self.ui.btnScore2.setMask(region) - + QtWidgets.QWidget.resizeEvent(self, event) def keyPressEvent(self, e): @@ -112,54 +115,54 @@ class GameModule(Module): def updateChrono(self): # Updated each second self.ui.lcdChrono.display(QTime(0,0).addSecs(self.getGameTime()).toString("hh:mm:ss")) - - # Don't check scores while showing a replay to avoid closing the engame screen too soon + + # Don't check scores while showing a replay to avoid closing the engame screen too soon if not self.showingReplay: self.checkEndGame() def getGameTime(self): return self.gameStartTime.secsTo(QTime.currentTime()) - + def updateScores(self): self.ui.btnScore1.setText(str(self.scores[Side.Left])) self.ui.btnScore2.setText(str(self.scores[Side.Right])) self.checkEndGame() - + def goal(self, side): if side not in Side: logging.error('Wrong goal side: {}'.format(side)) else: self.scores[side] += 1 - + # Show replay # May require `sudo apt-get install qtmultimedia5-examples` in order to install the right libraries - replayFile = self.mainwin.getContent('Replay {}.mp4'.format(side.name)) - - if os.path.exists(replayFile): - if True: # Debug Mode - self.updateScores() - else: - self.showingReplay = True - self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) - self.player.stateChanged.connect(self.endOfReplay) - self.player.setMuted(True) - self.player.setVideoOutput(self.ui.videoWidget) - self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile))) - self.player.play() - self.ui.videoWidget.setFullScreen(True) - + + replayFile = self.replayer.stop_recording() + + if not (Settings['replay.show'] and os.path.exists(replayFile)): + self.updateScores() + else: + self.showingReplay = True + self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) + self.player.stateChanged.connect(self.endOfReplay) + self.player.setMuted(True) + self.player.setVideoOutput(self.ui.videoWidget) + self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile))) + self.player.play() + self.ui.videoWidget.setFullScreen(True) + def endOfReplay(self, status): if status!=QMediaPlayer.PlayingState: self.ui.videoWidget.setFullScreen(False); self.showingReplay = False self.updateScores() - + def handleCancel(self): self.switchModule(modules.MenuModule) - + def checkEndGame(self): winSide = self.gameoverChecker.check(self.getGameTime(), self.scores) - + if winSide!=Side.Undef: self.send(modules.EndGameModule, players=self.players, winSide=winSide, scores=self.scores, time=self.getGameTime()) self.switchModule(modules.EndGameModule) diff --git a/replay.py b/replay.py index 6154bbd..00e036c 100644 --- a/replay.py +++ b/replay.py @@ -7,6 +7,10 @@ Created on Wed Apr 18 18:34:40 2018 """ import os +from threading import Event +from multiprocessing import Process, Lock + +from main import MainWin from settings import Settings onRasp = os.uname()[1] == 'raspberrypi' @@ -15,8 +19,17 @@ if onRasp: import picamera +class LockableValue(): + def __init__(self, value): + self.value = value + self.mutex = Lock() + class Replay(): - def __init__(self): + def __init__(self, side): + self.recording = LockableValue(False) + self.replayPath = MainWin.getContent('Replay {}.mp4'.format(side.name)) + self.stopped = Event() + if onRasp: self.cam = picamera.PiCamera() self.cam.resolution = Settings['picam.resolution'] @@ -24,23 +37,36 @@ class Replay(): self.cam.hflip = Settings['picam.hflip'] self.cam.vflip = Settings['picam.vflip'] self.format = Settings['picam.format'] - self.continue_recording = False self.stream = picamera.PiCameraCircularIO(self.cam, seconds=Settings['replay.duration']) - + + def start_recording(self): + if not onRasp: + self.stopped.set() + else: + self.recording.value = True + self.stopped.clear() + self.capture_process = Process(target=self.__capture) + self.capture_process.start() + + def stop_recording(self): + self.recording.val = True + self.stopped.wait(timeout=2.0) + return self.replayPath + def capture(self, fileToSave): if onRasp: self.cam.start_recording(self.stream, self.format) - self.continue_recording = True - + try: - while self.continue_recording: + recording = self.recording.val + + while recording: self.cam.wait_recording(1) + recording = self.recording.val finally: self.cam.stop_recording() - - self.stream.copy_to(fileToSave) + + self.stopped.set() + self.stream.copy_to(self.replayPath) self.cam.close() self.stream.close() - - def stop(): - self.continue_recording = False -- GitLab