热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[Python]将视频转成ASCII符号形式、生成GIF图片

一、简要说明简述:本文主要展示将视频转成ASCII符号形式展示出来,带音频。运行环境:Win10Python3.5。主要模块:PIL、numpy、shutil。注意点:ffmpeg








一、简要说明


  • 简述:本文主要展示将视频转成ASCII符号形式展示出来,带音频。

  • 运行环境:Win10/Python3.5。

  • 主要模块: PIL、numpy、shutil。



  • 注意点:ffmpeg.exe(视频处理) 可以自行网上下载。

  • 本文主要参考:Python将视频转换为全字符视频(含音频)








二、简单分析

  在网上看到转成字符形式的视频,感觉挺有趣的,于是查阅相关资料,开始实现一下。基本思路:主要使用 ffmpeg 对进行视频操作,然后使用 PIL 对图片进行缩小、灰度和转码的处理。流程如下:


1. 创建临时路径。
2. 将视频按帧分割成图片存入临时目录。
3. 遍历将图片缩放、转成灰度,再转成ASCII形式的图片。
4. 将ASCII形式的图片合成视频。
5. 获取源文件的音频文件。
6. 合并视频和音频文件。


  再来看看效果图:

   









三、开发流程

  3.1、创建目录,存储图片的临时路径

