YouTube影片下載(八):透過Termux在Android手機執行Python

本文將示範在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版:

查看ffmpeg

從檔案管理器存取Termux檔案系統

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

新增外部儲存空間

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

選取Termux資料夾

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

使用者家目錄

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

新增code資料夾

從Termux存取Android檔案

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

允許存取

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

~/storage路徑

本文的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畫質的所有串流格式。

測試pytube套件

測試完畢,按Ctrl+Z鍵退出Python直譯器。

修正PyTube套件的錯誤

筆者執行上一節的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\(''\)\,',
]
nano編輯器

然後按下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的code資料夾

然後回到Termux,執行底下的命令,它將下載影片、存入Android的Movies資料夾:

python tube.py ~/code/tube.py https://youtu.be/GHenFGnixzU  -fhd

下載完畢後,若要關閉Termux,請輸入exit,再按下Enter鍵

延伸閱讀

Posts created 470

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top