本文將示範在Android手機執行Python下載YouTube影片。
Termux是Android系統上的開放原始碼終端機模擬器,提供豐富的Linux工具軟體,例如:Python, Node.js, PHP, FFMpeg, … 等,官方的工具套件列表,請參閱這個GitHub頁面。
你可以在Google Play商店下載Termux,但是這個App的新版本已不在Google Play上架,Termux官方維基網頁有說,不要到Google Play下載;最新版本,請到開放原始碼應用商店F-Droid下載。
此外,為了便於輸入文字命令和程式,建議安裝CodeBoard鍵盤,或者外接USB或藍牙鍵盤。
在Termux中安裝Python和FFmpeg
Termux是終端機軟體,所有操作都要透過Linux命令,安裝軟體要透過pkg套件管理員。本文的應用需要安裝nano文字編輯器、Python 3以及FFmpeg。請在Termux中執行:
pkg update && pkg upgrade pkg install nano ffmpeg python3 -y
實際執行畫面:

安裝過程若詢問任何問題,請鍵入y再按下Enter。安裝完畢後,可執行底下命令列舉所有已安裝的套件:
pkg list-all
或者執行底下命令搜尋套件名稱:
pkg search 套件名稱
例如,執行pgk search ffmpeg,它將顯示目前已安裝5.0版:

從檔案管理器存取Termux檔案系統
一般的Android檔案管理軟體無法存取Termux終端機的檔案系統,官方建議採用FX File Explorer或者開放原始碼的Material Files檔案管理器。底下是在Material Files中瀏覽Termux檔案系統的操作畫面(從主選單選擇「新增儲存→外部儲存空間」)。

然後按右下角的「選取」(底下畫面可能和你的不同),即可把Termux的使用者家目錄home加入主選單。

新增儲存之後,再點擊左上角的主選單,即可看見Termux的使用者家目錄home。

稍後將會透過這個檔案管理器存入Python程式檔,請點擊主選單的home,進入使用者家目錄,再按右下角的「+」,新增一個資料夾,將它命名成code(代表用於存放程式碼)。

從Termux存取Android檔案
根據官網的Internal and external storage(內部和外部儲存空間)文件說明,要在Termux中存取Anrdoid的檔案,要執行termux-setup-storage命令。執行之後,系統將詢問你是否允許Termux存取檔案,請按下「允許」。

接著,你就能透過~/storage路徑(實際是個「捷徑」),瀏覽到Android系統的dcim(相簿)、music(音樂)、movies(電影)、downloads(下載)、pictures(圖像)和external-1(外部記憶體,如:MicroSD記憶卡)資料夾。

本文的Python程式將把下載的YouTube影片存入storage/movies路徑。
在Termux的Python直譯器中執行PyTube下載YouTube影片
先執行pip命令安裝pytube套件,然後啟動Python直譯器測試存取YouTube影片資訊:
from pytube import YouTube yt = YouTube('https://youtu.be/GHenFGnixzU') yt.streams.filter(resolution='1080p').all()
它將顯示此影片1080p畫質的所有串流格式。

測試完畢,按Ctrl+Z鍵退出Python直譯器。
修正PyTube套件的錯誤
筆者執行上一節的PyTube程式時,實際上出現底下的錯誤訊息。

