字符视频就是画面全部由字符组成的,

那么用代码怎么实现的呢?下面用python实现,话不多说,直接上干货。

代码实现详解

其实总体思路分为3个步骤:

1.将原视频分割成若干个图片以及分离出音频

2.将每张图片转为字符画图片(重点部分)

3.将若干个字符画图片和音频合并成新的视频(字符视频)

  • 将原视频分割成若干个图片以及分离出音频

这个过程我们可以用python调用ffmpeg工具进行切割,ffmpeg是专门处理音视频的工具库。可以在ffmpeg官网下载可执行文件放在程序的当前目录

分离音频命令为:

ffmpeg.exe -i filename -vn temp.mp3
    #分离音频
    slice_audio_cmd = 'ffmpeg.exe -i {0} -vn temp.mp3'.format(src_file)
    os.system(slice_audio_cmd)

分割视频成若干图片的命令为:

ffmpeg.exe -i filename -r 24 temp_pic/%06d.jpeg
    #切割成图片
    slice_pic_cmd = 'ffmpeg.exe -i {0} -r 24 temp_pic/%06d.jpeg'.format(src_file)
    os.system(slice_pic_cmd)

将分割出来的图片和音频临时存储起来,为了后面若干图片转字符图片效率及速度有所提高,还需将分割后的图片转为缩略图,就是改变图片的尺寸

这里使用python的PIL图形处理库来进行缩略图转化,同样将缩略图临时存储起来

def create_thumbnail(src_dir, dst_dir):
    picts_list = sorted(os.listdir(src_dir))

    for picture in picts_list:
        base_name = os.path.basename(picture)
        img = Image.open(os.path.join(src_dir, picture))
        size = 200200
        img.thumbnail(size, Image.ANTIALIAS)
        img.save(os.path.join(dst_dir, base_name))
  • 将每张图片转为字符画图片

如何将一张图片转为字符形式呢?其实很简单,分3步:

1.将图片转为灰度图

2.将灰度图的每个像素点替换为相应的字符

3.将所有替换后的字符画成一张字符图片

1.将图片转为灰度图

灰度图,Gray Scale Image 或是Grey Scale Image,又称灰阶图。把白色与黑色之间按对数关系分为若干等级,称为灰度。灰度分为256阶

公式为:Gray = R0.299 + G0.587 + B*0.114

同样在python中可以用PIL库直接转灰度:

def load_picture(filename):

    # Gray = R*0.299 + G*0.587 + B*0.114
    img = Image.open(filename).convert('L')
    (x, y) = img.size

    pixels = list(img.getdata())
    img.close()
    return (pixels, x, y)

2.将灰度图的每个像素点替换为相应的字符

这里如何替换呢?可以根据灰阶值来替换为我们自己设定的字符,例如:

symbols = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^`'. ")

从上面列表可以看到:越靠前的越密集,越往后越稀疏,于是我们根据灰阶值的大小按比例取列表中的字符,灰阶值越大,取越靠后的字符,这样图片轮廓才能更好的清晰显示

为了是转化后的字符图片看起来不密集以及提高转化时间,我这里将每间隔1个像素来替换字符,初始还要指定图片的边框及尺寸,这些参数可以自行调整,具体展示如下代码:

def create_ascii_picture(pixels, symbols, dest_name, x_size, y_size):
    scale 
4    # 长宽扩大倍数
    border = 1  # 边框宽度

    interval_pixel = 2     #原图片间隔多少个像素点来填充

    img = Image.new('L',
                    (x_size*scale + 2*border,
                     y_size*scale + 2*border),
                    255)
    fnt = ImageFont.truetype('DejaVuSansMono.ttf'int(scale*3))
    t = ImageDraw.Draw(img)

    x = border
    y = border
    for j in range(0, y_size, interval_pixel):
        for i in range(0, x_size, interval_pixel):
            t.text( (x, y),
                    symbols[int(pixels[j*x_size + i]/256 * len(symbols))],
                    font
=fnt,
                    fill=0
                    )
            x += scale * interval_pixel
        x = border
        y += scale * interval_pixel

    img.save(dest_name, "JPEG")

3.将所有替换后的字符画成一张字符图片

这步只需调用PIL库的save方法,如上面代码最后一行。

同样,我们将转化后的字符图片临时保存起来。

至此第2大步完成,即:将一张图片转为字符图片完成

  • 将若干个字符画图片和音频合并成新的视频(字符视频)

这里也是使用ffmpeg工具进行合成,命令为:

ffmpeg -threads 2 -start_number 000001 -r 24 -i 路径名/%06d.jpeg -i temp.mp3 -vcodec mpeg4 生成的文件名
    merge_ascii_video_cmd = 'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -i temp.mp3 -vcodec mpeg4 {1}'.format('temp_ascii', dst_name)
    os.system(merge_ascii_video_cmd)

这一步完成后,字符视频已经生成了。最后还需删除一些临时的文件及文件夹。

完整代码展示

from PIL import Image, ImageDraw, ImageFont
import os, sys
import shutil

symbols = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^`'. ")

