code is now fully python3 compatible (python2 is no longer supported)pull/20/merge
| @@ -53,3 +53,4 @@ docs/_build/ | |||||
| # PyBuilder | # PyBuilder | ||||
| target/ | target/ | ||||
| .idea/ | |||||
| @@ -1,7 +1,7 @@ | |||||
| # Copyright 2015 Adafruit Industries. | # Copyright 2015 Adafruit Industries. | ||||
| # Author: Tony DiCola | # Author: Tony DiCola | ||||
| # License: GNU GPLv2, see LICENSE.txt | # License: GNU GPLv2, see LICENSE.txt | ||||
| class DirectoryReader(object): | |||||
| class DirectoryReader: | |||||
| def __init__(self, config): | def __init__(self, config): | ||||
| """Create an instance of a file reader that just reads a single | """Create an instance of a file reader that just reads a single | ||||
| @@ -6,7 +6,7 @@ import subprocess | |||||
| import time | import time | ||||
| class HelloVideoPlayer(object): | |||||
| class HelloVideoPlayer: | |||||
| def __init__(self, config): | def __init__(self, config): | ||||
| """Create an instance of a video player that runs hello_video.bin in the | """Create an instance of a video player that runs hello_video.bin in the | ||||
| @@ -17,7 +17,7 @@ class HelloVideoPlayer(object): | |||||
| def _load_config(self, config): | def _load_config(self, config): | ||||
| self._extensions = config.get('hello_video', 'extensions') \ | self._extensions = config.get('hello_video', 'extensions') \ | ||||
| .translate(None, ' \t\r\n.') \ | |||||
| .translate(str.maketrans('', '', ' \t\r\n.')) \ | |||||
| .split(',') | .split(',') | ||||
| def supported_extensions(self): | def supported_extensions(self): | ||||
| @@ -44,7 +44,7 @@ class HelloVideoPlayer(object): | |||||
| self._process.poll() | self._process.poll() | ||||
| return self._process.returncode is None | return self._process.returncode is None | ||||
| def stop(self, block_timeout_sec=None): | |||||
| def stop(self, block_timeout_sec=0): | |||||
| """Stop the video player. block_timeout_sec is how many seconds to | """Stop the video player. block_timeout_sec is how many seconds to | ||||
| block waiting for the player to stop before moving on. | block waiting for the player to stop before moving on. | ||||
| """ | """ | ||||
| @@ -3,7 +3,7 @@ | |||||
| # License: GNU GPLv2, see LICENSE.txt | # License: GNU GPLv2, see LICENSE.txt | ||||
| import random | import random | ||||
| class Playlist(object): | |||||
| class Playlist: | |||||
| """Representation of a playlist of movies.""" | """Representation of a playlist of movies.""" | ||||
| def __init__(self, movies, is_random): | def __init__(self, movies, is_random): | ||||
| @@ -6,7 +6,7 @@ import subprocess | |||||
| import time | import time | ||||
| class OMXPlayer(object): | |||||
| class OMXPlayer: | |||||
| def __init__(self, config): | def __init__(self, config): | ||||
| """Create an instance of a video player that runs omxplayer in the | """Create an instance of a video player that runs omxplayer in the | ||||
| @@ -17,7 +17,7 @@ class OMXPlayer(object): | |||||
| def _load_config(self, config): | def _load_config(self, config): | ||||
| self._extensions = config.get('omxplayer', 'extensions') \ | self._extensions = config.get('omxplayer', 'extensions') \ | ||||
| .translate(None, ' \t\r\n.') \ | |||||
| .translate(str.maketrans('', '', ' \t\r\n.')) \ | |||||
| .split(',') | .split(',') | ||||
| self._extra_args = config.get('omxplayer', 'extra_args').split() | self._extra_args = config.get('omxplayer', 'extra_args').split() | ||||
| self._sound = config.get('omxplayer', 'sound').lower() | self._sound = config.get('omxplayer', 'sound').lower() | ||||
| @@ -51,7 +51,7 @@ class OMXPlayer(object): | |||||
| self._process.poll() | self._process.poll() | ||||
| return self._process.returncode is None | return self._process.returncode is None | ||||
| def stop(self, block_timeout_sec=None): | |||||
| def stop(self, block_timeout_sec=0): | |||||
| """Stop the video player. block_timeout_sec is how many seconds to | """Stop the video player. block_timeout_sec is how many seconds to | ||||
| block waiting for the player to stop before moving on. | block waiting for the player to stop before moving on. | ||||
| """ | """ | ||||
| @@ -3,10 +3,10 @@ | |||||
| # License: GNU GPLv2, see LICENSE.txt | # License: GNU GPLv2, see LICENSE.txt | ||||
| import glob | import glob | ||||
| from usb_drive_mounter import USBDriveMounter | |||||
| from .usb_drive_mounter import USBDriveMounter | |||||
| class USBDriveReader(object): | |||||
| class USBDriveReader: | |||||
| def __init__(self, config): | def __init__(self, config): | ||||
| """Create an instance of a file reader that uses the USB drive mounter | """Create an instance of a file reader that uses the USB drive mounter | ||||
| @@ -8,7 +8,7 @@ import time | |||||
| import pyudev | import pyudev | ||||
| class USBDriveMounter(object): | |||||
| class USBDriveMounter: | |||||
| """Service for automatically mounting attached USB drives.""" | """Service for automatically mounting attached USB drives.""" | ||||
| def __init__(self, root='/mnt/usbdrive', readonly=False): | def __init__(self, root='/mnt/usbdrive', readonly=False): | ||||
| @@ -72,9 +72,9 @@ if __name__ == '__main__': | |||||
| drive_mounter = USBDriveMounter(readonly=True) | drive_mounter = USBDriveMounter(readonly=True) | ||||
| drive_mounter.mount_all() | drive_mounter.mount_all() | ||||
| drive_mounter.start_monitor() | drive_mounter.start_monitor() | ||||
| print 'Listening for USB drive changes (press Ctrl-C to quite)...' | |||||
| print ('Listening for USB drive changes (press Ctrl-C to quit)...') | |||||
| while True: | while True: | ||||
| if drive_mounter.poll_changes(): | if drive_mounter.poll_changes(): | ||||
| print 'USB drives changed!' | |||||
| print ('USB drives changed!') | |||||
| drive_mounter.mount_all() | drive_mounter.mount_all() | ||||
| time.sleep(0) | time.sleep(0) | ||||
| @@ -1,7 +1,8 @@ | |||||
| # Copyright 2015 Adafruit Industries. | # Copyright 2015 Adafruit Industries. | ||||
| # Author: Tony DiCola | # Author: Tony DiCola | ||||
| # License: GNU GPLv2, see LICENSE.txt | # License: GNU GPLv2, see LICENSE.txt | ||||
| import ConfigParser | |||||
| import configparser | |||||
| import importlib | import importlib | ||||
| import os | import os | ||||
| import re | import re | ||||
| @@ -11,7 +12,7 @@ import time | |||||
| import pygame | import pygame | ||||
| from model import Playlist | |||||
| from .model import Playlist | |||||
| # Basic video looper architecure: | # Basic video looper architecure: | ||||
| @@ -37,14 +38,14 @@ from model import Playlist | |||||
| # - Future file readers and video players can be provided and referenced in the | # - Future file readers and video players can be provided and referenced in the | ||||
| # config to extend the video player use to read from different file sources | # config to extend the video player use to read from different file sources | ||||
| # or use different video players. | # or use different video players. | ||||
| class VideoLooper(object): | |||||
| class VideoLooper: | |||||
| def __init__(self, config_path): | def __init__(self, config_path): | ||||
| """Create an instance of the main video looper application class. Must | """Create an instance of the main video looper application class. Must | ||||
| pass path to a valid video looper ini configuration file. | pass path to a valid video looper ini configuration file. | ||||
| """ | """ | ||||
| # Load the configuration. | # Load the configuration. | ||||
| self._config = ConfigParser.SafeConfigParser() | |||||
| self._config = configparser.ConfigParser() | |||||
| if len(self._config.read(config_path)) == 0: | if len(self._config.read(config_path)) == 0: | ||||
| raise RuntimeError('Failed to find configuration file at {0}, is the application properly installed?'.format(config_path)) | raise RuntimeError('Failed to find configuration file at {0}, is the application properly installed?'.format(config_path)) | ||||
| self._console_output = self._config.getboolean('video_looper', 'console_output') | self._console_output = self._config.getboolean('video_looper', 'console_output') | ||||
| @@ -57,12 +58,12 @@ class VideoLooper(object): | |||||
| self._keyboard_control = self._config.getboolean('video_looper', 'keyboard_control') | self._keyboard_control = self._config.getboolean('video_looper', 'keyboard_control') | ||||
| # Parse string of 3 comma separated values like "255, 255, 255" into | # Parse string of 3 comma separated values like "255, 255, 255" into | ||||
| # list of ints for colors. | # list of ints for colors. | ||||
| self._bgcolor = map(int, self._config.get('video_looper', 'bgcolor') \ | |||||
| .translate(None, ',') \ | |||||
| .split()) | |||||
| self._fgcolor = map(int, self._config.get('video_looper', 'fgcolor') \ | |||||
| .translate(None, ',') \ | |||||
| .split()) | |||||
| self._bgcolor = list(map(int, self._config.get('video_looper', 'bgcolor') | |||||
| .translate(str.maketrans('','', ',')) | |||||
| .split())) | |||||
| self._fgcolor = list(map(int, self._config.get('video_looper', 'fgcolor') | |||||
| .translate(str.maketrans('','', ',')) | |||||
| .split())) | |||||
| # Load sound volume file name value | # Load sound volume file name value | ||||
| self._sound_vol_file = self._config.get('omxplayer', 'sound_vol_file'); | self._sound_vol_file = self._config.get('omxplayer', 'sound_vol_file'); | ||||
| # default value to 0 millibels (omxplayer) | # default value to 0 millibels (omxplayer) | ||||
| @@ -1,332 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| """Bootstrap setuptools installation | |||||
| To use setuptools in your package's setup.py, include this | |||||
| file in the same directory and add this to the top of your setup.py:: | |||||
| from ez_setup import use_setuptools | |||||
| use_setuptools() | |||||
| To require a specific version of setuptools, set a download | |||||
| mirror, or use an alternate download directory, simply supply | |||||
| the appropriate options to ``use_setuptools()``. | |||||
| This file can also be run as a script to install or upgrade setuptools. | |||||
| """ | |||||
| import os | |||||
| import shutil | |||||
| import sys | |||||
| import tempfile | |||||
| import zipfile | |||||
| import optparse | |||||
| import subprocess | |||||
| import platform | |||||
| import textwrap | |||||
| import contextlib | |||||
| from distutils import log | |||||
| try: | |||||
| from site import USER_SITE | |||||
| except ImportError: | |||||
| USER_SITE = None | |||||
| DEFAULT_VERSION = "3.5.1" | |||||
| DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" | |||||
| def _python_cmd(*args): | |||||
| """ | |||||
| Return True if the command succeeded. | |||||
| """ | |||||
| args = (sys.executable,) + args | |||||
| return subprocess.call(args) == 0 | |||||
| def _install(archive_filename, install_args=()): | |||||
| with archive_context(archive_filename): | |||||
| # installing | |||||
| log.warn('Installing Setuptools') | |||||
| if not _python_cmd('setup.py', 'install', *install_args): | |||||
| log.warn('Something went wrong during the installation.') | |||||
| log.warn('See the error message above.') | |||||
| # exitcode will be 2 | |||||
| return 2 | |||||
| def _build_egg(egg, archive_filename, to_dir): | |||||
| with archive_context(archive_filename): | |||||
| # building an egg | |||||
| log.warn('Building a Setuptools egg in %s', to_dir) | |||||
| _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) | |||||
| # returning the result | |||||
| log.warn(egg) | |||||
| if not os.path.exists(egg): | |||||
| raise IOError('Could not build the egg.') | |||||
| def get_zip_class(): | |||||
| """ | |||||
| Supplement ZipFile class to support context manager for Python 2.6 | |||||
| """ | |||||
| class ContextualZipFile(zipfile.ZipFile): | |||||
| def __enter__(self): | |||||
| return self | |||||
| def __exit__(self, type, value, traceback): | |||||
| self.close | |||||
| return zipfile.ZipFile if hasattr(zipfile.ZipFile, '__exit__') else \ | |||||
| ContextualZipFile | |||||
| @contextlib.contextmanager | |||||
| def archive_context(filename): | |||||
| # extracting the archive | |||||
| tmpdir = tempfile.mkdtemp() | |||||
| log.warn('Extracting in %s', tmpdir) | |||||
| old_wd = os.getcwd() | |||||
| try: | |||||
| os.chdir(tmpdir) | |||||
| with get_zip_class()(filename) as archive: | |||||
| archive.extractall() | |||||
| # going in the directory | |||||
| subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | |||||
| os.chdir(subdir) | |||||
| log.warn('Now working in %s', subdir) | |||||
| yield | |||||
| finally: | |||||
| os.chdir(old_wd) | |||||
| shutil.rmtree(tmpdir) | |||||
| def _do_download(version, download_base, to_dir, download_delay): | |||||
| egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' | |||||
| % (version, sys.version_info[0], sys.version_info[1])) | |||||
| if not os.path.exists(egg): | |||||
| archive = download_setuptools(version, download_base, | |||||
| to_dir, download_delay) | |||||
| _build_egg(egg, archive, to_dir) | |||||
| sys.path.insert(0, egg) | |||||
| # Remove previously-imported pkg_resources if present (see | |||||
| # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). | |||||
| if 'pkg_resources' in sys.modules: | |||||
| del sys.modules['pkg_resources'] | |||||
| import setuptools | |||||
| setuptools.bootstrap_install_from = egg | |||||
| def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | |||||
| to_dir=os.curdir, download_delay=15): | |||||
| to_dir = os.path.abspath(to_dir) | |||||
| rep_modules = 'pkg_resources', 'setuptools' | |||||
| imported = set(sys.modules).intersection(rep_modules) | |||||
| try: | |||||
| import pkg_resources | |||||
| except ImportError: | |||||
| return _do_download(version, download_base, to_dir, download_delay) | |||||
| try: | |||||
| pkg_resources.require("setuptools>=" + version) | |||||
| return | |||||
| except pkg_resources.DistributionNotFound: | |||||
| return _do_download(version, download_base, to_dir, download_delay) | |||||
| except pkg_resources.VersionConflict as VC_err: | |||||
| if imported: | |||||
| msg = textwrap.dedent(""" | |||||
| The required version of setuptools (>={version}) is not available, | |||||
| and can't be installed while this script is running. Please | |||||
| install a more recent version first, using | |||||
| 'easy_install -U setuptools'. | |||||
| (Currently using {VC_err.args[0]!r}) | |||||
| """).format(VC_err=VC_err, version=version) | |||||
| sys.stderr.write(msg) | |||||
| sys.exit(2) | |||||
| # otherwise, reload ok | |||||
| del pkg_resources, sys.modules['pkg_resources'] | |||||
| return _do_download(version, download_base, to_dir, download_delay) | |||||
| def _clean_check(cmd, target): | |||||
| """ | |||||
| Run the command to download target. If the command fails, clean up before | |||||
| re-raising the error. | |||||
| """ | |||||
| try: | |||||
| subprocess.check_call(cmd) | |||||
| except subprocess.CalledProcessError: | |||||
| if os.access(target, os.F_OK): | |||||
| os.unlink(target) | |||||
| raise | |||||
| def download_file_powershell(url, target): | |||||
| """ | |||||
| Download the file at url to target using Powershell (which will validate | |||||
| trust). Raise an exception if the command cannot complete. | |||||
| """ | |||||
| target = os.path.abspath(target) | |||||
| cmd = [ | |||||
| 'powershell', | |||||
| '-Command', | |||||
| "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), | |||||
| ] | |||||
| _clean_check(cmd, target) | |||||
| def has_powershell(): | |||||
| if platform.system() != 'Windows': | |||||
| return False | |||||
| cmd = ['powershell', '-Command', 'echo test'] | |||||
| devnull = open(os.path.devnull, 'wb') | |||||
| try: | |||||
| try: | |||||
| subprocess.check_call(cmd, stdout=devnull, stderr=devnull) | |||||
| except Exception: | |||||
| return False | |||||
| finally: | |||||
| devnull.close() | |||||
| return True | |||||
| download_file_powershell.viable = has_powershell | |||||
| def download_file_curl(url, target): | |||||
| cmd = ['curl', url, '--silent', '--output', target] | |||||
| _clean_check(cmd, target) | |||||
| def has_curl(): | |||||
| cmd = ['curl', '--version'] | |||||
| devnull = open(os.path.devnull, 'wb') | |||||
| try: | |||||
| try: | |||||
| subprocess.check_call(cmd, stdout=devnull, stderr=devnull) | |||||
| except Exception: | |||||
| return False | |||||
| finally: | |||||
| devnull.close() | |||||
| return True | |||||
| download_file_curl.viable = has_curl | |||||
| def download_file_wget(url, target): | |||||
| cmd = ['wget', url, '--quiet', '--output-document', target] | |||||
| _clean_check(cmd, target) | |||||
| def has_wget(): | |||||
| cmd = ['wget', '--version'] | |||||
| devnull = open(os.path.devnull, 'wb') | |||||
| try: | |||||
| try: | |||||
| subprocess.check_call(cmd, stdout=devnull, stderr=devnull) | |||||
| except Exception: | |||||
| return False | |||||
| finally: | |||||
| devnull.close() | |||||
| return True | |||||
| download_file_wget.viable = has_wget | |||||
| def download_file_insecure(url, target): | |||||
| """ | |||||
| Use Python to download the file, even though it cannot authenticate the | |||||
| connection. | |||||
| """ | |||||
| try: | |||||
| from urllib.request import urlopen | |||||
| except ImportError: | |||||
| from urllib2 import urlopen | |||||
| src = dst = None | |||||
| try: | |||||
| src = urlopen(url) | |||||
| # Read/write all in one block, so we don't create a corrupt file | |||||
| # if the download is interrupted. | |||||
| data = src.read() | |||||
| dst = open(target, "wb") | |||||
| dst.write(data) | |||||
| finally: | |||||
| if src: | |||||
| src.close() | |||||
| if dst: | |||||
| dst.close() | |||||
| download_file_insecure.viable = lambda: True | |||||
| def get_best_downloader(): | |||||
| downloaders = [ | |||||
| download_file_powershell, | |||||
| download_file_curl, | |||||
| download_file_wget, | |||||
| download_file_insecure, | |||||
| ] | |||||
| for dl in downloaders: | |||||
| if dl.viable(): | |||||
| return dl | |||||
| def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | |||||
| to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader): | |||||
| """ | |||||
| Download setuptools from a specified location and return its filename | |||||
| `version` should be a valid setuptools version number that is available | |||||
| as an egg for download under the `download_base` URL (which should end | |||||
| with a '/'). `to_dir` is the directory where the egg will be downloaded. | |||||
| `delay` is the number of seconds to pause before an actual download | |||||
| attempt. | |||||
| ``downloader_factory`` should be a function taking no arguments and | |||||
| returning a function for downloading a URL to a target. | |||||
| """ | |||||
| # making sure we use the absolute path | |||||
| to_dir = os.path.abspath(to_dir) | |||||
| zip_name = "setuptools-%s.zip" % version | |||||
| url = download_base + zip_name | |||||
| saveto = os.path.join(to_dir, zip_name) | |||||
| if not os.path.exists(saveto): # Avoid repeated downloads | |||||
| log.warn("Downloading %s", url) | |||||
| downloader = downloader_factory() | |||||
| downloader(url, saveto) | |||||
| return os.path.realpath(saveto) | |||||
| def _build_install_args(options): | |||||
| """ | |||||
| Build the arguments to 'python setup.py install' on the setuptools package | |||||
| """ | |||||
| return ['--user'] if options.user_install else [] | |||||
| def _parse_args(): | |||||
| """ | |||||
| Parse the command line for options | |||||
| """ | |||||
| parser = optparse.OptionParser() | |||||
| parser.add_option( | |||||
| '--user', dest='user_install', action='store_true', default=False, | |||||
| help='install in user site package (requires Python 2.6 or later)') | |||||
| parser.add_option( | |||||
| '--download-base', dest='download_base', metavar="URL", | |||||
| default=DEFAULT_URL, | |||||
| help='alternative URL from where to download the setuptools package') | |||||
| parser.add_option( | |||||
| '--insecure', dest='downloader_factory', action='store_const', | |||||
| const=lambda: download_file_insecure, default=get_best_downloader, | |||||
| help='Use internal, non-validating downloader' | |||||
| ) | |||||
| parser.add_option( | |||||
| '--version', help="Specify which version to download", | |||||
| default=DEFAULT_VERSION, | |||||
| ) | |||||
| options, args = parser.parse_args() | |||||
| # positional arguments are ignored | |||||
| return options | |||||
| def main(): | |||||
| """Install or upgrade setuptools and EasyInstall""" | |||||
| options = _parse_args() | |||||
| archive = download_setuptools( | |||||
| version=options.version, | |||||
| download_base=options.download_base, | |||||
| downloader_factory=options.downloader_factory, | |||||
| ) | |||||
| return _install(archive, _build_install_args(options)) | |||||
| if __name__ == '__main__': | |||||
| sys.exit(main()) | |||||
| @@ -11,8 +11,7 @@ fi | |||||
| echo "Installing dependencies..." | echo "Installing dependencies..." | ||||
| echo "==========================" | echo "==========================" | ||||
| apt-get update | |||||
| apt-get -y install build-essential python-dev python-pip python-pygame supervisor git omxplayer | |||||
| apt update && apt -y install git build-essential python3-dev python3 python3-pip python3-pygame supervisor omxplayer | |||||
| echo "Installing hello_video..." | echo "Installing hello_video..." | ||||
| echo "=========================" | echo "=========================" | ||||
| @@ -27,7 +26,8 @@ rm -rf pi_hello_video | |||||
| echo "Installing video_looper program..." | echo "Installing video_looper program..." | ||||
| echo "==================================" | echo "==================================" | ||||
| mkdir -p /mnt/usbdrive0 # This is very important if you put your system in readonly after | mkdir -p /mnt/usbdrive0 # This is very important if you put your system in readonly after | ||||
| python setup.py install --force | |||||
| pip3 install setuptools | |||||
| python3 setup.py install --force | |||||
| cp video_looper.ini /boot/video_looper.ini | cp video_looper.ini /boot/video_looper.ini | ||||
| echo "Configuring video_looper to run on start..." | echo "Configuring video_looper to run on start..." | ||||
| @@ -1,5 +1,3 @@ | |||||
| from ez_setup import use_setuptools | |||||
| use_setuptools() | |||||
| from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||
| setup(name = 'Adafruit_Video_Looper', | setup(name = 'Adafruit_Video_Looper', | ||||
| @@ -1,7 +1,7 @@ | |||||
| # Supervisord configuration to run video looper at boot and | # Supervisord configuration to run video looper at boot and | ||||
| # ensure it runs continuously. | # ensure it runs continuously. | ||||
| [program:video_looper] | [program:video_looper] | ||||
| command=python -u -m Adafruit_Video_Looper.video_looper | |||||
| command=python3 -u -m Adafruit_Video_Looper.video_looper | |||||
| autostart=true | autostart=true | ||||
| autorestart=unexpected | autorestart=unexpected | ||||
| startsecs=5 | startsecs=5 | ||||