先按Ctrl+Z鍵退出Python直譯器。
使用pytube下載YouTube影片出錯的原因,大多是Google修改了YouTube網站程式,pytube必須跟著修改,才能正確下載。pytube原始碼網站的issues頁面,張貼了網民發現的各種問題,某些問題尚待解決。
通常過一陣子pytube就會釋出更新版本,可以透過執行這個命令安裝更新:
pip install pytube --upgrade
還可以在pypi網站查看pytube的現行版本和發行日期。
根據pytube的這個錯誤回報頁面,網友提到,修改cipher.py裡面的一個敘述即可修正11.0.2版的錯誤。請先複製錯誤訊息顯示的cipher.py檔案路徑,透過nano文字編輯器打開它:
nano /data/data/com.termux/files/usr/lib/python3.10/site-packages/pytube/cipher.py
按Ctrl+W鍵(搜尋),然後輸入 “function_pat”(大約執行3次)找到底下的程式片段,把該列表的最後一行:
function_patterns = [ # https://github.com/ytdl-org/youtube-dl/issues/29326#issuecomment-865985377 # a.C&&(b=a.get('n'))&&(b=Dea(b),a.set('n',b))}}; # In above case, `Dea` is the relevant function name r'a\.[A-Z]&&\(b=a\.get\('n'\)\)&&\(b=([^(]+)\(b\)', ]
替換成:
function_patterns = [ # https://github.com/ytdl-org/youtube-dl/issues/29326#issuecomment-865985377 # a.C&&(b=a.get('n'))&&(b=Dea(b),a.set('n',b))}}; # In above case, `Dea` is the relevant function name r'([A-Za-z]{3})=function\(a\){var b=a\.split\(''\)\,', ]

然後按下Ctrl+O、Enter鍵寫入、Ctrl+X退出nano。
再次執行上一節的Python程式,測試沒問題!
執行Python程式下載YouTube影片存入手機的Movies資料夾
下載YouTube影片存入Android手機的Movies資料夾的完整Python程式碼如下:
# -*- coding: utf-8 -*- import argparse import os from pytube import YouTube import subprocess args = {} fileobj = {} download_count = 1 pyTube_folder = 'storage/movies' # 存檔路徑 def onProgress(stream, chunk, remains): total = stream.filesize percent = (total-remains) / total * 100 print('下載中… {:05.2f}%'.format(percent), end='\r') # 列舉可用的解析度 def video_res(yt): res_set = set() video_list = yt.streams.filter(type="video") for v in video_list: res_set.add(v.resolution) # 傳回解析度表列,例如:['720p', '480p', '360p', '240p', '144p'] return sorted(res_set, reverse=True, key=lambda s: int(s[:-1])) def download_media(args): try: yt = YouTube(args.url, on_progress_callback=onProgress, on_complete_callback=onComplete) except: print('下載影片時發生錯誤,請確認網路連線和YouTube網址無誤。') return filter = yt.streams.filter if args.a: # 只下載聲音 target = filter(type="audio").first() elif args.fhd: target = filter(type="video", resolution="1080p").first() elif args.hd: target = filter(type="video", resolution="720p").first() elif args.sd: target = filter(type="video", resolution="480p").first() else: target = filter(type="video").first() if target is None: print('沒有您指定的解析度,可用的解析度如下:') res_list = video_res(yt) for i, res in enumerate(res_list): print('{}) {}'.format(i+1, res)) val = input('請選擇(預設{}):'.format(res_list[0])) try: res = res_list[int(val)-1] except: res = res_list[0] print('您選擇的是 {} 。'.format(res)) target = filter(type="video", resolution=res).first() # 開始下載 target.download(output_path=pyTube_folder) def check_media(filename): out = subprocess.run(["ffprobe", filename], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if (out.stdout.decode('utf-8').rfind('Audio') == -1): return -1 # 沒有聲音 else: return 1 def merge_media(): temp_video = os.path.join(fileobj['dir'], 'temp_video.mp4') temp_audio = os.path.join(fileobj['dir'], 'temp_audio.mp4') temp_output = os.path.join(fileobj['dir'], 'output.mp4') cmd = f'ffmpeg -i {temp_video} -i {temp_audio} \ -map 0:v -map 1:a -c copy -y {temp_output}' try: subprocess.run(cmd, shell=True) # 視訊檔重新命名 os.rename(temp_output, os.path.join(fileobj['dir'], fileobj['name'])) os.remove(temp_audio) os.remove(temp_video) print('視訊和聲音合併完成') except: print('視訊和聲音合併失敗') def onComplete(stream, file_path): global download_count, fileobj fileobj['name'] = os.path.basename(file_path) fileobj['dir'] = os.path.dirname(file_path) print('\r') if download_count == 1: if check_media(file_path) == -1: print('此影片沒有聲音') download_count += 1 try: # 視訊檔重新命名 os.rename(file_path, os.path.join( fileobj['dir'], 'temp_video.mp4')) except: print('視訊檔重新命名失敗') return print('準備下載聲音檔') vars(args)['a'] = True # 設定成a參數 download_media(args) # 下載聲音 else: print('此影片有聲音,下載完畢!') else: try: # 聲音檔重新命名 os.rename(file_path, os.path.join( fileobj['dir'], 'temp_audio.mp4')) except: print("聲音檔重新命名失敗") # 合併聲音檔 merge_media() def main(): global args parser = argparse.ArgumentParser() parser.add_argument("url", help="指定YouTube視訊網址") parser.add_argument("-sd", action="store_true", help="選擇普通(480P)畫質") parser.add_argument("-hd", action="store_true", help="選擇HD(720P)畫質") parser.add_argument("-fhd", action="store_true", help="選擇Full HD(1080P)畫質") parser.add_argument("-a", action="store_true", help="僅下載聲音") args = parser.parse_args() download_media(args) if __name__ == '__main__': main()
筆者把它命名成tube.py檔,先存入手機的microSD記憶卡,然後透過Material Files檔案管理器,把tube.py複製到Termux使用者家目錄的code資料夾。

然後回到Termux,執行底下的命令,它將下載影片、存入Android的Movies資料夾:
python tube.py ~/code/tube.py https://youtu.be/GHenFGnixzU -fhd
下載完畢後,若要關閉Termux,請輸入exit,再按下Enter鍵。