这篇文章是 视频转字符动画 - Python-60 行代码 的后续, 如果感兴趣, 请先看看它.
1. 速度优化
要是每次播放都要等个一分钟, 也太痛苦了一点.
所以可以用 pickle 模块把 video_chars 保存下来, 下次播放时, 如果发现当前目录下有这个保存下来的数据, 就跳过转换, 直接播放了. 这样就快多了.
只需要改一下测试代码,
先在开头添加两个依赖
- import os
- import pickle
然后在文件结尾添加代码:
- 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)
另一个优化方法就是边转换边播放, 就是同时执行上述三个步骤. 学会了的话, 可以自己实现一下试试.
4. 字符视频和音乐同时播放
没有配乐的动画, 虽然做出来了是很有成就感, 但是你可能看上两遍就厌倦了.
所以让我们来给它加上配乐.(不要担心, 其实就只需要添加几行代码而已)
首先我们需要找个方法来播放视频的配乐, 怎么做呢?
先介绍一下一个跨平台视频播放器: https://mpv.io/ , 它有很棒的命令行支持, 请先安装好它.
要让 mpv 只播放视频的音乐部分, 只需要命令:
mpv --no-video video_path
好了, 现在有了音乐, 可总不能还让人开俩 shell, 先放音乐, 再放字符画吧.
这时候, 我们需要的功能是: 使用 Python 调用外部应用.
但是 mpv 使用了类似 curses 的功能, 标准库的 os.system 和 subprocess 都不能隐藏掉这个部分, 播放效果不尽如人意.
因此我使用了 https://github.com/pyinvoke/invoke 模块, 只要给它指定参数 hide=True, 就可以完美隐藏掉被调用程序的输出 (指 stdout). 运行下面代码前, 请先用 pip 安装好 invoke.(能够看到这里的, 安装个模块还不是小菜一碟)
好了废话说这么多, 上代码:
- import invoke
- video_path = "BadApple.mp4"
- invoke.run(f"mpv --no-video {video_path}", hide=True, warn=True)
运行上面的测试代码, 如果听到了音乐, 而 shell 啥都没输出的话, 就正常了. 我们继续.
音乐已经有了, 那就好办了.
添加一个播放音乐的函数
- 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, 这就可以看到最终效果了. 实际上只添加了十多行代码而已.
代码:
https://github.com/yuansuye/video2chars 里面有个 Pipfile, 是因为我使用了 pipenv 来管理依赖的缘故. 不清楚的话忽略它就行.
来源: https://www.cnblogs.com/kirito-c/p/9089873.html