本文將示範在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鍵。
