[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、創建目錄,存儲圖片的臨時路徑

    # [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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章