diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..90ae317c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI +on: + - push + - pull_request +jobs: + test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + python-version: + - "2.7" + - "3.5" + - "3.6" + - "3.7" + - "3.8" + - "3.9" + - "3.10" + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install ffmpeg + run: | + sudo apt update + sudo apt install ffmpeg + - name: Setup pip + tox + run: | + python -m pip install --upgrade \ + "pip==20.3.4; python_version < '3.6'" \ + "pip==21.3.1; python_version >= '3.6'" + python -m pip install tox==3.24.5 tox-gh-actions==2.9.1 + - name: Test with tox + run: tox + black: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Black + run: | + # TODO: use standard `psf/black` action after dropping Python 2 support. + pip install black==21.12b0 click==8.0.2 # https://stackoverflow.com/questions/71673404 + black ffmpeg --check --color --diff diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 67b1a892..00000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: python -before_install: - - > - [ -f ffmpeg-release/ffmpeg ] || ( - curl -O https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-64bit-static.tar.xz && - mkdir -p ffmpeg-release && - tar Jxf ffmpeg-release-64bit-static.tar.xz --strip-components=1 -C ffmpeg-release - ) -matrix: - include: - - python: 2.7 - env: - - TOX_ENV=py27 - - python: 3.4 - env: - - TOX_ENV=py34 - - python: 3.5 - env: - - TOX_ENV=py35 - - python: 3.6 - env: - - TOX_ENV=py36 - - python: pypy - env: - - TOX_ENV=pypy -install: - - pip install tox -script: - - export PATH=$(readlink -f ffmpeg-release):$PATH - - tox -e $TOX_ENV -cache: - directories: - - .tox - - ffmpeg-release diff --git a/README.md b/README.md index f5bf6c3d..b8ee9221 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # ffmpeg-python: Python bindings for FFmpeg -[![Build status](https://travis-ci.org/kkroening/ffmpeg-python.svg?branch=master)](https://travis-ci.org/kkroening/ffmpeg-python) +[![CI][ci-badge]][ci] + +[ci-badge]: https://github.com/kkroening/ffmpeg-python/actions/workflows/ci.yml/badge.svg +[ci]: https://github.com/kkroening/ffmpeg-python/actions/workflows/ci.yml ffmpeg-python logo @@ -23,7 +26,8 @@ ffmpeg.run(stream) Or if you prefer a fluent interface: ```python import ffmpeg -(ffmpeg +( + ffmpeg .input('input.mp4') .hflip() .output('output.mp4') @@ -31,8 +35,10 @@ import ffmpeg ) ``` +## [API reference](https://kkroening.github.io/ffmpeg-python/) + ## Complex filter graphs -FFmpeg is extremely powerful, but its command-line interface gets really complicated really quickly - especially when working with signal graphs and doing anything more than trivial. +FFmpeg is extremely powerful, but its command-line interface gets really complicated rather quickly - especially when working with signal graphs and doing anything more than trivial. Take for example a signal graph that looks like this: @@ -40,26 +46,22 @@ Take for example a signal graph that looks like this: The corresponding command-line arguments are pretty gnarly: ```bash -ffmpeg -i input.mp4 \ - -filter_complex "\ - [0]trim=start_frame=10:end_frame=20[v0];\ - [0]trim=start_frame=30:end_frame=40[v1];\ - [v0][v1]concat=n=2[v2];\ - [1]hflip[v3];\ - [v2][v3]overlay=eof_action=repeat[v4];\ - [v4]drawbox=50:50:120:120:red:t=5[v5]"\ - -map [v5] output.mp4 +ffmpeg -i input.mp4 -i overlay.png -filter_complex "[0]trim=start_frame=10:end_frame=20[v0];\ + [0]trim=start_frame=30:end_frame=40[v1];[v0][v1]concat=n=2[v2];[1]hflip[v3];\ + [v2][v3]overlay=eof_action=repeat[v4];[v4]drawbox=50:50:120:120:red:t=5[v5]"\ + -map [v5] output.mp4 ``` Maybe this looks great to you, but if you're not an FFmpeg command-line expert, it probably looks alien. -If you're like me and find Python to be powerful and readable, it's easy with `ffmpeg-python`: +If you're like me and find Python to be powerful and readable, it's easier with `ffmpeg-python`: ```python import ffmpeg in_file = ffmpeg.input('input.mp4') overlay_file = ffmpeg.input('overlay.png') -(ffmpeg +( + ffmpeg .concat( in_file.trim(start_frame=10, end_frame=20), in_file.trim(start_frame=30, end_frame=40), @@ -71,85 +73,215 @@ overlay_file = ffmpeg.input('overlay.png') ) ``` -`ffmpeg-python` takes care of running `ffmpeg` with the command-line arguments that correspond to the above filter diagram, and it's easy to see what's going on and make changes as needed. +`ffmpeg-python` takes care of running `ffmpeg` with the command-line arguments that correspond to the above filter diagram, in familiar Python terms. Screenshot -Real-world signal graphs can get a heck of a lot more complex, but `ffmpeg-python` handles them with ease. - +Real-world signal graphs can get a heck of a lot more complex, but `ffmpeg-python` handles arbitrarily large (directed-acyclic) signal graphs. ## Installation -The easiest way to acquire the latest version of `ffmpeg-python` is through pip: +### Installing `ffmpeg-python` -``` +The latest version of `ffmpeg-python` can be acquired via a typical pip install: + +```bash pip install ffmpeg-python ``` -It's also possible to clone the source and put it on your python path (`$PYTHONPATH`, `sys.path`, etc.): - +Or the source can be cloned and installed from locally: ```bash -$ git clone git@github.com:kkroening/ffmpeg-python.git -$ export PYTHONPATH=${PYTHONPATH}:ffmpeg-python -$ python ->>> import ffmpeg +git clone git@github.com:kkroening/ffmpeg-python.git +pip install -e ./ffmpeg-python ``` +> **Note**: `ffmpeg-python` makes no attempt to download/install FFmpeg, as `ffmpeg-python` is merely a pure-Python wrapper - whereas FFmpeg installation is platform-dependent/environment-specific, and is thus the responsibility of the user, as described below. + +### Installing FFmpeg + +Before using `ffmpeg-python`, FFmpeg must be installed and accessible via the `$PATH` environment variable. + +There are a variety of ways to install FFmpeg, such as the [official download links](https://ffmpeg.org/download.html), or using your package manager of choice (e.g. `sudo apt install ffmpeg` on Debian/Ubuntu, `brew install ffmpeg` on OS X, etc.). + +Regardless of how FFmpeg is installed, you can check if your environment path is set correctly by running the `ffmpeg` command from the terminal, in which case the version information should appear, as in the following example (truncated for brevity): + +``` +$ ffmpeg +ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers + built with gcc 9 (Ubuntu 9.3.0-10ubuntu2) +``` + +> **Note**: The actual version information displayed here may vary from one system to another; but if a message such as `ffmpeg: command not found` appears instead of the version information, FFmpeg is not properly installed. + ## [Examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples) When in doubt, take a look at the [examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples) to see if there's something that's close to whatever you're trying to do. -## [API Reference](https://kkroening.github.io/ffmpeg-python/) +Here are a few: +- [Convert video to numpy array](https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md#convert-video-to-numpy-array) +- [Generate thumbnail for video](https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md#generate-thumbnail-for-video) +- [Read raw PCM audio via pipe](https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md#convert-sound-to-raw-pcm-audio) -API documentation is automatically generated from python docstrings and hosted on github pages: https://kkroening.github.io/ffmpeg-python/ +- [JupyterLab/Notebook stream editor](https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md#jupyter-stream-editor) -Alternatively, standard python help is available, such as at the python REPL prompt as follows: +jupyter demo -```python ->>> import ffmpeg ->>> help(ffmpeg) -``` +- [Tensorflow/DeepDream streaming](https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md#tensorflow-streaming) + +deep dream streaming + +See the [Examples README](https://github.com/kkroening/ffmpeg-python/tree/master/examples) for additional examples. ## Custom Filters -Don't see the filter you're looking for? `ffmpeg-python` is a work in progress, but it's easy to use any arbitrary ffmpeg filter: +Don't see the filter you're looking for? While `ffmpeg-python` includes shorthand notation for some of the most commonly used filters (such as `concat`), all filters can be referenced via the `.filter` operator: ```python stream = ffmpeg.input('dummy.mp4') -stream = ffmpeg.filter_(stream, 'fps', fps=25, round='up') +stream = ffmpeg.filter(stream, 'fps', fps=25, round='up') stream = ffmpeg.output(stream, 'dummy2.mp4') ffmpeg.run(stream) ``` Or fluently: ```python -(ffmpeg +( + ffmpeg .input('dummy.mp4') - .filter_('fps', fps=25, round='up') + .filter('fps', fps=25, round='up') .output('dummy2.mp4') .run() ) ``` -When in doubt, refer to the [existing filters](https://github.com/kkroening/ffmpeg-python/blob/master/ffmpeg/_filters.py) and/or the [official ffmpeg documentation](https://ffmpeg.org/ffmpeg-filters.html). +**Special option names:** + +Arguments with special names such as `-qscale:v` (variable bitrate), `-b:v` (constant bitrate), etc. can be specified as a keyword-args dictionary as follows: +```python +( + ffmpeg + .input('in.mp4') + .output('out.mp4', **{'qscale:v': 3}) + .run() +) +``` + +**Multiple inputs:** + +Filters that take multiple input streams can be used by passing the input streams as an array to `ffmpeg.filter`: +```python +main = ffmpeg.input('main.mp4') +logo = ffmpeg.input('logo.png') +( + ffmpeg + .filter([main, logo], 'overlay', 10, 10) + .output('out.mp4') + .run() +) +``` + +**Multiple outputs:** + +Filters that produce multiple outputs can be used with `.filter_multi_output`: +```python +split = ( + ffmpeg + .input('in.mp4') + .filter_multi_output('split') # or `.split()` +) +( + ffmpeg + .concat(split[0], split[1].reverse()) + .output('out.mp4') + .run() +) +``` +(In this particular case, `.split()` is the equivalent shorthand, but the general approach works for other multi-output filters) + +**String expressions:** + +Expressions to be interpreted by ffmpeg can be included as string parameters and reference any special ffmpeg variable names: +```python +( + ffmpeg + .input('in.mp4') + .filter('crop', 'in_w-2*10', 'in_h-2*20') + .input('out.mp4') +) +``` + +
+ +When in doubt, refer to the [existing filters](https://github.com/kkroening/ffmpeg-python/blob/master/ffmpeg/_filters.py), [examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples), and/or the [official ffmpeg documentation](https://ffmpeg.org/ffmpeg-filters.html). + +## Frequently asked questions + +**Why do I get an import/attribute/etc. error from `import ffmpeg`?** + +Make sure you ran `pip install ffmpeg-python` and _**not**_ `pip install ffmpeg` (wrong) or `pip install python-ffmpeg` (also wrong). + +**Why did my audio stream get dropped?** + +Some ffmpeg filters drop audio streams, and care must be taken to preserve the audio in the final output. The ``.audio`` and ``.video`` operators can be used to reference the audio/video portions of a stream so that they can be processed separately and then re-combined later in the pipeline. + +This dilemma is intrinsic to ffmpeg, and ffmpeg-python tries to stay out of the way while users may refer to the official ffmpeg documentation as to why certain filters drop audio. + +As usual, take a look at the [examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples#audiovideo-pipeline) (*Audio/video pipeline* in particular). + +**How can I find out the used command line arguments?** + +You can run `stream.get_args()` before `stream.run()` to retrieve the command line arguments that will be passed to `ffmpeg`. You can also run `stream.compile()` that also includes the `ffmpeg` executable as the first argument. + +**How do I do XYZ?** + +Take a look at each of the links in the [Additional Resources](https://kkroening.github.io/ffmpeg-python/) section at the end of this README. If you look everywhere and can't find what you're looking for and have a question that may be relevant to other users, you may open an issue asking how to do it, while providing a thorough explanation of what you're trying to do and what you've tried so far. + +Issues not directly related to `ffmpeg-python` or issues asking others to write your code for you or how to do the work of solving a complex signal processing problem for you that's not relevant to other users will be closed. + +That said, we hope to continue improving our documentation and provide a community of support for people using `ffmpeg-python` to do cool and exciting things. ## Contributing ffmpeg-python logo -Feel free to report any bugs or feature requests. +One of the best things you can do to help make `ffmpeg-python` better is to answer [open questions](https://github.com/kkroening/ffmpeg-python/labels/question) in the issue tracker. The questions that are answered will be tagged and incorporated into the documentation, examples, and other learning resources. + +If you notice things that could be better in the documentation or overall development experience, please say so in the [issue tracker](https://github.com/kkroening/ffmpeg-python/issues). And of course, feel free to report any bugs or submit feature requests. + +Pull requests are welcome as well, but it wouldn't hurt to touch base in the issue tracker or hop on the [Matrix chat channel](https://riot.im/app/#/room/#ffmpeg-python:matrix.org) first. + +Anyone who fixes any of the [open bugs](https://github.com/kkroening/ffmpeg-python/labels/bug) or implements [requested enhancements](https://github.com/kkroening/ffmpeg-python/labels/enhancement) is a hero, but changes should include passing tests. -It should be fairly easy to use filters that aren't explicitly built into `ffmpeg-python` but if there's a feature or filter you'd really like to see included in the library, don't hesitate to open a feature request. +### Running tests -Pull requests are welcome as well. +```bash +git clone git@github.com:kkroening/ffmpeg-python.git +cd ffmpeg-python +virtualenv venv +. venv/bin/activate # (OS X / Linux) +venv\bin\activate # (Windows) +pip install -e .[dev] +pytest +```
+### Special thanks + +- [Fabrice Bellard](https://bellard.org/) +- [The FFmpeg team](https://ffmpeg.org/donations.html) +- [Arne de Laat](https://github.com/153957) +- [Davide Depau](https://github.com/depau) +- [Dim](https://github.com/lloti) +- [Noah Stier](https://github.com/noahstier) + ## Additional Resources - [API Reference](https://kkroening.github.io/ffmpeg-python/) +- [Examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples) - [Filters](https://github.com/kkroening/ffmpeg-python/blob/master/ffmpeg/_filters.py) -- [Tests](https://github.com/kkroening/ffmpeg-python/blob/master/ffmpeg/tests/test_ffmpeg.py) - [FFmpeg Homepage](https://ffmpeg.org/) - [FFmpeg Documentation](https://ffmpeg.org/ffmpeg.html) - [FFmpeg Filters Documentation](https://ffmpeg.org/ffmpeg-filters.html) +- [Test cases](https://github.com/kkroening/ffmpeg-python/blob/master/ffmpeg/tests/test_ffmpeg.py) +- [Issue tracker](https://github.com/kkroening/ffmpeg-python/issues) - Matrix Chat: [#ffmpeg-python:matrix.org](https://riot.im/app/#/room/#ffmpeg-python:matrix.org) diff --git a/doc/html/.buildinfo b/doc/html/.buildinfo index 6f8859e7..c4716546 100644 --- a/doc/html/.buildinfo +++ b/doc/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: d3019c15b90af9d4beabe6f0fbc238a9 +config: f3635c9edf6e9bff1735d57d26069ada tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/doc/html/_static/ajax-loader.gif b/doc/html/_static/ajax-loader.gif deleted file mode 100644 index 61faf8ca..00000000 Binary files a/doc/html/_static/ajax-loader.gif and /dev/null differ diff --git a/doc/html/_static/basic.css b/doc/html/_static/basic.css index 3c7223b1..c41d718e 100644 --- a/doc/html/_static/basic.css +++ b/doc/html/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -81,10 +81,26 @@ div.sphinxsidebar input { font-size: 1em; } +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; } +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + img { border: 0; max-width: 100%; @@ -199,6 +215,11 @@ table.modindextable td { /* -- general body styles --------------------------------------------------- */ +div.body { + min-width: 450px; + max-width: 800px; +} + div.body p, div.body dd, div.body li, div.body blockquote { -moz-hyphens: auto; -ms-hyphens: auto; @@ -210,6 +231,16 @@ a.headerlink { visibility: hidden; } +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -258,6 +289,12 @@ img.align-center, .figure.align-center, object.align-center { margin-right: auto; } +img.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + .align-left { text-align: left; } @@ -266,6 +303,10 @@ img.align-center, .figure.align-center, object.align-center { text-align: center; } +.align-default { + text-align: center; +} + .align-right { text-align: right; } @@ -332,6 +373,16 @@ table.docutils { border-collapse: collapse; } +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + table caption span.caption-number { font-style: italic; } @@ -365,6 +416,16 @@ table.citation td { border-bottom: none; } +th > p:first-child, +td > p:first-child { + margin-top: 0px; +} + +th > p:last-child, +td > p:last-child { + margin-bottom: 0px; +} + /* -- figures --------------------------------------------------------------- */ div.figure { @@ -405,6 +466,13 @@ table.field-list td, table.field-list th { hyphens: manual; } +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist td { + vertical-align: top; +} + + /* -- other body styles ----------------------------------------------------- */ ol.arabic { @@ -427,11 +495,57 @@ ol.upperroman { list-style: upper-roman; } +li > p:first-child { + margin-top: 0px; +} + +li > p:last-child { + margin-bottom: 0px; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: flex; + flex-wrap: wrap; +} + +dl.field-list > dt { + flex-basis: 20%; + font-weight: bold; + word-break: break-word; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + flex-basis: 70%; + padding-left: 1em; + margin-left: 0em; + margin-bottom: 0em; +} + dl { margin-bottom: 15px; } -dd p { +dd > p:first-child { margin-top: 0px; } @@ -504,6 +618,12 @@ dl.glossary dt { font-style: oblique; } +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + abbr, acronym { border-bottom: dotted 1px; cursor: help; diff --git a/doc/html/_static/comment-bright.png b/doc/html/_static/comment-bright.png deleted file mode 100644 index 15e27edb..00000000 Binary files a/doc/html/_static/comment-bright.png and /dev/null differ diff --git a/doc/html/_static/comment-close.png b/doc/html/_static/comment-close.png deleted file mode 100644 index 4d91bcf5..00000000 Binary files a/doc/html/_static/comment-close.png and /dev/null differ diff --git a/doc/html/_static/comment.png b/doc/html/_static/comment.png deleted file mode 100644 index dfbc0cbd..00000000 Binary files a/doc/html/_static/comment.png and /dev/null differ diff --git a/doc/html/_static/doctools.js b/doc/html/_static/doctools.js index 24992e64..b33f87fc 100644 --- a/doc/html/_static/doctools.js +++ b/doc/html/_static/doctools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for all documentation. * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -70,7 +70,9 @@ jQuery.fn.highlightText = function(text, className) { if (node.nodeType === 3) { var val = node.nodeValue; var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { var span; var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); if (isInSVG) { @@ -85,14 +87,13 @@ jQuery.fn.highlightText = function(text, className) { node.nextSibling)); node.nodeValue = val.substr(0, pos); if (isInSVG) { - var bbox = span.getBBox(); var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - rect.x.baseVal.value = bbox.x; + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; rect.y.baseVal.value = bbox.y; rect.width.baseVal.value = bbox.width; rect.height.baseVal.value = bbox.height; rect.setAttribute('class', className); - var parentOfText = node.parentNode.parentNode; addItems.push({ "parent": node.parentNode, "target": rect}); @@ -148,7 +149,9 @@ var Documentation = { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); this.initIndexTable(); - + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } }, /** @@ -204,7 +207,7 @@ var Documentation = { * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 */ fixFirefoxAnchorBug : function() { - if (document.location.hash) + if (document.location.hash && $.browser.mozilla) window.setTimeout(function() { document.location.href += ''; }, 10); @@ -308,4 +311,4 @@ _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); -}); \ No newline at end of file +}); diff --git a/doc/html/_static/documentation_options.js b/doc/html/_static/documentation_options.js new file mode 100644 index 00000000..6d865102 --- /dev/null +++ b/doc/html/_static/documentation_options.js @@ -0,0 +1,10 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/doc/html/_static/down-pressed.png b/doc/html/_static/down-pressed.png deleted file mode 100644 index 5756c8ca..00000000 Binary files a/doc/html/_static/down-pressed.png and /dev/null differ diff --git a/doc/html/_static/down.png b/doc/html/_static/down.png deleted file mode 100644 index 1b3bdad2..00000000 Binary files a/doc/html/_static/down.png and /dev/null differ diff --git a/doc/html/_static/jquery-3.1.0.js b/doc/html/_static/jquery-3.2.1.js similarity index 94% rename from doc/html/_static/jquery-3.1.0.js rename to doc/html/_static/jquery-3.2.1.js index f2fc2747..d2d8ca47 100644 --- a/doc/html/_static/jquery-3.1.0.js +++ b/doc/html/_static/jquery-3.2.1.js @@ -1,16 +1,15 @@ -/*eslint-disable no-unused-vars*/ /*! - * jQuery JavaScript Library v3.1.0 + * jQuery JavaScript Library v3.2.1 * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2016-07-07T21:44Z + * Date: 2017-03-20T18:59Z */ ( function( global, factory ) { @@ -83,13 +82,13 @@ var support = {}; doc.head.appendChild( script ).parentNode.removeChild( script ); } /* global Symbol */ -// Defining this global in .eslintrc would create a danger of using the global +// Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module var - version = "3.1.0", + version = "3.2.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -129,13 +128,14 @@ jQuery.fn = jQuery.prototype = { // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { - return num != null ? - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } - // Return all the elements in a clean array - slice.call( this ); + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack @@ -236,11 +236,11 @@ jQuery.extend = jQuery.fn.extend = function() { // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + ( copyIsArray = Array.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; + clone = src && Array.isArray( src ) ? src : []; } else { clone = src && jQuery.isPlainObject( src ) ? src : {}; @@ -279,8 +279,6 @@ jQuery.extend( { return jQuery.type( obj ) === "function"; }, - isArray: Array.isArray, - isWindow: function( obj ) { return obj != null && obj === obj.window; }, @@ -355,10 +353,6 @@ jQuery.extend( { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); }, - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - each: function( obj, callback ) { var length, i = 0; @@ -543,14 +537,14 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.0 + * Sizzle CSS Selector Engine v2.3.3 * https://sizzlejs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2016-01-04 + * Date: 2016-08-08 */ (function( window ) { @@ -696,7 +690,7 @@ var i, // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { @@ -723,7 +717,7 @@ var i, disabledAncestor = addCombinator( function( elem ) { - return elem.disabled === true; + return elem.disabled === true && ("form" in elem || "label" in elem); }, { dir: "parentNode", next: "legend" } ); @@ -1009,26 +1003,54 @@ function createButtonPseudo( type ) { * @param {Boolean} disabled true for :disabled; false for :enabled */ function createDisabledPseudo( disabled ) { - // Known :disabled false positives: - // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) - // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return function( elem ) { - // Check form elements and option elements for explicit disabling - return "label" in elem && elem.disabled === disabled || - "form" in elem && elem.disabled === disabled || + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } - // Check non-disabled form elements for fieldset[disabled] ancestors - "form" in elem && elem.disabled === false && ( - // Support: IE6-11+ - // Ancestry is covered for us - elem.isDisabled === disabled || + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || - // Otherwise, assume any non-