這篇文章是 視頻轉字符動畫-Python-60行代碼 的後續,若是感興趣,請先看看它。javascript
最新版使用了畫布方式實現,和本文相比改動很是大,若是對舊版本的實現沒啥興趣,能夠直接移步 video2chars,它的效果動畫見 極樂淨土。新版本的核心代碼不算註釋70行不到,功能更強大。html
下面的效果動畫是使用 html 實現的字符動畫效果(上一篇的效果動畫是 shell 版的):
java
本文的優化仍然是針對 shell 版本的,html 版因爲缺陷太大就不寫文章介紹了。python
要是每次播放都要等個一分鐘,也太痛苦了一點。
因此能夠用 pickle 模塊把 video_chars 保存下來,下次播放時,若是發現當前目錄下有這個保存下來的數據,就跳過轉換,直接播放了。這樣就快多了。
只須要改一下測試代碼,
先在開頭添加兩個依賴git
import os import pickle
而後在文件結尾添加代碼:github
def dump(obj, file_name): """ 將指定對象,以file_nam爲名,保存到本地 """ with open(file_name, 'wb') as f: pickle.dump(obj, f) return def load(filename): """ 從當前文件夾的指定文件中load對象 """ with open(filename, 'rb') as f: return pickle.load(f) def get_file_name(file_path): """ 從文件路徑中提取出不帶拓展名的文件名 """ # 從文件路徑獲取文件名 _name path, file_name_with_extension = os.path.split(file_path) # 拿到文件名前綴 file_name, file_extension = os.path.splitext(file_name_with_extension) return file_name def has_file(path, file_name): """ 判斷指定目錄下,是否存在某文件 """ return file_name in os.listdir(path) def get_video_chars(video_path, size): """ 返回視頻對應的字符視頻 """ video_dump = get_file_name(video_path) + ".pickle" # 若是 video_dump 已經存在於當前文件夾,就能夠直接讀取進來了 if has_file(".", video_dump): print("發現該視頻的轉換緩存,直接讀取") video_chars = load(video_dump) else: print("未發現緩存,開始字符視頻轉換") print("開始逐幀讀取") # 視頻轉字符動畫 imgs = video2imgs(video_path, size) print("視頻已所有轉換到圖像, 開始逐幀轉換爲字符畫") video_chars = imgs2chars(imgs) print("轉換完成,開始緩存結果") # 把轉換結果保存下來 dump(video_chars, video_dump) print("緩存完畢") return video_chars if __name__ == "__main__": # 寬,高 size = (64, 48) # 視頻路徑,換成你本身的 video_path = "BadApple.mp4" video_chars = get_video_chars(video_path, size) play_video(video_chars)
另外一個優化方法就是邊轉換邊播放,就是同時執行上述三個步驟。學會了的話,能夠本身實現一下試試。shell
沒有配樂的動畫,雖然作出來了是頗有成就感,可是你可能看上兩遍就厭倦了。
因此讓咱們來給它加上配樂。(不要擔憂,其實就只須要添加幾行代碼而已)緩存
首先咱們須要找個方法來播放視頻的配樂,怎麼作呢?
先介紹一下一個跨平臺視頻播放器:mpv,它有很棒的命令行支持,請先安裝好它。
要讓 mpv 只播放視頻的音樂部分,只須要命令:多線程
mpv --no-video video_path
好了,如今有了音樂,可總不能還讓人開倆shell,先放音樂,再放字符畫吧。
這時候,咱們須要的功能是:使用 Python 調用外部應用.
可是 mpv 使用了相似 curses 的功能,標準庫的 os.system 和 subprocess 都不能隱藏掉這個部分,播放效果不盡如人意。
所以我使用了 pyinvoke 模塊,只要給它指定參數hide=True
,就能夠完美隱藏掉被調用程序的輸出(指stdout)。運行下面代碼前,請先用pip安裝好 invoke.(可以看到這裏的,安裝個模塊還不是小菜一碟)app
好了廢話說這麼多,上代碼:
import invoke video_path = "BadApple.mp4" invoke.run(f"mpv --no-video {video_path}", hide=True, warn=True)
運行上面的測試代碼,若是聽到了音樂,而shell啥都沒輸出,可是能聽到音樂的話,就正常了。咱們繼續。(這裏使用了python3.6的f字符串)
音樂已經有了,那就好辦了。
添加一個播放音樂的函數
import invoke def play_audio(video_path): invoke.run(f"mpv --no-video {video_path}", hide=True, warn=True)
而後修改main()方法:
def main(): # 寬,高 size = (64, 48) # 視頻路徑,換成你本身的 video_path = "BadApple.mp4" # 只轉換三十秒,這個屬性是才添加的,可是上一篇的代碼沒有更新。你可能須要先上github看看最新的代碼。其實就稍微改了一點。 seconds = 30 # 這裏的fps是幀率,也就是每秒鐘播放的的字符畫數。用於和音樂同步。這個更新也沒寫進上一篇,請上github看看新代碼。 video_chars, fps = get_video_chars(video_path, size, seconds) # 播放音軌 play_audio(video_path) # 播放視頻 play_video(video_chars, fps) if __name__ == "__main__": main()
而後運行。。並非我坑你,你只聽到了聲音,卻沒看到字符畫。。緣由是: invoke.run()函數是阻塞的,音樂沒放完,代碼就到不了play_video(video_chars, fps)
這一行。
因此 play_audio
還要改一下,改爲這樣:
import invoke from threading import Thread def play_audio(video_path): def call(): invoke.run(f"mpv --no-video {video_path}", hide=True, warn=True) # 這裏建立子線程來執行音樂播放指令,由於 invoke.run() 是一個阻塞的方法,要同時播放字符畫和音樂的話,就要用多線程/進程。 p = Thread(target=call) p.setDaemon(True) p.start()
這裏使用標準庫的 threading.Thread 類來建立子線程,讓音樂的播放在子線程裏執行,而後字符動畫仍是主線程執行,Ok,這就能夠看到最終效果了。實際上只添加了十多行代碼而已。