├── ._.gitignore ├── ._README.md ├── ._StdInTest.py ├── .gitignore ├── InstallSlooperWithRemote.py ├── LICENSE.txt ├── README.md └── RemoteVideo.py /._.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mokafolio/Slooper/1834b09aa84391bd8f6dff7d6c3014ae3e594128/._.gitignore -------------------------------------------------------------------------------- /._README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mokafolio/Slooper/1834b09aa84391bd8f6dff7d6c3014ae3e594128/._README.md -------------------------------------------------------------------------------- /._StdInTest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mokafolio/Slooper/1834b09aa84391bd8f6dff7d6c3014ae3e594128/._StdInTest.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store -------------------------------------------------------------------------------- /InstallSlooperWithRemote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import subprocess 5 | 6 | #helper function to log in color so we see stuff 7 | def log(_text): 8 | print("\033[92m\033[1m" + _text + "\033[0m") 9 | 10 | #this script os roughly an automated version of this step by step guide: 11 | #http://curioustechnologist.com/post/90061671996/rpilooper-v2-seamless-video-looper-step-by-step 12 | 13 | #make sure we are up to date 14 | log("Updating raspbian.") 15 | subprocess.call("sudo apt-get update", shell=True) 16 | subprocess.call("sudo apt-get upgrade", shell=True) 17 | 18 | #build the pi examples (we need the dependenvies for the video example) 19 | log("Building the pi examples.") 20 | cwd = os.getcwd() 21 | os.chdir("/opt/vc/src/hello_pi/") 22 | subprocess.call("./rebuild.sh", shell=True) 23 | 24 | log("Changing the video example to do the seamless looping.") 25 | #this will adjust the hello_video raspberry pi example to do seamless looping 26 | os.chdir("/opt/vc/src/hello_pi/hello_video/") 27 | 28 | # Read in the file 29 | filedata = None 30 | f = open("video.c", "r") 31 | filedata = f.read() 32 | f.close() 33 | 34 | #replace a piece of code to loop instead of end the app 35 | filedata = filedata.replace("""if(!data_len) 36 | break;""", "if(!data_len) fseek(in, 0, SEEK_SET);") 37 | 38 | #print filedata 39 | f = open("video.c", "w") 40 | f.write(filedata) 41 | f.close() 42 | 43 | log("Rebuild the video example.") 44 | #build the example 45 | subprocess.call("make", shell=True) 46 | 47 | #make an auto mounting usb thingy (this is where we will look for videos) 48 | #if the path already exists, we simply assume, that we allready did this...and don't do it again 49 | log("Creating a mounting point to play the Video from USB.") 50 | if not os.path.exists("/home/pi/USBVideoMount"): 51 | os.mkdir("/home/pi/USBVideoMount") 52 | subprocess.call("mount /dev/sda1 /home/pi/USBVideoMount", shell=True) 53 | 54 | log("Changing /etc/fstab to auto mount the USB.") 55 | #make sure this mounts on every boot up 56 | f = open("/etc/fstab", "a+") 57 | filedata = f.read() 58 | if not "/dev/sda1 /home/pi/USBVideoMount vfat defaults 0 0" in filedata: 59 | f.write("/dev/sda1 /home/pi/USBVideoMount vfat defaults 0 0") 60 | f.close() 61 | 62 | #now install the remote script 63 | os.chdir(cwd) 64 | remoteScriptFile = open("RemoteVideo.py", "r") 65 | remoteScript = remoteScriptFile.read() 66 | remoteScriptFile.close() 67 | 68 | log("Installing RemoteVideo.py script.") 69 | if not os.path.exists("/home/pi/Scripts"): 70 | os.mkdir("/home/pi/Scripts") 71 | 72 | f = open("/home/pi/Scripts/RemoteVideo.py", "w") 73 | f.write(remoteScript) 74 | f.close() 75 | 76 | # make the remote video script executable 77 | subprocess.call("chmod +x /home/pi/Scripts/RemoteVideo.py", shell=True) 78 | 79 | # add the remote script to the start up items 80 | log("Add RemoteVideo.py to /etc/rc.local to run after booting.") 81 | f = open("/etc/rc.local", "r") 82 | filedata = f.read() 83 | f.close() 84 | if not "(/home/pi/Scripts/RemoteVideo.py)&\nexit 0" in filedata: 85 | f = open("/etc/rc.local", "w") 86 | filedata = filedata.replace("\nexit 0", "(/home/pi/Scripts/RemoteVideo.py)&\nexit 0") 87 | f.write(filedata) 88 | f.close() 89 | 90 | # change the /boot/cmdline.txt to hide boot up stuff 91 | log("Changing /boot/cmdline.txt to hide boot up logo and text") 92 | f = open("/boot/cmdline.txt", "r") 93 | filedata = f.read() 94 | f.close() 95 | filedata = filedata.replace("console=tty1", "console=tty3") 96 | if not "loglevel=3" in filedata: 97 | filedata = "loglevel=3 " + filedata 98 | if not "logo.nologo" in filedata: 99 | filedata = "logo.nologo " + filedata 100 | f = open("/boot/cmdline.txt", "w") 101 | f.write(filedata) 102 | f.close() 103 | 104 | log("Done!") 105 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015, Matthias Dörfelt 4 | www.mokafolio.de 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slooper 2 | 3 | ## Overview 4 | - **Slooper** turns your raspberry pi into a seamless HD video looper. (no pause, hickup between loop iterations) 5 | - **Slooper** will play a video from a usb drive. Changing the video means simply replacing the video file on the usb drive. 6 | - **Slooper** will cleanly boot into the video to turn your pi into a video playback kiosk. 7 | - **Slooper** will allow you to turn the pi on and off with a remote. 8 | - **Slooper** comes in the form of a python install script that modifies an existing Raspbian installation which is a lot more maintainable than creating a custom image. 9 | - **Slooper** is MIT licensed. 10 | - **Slooper** does **not** support audio right now. 11 | - **Slooper** does **not** support arbitrary video containers right now. **H264 ONLY!** 12 | 13 | ## What do you need 14 | - A Raspberry Pi and a fitting SD card, power supply etc. 15 | - A remote board: http://www.msldigital.com/collections/all-products 16 | - Any IR remote. 17 | - A USB thumb drive 18 | 19 | ## What is contained in this repository? 20 | - *InstallSlooperWithRemote.py* is the installation script to modify your clean Raspbian image. 21 | - *RemoteVideo.py* is the python script that is installed by the installation script to play the video and handle the remote. It exists so you can test it separately. 22 | 23 | ## Installation 24 | - Install the remote board on your raspberry pi as described in the *Installation* section here: http://www.msldigital.com/pages/support-for-remotepi-board-plus-2015/ 25 | - Put a **.m4v** or **.h264** video on the USB thumbdrive. 26 | - Create a **Videos.json** file and place it on the USB drive. See separate section below. 27 | - Plug the USB drive into the Pi. 28 | - Download the most recent *Raspbian* image from the raspberry pi website: http://www.raspberrypi.org/downloads/ 29 | - Install the image on the SD card of your choice as described here: http://www.raspberrypi.org/documentation/installation/installing-images/mac.md 30 | - Connect the Pi to the internet (with an ethernet cable). 31 | - Start up your raspberry pi by plugging the power into its power slot. 32 | - Boot up your pi and perform all the *raspi-config* steps that you want to do (look here for more info: http://www.raspberrypi.org/documentation/configuration/raspi-config.md) 33 | - Run `sudo apt-get install git` to make sure git is up to date. 34 | - Enter the directory that you want to clone the **Slooper** repository to, i.e. `cd /home/pi` 35 | - Clone this git repository `git clone https://github.com/mokafolio/Slooper.git` 36 | - Enter the repository directory, i.e. `cd Slooper` 37 | - Run the installation script: `sudo python InstallSlooperWithRemote.py`, this will take a while. 38 | - Shut down the pi: `sudo poweroff` 39 | - Replug the power to the remote pi board. 40 | - To install the powerbutton of your remote, do the following: *Choose the button on the remote you want to use to switch your RPi on and off in the future, then press the hardware button on top of the RemotePi Board for about 10 seconds until you see the LED blinking green and red. Now you have about 20 seconds time to aim the remote towards the RemotePi Board’s infrared receiver and press the button you want to use for powering on your RPi. You will see the green LED flashing once when the button was learned. If no infrared command is learned within 20 seconds you see the red LED flash and the learning mode is exited without changing the current configuration.* 41 | - **Done**, you should be able to turn the Pi on and off using your remote now and it should nicely boot into the video on the USB stick. 42 | 43 | ## Videos.json file 44 | - The **Videos.json** file allows you to map videos to display configurations (i.e. a certain resolution and display refresh rate), as well as mapping videos to keys on the keyboard, so you can cycle through them. 45 | Here is an example: 46 | ``` 47 | { 48 | "maxPlayMinutes": 720, #pi will shut down after this time has elapsed 49 | "settings": 50 | { 51 | "LowRes": 52 | { 53 | "displayPixelWidth": 1024, 54 | "displayPixelHeight": 768, 55 | "displayScan": "progressive", 56 | "displayRate": 60 57 | }, 58 | "HD": 59 | { 60 | "displayPixelWidth": 1920, 61 | "displayPixelHeight": 1080, 62 | "displayScan": "progressive", 63 | "displayRate": 60 64 | } 65 | }, 66 | "videoGroups": 67 | [ 68 | { 69 | "files": 70 | [ 71 | "a.m4v", 72 | "b.m4v" 73 | ], 74 | "settings": "LowRes", 75 | "key": "1" 76 | }, 77 | { 78 | "files": 79 | [ 80 | "c.m4v" 81 | ], 82 | "settings": "HD", 83 | "key": "2" 84 | } 85 | ] 86 | } 87 | ``` 88 | 89 | This **Videos.json** has two different display settings, one for HD playback and one for 1024*768, and two video groups, holding three videos. 90 | The first video group contains two videos, *a.m4v* and *b.m4v*, using the low res settings. They are mapped to key "1" meaning that whenever you hit "1" on your keyboard, it will go to the next video in the *files* list. 91 | The second video group only contains one video, uses the *HD* settings and is mapped to key "2". 92 | 93 | ## Temporary Files 94 | **Slooper** creates two temporary files on your usb drive. *SlooperLog.txt* which mainly servers debugging purposes. It is a file that Slooper uses to log information to. The other file is called *SlooperCache.json* which is a file used by **Slooper** to cache information. Right now it is mainly used to save which video was played, when Slooper exited the last time. This will be the video that will continue playing on the next start of Slooper. 95 | 96 | ## What does the installation script do? 97 | - The installation script is losely based on this article: http://curioustechnologist.com/post/90061671996/rpilooper-v2-seamless-video-looper-step-by-step , fully automating the steps that are necessary while adding some extra scripts and things to handle the remote and json configuration files. 98 | 99 | ### So what is happening in detail? 100 | - It will update your Raspbian. 101 | - It will build all the pi software examples. 102 | - It will slightly change the *hello_pi/video* example to perform the seamless looping. This will be used to playback the video. (I did a lot of research to see what method provides the best seamless loop, omxplayer, openFrameworks etc.. The hello_video example performed best by far! That's also why there is now audio support right now). 103 | - It will create a mounting point for the USB drive and make sure it mounts on every boot. 104 | - It will install a python script called *RemoteVideo.py* to */home/pi/Scripts*. This script will start and stop the video and handle the remote. 105 | - It will add that script to the startup items (by changing */etc/rc.local*). 106 | - It will adjust */boot/cmdline.txt* to hide the Pi logo and console text while booting. 107 | 108 | For implementation details, check out the actual script! 109 | 110 | ## What has Slooper been tested on? 111 | - Slooper has been tested on a *Raspberry Pi 1 Model B+* and *Raspberry Pi 2 Model B* 112 | - Slooper has been tested with *2015-02-16-raspbian-wheezy.img* 113 | 114 | ## Roadmap 115 | - Expose some of the internals of the installation script through settings. 116 | - Make a **Slooper** version without a remote. 117 | - Make a **Slooper** version that starts/exits videos on a timer. 118 | 119 | ## Video Compression Recommendations 120 | - 1080p 121 | - H264 format 122 | - VBR 2 pass 123 | - setting the bitrate target and max to 60 seemed a good compromise of speed and quality. 124 | - Export as .m4v or .h264 125 | -------------------------------------------------------------------------------- /RemoteVideo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import RPi.GPIO as GPIO 4 | import time 5 | from datetime import datetime, timedelta 6 | import subprocess 7 | import signal 8 | import os 9 | import json 10 | import sys 11 | import struct 12 | import curses 13 | 14 | damn = curses.initscr() 15 | damn.nodelay(1) # doesn't keep waiting for a key press 16 | damn.keypad(1) # i.e. arrow keys 17 | curses.noecho() # stop keys echoing 18 | 19 | def isKeyDown(_key): 20 | infile_path = "/dev/input/event" + (sys.argv[1] if len(sys.argv) > 1 else "0") 21 | 22 | #long int, long int, unsigned short, unsigned short, unsigned int 23 | FORMAT = 'llHHI' 24 | EVENT_SIZE = struct.calcsize(FORMAT) 25 | 26 | #open file in binary mode 27 | in_file = open(infile_path, "rb") 28 | 29 | event = in_file.read(EVENT_SIZE) 30 | 31 | while event: 32 | (tv_sec, tv_usec, type, code, value) = struct.unpack(FORMAT, event) 33 | 34 | if type != 0 or code != 0 or value != 0: 35 | print("Event type %u, code %u, value: %u at %d, %d" % \ 36 | (type, code, value, tv_sec, tv_usec)) 37 | else: 38 | # Events with code, type and value == 0 are "separator" events 39 | print("===========================================") 40 | 41 | event = in_file.read(EVENT_SIZE) 42 | 43 | in_file.close() 44 | 45 | def log(_str, _bTimeStamp = True): 46 | logFilePath = "/home/pi/USBVideoMount/SlooperLog.txt" 47 | 48 | if os.path.exists(logFilePath): 49 | with open(logFilePath, "r") as fin: 50 | data = fin.read().splitlines(True); 51 | if len(data) > 100: 52 | diff = len(data) - 99 53 | with open(logFilePath, "w") as fout: 54 | fout.writelines(data[diff:]) 55 | 56 | logFile = open(logFilePath, "a") 57 | message = "" 58 | if _bTimeStamp: 59 | message += str(datetime.now()) + ": " 60 | message += _str 61 | print message 62 | logFile.write(message + "\n") 63 | 64 | with open('/home/pi/USBVideoMount/Videos.json') as jsonFile: 65 | jsonData = json.load(jsonFile) 66 | 67 | # discribes a display configuration 68 | class DisplaySettings: 69 | # some reasonable defaults 70 | displayPixelWidth = 1920 71 | displayPixelHeight = 1080 72 | displayScan = "progressive" 73 | displayRate = 60 74 | 75 | # describes a collection of video files and that share settings and keyboard key 76 | class VideoGroup: 77 | files = [] 78 | currentIndex = 0 #current videoIndex 79 | settings = "" 80 | myIndex = 0 #index into the videos array 81 | 82 | # describes a playing video 83 | class VideoSession: 84 | processID = -1 85 | videoGroup = None 86 | 87 | # dict mapping a settings name to its DisplaySettings obj 88 | displaySettings = {} 89 | # array holding all the Video objects 90 | videos = [] 91 | # dict mapping a key press to a Video object 92 | keyVideoMap = {} 93 | #variable holding the maximum play time, after which th pi will shut down 94 | maxPlayMinutes = int(jsonData["maxPlayMinutes"]) 95 | 96 | # parse the settings from the Videos.json 97 | for k,v in jsonData["settings"].items(): 98 | settings = DisplaySettings() 99 | if v.get('displayPixelWidth'): 100 | settings.displayPixelWidth = v["displayPixelWidth"] 101 | if v.get('displayPixelHeight'): 102 | settings.displayPixelHeight = v["displayPixelHeight"] 103 | if v.get('displayScan'): 104 | settings.displayScan = v["displayScan"] 105 | if v.get('displayRate'): 106 | settings.displayRate = v["displayRate"] 107 | displaySettings[k] = settings 108 | 109 | # parse the videos from Videos.json 110 | for video in jsonData["videoGroups"]: 111 | vid = VideoGroup() 112 | vid.files = video["files"] 113 | vid.settings = video["settings"] 114 | videos.append(vid) 115 | vid.myIndex = len(videos) - 1 116 | keyVideoMap[video["key"]] = vid 117 | 118 | # holds the currently playing video and some extra info 119 | currentVideoSession = None 120 | 121 | # helper to stop the currently playing video process 122 | def stopCurrentVideo(): 123 | os.killpg(currentVideoSession.processID, signal.SIGTERM) 124 | 125 | # this function stops the current video, if any, and starts playing the requested one 126 | # Before it starts playing, it will try to apply the new videos requested DisplaySettings 127 | def playVideoInGroup(videoGroup, videoIndex): 128 | global currentVideoSession 129 | lastGroup = None 130 | videoGroup.currentIndex = videoIndex 131 | 132 | # quit the previos video process if any 133 | if currentVideoSession: 134 | stopCurrentVideo() 135 | lastGroup = currentVideoSession.videoGroup 136 | 137 | # make the new session 138 | currentVideoSession = VideoSession() 139 | currentVideoSession.videoGroup = videoGroup 140 | # make sure the settings exist 141 | currentSettings = displaySettings[currentVideoSession.videoGroup.settings] 142 | assert currentSettings != None 143 | 144 | log("The current video group index is: " + str(currentVideoSession.videoGroup.myIndex)) 145 | log("The current video index is: " + str(currentVideoSession.videoGroup.currentIndex)) 146 | 147 | videoPath = "/home/pi/USBVideoMount/" + currentVideoSession.videoGroup.files[currentVideoSession.videoGroup.currentIndex] 148 | if not os.path.exists(videoPath): 149 | log("The video files does not exist") 150 | sys.exit() 151 | 152 | if lastGroup == None or lastGroup != videoGroup: 153 | requestedWidth = currentSettings.displayPixelWidth 154 | requestedHeight = currentSettings.displayPixelHeight 155 | requestedScan = currentSettings.displayScan 156 | requestedRate = currentSettings.displayRate 157 | 158 | log("The requested display pixel width is " + str(requestedWidth) + ".") 159 | log("The requested display pixel height is " + str(requestedHeight) + ".") 160 | log("The requested display scan is " + str(requestedScan) + ".") 161 | log("The requested display rate is " + str(requestedRate) + ".") 162 | 163 | # get all the available display modes and find the best one for the requested settings 164 | availableModes = subprocess.check_output("tvservice -j -m CEA", shell=True) 165 | availableModes2 = subprocess.check_output("tvservice -j -m DMT", shell=True) 166 | jsonData = json.loads(availableModes) 167 | jsonData2 = json.loads(availableModes2) 168 | 169 | def findBestDisplayMode(_dmtModes, _ceaModes): 170 | global bestWDiff, bestHDiff, bestRDiff, bestMode 171 | log("The available display modes are:") 172 | bestWDiff = bestHDiff = bestRDiff = float("inf") 173 | bestWidth = bestHeight = 0 174 | bestMode = {} 175 | def compare(_modes, _type): 176 | global bestWDiff, bestHDiff, bestRDiff, bestMode 177 | for obj in _modes: 178 | scan = "interlaced" if obj["scan"] == "i" else "progressive" 179 | log("Width: " + str(obj["width"]) + " Height: " + str(obj["height"]) + " Rate: " + str(obj["rate"]) + " Scan: " + scan) 180 | #check if we have a perfect match 181 | if obj["width"] == requestedWidth and obj["height"] == requestedHeight and obj["rate"] == requestedRate and scan == requestedScan: 182 | bestMode = {"width" : requestedWidth, "height" : requestedHeight, "rate" : requestedRate, "scan" : requestedScan, "code" : obj["code"], "type" : _type} 183 | break 184 | 185 | wdiff = abs(obj["width"] - requestedWidth) 186 | hdiff = abs(obj["height"] - requestedHeight) 187 | rDiff = abs(obj["rate"] - requestedRate) 188 | if wdiff <= bestWDiff and hdiff <= bestHDiff and rDiff <= bestRDiff: 189 | bestWDiff = wdiff 190 | bestHDiff = hdiff 191 | bestRDiff = rDiff 192 | bestMode = {"width" : obj["width"], "height" : obj["height"], "rate" : obj["rate"], "scan" : scan, "code" : obj["code"], "type" : _type} 193 | compare(_dmtModes, "DMT") 194 | compare(_ceaModes, "CEA") 195 | return bestMode 196 | 197 | bestMode = findBestDisplayMode(jsonData2, jsonData) 198 | log("Best Mode, Width: " + str(bestMode["width"]) + " Height: " + str(bestMode["height"]) + " Rate: " + str(bestMode["rate"]) + " Scan: " + bestMode["scan"]) 199 | 200 | # apply the best mode 201 | subprocess.call("tvservice -e \"" + bestMode["type"] + " " + str(bestMode["code"]) + "\"" , shell=True) 202 | 203 | # start the video process 204 | log("Starting Video: " + videoPath) 205 | videoProcess = subprocess.Popen("/opt/vc/src/hello_pi/hello_video/hello_video.bin " + videoPath, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid) 206 | currentVideoSession.processID = videoProcess.pid 207 | 208 | def playNextVideoInGroup(_videoGroup): 209 | 210 | # generate the full video path and make sure the video file exists 211 | _videoGroup.currentIndex += 1 212 | if _videoGroup.currentIndex >= len(_videoGroup.files): 213 | _videoGroup.currentIndex = 0 214 | playVideoInGroup(_videoGroup, _videoGroup.currentIndex) 215 | 216 | def gpioShutdownSequence(): 217 | #this is the shutdown sequence of the remote board, see the site linked below 218 | offPin = 15 219 | GPIO.setup(offPin, GPIO.OUT) 220 | GPIO.output(offPin, 1) 221 | time.sleep(0.125) 222 | GPIO.output(offPin, 0) 223 | time.sleep(0.2) 224 | GPIO.output(offPin, 1) 225 | time.sleep(0.4) 226 | GPIO.output(offPin, 0) 227 | 228 | 229 | #check if we cached where to play from 230 | cachePath = "/home/pi/USBVideoMount/SlooperCache.json" 231 | if os.path.exists(cachePath): 232 | with open(cachePath) as jsonFile: 233 | jsonData = json.load(jsonFile) 234 | playVideoInGroup(videos[jsonData["lastVideoGroupIndex"]], jsonData["lastVideoIndex"]) 235 | else: 236 | #play the first video in the array as the default one 237 | playNextVideoInGroup(videos[0]) 238 | 239 | #GPIO pin stuff based on the instructions from the remote board people here: 240 | #http://www.msldigital.com/pages/support-for-remotepi-board-plus-2015/ 241 | pin=14 242 | GPIO.setmode(GPIO.BCM) 243 | GPIO.setup(pin, GPIO.IN) 244 | 245 | bShutDown = False 246 | startTime = time.time() 247 | # the main loop 248 | while True: 249 | bQuit = False 250 | minutesElapsed = (time.time() - startTime) / 60.0 251 | 252 | if minutesElapsed > maxPlayMinutes: 253 | log("Max time elapsed, shutting down now") 254 | gpioShutdownSequence() 255 | bQuit = True 256 | else: 257 | # query if a key is pressed 258 | c = damn.getch() 259 | if c == ord('q'): 260 | # if q is pressed, we quit and shutdown, replicating the remote behavior 261 | log("Keyboard q pressed, we quit and shut down now") 262 | gpioShutdownSequence() 263 | bQuit = True 264 | if c == ord('w'): 265 | # if w is pressed, we simply quit the video and this app, without shutting down 266 | log("Keyboard w pressed, we quit now") 267 | stopCurrentVideo() 268 | break 269 | elif c in range(256): 270 | # otherwise we check if the key is mapped to a video. If so, we start playing that video 271 | if chr(c) in keyVideoMap: 272 | playNextVideoInGroup(keyVideoMap[chr(c)]) 273 | 274 | # check if the remote is pressed 275 | if GPIO.input(pin) != 0: 276 | log("Remote pressed, we quit and shut down now") 277 | bQuit = True 278 | 279 | # do shutdown procedure 280 | if bQuit: 281 | log("Remote shut down procedure") 282 | stopCurrentVideo() 283 | GPIO.setup(pin, GPIO.OUT) 284 | GPIO.output(pin, 1) 285 | time.sleep(3) 286 | bShutDown = True 287 | break 288 | time.sleep(0.1) 289 | 290 | # close curses 291 | curses.endwin() 292 | 293 | # show the console again 294 | subprocess.call("fbset -depth 8 && fbset -depth 16", shell=True) 295 | 296 | # cleanup GPIO 297 | GPIO.cleanup() 298 | 299 | #save which video we were at 300 | with open(cachePath, "w") as cache: 301 | dc = {"lastVideoGroupIndex": currentVideoSession.videoGroup.myIndex, "lastVideoIndex": currentVideoSession.videoGroup.currentIndex} 302 | json.dump(dc, cache) 303 | 304 | # and shutdown if requested 305 | if bShutDown: 306 | log("Shutting down") 307 | subprocess.call("sudo poweroff", shell=True) --------------------------------------------------------------------------------