前幾天我把下載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/