【Python 字符视频】Python 实现将抖音视频转换成字符视频

以前就在抖音上看到过字符视频,直到昨天才突然想自己动手做一个,然后就利用各种博客,自己总结 兼 借鉴,终于完成了字符视频的制作

一、思路:
众所周知,视频是一帧一帧的图片组成的,所以我想的就是将抖音的视频先解析成好多帧图片,然后对图片进行操作,最后再把图片合成视频

二、遇到的问题:

  1. 其中我遇到的最大问题就是 pycharm 的 BUG,我从来没有想过 pycharm 会有 BUG,所以在我安装 opencv-python 库的时候,我一直以为是我的安装错误,可最后翻阅了超多博客后,才下定结论是 pycharm 的 BUG,并且代码验证确实可以运行,所以石锤了。

    错误显示:

    Cannot find reference 'waitKey' in '__init__.py' less... (Ctrl+F1) 
    Inspection info: This inspection detects names that should resolve but don't. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Top-level and class-level items are supported better than instance items.
    

    这个错误可以忽略,因为这是 pycharm 本身的 BUG,不影响代码运行

  2. 视频格式错误
    从抖音上下载的视频格式显示是 .Mp4 ,可是并不能用,所以需要转换编码,网上有很多免费的在线转格式的网站。

三、具体操作步骤

  1. 转换视频格式
    在抖音上下载的视频是无法直接转换的,所以需要将视频转码:->免费转码网站<-,转成原来的格式,即还是 .Mp4 就足够了
  2. 首先是要将视频中的帧提取出来:
    部分代码如下:
    while ret:
        # 进行单张图片的读取,ret的值为True或者Flase,frame表示读入的图片
        ret, frame = vc.read()
        if ret:
            # 存储为图像
            cv2.imwrite(folder_path + str(c) + '.jpg', frame)
            # 输出图像名称
            #print(folder_path + str(c) + '.jpg')
            c = c + 1
            # 在一个给定的时间内(单位ms)等待用户按键触发,1ms
            cv2.waitKey(1)
        else:
            break
    
  3. 其次就是单独处理每个图片(也是最复杂的)
    部分代码如下:
    for j in range(len(txts)):
          for i in range(len(txts[j])):
              if isgray:
                  draw_handle.text((i*block_x, j*block_y), txts[j][i], (50, 50, 50))
              else:
                  draw_handle.text((i*block_x, j*block_y), txts[j][i], colors[j][i])
    
    整体思路:读出旧图片,新建一个Image对象,基于旧图片,构造新图片
    具体思路:采用多线程来读出旧图片(因为单线程实在太慢了),自定义字体格式,获取此格式的一个字符的长宽,获取旧图片的长宽,新建一个图片对象,新图片的长宽为:(旧图片长宽 / 字体格式一个字符的长宽),相当于对图片进行了缩放,将每一个字体格式的字符,缩为新图片的一个像素,在扫描就图片的时候,记录下 RGB,并计算出字符,随后填入新图片。
  4. 最后合成视频
    部分代码如下:
    for i in range(1, 1000):
        filename = folder_path_char + str(i) + '.jpg'
        # 判断图片是否存在
        if os.path.exists(filename):
            img = cv2.imread(filename=filename)
            # 在一个给定的时间内(单位ms)等待用户按键触发,100ms
            cv2.waitKey(100)
            # 将图片写入视频中
            videoWriter.write(img)
           # print(str(i) + '.jpg' + ' done!')
    
  5. 提取原视频 Mp3
    这里有一个网站:->Mp3提取网站<-,可以提取出视频的 Mp3
  6. 合成
    我用的是一个视频编辑软件,加入了音频 Mp3,并且压缩了视频(因为合成的视频太大了,抖音 2 M 的视频,合成后变成了 200 M)

四、详细代码

from PIL import Image, ImageDraw, ImageFont
import cv2
import os
import threading
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")

#在当前目录下新建文件夹
folder_path = r"D:\Python_workspace\char_video_sources\pictures\\"#每帧
folder_path_char =r"D:\Python_workspace\char_video_sources\char_pitcures\\"#彩色字符帧

width=None
height=None
fps=None
count=None
def getVideo(url):
    # 进行视频的载入
    vc = cv2.VideoCapture(url)
    c = 0
    # 判断载入的视频是否可以打开
    ret = vc.isOpened()
    """获取视频参数"""
    global fps
    fps=vc.get(cv2.CAP_PROP_FPS)#获取视频帧数
    global count
    count=vc.get(cv2.CAP_PROP_FRAME_COUNT)
    global width
    global height
    width =int(vc.get(cv2.CAP_PROP_FRAME_WIDTH))  # 获取长宽
    height =int(vc.get(cv2.CAP_PROP_FRAME_HEIGHT))
    """end"""
    # 循环读取视频帧
    while ret:
        # 进行单张图片的读取,ret的值为True或者Flase,frame表示读入的图片
        ret, frame = vc.read()
        if ret:
            # 存储为图像
            cv2.imwrite(folder_path + str(c) + '.jpg', frame)
            # 输出图像名称
            #print(folder_path + str(c) + '.jpg')
            c = c + 1
            # 在一个给定的时间内(单位ms)等待用户按键触发,1ms
            cv2.waitKey(1)
        else:
            break
    vc.release()


def get_char(r,g,b,alpha = 256):#获取对应的字符
    if alpha == 0:
        return ' '
    length = len(ascii_char)
    gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
    unit = (256.0 + 1)/length
    return ascii_char[int(gray/unit)]


