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

一、简要说明
  • 简述:本文主要展示将视频转成ASCII符号形式展示出来,带音频。
  • 运行环境:Win10/Python3.5。
  • 主要模块: PIL、numpy、shutil。
    [PIL]: 图像处理
    [numpy]: 矩阵形式读取图片数据
    *[shutil]: 删除目录
  • 注意点:ffmpeg.exe(视频处理) 可以自行网上下载。
  • 本文主要参考:Python将视频转换为全字符视频(含音频)
二、简单分析

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

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

  再来看看效果图:

在这里插入图片描述   在这里插入图片描述

在这里插入图片描述在这里插入图片描述

三、开发流程

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

<span style="color:#333333"><code>    <span style="color:green"># [1]、创建存储临时图片的路径</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">createpath</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[1/6]正在创建临时路径..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)

        <span style="color:green"># 源视频文件的图片路径</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.exists(self.pic_path):
            os.makedirs(self.pic_path)
        <span style="color:#0000ff">else</span>:
            <span style="color:green"># 清空在创建</span>
            shutil.rmtree(self.pic_path)
            os.makedirs(self.pic_path)

        <span style="color:green"># 转换之后的图片路径</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.exists(self.ascii_path):
            os.makedirs(self.ascii_path)
        <span style="color:#0000ff">else</span>:
            <span style="color:green"># 清空再创建</span>
            shutil.rmtree(self.ascii_path)
            os.makedirs(self.ascii_path)
        
        <span style="color:green"># 存储输出文件的目录</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.exists(self.outpath):
            os.makedirs(self.outpath)</code></span>

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

  3.2、将视频分割成图片

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

        <span style="color:green"># 执行命令</span>
        os.system(cmd)</code></span>
cmdffmpeg -i [输入文件名] -r [fps,帧率] [分割图存储路径]

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

  3.3、将视频分割成图片

<span style="color:#333333"><code>    <span style="color:green"># [3]、将图片缩放、转成ascii形式</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">pic2ascii</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[3/6]正在处理分析图片,转成ascii形式..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)
        <span style="color:green"># 读取原始图片目录</span>
        pic_list = sorted(os.listdir(self.pic_path))

        total_len = len(pic_list)
        count = 1

        <span style="color:green"># 遍历每张图片</span>
        <span style="color:#0000ff">for</span> pic <span style="color:#0000ff">in</span> pic_list:
            <span style="color:green"># 图片完整路径</span>
            imgpath = os.path.join(self.pic_path, pic)

            <span style="color:green"># 1、缩小图片,转成灰度模式,存入数组</span>
            origin_img = Image.open(imgpath)

            <span style="color:green"># 缩小之后宽高</span>
            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(<span style="color:#a31515">"L"</span>)

            img_arr = np.array(resize_img)

            <span style="color:green"># 2、新建空白图片(灰度模式、与原始图片等宽高)</span>
            new_img = Image.new(<span style="color:#a31515">"L"</span>, origin_img.size, 255)
            draw_obj = ImageDraw.Draw(new_img)
            font = ImageFont.truetype(<span style="color:#a31515">"arial.ttf"</span>, 8)

            <span style="color:green"># 3、将每个字符绘制在一定的区域内</span>
            <span style="color:#0000ff">for</span> i <span style="color:#0000ff">in</span> range(resize_height):
                <span style="color:#0000ff">for</span> j <span style="color:#0000ff">in</span> 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)

            <span style="color:green"># 4、保存字符图片</span>
            new_img.save(os.path.join(<span style="color:#a31515">'temp_ascii'</span>, pic), <span style="color:#a31515">"JPEG"</span>)
            print(<span style="color:#a31515">"已生成ascii图(%d/%d)"</span> % (count, total_len))
            count += 1</code></span>

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

  1. 缩小图片、转成灰度模式,存入数组。
  2. 新建空白图片(灰度模式、与原始图片等宽高)。
  3. 将每个字符绘制在一定的区域内。
  4. 保存字符图片。

下面就是替换的字符:

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

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

<span style="color:#333333"><code>    <span style="color:green"># [4]、合成视频</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">ascii2video</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[4/6]正在合成视频..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)
        <span style="color:green"># 输出视频保存路径</span>
        savepath = os.path.join(self.outpath, self.outname)

        cmd = <span style="color:#a31515">'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}'</span>.format(self.ascii_path, savepath)

        os.system(cmd)</code></span>

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

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

  3.5、获取音频mp3文件

<span style="color:#333333"><code>    <span style="color:green"># [5]、获取原始视频的mp3文件</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">video2mp3</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[5/6]正在分离音频文件..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)

        <span style="color:green"># mp3名字和保存路径</span>
        name = self.filename.split(<span style="color:#a31515">'.'</span>)[0] + <span style="color:#a31515">'.mp3'</span>
        savepath = os.path.join(self.outpath, name)
        cmd = <span style="color:#a31515">'ffmpeg -i {0} -f mp3 {1}'</span>.format(self.filename, savepath)

        os.system(cmd)</code></span>