# [1]、创建存储临时图片的路径
def createpath(self):
print("-" * 30)
print("[1/6]正在创建临时路径...")
print("-" * 30 + \'\r\n\')
# 源视频文件的图片路径
if not os.path.exists(self.pic_path):
os.makedirs(self.pic_path)
else:
# 清空在创建
shutil.rmtree(self.pic_path)
os.makedirs(self.pic_path)
# 转换之后的图片路径
if not os.path.exists(self.ascii_path):
os.makedirs(self.ascii_path)
else:
# 清空再创建
shutil.rmtree(self.ascii_path)
os.makedirs(self.ascii_path)

# 存储输出文件的目录
if not os.path.exists(self.outpath):
os.makedirs(self.outpath)

以上代码主要创建源视频切割图片存储路径、转码后图片存储路径和输出文件的存储路径,图片的存储路径为 临时路径 ,每次执行前会先清空之前的文件,请注意。


  3.2、将视频分割成图片

# [2]、将视频分割成图片
def video2pic(self):
print("-" * 30)
print("[2/6]正在切割原始视频为图片...")
print("-" * 30 + \'\r\n\')
# 使用ffmpeg切割图片,命令行如下
cmd = \'ffmpeg -i {0} -r 24 {1}/%06d.jpeg\'.format(self.filename, self.pic_path)
# 执行命令
os.system(cmd)

cmd:ffmpeg -i [输入文件名] -r [fps,帧率] [分割图存储路径]

这里就比较简单,使用 ffmpeg 将视频分割成图片并按照相应个数存储在临时路径即可。查阅ffmpeg命令行说明


  3.3、将视频分割成图片

# [3]、将图片缩放、转成ascii形式
def pic2ascii(self):
print("-" * 30)
print("[3/6]正在处理分析图片,转成ascii形式...")
print("-" * 30 + \'\r\n\')
# 读取原始图片目录
pic_list = sorted(os.listdir(self.pic_path))
total_len = len(pic_list)
count = 1
# 遍历每张图片
for pic in pic_list:
# 图片完整路径
imgpath = os.path.join(self.pic_path, pic)
# 1、缩小图片,转成灰度模式,存入数组
origin_img = Image.open(imgpath)
# 缩小之后宽高
resize_width = int(origin_img.size[0] / self.resize_times)
resize_height = int(origin_img.size[1] / self.resize_times)
resize_img = origin_img.resize((resize_width, resize_height), Image.ANTIALIAS).convert("L")
img_arr = np.array(resize_img)
# 2、新建空白图片(灰度模式、与原始图片等宽高)
new_img = Image.new("L", origin_img.size, 255)
draw_obj = ImageDraw.Draw(new_img)
fOnt= ImageFont.truetype("arial.ttf", 8)
# 3、将每个字符绘制在一定的区域内
for i in range(resize_height):
for j in range(resize_width):
x, y = j*self.resize_times, i*self.resize_times
index = int(img_arr[i][j]/4)
draw_obj.text((x, y), self.ascii_char[index], fOnt=font, fill=0)
# 4、保存字符图片
new_img.save(os.path.join(\'temp_ascii\', pic), "JPEG")
print("已生成ascii图(%d/%d)" % (count, total_len))
count += 1

这一步是重点,在遍历获取源图片目录列表之后,就可以分步进行操作了:



  1. 缩小图片、转成灰度模式,存入数组。

  2. 新建空白图片(灰度模式、与原始图片等宽高)。

  3. 将每个字符绘制在一定的区域内。

  4. 保存字符图片。

下面就是替换的字符:

self.ascii_char = list("$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/\|()1[]?-_+~<>i!......... ")

  3.4、将ascii形式的图片合成视频

# [4]、合成视频
def ascii2video(self):
print("-" * 30)
print("[4/6]正在合成视频...")
print("-" * 30 + \'\r\n\')
# 输出视频保存路径
savepath = os.path.join(self.outpath, self.outname)
cmd = \'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}\'.format(self.ascii_path, savepath)
os.system(cmd)

遍历转码的图片,合成视频。

cmd:ffmpeg -threads 2 -start_number [开始图片编号] -r [帧率,fps] -i [图片路径] -vcodec [指定解码器] [输出文件名]

  3.5、获取音频mp3文件

# [5]、获取原始视频的mp3文件
def video2mp3(self):
print("-" * 30)
print("[5/6]正在分离音频文件...")
print("-" * 30 + \'\r\n\')
# mp3名字和保存路径
name = self.filename.split(\'.\')[0] + \'.mp3\'
savepath = os.path.join(self.outpath, name)
cmd = \'ffmpeg -i {0} -f mp3 {1}\'.format(self.filename, savepath)
os.system(cmd)

cmd:ffmpeg -i [输入视频文件名] -f mp3 [输出的mp3文件名]

  3.5、合并视频和音频文件

# [6]、将视频和音频合并
def mp4andmp3(self):
print("-"*30)
print("[6/6]正在合并视频和音频...")
print("-" * 30 + \'\r\n\')
cmd = \'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}\'.format(self.mp4filename, self.mp3ilename, self.mergefilename)
os.system(cmd)

上面代码就是将视频和音频进行合并,转成全符号的视频也不会丢失音频。

cmd :ffmpeg -i [视频文件名] -i [音频文件名] -strict -2 -f mp4 [合并后的文件名]







四、生成GIF动图

# -*- coding:utf-8 -*-
import imageio
import os
# 图片路径
pic_path = "temp_pic"
# 输出文件名
outname = "jljt.gif"
# 越过的图片数
skip_num = 10
pic_list = sorted(os.listdir(pic_path))
frames = []
total_len = len(pic_list)
# 遍历、读取图片,这里的
for i in range(0, total_len, skip_num):
path = os.path.join(pic_path, pic_list[i])
frames.append(imageio.imread(path))
# 生成GIF图片
imageio.mimsave(outname, frames, "GIF", duration=0.1)
print("生成完成")

上面主要实现:将分割出来的图片,合成一张GIF动图,通过设置越过的图片数,可以减小容量,但是会加速动画效果,上面的效果图,就是通过这里生成的。








五、附录

*转发需注明出处

# -*- coding:utf-8 -*-
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import os
import sys
import shutil
class Video2Ascii:
def __init__(self, filename):
# 执行前的一些判断
if not os.path.isfile(filename):
print("源文件找不到,或者不存在!")
exit()
temp_arr = filename.split(\'.\')
# 字符列表,从左至右逐渐变得稀疏,对应着颜色由深到浅
self.ascii_char = list("$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/\|()1[]?-_+~<>i!......... ")
# 传入视频文件名
self.filename = filename
# 输出视频文件名
self.outname = temp_arr[0] + "_out." + temp_arr[1]
# 存储图片的临时路径、输出路径
self.pic_path = \'temp_pic\'
self.ascii_path = \'temp_ascii\'
self.outpath = \'temp_out\'
# 设置图片缩小的倍数
self.resize_times = 6
# 设置输出文件的名字,声音文件以及带声音的输出文件
self.mp3ilename = os.path.join(self.outpath, temp_arr[0] + \'.mp3\')
self.mp4filename = os.path.join(self.outpath, self.outname)
# 合并输出的视频文件
self.mergefilename = os.path.join(self.outpath, temp_arr[0] + \'_voice.\' + temp_arr[1])
# [1]、创建存储临时图片的路径
def createpath(self):
print("-" * 30)
print("[1/6]正在创建临时路径...")
print("-" * 30 + \'\r\n\')
# 源视频文件的图片路径
if not os.path.exists(self.pic_path):
os.makedirs(self.pic_path)
else:
# 清空在创建
shutil.rmtree(self.pic_path)
os.makedirs(self.pic_path)
# 转换之后的图片路径
if not os.path.exists(self.ascii_path):
os.makedirs(self.ascii_path)
else:
# 清空再创建
shutil.rmtree(self.ascii_path)
os.makedirs(self.ascii_path)
# 存储输出文件的目录
if not os.path.exists(self.outpath):
os.makedirs(self.outpath)
# [2]、将视频分割成图片
def video2pic(self):
print("-" * 30)
print("[2/6]正在切割原始视频为图片...")
print("-" * 30 + \'\r\n\')
# 使用ffmpeg切割图片,命令行如下
cmd = \'ffmpeg -i {0} -r 24 {1}/%06d.jpeg\'.format(self.filename, self.pic_path)
# 执行命令
os.system(cmd)
# [3]、将图片缩放、转成ascii形式
def pic2ascii(self):
print("-" * 30)
print("[3/6]正在处理分析图片,转成ascii形式...")
print("-" * 30 + \'\r\n\')
# 读取原始图片目录
pic_list = sorted(os.listdir(self.pic_path))
total_len = len(pic_list)
count = 1
# 遍历每张图片
for pic in pic_list:
# 图片完整路径
imgpath = os.path.join(self.pic_path, pic)
# 1、缩小图片,转成灰度模式,存入数组
origin_img = Image.open(imgpath)
# 缩小之后宽高
resize_width = int(origin_img.size[0] / self.resize_times)
resize_height = int(origin_img.size[1] / self.resize_times)
resize_img = origin_img.resize((resize_width, resize_height), Image.ANTIALIAS).convert("L")
img_arr = np.array(resize_img)
# 2、新建空白图片(灰度模式、与原始图片等宽高)
new_img = Image.new("L", origin_img.size, 255)
draw_obj = ImageDraw.Draw(new_img)
fOnt= ImageFont.truetype("arial.ttf", 8)
# 3、将每个字符绘制在 8*8 的区域内
for i in range(resize_height):
for j in range(resize_width):
x, y = j*self.resize_times, i*self.resize_times
index = int(img_arr[i][j]/4)
draw_obj.text((x, y), self.ascii_char[index], fOnt=font, fill=0)
# 4、保存字符图片
new_img.save(os.path.join(\'temp_ascii\', pic), "JPEG")
print("已生成ascii图(%d/%d)" % (count, total_len))
count += 1
# exit()
# [4]、合成视频
def ascii2video(self):
print("-" * 30)
print("[4/6]正在合成视频...")
print("-" * 30 + \'\r\n\')
# 输出视频保存路径
savepath = os.path.join(self.outpath, self.outname)
cmd = \'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}\'.format(self.ascii_path, savepath)
os.system(cmd)
# [5]、获取原始视频的mp3文件
def video2mp3(self):
print("-" * 30)
print("[5/6]正在分离音频文件...")
print("-" * 30 + \'\r\n\')
# mp3名字和保存路径
name = self.filename.split(\'.\')[0] + \'.mp3\'
savepath = os.path.join(self.outpath, name)
cmd = \'ffmpeg -i {0} -f mp3 {1}\'.format(self.filename, savepath)
os.system(cmd)
# [6]、将视频和音频合并
def mp4andmp3(self):
print("-"*30)
print("[6/6]正在合并视频和音频...")
print("-" * 30 + \'\r\n\')
cmd = \'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}\'.format(self.mp4filename, self.mp3ilename, self.mergefilename)
os.system(cmd)
# [0]、启动
def start(self):
"""
> 程序流程:
1、创建路径
2、将原始视频分割成图片
3、将图片缩放、转成ascii形式
4、将ascii形式的图片合成视频
5、获取音频mp3文件
6、合并视频和音频文件
:return:
"""
self.createpath()
self.video2pic()
self.pic2ascii()
self.ascii2video()
self.video2mp3()
self.mp4andmp3()
print("程序执行完成")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("参数不匹配,请参考(脚本名 原始视频):xxx.py test.mp4 ")
exit()
demo = Video2Ascii(sys.argv[1])
demo.start()



推荐阅读
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • 这篇文章主要介绍了Python拼接字符串的七种方式,包括使用%、format()、join()、f-string等方法。每种方法都有其特点和限制,通过本文的介绍可以帮助读者更好地理解和运用字符串拼接的技巧。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • Vagrant虚拟化工具的安装和使用教程
    本文介绍了Vagrant虚拟化工具的安装和使用教程。首先介绍了安装virtualBox和Vagrant的步骤。然后详细说明了Vagrant的安装和使用方法,包括如何检查安装是否成功。最后介绍了下载虚拟机镜像的步骤,以及Vagrant镜像网站的相关信息。 ... [详细]
  • Python教学练习二Python1-12练习二一、判断季节用户输入月份,判断这个月是哪个季节?3,4,5月----春 ... [详细]
author-avatar
herozhx
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有