前幾天我把下載YouTube影片的PyTube3程式庫更新到最新版:
pip install pytube3 --upgrade
結果之前寫的YouTube影片下載程式出現錯誤,不能用了。經修改幾個地方,程式即可正常運作,而且測試下載幾個影片,原本無法下載的音樂影片都能正常下載了。
修改的部分如下:
all()函式已不推薦使用(deprecated)。filter()方法將傳回符合篩選結果全部媒體格式,因此敘述後面不必加上all()。例如,篩選出全部1080p解析度的視訊:
yt.streams.filter(res=”1080p”).all()
在新版pytube3程式庫中寫成:
yt.streams.filter(res=”1080p”)
處理下載進度的onProgress回呼函式,原本接收4個參數,像這樣:
def onProgress(stream, chunk, file_handle, remaining):
total = stream.filesize
percent = (total - remaining) / total * 100
print('下載中…{:05.2f}%'.format(percent), end='\r')
新版本變成3個參數,刪除了file_handle,所以程式改寫成:
def onProgress(stream, chunk, remaining):
total = stream.filesize
percent = (total - remaining) / total * 100
print('下載中…{:05.2f}%'.format(percent), end='\r')
處理下載完成的onComplete回呼函式,原本第2個參數是檔案物件(file_handle),函式程式透過該物件的name屬性取得檔案路徑:
def onComplete(stream, file_handle):
global download_count, fileobj
fileobj['name'] = os.path.basename(file_handle.name)
fileobj['dir'] = os.path.dirname(file_handle.name)
:
新版本把「檔案物件」改成「檔案路徑」參數,所以程式改寫成:
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')
:
改寫之後的合併視訊和音軌的Python程式如下:
import argparse
import os
import platform
from pytube import YouTube
import subprocess
args = {}
fileobj = {}
download_count = 1
def pyTube_folder():
sys = platform.system()
home = os.path.expanduser('~')
if sys == 'Windows':
folder = os.path.join(home, 'Videos', 'PyTube')
elif sys == 'Darwin':
folder = os.path.join(home, 'Movies', 'PyTube')
if not os.path.isdir(folder): # 若'PyTube'資料夾不存在…
os.mkdir(folder) # 則新增資料夾
return folder
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):
# r = subprocess.Popen(["ffprobe", filename],
# stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# out, err = r.communicate()
# 上面的敘述可改寫成:
# out = subprocess.check_output(["ffprobe", 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()

你好,當以上程式運行完,出現以下訊息:
下載中… 100.00%
此影片沒有聲音
準備下載聲音檔
下載中… 100.00%
‘ffmpeg’ 不是內部或外部命令、可執行的程式或批次檔。
視訊和聲音合併失敗
請問該改寫程式中哪一部分?
謝謝
你需要下載ffmpeg,然後在ffmpeg程式所在路徑執行此Python程式,或者像《超圖解Python程式設計入門》第5-2單元那樣,設定一個ffmpeg的路徑變數,或是在系統環境變數Path裡面,加入ffmpeg所在路徑(例如:”D:\Program Files\ffmpeg\bin”)。
thanks,
jeffrey
執行過後
這行 vars(args)[‘a’] = True # 設定成a參數
報錯 vars() argument must have __dict__ attribute
我原先問題已解決了 我是看著書上做的 我把global args 加進def main(): 然後書上寫的是download_video(yt, args) 所以我在第10行建立一個 yt={} 並把global yt 加進 def main() 然後 140行 改成 download_video(yt , args)就可以了 但我發現新的問題 第55行的 if args.a: 我把 args.a跟其他對調像是args.fhd 寫成 elif args.a:後 在下載音檔時 會變成下載影像 變成兩個影像檔在合併 進而合併失敗 請問這是為甚麼?
我目前使用的是pytube 11.0.1版,相關說明請參閱這篇貼文:
YouTube影片下載(六):改用PyTube程式庫解決執行錯誤
測試執行這篇貼文的程式沒問題:
YouTube影片下載(七):下載播放清單中的全部影片
至於程式的執行流程,你可以用print將參數內容顯示在終端機,釐清程式的判斷依據。
thanks,
jeffrey
趙老師好,我今天在執行下載程式時遇到以下錯誤
Traceback (most recent call last):
File “E:\Coding\python\youtube.py”, line 188, in
main()
File “E:\Coding\python\youtube.py”, line 185, in main
check_urls()
File “E:\Coding\python\youtube.py”, line 154, in check_urls
if file_index < len(videos):
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\helpers.py", line 89, in __len__
self.generate_all()
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\helpers.py", line 105, in generate_all
next_item = next(self.gen)
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\contrib\playlist.py", line 281, in url_generator
for page in self._paginate():
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\contrib\playlist.py", line 118, in _paginate
json.dumps(extract.initial_data(self.html))
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\contrib\playlist.py", line 58, in html
self._html = request.get(self.playlist_url)
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\contrib\playlist.py", line 48, in playlist_url
return f"https://www.youtube.com/playlist?list={self.playlist_id}"
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\contrib\playlist.py", line 39, in playlist_id
self._playlist_id = extract.playlist_id(self._input_url)
File "C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pytube\extract.py", line 151, in playlist_id
return parse_qs(parsed.query)['list'][0]
KeyError: 'list'
我不太懂Key Error是什麼類型的錯誤,請問要如何解決?
另外VS CODE 的語法檢查說:
Import "pytube" could not be resolved
請問為什麼無法引用Pytube模組?
我在python 3.8.9虛擬環境中測試沒問題:
我安裝的pytube是:
pip install pytube
而非pytube3,你可以執行這個命令先移除它再重裝:
pip uninstall pytube3
我把版本改成3.8.9就可以了,謝謝幫助!
感謝告知!pypi的pytube頁面只提到版本要求是Python >=3.6。
https://pypi.org/project/pytube/