cmdffmpeg -i [输入视频文件名] -f mp3 [输出的mp3文件名]

  3.5、合并视频和音频文件

<span style="color:#333333"><code>    <span style="color:green"># [6]、将视频和音频合并</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">mp4andmp3</span>(self):
        print(<span style="color:#a31515">"-"</span>*30)
        print(<span style="color:#a31515">"[6/6]正在合并视频和音频..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)

        cmd = <span style="color:#a31515">'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}'</span>.format(self.mp4filename, self.mp3ilename,  self.mergefilename)

        os.system(cmd)</code></span>

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

cmd :ffmpeg -i [视频文件名] -i [音频文件名] -strict -2 -f mp4 [合并后的文件名]
四、生成GIF动图
<span style="color:#333333"><code><span style="color:green"># -*- coding:utf-8 -*-</span>
<span style="color:#0000ff">import</span> imageio
<span style="color:#0000ff">import</span> os

<span style="color:green"># 图片路径</span>
pic_path = <span style="color:#a31515">"temp_pic"</span>

<span style="color:green"># 输出文件名</span>
outname = <span style="color:#a31515">"jljt.gif"</span>

<span style="color:green"># 越过的图片数</span>
skip_num = 10

pic_list = sorted(os.listdir(pic_path))

frames = []
total_len = len(pic_list)

<span style="color:green"># 遍历、读取图片,这里的</span>
<span style="color:#0000ff">for</span> i <span style="color:#0000ff">in</span> range(0, total_len, skip_num):
    path = os.path.join(pic_path, pic_list[i])
    frames.append(imageio.imread(path))

<span style="color:green"># 生成GIF图片</span>
imageio.mimsave(outname, frames, <span style="color:#a31515">"GIF"</span>, duration=0.1)
print(<span style="color:#a31515">"生成完成"</span>)
</code></span>

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

五、附录
*转发需注明出处
<span style="color:#333333"><code><span style="color:green"># -*- coding:utf-8 -*-</span>

<span style="color:#0000ff">from</span> PIL <span style="color:#0000ff">import</span> Image, ImageDraw, ImageFont
<span style="color:#0000ff">import</span> numpy <span style="color:#0000ff">as</span> np
<span style="color:#0000ff">import</span> os
<span style="color:#0000ff">import</span> sys
<span style="color:#0000ff">import</span> shutil


<span style="color:#0000ff">class</span> <span style="color:#a31515">Video2Ascii</span>:

    <span style="color:#0000ff">def</span> <span style="color:#a31515">__init__</span>(self, filename):
        <span style="color:green"># 执行前的一些判断</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.isfile(filename):
            print(<span style="color:#a31515">"源文件找不到,或者不存在!"</span>)
            exit()

        temp_arr = filename.split(<span style="color:#a31515">'.'</span>)

        <span style="color:green"># 字符列表,从左至右逐渐变得稀疏,对应着颜色由深到浅</span>
        self.ascii_char = list(<span style="color:#a31515">"$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/\|()1[]?-_+~<>i!......... "</span>)

        <span style="color:green"># 传入视频文件名</span>
        self.filename = filename
        <span style="color:green"># 输出视频文件名</span>
        self.outname = temp_arr[0] + <span style="color:#a31515">"_out."</span> + temp_arr[1]

        <span style="color:green"># 存储图片的临时路径、输出路径</span>
        self.pic_path = <span style="color:#a31515">'temp_pic'</span>
        self.ascii_path = <span style="color:#a31515">'temp_ascii'</span>
        self.outpath = <span style="color:#a31515">'temp_out'</span>

        <span style="color:green"># 设置图片缩小的倍数</span>
        self.resize_times = 6

        <span style="color:green"># 设置输出文件的名字,声音文件以及带声音的输出文件</span>
        self.mp3ilename = os.path.join(self.outpath, temp_arr[0] + <span style="color:#a31515">'.mp3'</span>)
        self.mp4filename = os.path.join(self.outpath, self.outname)

        <span style="color:green"># 合并输出的视频文件</span>
        self.mergefilename = os.path.join(self.outpath, temp_arr[0] + <span style="color:#a31515">'_voice.'</span> + temp_arr[1])

    <span style="color:green"># [1]、创建存储临时图片的路径</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">createpath</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[1/6]正在创建临时路径..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)

        <span style="color:green"># 源视频文件的图片路径</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.exists(self.pic_path):
            os.makedirs(self.pic_path)
        <span style="color:#0000ff">else</span>:
            <span style="color:green"># 清空在创建</span>
            shutil.rmtree(self.pic_path)
            os.makedirs(self.pic_path)

        <span style="color:green"># 转换之后的图片路径</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.exists(self.ascii_path):
            os.makedirs(self.ascii_path)
        <span style="color:#0000ff">else</span>:
            <span style="color:green"># 清空再创建</span>
            shutil.rmtree(self.ascii_path)
            os.makedirs(self.ascii_path)

        <span style="color:green"># 存储输出文件的目录</span>
        <span style="color:#0000ff">if</span> <span style="color:#0000ff">not</span> os.path.exists(self.outpath):
            os.makedirs(self.outpath)

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

        <span style="color:green"># 执行命令</span>
        os.system(cmd)

    <span style="color:green"># [3]、将图片缩放、转成ascii形式</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">pic2ascii</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[3/6]正在处理分析图片,转成ascii形式..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)
        <span style="color:green"># 读取原始图片目录</span>
        pic_list = sorted(os.listdir(self.pic_path))

        total_len = len(pic_list)
        count = 1

        <span style="color:green"># 遍历每张图片</span>
        <span style="color:#0000ff">for</span> pic <span style="color:#0000ff">in</span> pic_list:
            <span style="color:green"># 图片完整路径</span>
            imgpath = os.path.join(self.pic_path, pic)

            <span style="color:green"># 1、缩小图片,转成灰度模式,存入数组</span>
            origin_img = Image.open(imgpath)

            <span style="color:green"># 缩小之后宽高</span>
            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(<span style="color:#a31515">"L"</span>)

            img_arr = np.array(resize_img)

            <span style="color:green"># 2、新建空白图片(灰度模式、与原始图片等宽高)</span>
            new_img = Image.new(<span style="color:#a31515">"L"</span>, origin_img.size, 255)
            draw_obj = ImageDraw.Draw(new_img)
            font = ImageFont.truetype(<span style="color:#a31515">"arial.ttf"</span>, 8)

            <span style="color:green"># 3、将每个字符绘制在 8*8 的区域内</span>
            <span style="color:#0000ff">for</span> i <span style="color:#0000ff">in</span> range(resize_height):
                <span style="color:#0000ff">for</span> j <span style="color:#0000ff">in</span> 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)

            <span style="color:green"># 4、保存字符图片</span>
            new_img.save(os.path.join(<span style="color:#a31515">'temp_ascii'</span>, pic), <span style="color:#a31515">"JPEG"</span>)
            print(<span style="color:#a31515">"已生成ascii图(%d/%d)"</span> % (count, total_len))
            count += 1

            <span style="color:green"># exit()</span>

    <span style="color:green"># [4]、合成视频</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">ascii2video</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[4/6]正在合成视频..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)
        <span style="color:green"># 输出视频保存路径</span>
        savepath = os.path.join(self.outpath, self.outname)

        cmd = <span style="color:#a31515">'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}'</span>.format(self.ascii_path, savepath)

        os.system(cmd)

    <span style="color:green"># [5]、获取原始视频的mp3文件</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">video2mp3</span>(self):
        print(<span style="color:#a31515">"-"</span> * 30)
        print(<span style="color:#a31515">"[5/6]正在分离音频文件..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)

        <span style="color:green"># mp3名字和保存路径</span>
        name = self.filename.split(<span style="color:#a31515">'.'</span>)[0] + <span style="color:#a31515">'.mp3'</span>
        savepath = os.path.join(self.outpath, name)
        cmd = <span style="color:#a31515">'ffmpeg -i {0} -f mp3 {1}'</span>.format(self.filename, savepath)

        os.system(cmd)

    <span style="color:green"># [6]、将视频和音频合并</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">mp4andmp3</span>(self):
        print(<span style="color:#a31515">"-"</span>*30)
        print(<span style="color:#a31515">"[6/6]正在合并视频和音频..."</span>)
        print(<span style="color:#a31515">"-"</span> * 30 + <span style="color:#a31515">'\r\n'</span>)

        cmd = <span style="color:#a31515">'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}'</span>.format(self.mp4filename, self.mp3ilename,  self.mergefilename)

        os.system(cmd)

    <span style="color:green"># [0]、启动</span>
    <span style="color:#0000ff">def</span> <span style="color:#a31515">start</span>(self):
        <span style="color:#a31515">"""
            > 程序流程:
                1、创建路径
                2、将原始视频分割成图片
                3、将图片缩放、转成ascii形式
                4、将ascii形式的图片合成视频
                5、获取音频mp3文件
                6、合并视频和音频文件
        :return:
        """</span>
        self.createpath()
        self.video2pic()
        self.pic2ascii()
        self.ascii2video()

        self.video2mp3()
        self.mp4andmp3()

        print(<span style="color:#a31515">"程序执行完成"</span>)


<span style="color:#0000ff">if</span> __name__ == <span style="color:#a31515">"__main__"</span>:
    <span style="color:#0000ff">if</span> len(sys.argv) != 2:
        print(<span style="color:#a31515">"参数不匹配,请参考(脚本名 原始视频):xxx.py test.mp4 "</span>)
        exit()

    demo = Video2Ascii(sys.argv[1])
    demo.start()</code></span>

生命不息,学习不止

郑州专业不孕不育医院

郑州不孕不育医院

郑州男科医院哪家便宜

郑州专业人流医院

猜你喜欢

转载自blog.csdn.net/qq_42564846/article/details/85249269