1、簡要說明 |
2、簡單分析 |
在網上看到轉成字符形式的視頻,感受挺有趣的,因而查閱相關資料,開始實現一下。基本思路:主要使用 ffmpeg 對進行視頻操做,而後使用 PIL 對圖片進行縮小、灰度和轉碼的處理。流程以下:html
1. 建立臨時路徑。
2. 將視頻按幀分割成圖片存入臨時目錄。
3. 遍歷將圖片縮放、轉成灰度,再轉成ASCII形式的圖片。
4. 將ASCII形式的圖片合成視頻。
5. 獲取源文件的音頻文件。
6. 合併視頻和音頻文件。python
再來看看效果圖:數組
3、開發流程 |
# [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)
以上代碼主要建立源視頻切割圖片存儲路徑、轉碼後圖片存儲路徑和輸出文件的存儲路徑,圖片的存儲路徑爲 ==臨時路徑== ,每次執行前會先清空以前的文件,請注意。app
# [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命令行說明ide
# [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) # 一、縮小圖片,轉成灰度模式,存入數組 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) # 二、新建空白圖片(灰度模式、與原始圖片等寬高) new_img = Image.new("L", origin_img.size, 255) draw_obj = ImageDraw.Draw(new_img) font = ImageFont.truetype("arial.ttf", 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) # 四、保存字符圖片 new_img.save(os.path.join('temp_ascii', pic), "JPEG") print("已生成ascii圖(%d/%d)" % (count, total_len)) count += 1
這一步是重點,在遍歷獲取源圖片目錄列表以後,就能夠分步進行操做了:動畫
下面就是替換的字符:.net
self.ascii_char = list("$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/\|()1[]?-_+~<>i!......... ")
# [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 [指定解碼器] [輸出文件名]
# [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文件名]
# [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)
上面代碼就是將視頻和音頻進行合併,轉成全符號的視頻也不會丟失音頻。code
cmd :ffmpeg -i [視頻文件名] -i [音頻文件名] -strict -2 -f mp4 [合併後的文件名]
4、生成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動圖,經過設置越過的圖片數,能夠減少容量,可是會加速動畫效果,上面的效果圖,就是經過這裏生成的。orm
5、附錄 |
*轉發需註明出處
# -*- 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) # 一、縮小圖片,轉成灰度模式,存入數組 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) # 二、新建空白圖片(灰度模式、與原始圖片等寬高) new_img = Image.new("L", origin_img.size, 255) draw_obj = ImageDraw.Draw(new_img) font = ImageFont.truetype("arial.ttf", 8) # 三、將每一個字符繪製在 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) # 四、保存字符圖片 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): """ > 程序流程: 一、建立路徑 二、將原始視頻分割成圖片 三、將圖片縮放、轉成ascii形式 四、將ascii形式的圖片合成視頻 五、獲取音頻mp3文件 六、合併視頻和音頻文件 :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()