def ascii_art_convert(src_dir, dest_dir):
    print('开始生成...')
    picts_list = sorted(os.listdir(src_dir))
    len_picts = len(picts_list)

    i = 0

    for picture in picts_list:
        (pixels, x_size, y_size) = load_picture(os.path.join(src_dir, picture))

        #生成字符画图片
        create_ascii_picture(pixels, symbols, os.path.join(dest_dir, picture), x_size, y_size)

        print('正在生成中... {0}/{1}'.format(i, len_picts))
        i += 1

def create_thumbnail(src_dir, dst_dir):
    picts_list = sorted(os.listdir(src_dir))

    for picture in picts_list:
        base_name = os.path.basename(picture)
        img = Image.open(os.path.join(src_dir, picture))
        size = 200200
        img.thumbnail(size, Image.ANTIALIAS)
        img.save(os.path.join(dst_dir, base_name))


def load_picture(filename):

    # Gray = R*0.299 + G*0.587 + B*0.114
    img = Image.open(filename).convert('L')
    (x, y) = img.size

    pixels = list(img.getdata())
    img.close()
    return (pixels, x, y)


def create_ascii_picture(pixels, symbols, dest_name, x_size, y_size):
    scale = 4    # 长宽扩大倍数
    border = 1  # 边框宽度

    interval_pixel = 2     #原图片间隔多少个像素点来填充

    img = Image.new('L',
                    (x_size*scale + 2*border,
                     y_size*scale + 2*border),
                    255)
    fnt = ImageFont.truetype('DejaVuSansMono.ttf', int(scale*3))
    t = ImageDraw.Draw(img)

    x = border
    y = border
    for j in range(0, y_size, interval_pixel):
        for i in range(0, x_size, interval_pixel):
            t.text( (x, y),
                    symbols[int(pixels[j*x_size + i]/256 * len(symbols))],
                    font=fnt,
                    fill=0
                    )
            x += scale * interval_pixel
        x = border
        y += scale * interval_pixel

    img.save(dest_name, "JPEG")


def start_convert(src_file):

    if not os.path.exists('temp_pic'):
        os.mkdir('temp_pic')

    if not os.path.exists('temp_thum'):
        os.mkdir('temp_thum')

    if not os.path.exists('temp_ascii'):
        os.mkdir('temp_ascii')


    #分离音频
    slice_audio_cmd = 'ffmpeg.exe -i {0} -vn temp.mp3'.format(src_file)
    os.system(slice_audio_cmd)


    #切割成图片
    slice_pic_cmd = 'ffmpeg.exe -i {0} -r 24 temp_pic/%06d.jpeg'.format(src_file)
    os.system(slice_pic_cmd)

    #生成缩略图
    create_thumbnail('temp_pic''temp_thum')

    #生成字符画
    ascii_art_convert('temp_thum''temp_ascii')


    #合成字符视频
    dst_name = os.path.join(os.path.dirname(src_file), 'ascii_' + os.path.basename(src_file))
    merge_ascii_video_cmd = 'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -i temp.mp3 -vcodec mpeg4 {1}'.format('temp_ascii', dst_name)
    os.system(merge_ascii_video_cmd)

    print('生成完成!')


    if os.path.exists('temp_pic'):
        shutil.rmtree('temp_pic')

    if os.path.exists('temp_thum'):
        shutil.rmtree('temp_thum')

    if os.path.exists('temp_ascii'):
        shutil.rmtree('temp_ascii')

    if os.path.exists('temp.mp3'):
        os.remove('temp.mp3')


if __name__ == '__main__':

    src_file = sys.argv[1]
    start_convert(src_file)