youtube-dl on Android

最近在研究如何把youtube-dl跑在Android上(youtube-dl#967)。方法大致如下:

  1. 找一個可以跑的Python interpreter
  2. youtube-dl clone下來或是從yt-dl.org下載最新的tarball
  3. 用adb shell或terminal emulater執行youtube-dl

針對第一步,Google Play上面很多個,例如Kivy LauncherQPython3等等。我玩過之後都不太滿意。因為這些App要嘛我找不到一個單獨的python執行檔,要嘛是在app自己的資料夾下,shell讀不到。於是我決定用自己compile的版本。在網路上找"Python NDK",找到了python3-android這個project。照著說明下去跑,沒有compile error,看起來很棒。放到手機上,設定好LD_LIBRARY_PATH就可以用,也不錯,不過一跑youtube-dl就出錯:

root@GT-N7000:/data/local/tmp # python3 youtube_dl/__main__.py
Traceback (most recent call last):
  File "youtube_dl/__main__.py", line 16, in <module>
    import youtube_dl
  File "/data/local/tmp/youtube_dl/__init__.py", line 15, in <module>
    from .options import (
  File "/data/local/tmp/youtube_dl/options.py", line 7, in <module>
    from .downloader.external import list_external_downloaders
  File "/data/local/tmp/youtube_dl/downloader/__init__.py", line 3, in <module>
    from .common import FileDownloader
  File "/data/local/tmp/youtube_dl/downloader/common.py", line 8, in <module>
    from ..compat import compat_str
  File "/data/local/tmp/youtube_dl/compat.py", line 14, in <module>
    import subprocess
  File "/data/local/tmp/python3/lib/python3.4/subprocess.py", line 406, in <module>
    import select
ImportError: dlopen failed: cannot locate symbol "ceil" referenced by "select.cpython-34m.so"...

在另外一台手機HTC butterfly x920上error不太一樣:

shell@android:/data/local/tmp $ python3 -c 'import select'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: Cannot load library: reloc_library[1306]:  2142 cannot locate 'ceil'...

在網路上找到這篇,他說select需要link libm.so,我照做了之後select就可以被import進來了。

接下來是這個問題:

shell@android:/ $ python3 -c 'import platform; platform.platform()'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/data/local/tmp/python3/lib/python3.4/platform.py", line 1470, in platform
    system, node, release, version, machine, processor = uname()
  File "/data/local/tmp/python3/lib/python3.4/platform.py", line 1151, in uname
    processor = _syscmd_uname('-p', '')
  File "/data/local/tmp/python3/lib/python3.4/platform.py", line 905, in _syscmd_uname
    f = os.popen('uname %s 2> %s' % (option, DEV_NULL))
  File "/data/local/tmp/python3/lib/python3.4/os.py", line 943, in popen
    bufsize=buffering)
  File "/data/local/tmp/python3/lib/python3.4/subprocess.py", line 859, in __init__
    restore_signals, start_new_session)
  File "/data/local/tmp/python3/lib/python3.4/subprocess.py", line 1357, in _execute_child
    raise RuntimeError('Could not find system shell')
RuntimeError: Could not find system shell

看了下subprocess.py,這段code是python3-android的patch:

import platform
if platform.android_version()[0]:
    main = '/system/bin/sh'
else:
    raise RuntimeError('Could not find system shell')

在蝴蝶機上platform.android_version()給出的是('', ''),於是我又看了下android_version()的定義:

output = subprocess.check_output(['/system/bin/getprop',
                                  _android_version_property])
version = output.decode('ascii').strip()
version_obtained = True

奇怪的是,我在shell裡直接打/system/bin/getprop ro.build.version.release就會有正確的值4.1.1,用python下subprocess.check_output(['/system/bin/getprop', 'ro.build.version.release'])或是subprocess.check_output(['/system/bin/getprop'])都給我b''strace一下也沒啥發現。只好來改falling back讀檔案的部份。

不得不說這部份真是bug百出,原作者應該沒有好好的在各種手機上測過,最後改完大概像這個樣子

這兩個地方我把改過的部份送回上游python3-android#10,pull request裡也有說明我遇到的各種情況。

現在大致上可以跑了,除了SSL CERTIFICATE_VERIFY_FAILED之外:

shell@android:/sdcard/Android/data/me.sheimi.sgit/files/repo/ytdl $ python3 -m youtube_dl -v cnFmi7AXeK8
[debug] System config: []
[debug] User config: []
[debug] Command-line args: ['-v', 'cnFmi7AXeK8']
[debug] Encodings: locale ascii, fs utf-8, out ascii, pref ascii
[debug] youtube-dl version 2015.11.19
[debug] Python version 3.4.3 - Linux-3.4.10-ga9fe3b3-armv7l-with-libc
[debug] exe versions: none
[debug] Proxy map: {}
[youtube] cnFmi7AXeK8: Downloading webpage
ERROR: Unable to download webpage: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600)> (caused by URLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600)'),))
    response = self._open(req, data)
  File "/storage/sdcard0/Android/data/me.sheimi.sgit/files/repo/ytdl/youtube_dl/extractor/common.py", line 329, in _request_webpage
    return self._downloader.urlopen(url_or_request)
  File "/storage/sdcard0/Android/data/me.sheimi.sgit/files/repo/ytdl/youtube_dl/YoutubeDL.py", line 1874, in urlopen
    return self._opener.open(req, timeout=self._socket_timeout)
  File "/data/local/tmp/python3/lib/python3.4/urllib/request.py", line 463, in open
  File "/data/local/tmp/python3/lib/python3.4/urllib/request.py", line 481, in _open
    '_open', req)
  File "/data/local/tmp/python3/lib/python3.4/urllib/request.py", line 441, in _call_chain
    result = func(*args)
  File "/storage/sdcard0/Android/data/me.sheimi.sgit/files/repo/ytdl/youtube_dl/utils.py", line 798, in https_open
    req, **kwargs)
  File "/data/local/tmp/python3/lib/python3.4/urllib/request.py", line 1184, in do_open
    raise URLError(err)

這是個老問題了youtube-dl ssl certificate_verify_failed,一個常見的解法是設定$SSL_CERT_DIR,但是我設了/system/etc/security/cacerts還是失敗,strace一下:

stat64("/system/etc/security/cacerts/578d5c04.0", 0xbe99c130) = -1 ENOENT (No such file or directory)
stat64("/system/etc/security/cacerts/2c543cd1.0", 0xbe99c130) = -1 ENOENT (No such file or directory)
stat64("/system/etc/security/cacerts/c4c7a654.0", 0xbe99c130) = -1 ENOENT (No such file or directory)

Android的確沒有這三個檔案,而Arch Linux上有2c543cd1.0和578d5c04.0,莫非Android上的根憑證和一般人不一樣?

EDIT 2019-09-21

後來certificate的問題解決了。詳細可參考python3-android SSL/TLS

social