def severalThreadings(isgray=False):#多线程处理灰度图像
     lines=5
     eachPice=int(count/lines)#每个线程所需处理帧数
     threads=[]
     for line in range(lines):#对于一片进行处理,然后计算扩展到
         begin=line*eachPice
         end=(line+1)*eachPice
         if line==lines-1:#如果是最后一个线程,则其工作为收尾工作,将图片进行到最后
             end=int(count)
         thread = threading.Thread(target=getCharPitcure, args=(begin,end,isgray))
         threads.append(thread)
         thread.setDaemon(True)
         thread.start()
     for item in threads:
         item.join()


def getCharPitcure(begin=0,end=count,isgray=False):#传入是否生成灰色,默认为否
    for op in range(begin, end):
        #print("这是第{}个".format(op))
        img = folder_path + str(op) + '.jpg'
       # print(img)
        if os.path.exists(img):
            im = Image.open(img).convert('RGB') # 注意,此处需要先将图片转换为RGB模式
            # 设定处理后的字符画大小,需要为整型
            raw_width = int(im.width)
            raw_height = int(im.height)
            # 获取设定的字体的尺寸,ImageFont默认的尺寸大小为6x11,其他字体会有所不同
            # 此处使用的字体为truetype字体,大小为10px
            font = ImageFont.truetype('consola.ttf', 10, encoding='unic')
            font_x, font_y = font.getsize(' ')
            # 确定单元的大小
            block_x = int(font_x)
            block_y = int(font_y)
            # 确定长宽各有几个单元
            w = int(raw_width/block_x)
            h = int(raw_height/block_y)
            # 将每个单元缩小为一个像素
            im = im.resize((w, h), Image.NEAREST)
            # txts和colors分别存储对应块的ASCII字符和RGB值
            txts = []
            colors = []
            for i in range(h):#遍历行
                line = ''
                lineColor = []
                for j in range(w):#遍历列
                    pixel = im.getpixel((j, i))
                    lineColor.append((pixel[0], pixel[1], pixel[2]))
                    line += get_char(pixel[0], pixel[1], pixel[2])
                txts.append(line)
                colors.append(lineColor)
            # 创建新画布
            im_txt = Image.new("RGB", (raw_width, raw_height), (255, 255, 255))
            # 创建ImageDraw对象以写入ASCII
            draw_handle = ImageDraw.Draw(im_txt)
            for j in range(len(txts)):
                for i in range(len(txts[j])):
                    if isgray:
                        draw_handle.text((i*block_x, j*block_y), txts[j][i], (50, 50, 50))
                    else:
                        draw_handle.text((i*block_x, j*block_y), txts[j][i], colors[j][i])
            name = folder_path_char + str(op) + '.jpg'
           # print(name)
            im_txt.save(name, 'JPEG')


def createVideo(url):
    # 设置视频编码器,这里使用使用MJPG编码器
    fourcc = cv2.VideoWriter_fourcc(*'MJPG')
    # 输出视频参数设置,包含视频文件名、编码器、帧率、视频宽高(此处参数需和字符图片大小一致)
    videoWriter = cv2.VideoWriter(url, fourcc, fps, (width,height))
    for i in range(1, 1000):
        filename = folder_path_char + str(i) + '.jpg'
        # 判断图片是否存在
        if os.path.exists(filename):
            img = cv2.imread(filename=filename)
            # 在一个给定的时间内(单位ms)等待用户按键触发,100ms
            cv2.waitKey(100)
            # 将图片写入视频中
            videoWriter.write(img)
           # print(str(i) + '.jpg' + ' done!')
    # 视频释放
    videoWriter.release()


if __name__ == '__main__':
    """使用教程:
        将抖音视频转换编码,抖音的编码不能直接使用,转成 mp4 格式 -》https://app.xunjiepdf.com/video
        传入视频地址
        解析出无声音版的视频
        提取视频里的 mp3 -》http://audio-extractor.net/cn/
        用视频编辑器将视频压缩并配乐
        完成
    """
    print("BEGIN")
    """检查文件夹路径在不在"""
    if not os.path.exists(folder_path):
        os.mkdir(folder_path)
    if not os.path.exists(folder_path_char):
        os.mkdir(folder_path_char)
    """end"""

    url=r'D:\Python_workspace\char_video_sources\dance.mp4'
    getVideo(url)
    severalThreadings()#多线程处理图片
    # getCharPitcure()#单线程处理,已设置默认值为(0,count,False)
    createVideo(url.replace(".mp4",".avi"))
    """删除对应文件夹"""
    os.system("rd /s /q "+folder_path)
    os.system("rd /s /q "+folder_path_char)
    """END"""
    print("END")

五、参考博客

  1. https://blog.csdn.net/XZQ121963/article/details/90045996

  2. https://blog.csdn.net/qq_41841569/article/details/84940294

  3. https://blog.csdn.net/weixin_41010198/article/details/88535234

    综上:三篇博客中,综合了第一二篇的优点,我自己又加入了多线程,并且视频长宽完全是基于原视频的,所以在性能和功能上都优于前者,第三篇主要是参考了 cv2 的 get() 函数的属性表

六、运行结果(直接上视频截图,哈,因为需要看出效果,所以抖音上找的一个跳舞的小姐姐,人长得挺漂亮,大家感兴趣可以关注一下,还有用人家图片来写博客也挺不好意思的。)
在这里插入图片描述
在这里插入图片描述

发布了83 篇原创文章 · 获赞 15 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_42127861/article/details/90749741