#!/usr/bin/env python from __future__ import with_statement, print_function, unicode_literals import os import re import shlex try: from shutil import which except ImportError: def which(cmd, mode=os.F_OK | os.X_OK, path=None): """Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such file. `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result of os.environ.get("PATH"), or can be overridden with a custom search path. """ # Check that a given file can be accessed with the correct mode. # Additionally check that `file` is not a directory, as on Windows # directories pass the os.access check. def _access_check(fn, mode): return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn)) # If we're given a path with a directory part, look it up directly rather # than referring to PATH directories. This includes checking relative to the # current directory, e.g. ./script if os.path.dirname(cmd): if _access_check(cmd, mode): return cmd return None if path is None: path = os.environ.get("PATH", os.defpath) if not path: return None path = path.split(os.pathsep) if platform == "win32": # The current directory takes precedence on Windows. if not os.curdir in path: path.insert(0, os.curdir) # PATHEXT is necessary to check on Windows. pathext = os.environ.get("PATHEXT", "").split(os.pathsep) # See if the given file matches any of the expected path extensions. # This will allow us to short circuit when given "python.exe". # If it does match, only test that one, otherwise we have to try # others. if any(cmd.lower().endswith(ext.lower()) for ext in pathext): files = [cmd] else: files = [cmd + ext for ext in pathext] else: # On other platforms you don't have things like PATHEXT to tell you # what file suffixes are executable, so just pass on cmd as-is. files = [cmd] seen = set() for dir in path: normdir = os.path.normcase(dir) if not normdir in seen: seen.add(normdir) for thefile in files: name = os.path.join(dir, thefile) if _access_check(name, mode): return name return None from subprocess import CalledProcessError, STDOUT, check_call, check_output, list2cmdline try: from subprocess import DEVNULL except ImportError: DEVNULL = open(os.devnull, 'wb') from sys import platform, stderr cmds = {} warnings = [] class CheckError(Exception): pass def checking(thing): stderr.write("checking %s... " % thing) stderr.flush() def warn(string): warnings.append(string) stderr.write("WARNING: %s\n" % string) def check(name, why, flags=[], optional=False, var=None, run=True): global warn checking("for %s" % name) var = var or name.upper() varvalue = os.getenv(var) if varvalue == '': stderr.write("skipping\n") if not optional: warn("%s is required to %s, the code will not fall back gracefully without it!" % (name, why)) return elif varvalue is None: varvalue = name split = shlex.split(varvalue) exe = which(split[0]) if not exe: stderr.write("not found\n") if not optional: raise CheckError() else: warn("%s is recommended to %s." % (name, why)) return stderr.write("%s\n" % exe) cmd = [exe] + split[1:] + flags + shlex.split(os.getenv(var + "FLAGS") or "") if run: checking("%s usability" % exe) try: check_call(cmd + ["-h"], stdout=DEVNULL, stderr=STDOUT) stderr.write("yes\n") except CalledProcessError as e: raise CheckError(e) cmds[var] = cmd return cmd def run_ffmpeg(arg): return check_output(ffmpeg + [arg], stderr=STDOUT).decode('utf-8') def ffmpeg_check(name, regex, checks, output): for check in checks: checking("if %s supports %s %s" % (ffmpeg[0], name, check)) if re.search("\n %s %s " % (regex, check), output): stderr.write("yes\n") else: stderr.write("no\n") raise CheckError() if __name__ == "__main__": # no, this line isn't bugged. really. examine check() and Makefile if not check("zopfli", "compress gzip files better than gzip -9", optional=True, var="GZIP"): check("gzip", "pre-compress the JSON files", ["-9"]) check("apngasm", "assemble the click-to-continue image", run=False) check("convert", "perform miscellaneous actions on the images", run=False) check("cwebp", "convert all images to WebP for webkit browsers", ["-quiet", "-alpha_cleanup", "-m", "6"]) ffmpeg = check("ffmpeg", "convert audio and video to HTML5 formats", ["-v", "warning", "-y"], run=False) if ffmpeg: ffmpeg_formats = run_ffmpeg("-formats") ffmpeg_check("demuxing", "D.", ["matroska,webm", "ogg", "wav", "yuv4mpegpipe"], ffmpeg_formats) ffmpeg_check("muxing", ".E", ["ipod", "mp4", "ogg", "wav", "webm", "yuv4mpegpipe"], ffmpeg_formats) ffmpeg_check("decoding", ".{6}", ["mpeg4", "rawvideo", "pcm_s16le", "vorbis"], run_ffmpeg("-decoders")) ffmpeg_check("encoding", ".{6}", ["libx264", "rawvideo", "libtheora", "libvpx", "libvpx-vp9", "libfdk_aac", "libopus", "pcm_s16le"], run_ffmpeg("-encoders")) check("npm", "install uglifyjs", run=False) check("webpmux", "assemble the WebP click-to-continue image") check("defluff", "decrease the size of DEFLATE files including gzip and PNG files", optional=True, run=False) check("pngquant", "quantize PNG images, improving compatibility and dramatically decreasing size", optional=True) check("zopflipng", "recompress DEFLATE files including gzip and PNG files", optional=True) stderr.write("creating Makefile.inc\n") with open("Makefile.inc", "w") as f: f.write(''.join('%s := %s\n' % (k, list2cmdline(cmds[k])) for k in cmds)) stderr.write("Making various directories\n") if not os.path.isdir("www/json"): os.mkdir("www/json") if len(warnings): stderr.write("Warnings during configuration:\n") stderr.write('\n'.join(warnings) + '\n') stderr.write("Run `make' now to build HTML5KS.\n")