diff --git a/.github/ISSUE_TEMPLATE/1_broken_site.md b/.github/ISSUE_TEMPLATE/1_broken_site.md
index 4eb505231..e5405c235 100644
--- a/.github/ISSUE_TEMPLATE/1_broken_site.md
+++ b/.github/ISSUE_TEMPLATE/1_broken_site.md
@@ -18,7 +18,7 @@ title: ''
- [ ] I'm reporting a broken site support
-- [ ] I've verified that I'm running youtube-dl version **2021.06.06**
+- [ ] I've verified that I'm running youtube-dl version **2021.12.17**
- [ ] I've checked that all provided URLs are alive and playable in a browser
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
- [ ] I've searched the bugtracker for similar issues including closed ones
@@ -41,7 +41,7 @@ Add the `-v` flag to your command line you run youtube-dl with (`youtube-dl -v <
[debug] User config: []
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
- [debug] youtube-dl version 2021.06.06
+ [debug] youtube-dl version 2021.12.17
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
[debug] Proxy map: {}
diff --git a/.github/ISSUE_TEMPLATE/2_site_support_request.md b/.github/ISSUE_TEMPLATE/2_site_support_request.md
index 9fed0b489..33b01ce7f 100644
--- a/.github/ISSUE_TEMPLATE/2_site_support_request.md
+++ b/.github/ISSUE_TEMPLATE/2_site_support_request.md
@@ -19,7 +19,7 @@ labels: 'site-support-request'
- [ ] I'm reporting a new site support request
-- [ ] I've verified that I'm running youtube-dl version **2021.06.06**
+- [ ] I've verified that I'm running youtube-dl version **2021.12.17**
- [ ] I've checked that all provided URLs are alive and playable in a browser
- [ ] I've checked that none of provided URLs violate any copyrights
- [ ] I've searched the bugtracker for similar site support requests including closed ones
diff --git a/.github/ISSUE_TEMPLATE/3_site_feature_request.md b/.github/ISSUE_TEMPLATE/3_site_feature_request.md
index 573e8ded0..285610cc7 100644
--- a/.github/ISSUE_TEMPLATE/3_site_feature_request.md
+++ b/.github/ISSUE_TEMPLATE/3_site_feature_request.md
@@ -18,13 +18,13 @@ title: ''
- [ ] I'm reporting a site feature request
-- [ ] I've verified that I'm running youtube-dl version **2021.06.06**
+- [ ] I've verified that I'm running youtube-dl version **2021.12.17**
- [ ] I've searched the bugtracker for similar site feature requests including closed ones
diff --git a/.github/ISSUE_TEMPLATE/4_bug_report.md b/.github/ISSUE_TEMPLATE/4_bug_report.md
index c0031bf7a..af73525fb 100644
--- a/.github/ISSUE_TEMPLATE/4_bug_report.md
+++ b/.github/ISSUE_TEMPLATE/4_bug_report.md
@@ -18,7 +18,7 @@ title: ''
- [ ] I'm reporting a broken site support issue
-- [ ] I've verified that I'm running youtube-dl version **2021.06.06**
+- [ ] I've verified that I'm running youtube-dl version **2021.12.17**
- [ ] I've checked that all provided URLs are alive and playable in a browser
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
- [ ] I've searched the bugtracker for similar bug reports including closed ones
@@ -43,7 +43,7 @@ Add the `-v` flag to your command line you run youtube-dl with (`youtube-dl -v <
[debug] User config: []
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
- [debug] youtube-dl version 2021.06.06
+ [debug] youtube-dl version 2021.12.17
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
[debug] Proxy map: {}
diff --git a/.github/ISSUE_TEMPLATE/5_feature_request.md b/.github/ISSUE_TEMPLATE/5_feature_request.md
index 1138ab2ca..42c878b83 100644
--- a/.github/ISSUE_TEMPLATE/5_feature_request.md
+++ b/.github/ISSUE_TEMPLATE/5_feature_request.md
@@ -19,13 +19,13 @@ labels: 'request'
- [ ] I'm reporting a feature request
-- [ ] I've verified that I'm running youtube-dl version **2021.06.06**
+- [ ] I've verified that I'm running youtube-dl version **2021.12.17**
- [ ] I've searched the bugtracker for similar feature requests including closed ones
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..3ba13e0ce
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 90bd63c32..93562afd7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,81 +1,476 @@
name: CI
-on: [push, pull_request]
+
+env:
+ all-cpython-versions: 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12
+ main-cpython-versions: 2.7, 3.2, 3.5, 3.9, 3.11
+ pypy-versions: pypy-2.7, pypy-3.6, pypy-3.7
+ cpython-versions: main
+ test-set: core
+ # Python beta version to be built using pyenv before setup-python support
+ # Must also be included in all-cpython-versions
+ next: 3.13
+
+on:
+ push:
+ # push inputs aren't known to GitHub
+ inputs:
+ cpython-versions:
+ type: string
+ default: all
+ test-set:
+ type: string
+ default: core
+ pull_request:
+ # pull_request inputs aren't known to GitHub
+ inputs:
+ cpython-versions:
+ type: string
+ default: main
+ test-set:
+ type: string
+ default: both
+ workflow_dispatch:
+ inputs:
+ cpython-versions:
+ type: choice
+ description: CPython versions (main = 2.7, 3.2, 3.5, 3.9, 3.11)
+ options:
+ - all
+ - main
+ required: true
+ default: main
+ test-set:
+ type: choice
+ description: core, download
+ options:
+ - both
+ - core
+ - download
+ required: true
+ default: both
+
+permissions:
+ contents: read
+
jobs:
+ select:
+ name: Select tests from inputs
+ runs-on: ubuntu-latest
+ outputs:
+ cpython-versions: ${{ steps.run.outputs.cpython-versions }}
+ test-set: ${{ steps.run.outputs.test-set }}
+ own-pip-versions: ${{ steps.run.outputs.own-pip-versions }}
+ steps:
+ # push and pull_request inputs aren't known to GitHub (pt3)
+ - name: Set push defaults
+ if: ${{ github.event_name == 'push' }}
+ env:
+ cpython-versions: all
+ test-set: core
+ run: |
+ echo "cpython-versions=${{env.cpython-versions}}" >> "$GITHUB_ENV"
+ echo "test_set=${{env.test_set}}" >> "$GITHUB_ENV"
+ - name: Get pull_request inputs
+ if: ${{ github.event_name == 'pull_request' }}
+ env:
+ cpython-versions: main
+ test-set: both
+ run: |
+ echo "cpython-versions=${{env.cpython-versions}}" >> "$GITHUB_ENV"
+ echo "test_set=${{env.test_set}}" >> "$GITHUB_ENV"
+ - name: Make version array
+ id: run
+ run: |
+ # Make a JSON Array from comma/space-separated string (no extra escaping)
+ json_list() { \
+ ret=""; IFS="${IFS},"; set -- $*; \
+ for a in "$@"; do \
+ ret=$(printf '%s"%s"' "${ret}${ret:+, }" "$a"); \
+ done; \
+ printf '[%s]' "$ret"; }
+ tests="${{ inputs.test-set || env.test-set }}"
+ [ $tests = both ] && tests="core download"
+ printf 'test-set=%s\n' "$(json_list $tests)" >> "$GITHUB_OUTPUT"
+ versions="${{ inputs.cpython-versions || env.cpython-versions }}"
+ if [ "$versions" = all ]; then \
+ versions="${{ env.all-cpython-versions }}"; else \
+ versions="${{ env.main-cpython-versions }}"; \
+ fi
+ printf 'cpython-versions=%s\n' \
+ "$(json_list ${versions}${versions:+, }${{ env.pypy-versions }})" >> "$GITHUB_OUTPUT"
+ # versions with a special get-pip.py in a per-version subdirectory
+ printf 'own-pip-versions=%s\n' \
+ "$(json_list 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6)" >> "$GITHUB_OUTPUT"
+
tests:
- name: Tests
+ name: Run tests
+ needs: select
+ permissions:
+ contents: read
+ packages: write
runs-on: ${{ matrix.os }}
+ env:
+ PIP: python -m pip
+ PIP_DISABLE_PIP_VERSION_CHECK: true
+ PIP_NO_PYTHON_VERSION_WARNING: true
strategy:
fail-fast: true
matrix:
- os: [ubuntu-18.04]
- # TODO: python 2.6
- python-version: [2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy-2.7, pypy-3.6, pypy-3.7]
+ os: [ubuntu-20.04]
+ python-version: ${{ fromJSON(needs.select.outputs.cpython-versions) }}
python-impl: [cpython]
- ytdl-test-set: [core, download]
+ ytdl-test-set: ${{ fromJSON(needs.select.outputs.test-set) }}
run-tests-ext: [sh]
include:
- # python 3.2 is only available on windows via setup-python
- - os: windows-latest
- python-version: 3.2
+ - os: windows-2019
+ python-version: 3.4
python-impl: cpython
- ytdl-test-set: core
+ ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'core') && 'core' || 'nocore' }}
run-tests-ext: bat
- - os: windows-latest
- python-version: 3.2
+ - os: windows-2019
+ python-version: 3.4
python-impl: cpython
- ytdl-test-set: download
+ ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'download') && 'download' || 'nodownload' }}
run-tests-ext: bat
# jython
- - os: ubuntu-18.04
+ - os: ubuntu-20.04
+ python-version: 2.7
python-impl: jython
- ytdl-test-set: core
+ ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'core') && 'core' || 'nocore' }}
run-tests-ext: sh
- - os: ubuntu-18.04
+ - os: ubuntu-20.04
+ python-version: 2.7
python-impl: jython
- ytdl-test-set: download
+ ytdl-test-set: ${{ contains(needs.select.outputs.test-set, 'download') && 'download' || 'nodownload' }}
run-tests-ext: sh
steps:
- - uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
- if: ${{ matrix.python-impl == 'cpython' }}
+ - name: Prepare Linux
+ if: ${{ startswith(matrix.os, 'ubuntu') }}
+ shell: bash
+ run: |
+ # apt in runner, if needed, may not be up-to-date
+ sudo apt-get update
+ - name: Checkout
+ uses: actions/checkout@v3
+ #-------- Python 3 -----
+ - name: Set up supported Python ${{ matrix.python-version }}
+ id: setup-python
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version != '2.6' && matrix.python-version != '2.7' && matrix.python-version != env.next }}
+ # wrap broken actions/setup-python@v4
+ # NB may run apt-get install in Linux
+ uses: ytdl-org/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
+ cache-build: true
+ allow-build: info
+ - name: Locate supported Python ${{ matrix.python-version }}
+ if: ${{ env.pythonLocation }}
+ shell: bash
+ run: |
+ echo "PYTHONHOME=${pythonLocation}" >> "$GITHUB_ENV"
+ export expected="${{ steps.setup-python.outputs.python-path }}"
+ dirname() { printf '%s\n' \
+ 'import os, sys' \
+ 'print(os.path.dirname(sys.argv[1]))' \
+ | ${expected} - "$1"; }
+ expd="$(dirname "$expected")"
+ export python="$(command -v python)"
+ [ "$expd" = "$(dirname "$python")" ] || echo "PATH=$expd:${PATH}" >> "$GITHUB_ENV"
+ [ -x "$python" ] || printf '%s\n' \
+ 'import os' \
+ 'exp = os.environ["expected"]' \
+ 'python = os.environ["python"]' \
+ 'exps = os.path.split(exp)' \
+ 'if python and (os.path.dirname(python) == exp[0]):' \
+ ' exit(0)' \
+ 'exps[1] = "python" + os.path.splitext(exps[1])[1]' \
+ 'python = os.path.join(*exps)' \
+ 'try:' \
+ ' os.symlink(exp, python)' \
+ 'except AttributeError:' \
+ ' os.rename(exp, python)' \
+ | ${expected} -
+ printf '%s\n' \
+ 'import sys' \
+ 'print(sys.path)' \
+ | ${expected} -
+ #-------- Python next (was 3.12) -
+ - name: Set up CPython 3.next environment
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == env.next }}
+ shell: bash
+ run: |
+ PYENV_ROOT=$HOME/.local/share/pyenv
+ echo "PYENV_ROOT=${PYENV_ROOT}" >> "$GITHUB_ENV"
+ - name: Cache Python 3.next
+ id: cachenext
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == env.next }}
+ uses: actions/cache@v3
+ with:
+ key: python-${{ env.next }}
+ path: |
+ ${{ env.PYENV_ROOT }}
+ - name: Build and set up Python 3.next
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == env.next && ! steps.cachenext.outputs.cache-hit }}
+ # dl and build locally
+ shell: bash
+ run: |
+ # Install build environment
+ sudo apt-get install -y build-essential llvm libssl-dev tk-dev \
+ libncursesw5-dev libreadline-dev libsqlite3-dev \
+ libffi-dev xz-utils zlib1g-dev libbz2-dev liblzma-dev
+ # Download PyEnv from its GitHub repository.
+ export PYENV_ROOT=${{ env.PYENV_ROOT }}
+ export PATH=$PYENV_ROOT/bin:$PATH
+ git clone "https://github.com/pyenv/pyenv.git" "$PYENV_ROOT"
+ pyenv install ${{ env.next }}
+ - name: Locate Python 3.next
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == env.next }}
+ shell: bash
+ run: |
+ PYTHONHOME="$(echo "${{ env.PYENV_ROOT }}/versions/${{ env.next }}."*)"
+ test -n "$PYTHONHOME"
+ echo "PYTHONHOME=$PYTHONHOME" >> "$GITHUB_ENV"
+ echo "PATH=${PYTHONHOME}/bin:$PATH" >> "$GITHUB_ENV"
+ #-------- Python 2.7 --
+ - name: Set up Python 2.7
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == '2.7' }}
+ # install 2.7
+ shell: bash
+ run: |
+ sudo apt-get install -y python2 python-is-python2
+ echo "PYTHONHOME=/usr" >> "$GITHUB_ENV"
+ #-------- Python 2.6 --
+ - name: Set up Python 2.6 environment
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == '2.6' }}
+ shell: bash
+ run: |
+ openssl_name=openssl-1.0.2u
+ echo "openssl_name=${openssl_name}" >> "$GITHUB_ENV"
+ openssl_dir=$HOME/.local/opt/$openssl_name
+ echo "openssl_dir=${openssl_dir}" >> "$GITHUB_ENV"
+ PYENV_ROOT=$HOME/.local/share/pyenv
+ echo "PYENV_ROOT=${PYENV_ROOT}" >> "$GITHUB_ENV"
+ sudo apt-get install -y openssl ca-certificates
+ - name: Cache Python 2.6
+ id: cache26
+ if: ${{ matrix.python-version == '2.6' }}
+ uses: actions/cache@v3
+ with:
+ key: python-2.6.9
+ path: |
+ ${{ env.openssl_dir }}
+ ${{ env.PYENV_ROOT }}
+ - name: Build and set up Python 2.6
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == '2.6' && ! steps.cache26.outputs.cache-hit }}
+ # dl and build locally
+ shell: bash
+ run: |
+ # Install build environment
+ sudo apt-get install -y build-essential llvm libssl-dev tk-dev \
+ libncursesw5-dev libreadline-dev libsqlite3-dev \
+ libffi-dev xz-utils zlib1g-dev libbz2-dev liblzma-dev
+ # Download and install OpenSSL 1.0.2, back in time
+ openssl_name=${{ env.openssl_name }}
+ openssl_targz=${openssl_name}.tar.gz
+ openssl_dir=${{ env.openssl_dir }}
+ openssl_inc=$openssl_dir/include
+ openssl_lib=$openssl_dir/lib
+ openssl_ssl=$openssl_dir/ssl
+ curl -L "https://www.openssl.org/source/$openssl_targz" -o $openssl_targz
+ tar -xf $openssl_targz
+ ( cd $openssl_name; \
+ ./config --prefix=$openssl_dir --openssldir=${openssl_dir}/ssl \
+ --libdir=lib -Wl,-rpath=${openssl_dir}/lib shared zlib-dynamic && \
+ make && \
+ make install )
+ rm -rf $openssl_name
+ rmdir $openssl_ssl/certs && ln -s /etc/ssl/certs $openssl_ssl/certs
+ # Download PyEnv from its GitHub repository.
+ export PYENV_ROOT=${{ env.PYENV_ROOT }}
+ export PATH=$PYENV_ROOT/bin:$PATH
+ git clone "https://github.com/pyenv/pyenv.git" "$PYENV_ROOT"
+ # Prevent pyenv build trying (and failing) to update pip
+ export GET_PIP=get-pip-2.6.py
+ echo 'import sys; sys.exit(0)' > ${GET_PIP}
+ GET_PIP=$(realpath $GET_PIP)
+ # Build and install Python
+ export CFLAGS="-I$openssl_inc"
+ export LDFLAGS="-L$openssl_lib"
+ export LD_LIBRARY_PATH="$openssl_lib"
+ pyenv install 2.6.9
+ - name: Locate Python 2.6
+ if: ${{ matrix.python-impl == 'cpython' && matrix.python-version == '2.6' }}
+ shell: bash
+ run: |
+ PYTHONHOME="${{ env.PYENV_ROOT }}/versions/2.6.9"
+ echo "PYTHONHOME=$PYTHONHOME" >> "$GITHUB_ENV"
+ echo "PATH=${PYTHONHOME}/bin:$PATH" >> "$GITHUB_ENV"
+ echo "LD_LIBRARY_PATH=${{ env.openssl_dir }}/lib${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" >> "$GITHUB_ENV"
+ #-------- Jython ------
- name: Set up Java 8
if: ${{ matrix.python-impl == 'jython' }}
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v3
with:
java-version: 8
+ distribution: 'zulu'
+ - name: Setup Jython environment
+ if: ${{ matrix.python-impl == 'jython' }}
+ shell: bash
+ run: |
+ echo "JYTHON_ROOT=${HOME}/jython" >> "$GITHUB_ENV"
+ echo "PIP=pip" >> "$GITHUB_ENV"
+ - name: Cache Jython
+ id: cachejy
+ if: ${{ matrix.python-impl == 'jython' && matrix.python-version == '2.7' }}
+ uses: actions/cache@v3
+ with:
+ # 2.7.3 now available, may solve SNI issue
+ key: jython-2.7.1
+ path: |
+ ${{ env.JYTHON_ROOT }}
- name: Install Jython
- if: ${{ matrix.python-impl == 'jython' }}
+ if: ${{ matrix.python-impl == 'jython' && matrix.python-version == '2.7' && ! steps.cachejy.outputs.cache-hit }}
+ shell: bash
run: |
- wget https://repo1.maven.org/maven2/org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar -O jython-installer.jar
- java -jar jython-installer.jar -s -d "$HOME/jython"
- echo "$HOME/jython/bin" >> $GITHUB_PATH
- - name: Install nose
- if: ${{ matrix.python-impl != 'jython' }}
- run: pip install nose
- - name: Install nose (Jython)
- if: ${{ matrix.python-impl == 'jython' }}
- # Working around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
+ JYTHON_ROOT="${{ env.JYTHON_ROOT }}"
+ curl -L "https://repo1.maven.org/maven2/org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar" -o jython-installer.jar
+ java -jar jython-installer.jar -s -d "${JYTHON_ROOT}"
+ echo "${JYTHON_ROOT}/bin" >> "$GITHUB_PATH"
+ - name: Set up cached Jython
+ if: ${{ steps.cachejy.outputs.cache-hit }}
+ shell: bash
run: |
- wget https://files.pythonhosted.org/packages/99/4f/13fb671119e65c4dce97c60e67d3fd9e6f7f809f2b307e2611f4701205cb/nose-1.3.7-py2-none-any.whl
- pip install nose-1.3.7-py2-none-any.whl
+ JYTHON_ROOT="${{ env.JYTHON_ROOT }}"
+ echo "${JYTHON_ROOT}/bin" >> $GITHUB_PATH
+ - name: Install supporting Python 2.7 if possible
+ if: ${{ steps.cachejy.outputs.cache-hit }}
+ shell: bash
+ run: |
+ sudo apt-get install -y python2.7 || true
+ #-------- pip ---------
+ - name: Set up supported Python ${{ matrix.python-version }} pip
+ if: ${{ (matrix.python-version != '3.2' && steps.setup-python.outputs.python-path) || matrix.python-version == '2.7' }}
+ # This step may run in either Linux or Windows
+ shell: bash
+ run: |
+ echo "$PATH"
+ echo "$PYTHONHOME"
+ # curl is available on both Windows and Linux, -L follows redirects, -O gets name
+ python -m ensurepip || python -m pip --version || { \
+ get_pip="${{ contains(needs.select.outputs.own-pip-versions, matrix.python-version) && format('{0}/', matrix.python-version) || '' }}"; \
+ curl -L -O "https://bootstrap.pypa.io/pip/${get_pip}get-pip.py"; \
+ python get-pip.py; }
+ - name: Set up Python 2.6 pip
+ if: ${{ matrix.python-version == '2.6' }}
+ shell: bash
+ run: |
+ python -m pip --version || { \
+ curl -L -O "https://bootstrap.pypa.io/pip/2.6/get-pip.py"; \
+ curl -L -O "https://files.pythonhosted.org/packages/ac/95/a05b56bb975efa78d3557efa36acaf9cf5d2fd0ee0062060493687432e03/pip-9.0.3-py2.py3-none-any.whl"; \
+ python get-pip.py --no-setuptools --no-wheel pip-9.0.3-py2.py3-none-any.whl; }
+ # work-around to invoke pip module on 2.6: https://bugs.python.org/issue2751
+ echo "PIP=python -m pip.__main__" >> "$GITHUB_ENV"
+ - name: Set up other Python ${{ matrix.python-version }} pip
+ if: ${{ matrix.python-version == '3.2' && steps.setup-python.outputs.python-path }}
+ shell: bash
+ run: |
+ python -m pip --version || { \
+ curl -L -O "https://bootstrap.pypa.io/pip/3.2/get-pip.py"; \
+ curl -L -O "https://files.pythonhosted.org/packages/b2/d0/cd115fe345dd6f07ec1c780020a7dfe74966fceeb171e0f20d1d4905b0b7/pip-7.1.2-py2.py3-none-any.whl"; \
+ python get-pip.py --no-setuptools --no-wheel pip-7.1.2-py2.py3-none-any.whl; }
+ #-------- unittest ----
+ - name: Upgrade Unittest for Python 2.6
+ if: ${{ matrix.python-version == '2.6' }}
+ shell: bash
+ run: |
+ # Work around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
+ $PIP -qq show unittest2 || { \
+ for u in "65/26/32b8464df2a97e6dd1b656ed26b2c194606c16fe163c695a992b36c11cdf/six-1.13.0-py2.py3-none-any.whl" \
+ "f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl" \
+ "c7/a3/c5da2a44c85bfbb6eebcfc1dde24933f8704441b98fdde6528f4831757a6/linecache2-1.0.0-py2.py3-none-any.whl" \
+ "17/0a/6ac05a3723017a967193456a2efa0aa9ac4b51456891af1e2353bb9de21e/traceback2-1.4.0-py2.py3-none-any.whl" \
+ "72/20/7f0f433060a962200b7272b8c12ba90ef5b903e218174301d0abfd523813/unittest2-1.1.0-py2.py3-none-any.whl"; do \
+ curl -L -O "https://files.pythonhosted.org/packages/${u}"; \
+ $PIP install ${u##*/}; \
+ done; }
+ # make tests use unittest2
+ for test in ./test/test_*.py ./test/helper.py; do
+ sed -r -i -e '/^import unittest$/s/test/test2 as unittest/' "$test"
+ done
+ #-------- nose --------
+ - name: Install nose for Python ${{ matrix.python-version }}
+ if: ${{ (matrix.python-version != '3.2' && steps.setup-python.outputs.python-path) || (matrix.python-impl == 'cpython' && (matrix.python-version == '2.7' || matrix.python-version == env.next)) }}
+ shell: bash
+ run: |
+ echo "$PATH"
+ echo "$PYTHONHOME"
+ # Use PyNose for recent Pythons instead of Nose
+ py3ver="${{ matrix.python-version }}"
+ py3ver=${py3ver#3.}
+ [ "$py3ver" != "${{ matrix.python-version }}" ] && py3ver=${py3ver%.*} || py3ver=0
+ [ "$py3ver" -ge 9 ] && nose=pynose || nose=nose
+ $PIP -qq show $nose || $PIP install $nose
+ - name: Install nose for other Python 2
+ if: ${{ matrix.python-impl == 'jython' || (matrix.python-impl == 'cpython' && matrix.python-version == '2.6') }}
+ shell: bash
+ run: |
+ # Work around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
+ $PIP -qq show nose || { \
+ curl -L -O "https://files.pythonhosted.org/packages/99/4f/13fb671119e65c4dce97c60e67d3fd9e6f7f809f2b307e2611f4701205cb/nose-1.3.7-py2-none-any.whl"; \
+ $PIP install nose-1.3.7-py2-none-any.whl; }
+ - name: Install nose for other Python 3
+ if: ${{ matrix.python-version == '3.2' && steps.setup-python.outputs.python-path }}
+ shell: bash
+ run: |
+ $PIP -qq show nose || { \
+ curl -L -O "https://files.pythonhosted.org/packages/15/d8/dd071918c040f50fa1cf80da16423af51ff8ce4a0f2399b7bf8de45ac3d9/nose-1.3.7-py3-none-any.whl"; \
+ $PIP install nose-1.3.7-py3-none-any.whl; }
+ - name: Set up nosetest test
+ if: ${{ contains(needs.select.outputs.test-set, matrix.ytdl-test-set ) }}
+ shell: bash
+ run: |
+ # set PYTHON_VER
+ PYTHON_VER=${{ matrix.python-version }}
+ [ "${PYTHON_VER#*-}" != "$PYTHON_VER" ] || PYTHON_VER="${{ matrix.python-impl }}-${PYTHON_VER}"
+ echo "PYTHON_VER=$PYTHON_VER" >> "$GITHUB_ENV"
+ echo "PYTHON_IMPL=${{ matrix.python-impl }}" >> "$GITHUB_ENV"
+ # define a test to validate the Python version used by nosetests
+ printf '%s\n' \
+ 'from __future__ import unicode_literals' \
+ 'import sys, os, platform' \
+ 'try:' \
+ ' import unittest2 as unittest' \
+ 'except ImportError:' \
+ ' import unittest' \
+ 'class TestPython(unittest.TestCase):' \
+ ' def setUp(self):' \
+ ' self.ver = os.environ["PYTHON_VER"].split("-")' \
+ ' def test_python_ver(self):' \
+ ' self.assertEqual(["%d" % v for v in sys.version_info[:2]], self.ver[-1].split(".")[:2])' \
+ ' self.assertTrue(sys.version.startswith(self.ver[-1]))' \
+ ' self.assertIn(self.ver[0], ",".join((sys.version, platform.python_implementation())).lower())' \
+ ' def test_python_impl(self):' \
+ ' self.assertIn(platform.python_implementation().lower(), (os.environ["PYTHON_IMPL"], self.ver[0]))' \
+ > test/test_python.py
+ #-------- TESTS -------
- name: Run tests
+ if: ${{ contains(needs.select.outputs.test-set, matrix.ytdl-test-set ) }}
continue-on-error: ${{ matrix.ytdl-test-set == 'download' || matrix.python-impl == 'jython' }}
env:
YTDL_TEST_SET: ${{ matrix.ytdl-test-set }}
- run: ./devscripts/run_tests.${{ matrix.run-tests-ext }}
+ run: |
+ ./devscripts/run_tests.${{ matrix.run-tests-ext }}
flake8:
name: Linter
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install flake8
run: pip install flake8
- name: Run flake8
run: flake8 .
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 58ab3a4b8..ff40cef78 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -150,7 +150,7 @@ After you have ensured this site is distributing its content legally, you can fo
# TODO more properties (see youtube_dl/extractor/common.py)
}
```
-5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/extractors.py).
+5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/extractors.py). This makes the extractor available for use, as long as the class ends with `IE`.
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc. Note that tests with `only_matching` key in test's dict are not counted in.
7. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/ytdl-org/youtube-dl/blob/7f41a598b3fba1bcab2817de64a08941200aa3c8/youtube_dl/extractor/common.py#L94-L303). Add tests and code for as many as you want.
8. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://flake8.pycqa.org/en/latest/index.html#quickstart):
diff --git a/ChangeLog b/ChangeLog
index 680fffdf8..658864282 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+version 2021.12.17
+
+Core
+* [postprocessor/ffmpeg] Show ffmpeg output on error (#22680, #29336)
+
+Extractors
+* [youtube] Update signature function patterns (#30363, #30366)
+* [peertube] Only call description endpoint if necessary (#29383)
+* [periscope] Pass referer to HLS requests (#29419)
+- [liveleak] Remove extractor (#17625, #24222, #29331)
++ [pornhub] Add support for pornhubthbh7ap3u.onion
+* [pornhub] Detect geo restriction
+* [pornhub] Dismiss tbr extracted from download URLs (#28927)
+* [curiositystream:collection] Extend _VALID_URL (#26326, #29117)
+* [youtube] Make get_video_info processing more robust (#29333)
+* [youtube] Workaround for get_video_info request (#29333)
+* [bilibili] Strip uploader name (#29202)
+* [youtube] Update invidious instance list (#29281)
+* [umg:de] Update GraphQL API URL (#29304)
+* [nrk] Switch psapi URL to https (#29344)
++ [egghead] Add support for app.egghead.io (#28404, #29303)
+* [appleconnect] Fix extraction (#29208)
++ [orf:tvthek] Add support for MPD formats (#28672, #29236)
+
+
version 2021.06.06
Extractors
diff --git a/README.md b/README.md
index 2841ed68f..47e686f84 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ Windows users can [download an .exe file](https://yt-dl.org/latest/youtube-dl.ex
You can also use pip:
sudo -H pip install --upgrade youtube-dl
-
+
This command will update youtube-dl if you have already installed it. See the [pypi page](https://pypi.python.org/pypi/youtube_dl) for more information.
macOS users can install youtube-dl with [Homebrew](https://brew.sh/):
@@ -563,7 +563,7 @@ The basic usage is not to set any template arguments when downloading a single f
- `is_live` (boolean): Whether this video is a live stream or a fixed-length video
- `start_time` (numeric): Time in seconds where the reproduction should start, as specified in the URL
- `end_time` (numeric): Time in seconds where the reproduction should end, as specified in the URL
- - `format` (string): A human-readable description of the format
+ - `format` (string): A human-readable description of the format
- `format_id` (string): Format code specified by `--format`
- `format_note` (string): Additional info about the format
- `width` (numeric): Width of the video
@@ -632,7 +632,7 @@ To use percent literals in an output template use `%%`. To output to stdout use
The current default template is `%(title)s-%(id)s.%(ext)s`.
-In some cases, you don't want special characters such as 中, spaces, or &, such as when transferring the downloaded filename to a Windows system or the filename through an 8bit-unsafe channel. In these cases, add the `--restrict-filenames` flag to get a shorter title:
+In some cases, you don't want special characters such as 中, spaces, or &, such as when transferring the downloaded filename to a Windows system or the filename through an 8bit-unsafe channel. In these cases, add the `--restrict-filenames` flag to get a shorter title.
#### Output template and Windows batch files
@@ -675,7 +675,7 @@ The general syntax for format selection is `--format FORMAT` or shorter `-f FORM
**tl;dr:** [navigate me to examples](#format-selection-examples).
-The simplest case is requesting a specific format, for example with `-f 22` you can download the format with format code equal to 22. You can get the list of available format codes for particular video using `--list-formats` or `-F`. Note that these format codes are extractor specific.
+The simplest case is requesting a specific format, for example with `-f 22` you can download the format with format code equal to 22. You can get the list of available format codes for particular video using `--list-formats` or `-F`. Note that these format codes are extractor specific.
You can also use a file extension (currently `3gp`, `aac`, `flv`, `m4a`, `mp3`, `mp4`, `ogg`, `wav`, `webm` are supported) to download the best quality format of a particular file extension served as a single file, e.g. `-f webm` will download the best quality format with the `webm` extension served as a single file.
@@ -760,7 +760,7 @@ Videos can be filtered by their upload date using the options `--date`, `--dateb
- Absolute dates: Dates in the format `YYYYMMDD`.
- Relative dates: Dates in the format `(now|today)[+-][0-9](day|week|month|year)(s)?`
-
+
Examples:
```bash
@@ -918,7 +918,7 @@ Either prepend `https://www.youtube.com/watch?v=` or separate the ID from the op
Use the `--cookies` option, for example `--cookies /path/to/cookies/file.txt`.
-In order to extract cookies from browser use any conforming browser extension for exporting cookies. For example, [Get cookies.txt](https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid/) (for Chrome) or [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/) (for Firefox).
+In order to extract cookies from browser use any conforming browser extension for exporting cookies. For example, [Get cookies.txt LOCALLY](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) (for Chrome) or [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/) (for Firefox).
Note that the cookies file must be in Mozilla/Netscape format and the first line of the cookies file must be either `# HTTP Cookie File` or `# Netscape HTTP Cookie File`. Make sure you have correct [newline format](https://en.wikipedia.org/wiki/Newline) in the cookies file and convert newlines if necessary to correspond with your OS, namely `CRLF` (`\r\n`) for Windows and `LF` (`\n`) for Unix and Unix-like systems (Linux, macOS, etc.). `HTTP Error 400: Bad Request` when using `--cookies` is a good sign of invalid newline format.
@@ -1000,6 +1000,8 @@ To run the test, simply invoke your favorite test runner, or execute a test file
python test/test_download.py
nosetests
+For Python versions 3.6 and later, you can use [pynose](https://pypi.org/project/pynose/) to implement `nosetests`. The original [nose](https://pypi.org/project/nose/) has not been upgraded for 3.10 and later.
+
See item 6 of [new extractor tutorial](#adding-support-for-a-new-site) for how to run extractor specific test cases.
If you want to create a build of youtube-dl yourself, you'll need
@@ -1069,9 +1071,11 @@ After you have ensured this site is distributing its content legally, you can fo
}
```
5. Add an import in [`youtube_dl/extractor/extractors.py`](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/extractors.py).
-6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc. Note that tests with `only_matching` key in test's dict are not counted in.
-7. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/ytdl-org/youtube-dl/blob/7f41a598b3fba1bcab2817de64a08941200aa3c8/youtube_dl/extractor/common.py#L94-L303). Add tests and code for as many as you want.
-8. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://flake8.pycqa.org/en/latest/index.html#quickstart):
+6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test (actually, test case) then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc. Note:
+ * the test names use the extractor class name **without the trailing `IE`**
+ * tests with `only_matching` key in test's dict are not counted.
+8. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/ytdl-org/youtube-dl/blob/7f41a598b3fba1bcab2817de64a08941200aa3c8/youtube_dl/extractor/common.py#L94-L303). Add tests and code for as many as you want.
+9. Make sure your code follows [youtube-dl coding conventions](#youtube-dl-coding-conventions) and check the code with [flake8](https://flake8.pycqa.org/en/latest/index.html#quickstart):
$ flake8 youtube_dl/extractor/yourextractor.py
@@ -1089,7 +1093,7 @@ In any case, thank you very much for your contributions!
## youtube-dl coding conventions
-This section introduces a guide lines for writing idiomatic, robust and future-proof extractor code.
+This section introduces guidelines for writing idiomatic, robust and future-proof extractor code.
Extractors are very fragile by nature since they depend on the layout of the source data provided by 3rd party media hosters out of your control and this layout tends to change. As an extractor implementer your task is not only to write code that will extract media links and metadata correctly but also to minimize dependency on the source's layout and even to make the code foresee potential future changes and be ready for that. This is important because it will allow the extractor not to break on minor layout changes thus keeping old youtube-dl versions working. Even though this breakage issue is easily fixed by emitting a new version of youtube-dl with a fix incorporated, all the previous versions become broken in all repositories and distros' packages that may not be so prompt in fetching the update from us. Needless to say, some non rolling release distros may never receive an update at all.
@@ -1112,7 +1116,7 @@ Say you have some source dictionary `meta` that you've fetched as JSON with HTTP
```python
meta = self._download_json(url, video_id)
```
-
+
Assume at this point `meta`'s layout is:
```python
@@ -1156,7 +1160,7 @@ description = self._search_regex(
```
On failure this code will silently continue the extraction with `description` set to `None`. That is useful for metafields that may or may not be present.
-
+
### Provide fallbacks
When extracting metadata try to do so from multiple sources. For example if `title` is present in several places, try extracting from at least some of them. This makes it more future-proof in case some of the sources become unavailable.
@@ -1204,7 +1208,7 @@ r'(id|ID)=(?P Decoy text418 I'm a teapot
"
@@ -35,13 +53,13 @@ class InfoExtractorTestRequestHandler(compat_http_server.BaseHTTPRequestHandler)
assert False
-class TestIE(InfoExtractor):
+class DummyIE(InfoExtractor):
pass
class TestInfoExtractor(unittest.TestCase):
def setUp(self):
- self.ie = TestIE(FakeYDL())
+ self.ie = DummyIE(FakeYDL())
def test_ie_key(self):
self.assertEqual(get_info_extractor(YoutubeIE.ie_key()), YoutubeIE)
@@ -62,6 +80,7 @@ class TestInfoExtractor(unittest.TestCase):
+
'''
self.assertEqual(ie._og_search_title(html), 'Foo')
self.assertEqual(ie._og_search_description(html), 'Some video\'s description ')
@@ -74,6 +93,7 @@ class TestInfoExtractor(unittest.TestCase):
self.assertEqual(ie._og_search_property(('test0', 'test1'), html), 'foo > < bar')
self.assertRaises(RegexNotFoundError, ie._og_search_property, 'test0', html, None, fatal=True)
self.assertRaises(RegexNotFoundError, ie._og_search_property, ('test0', 'test00'), html, None, fatal=True)
+ self.assertEqual(ie._og_search_property('test4', html), 'unquoted-value')
def test_html_search_meta(self):
ie = self.ie
@@ -98,6 +118,71 @@ class TestInfoExtractor(unittest.TestCase):
self.assertRaises(RegexNotFoundError, ie._html_search_meta, 'z', html, None, fatal=True)
self.assertRaises(RegexNotFoundError, ie._html_search_meta, ('z', 'x'), html, None, fatal=True)
+ def test_search_nextjs_data(self):
+ html = '''
+
+
+
+
+
+ Example heading
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;
"|\'|\b)(?P