python代码实现:视频转换终端字符,输出字符代码视频(python实现输出多行刷新)

Posted 呆呆象呆呆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python代码实现:视频转换终端字符,输出字符代码视频(python实现输出多行刷新)相关的知识,希望对你有一定的参考价值。

准备

需要安装curses
windows安装方法:需要在官网上下载whl文件
linux安装方法:直接使用pip在线安装

curses官网教程

推荐一个在线视频转gif的网址

代码结果展示

代码

import time
import cv2
import os
import shutil
from PIL import Image
import curses
import random
 
# 视频抽帧保存
def video2image(video_path):
    if not os.path.exists(video_path):
        raise ValueError("Don't find this video")

    # 保存图片的路径
    image_save_path = os.path.join(os.path.dirname(video_path),os.path.splitext(os.path.basename(video_path))[0])
    if not os.path.exists(image_save_path):
        os.makedirs(image_save_path)
    else:
        print('path of {} already exist and rebuild'.format(image_save_path))
 
    # 保存图片的帧率间隔,每隔多少帧进行一次图片保存
    count = 1

    # 获取当前终端的宽度和长度,为之后的显示做图像resize
    width = os.get_terminal_size().columns
    height = os.get_terminal_size().lines
    print("终端大小:宽{},高{}".format(width,height))
    
    # 开始读视频
    videoCapture = cv2.VideoCapture(video_path)
    i,j = 0,0
    while True:
        success, frame = videoCapture.read()
        i += 1
        if (i % count == 0) and success:
            # 保存图片
            j += 1
            # 找一个比较小的比例
            rate_x = (width-1) / frame.shape[1]
            rate_y = (height-1) / frame.shape[0]*2
            rate = min(rate_x, rate_y)
            frame = cv2.resize(frame, (0, 0), fx=rate, fy=rate, interpolation=cv2.INTER_NEAREST)
            savedname = os.path.splitext(os.path.basename(video_path))[0] +  '_' + '%04d' % j + '.jpg'
            cv2.imwrite(os.path.join(image_save_path,savedname), frame)
        elif success:
            # 不保存的帧
            pass
        else:
            print('抽帧完成')
            break
 
# 把抽帧得到的图片全部转化为字符画
def pic2str_V1(video_path):
    '''
    第一个版本的图片转换为字符串的代码
    像素点根据需要表示的字符进行等比例的划分
    之后进行字符串的一个一个对像素点的赋值
    '''
    # table = ("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\\"^`'. ")
    # table = ('#8XOHLTI)i=+;:,. ')  # 对于灰度图像效果不错
    table = ('#8Oo=:. ')  # 对于灰度图像效果不错
 
    # 拿到保存图片的路径
    pics_path = video_path.split('.')[0]
 
    # 创建保存字符画的路径
    str_pic_path = video_path.split('.')[0] + '_str'
    if not os.path.exists(str_pic_path):
        os.makedirs(str_pic_path)
        # print('path of %s is build' % (savedpath))
    else:
        shutil.rmtree(str_pic_path)
        os.makedirs(str_pic_path)
        # print('path of %s already exist and rebuild' % (savedpath))
 
    pics = os.listdir(pics_path)# 获取文件夹内部的所有图片的basename
    for pic in pics:
        pic_path = os.path.join(pics_path, pic)
        str_pic_name = os.path.join(str_pic_path, os.path.splitext(pic)[0] + '.txt')
 
        # 转换
        img = Image.open(pic_path)
        # img = img.resize((300, 200))  # 转换图像大小 可以调整字符串图像的长和宽
 
        if img.mode != "L":  # 如果不是灰度图像,转换为灰度图像
            im = img.convert("L")
        x = img.size[0]
        y = img.size[1]

        f = open(str_pic_name, 'w+')  # 目标文本文件
 
        for i in range(1, y, 2):# 因为显示字符的长款本来就是有点不太一样
            line = ('')
            for j in range(x):
                line += table[int((float(im.getpixel((j, i))) / 256.0) * len(table))]
            line += ("\\n")
            f.write(line)
        f.close()
 
    print('字符画完成')
    return str_pic_path

# 把抽帧得到的图片全部转化为字符画
def pic2str_V2(video_path):
    '''
    第二个版本的图片转换为字符串的代码
    像素点根据分布进行划分,需要使用到pic2dict
    之后进行字符串的一个一个对像素点的赋值
    '''
    # table = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\\"^`'. "
    # table = '#8XOHLTI)i=+;:,. '  # 对于灰度图像效果不错
    table = '@8Oo=:. '  # 对于灰度图像效果不错
    # table = '#=:. '  # 对于灰度图像效果不错
 
    # 拿到保存图片的路径
    pics_path = video_path.split('.')[0]
 
    # 创建保存字符画的路径
    str_pic_path = video_path.split('.')[0] + '_str'
    if not os.path.exists(str_pic_path):
        os.makedirs(str_pic_path)
        # print('path of %s is build' % (savedpath))
    else:
        shutil.rmtree(str_pic_path)
        os.makedirs(str_pic_path)
        # print('path of %s already exist and rebuild' % (savedpath))
    
    # 图像转换之后压入栈中
    pics = os.listdir(pics_path)# 获取文件夹内部的所有图片的basename
    pic_list = []
    str_pic_name_list = []
    for pic in pics:
        pic_path = os.path.join(pics_path, pic)
        str_pic_name = os.path.join(str_pic_path, os.path.splitext(pic)[0] + '.txt')
        str_pic_name_list.append(str_pic_name)
        # 转换
        img = Image.open(pic_path)
        if img.mode != "L":  # 如果不是灰度图像,转换为灰度图像
            img = img.convert("L")
        pic_list.append(img)
    # 栈内所有的图片进行函数运行
    # 将图片进行所有像素点的统计根据统计的像素点,得到像素点与字符的对应关系(字典)
    dic_pixel = pic2dict(pic_list,table = table,ifreversed=True)

    # 写入字符串存入文件
    for index in range(len(pic_list)):
        x = pic_list[index].size[0]
        y = pic_list[index].size[1]
        f = open(str_pic_name_list[index], 'w+')  # 目标文本文件
        for i in range(1, y, 2):# 因为显示字符的长款本来就是有点不太一样
            line = ('')
            for j in range(x):
                line += dic_pixel[pic_list[index].getpixel((j, i))]
            line += ("\\n")
            f.write(line)
        f.close()
 
    print('字符画完成')
    return str_pic_path

# 图片统计像素点,得到像素点对应字典
def pic2dict(pic_list,table = '#8Oo=:. ',ifreversed = False):
    '''
    ifreversed = True的时候反色
    反色状态下黑色的像素点对应比较复杂的字符
             白色的像素点对应比较简单的字符
    ifreversed = False的时候不反色
    反色状态下黑色的像素点对应比较简单的字符
             白色的像素点对应比较复杂的字符
    '''
    if ifreversed:
        pass
    else:
        table = table[::-1] 
    dic_pixel = {}
    for i in range(256):
        dic_pixel[i] = 0
    x = pic_list[0].size[0]
    y = pic_list[0].size[1]
    count = 0
    for pic in pic_list:
        for i in range(1, y, 2):# 因为显示字符的长款本来就是有点不太一样
            for j in range(x):
                dic_pixel[pic.getpixel((j, i))] += 1
                count += 1

    all_pixel_data = len(pic_list)*x*y/2
    start_pixel = 255
    for i in range(len(table)):
        sum_pixel = 0
        if_just_one = True
        for pixel in range(start_pixel,-1,-1):
            sum_pixel += dic_pixel[pixel]
            if sum_pixel >= all_pixel_data/(len(table)-1):
                if if_just_one:
                    dic_pixel[pixel] = table[i]
                    start_pixel = pixel-1
                else:
                    if all_pixel_data/(len(table)-1) -sum_pixel+ dic_pixel[pixel] > sum_pixel-all_pixel_data/(len(table)-1):
                        dic_pixel[pixel] = table[i]
                        start_pixel = pixel-1
                    else:
                        start_pixel = pixel
                break
            else:
                if_just_one = False
                dic_pixel[pixel] = table[i]
        if pixel == 0:
            break
    for i in dic_pixel.keys():
        if isinstance(dic_pixel[i],str):
            continue
        else:
            dic_pixel[i] = table[-1]
    return dic_pixel
 
# 第三步 把字符画按照顺序动态打印 实现动画效果
def play_str(play_dir):
    strs = os.listdir(play_dir)
    for str_txt in strs:
        real_dir = play_dir + '\\\\' + str_txt
        with open(real_dir, 'r') as f:
            ret = f.read()
        print(ret)
        time.sleep(0.03)
        # os.system("cls")
        # subprocess.run('cls', shell=True)
 
# 主函数V1
# 进行清屏然后打印
# 但是出现比较那一解决的空白显示在终端的情况(出现)
def main_V1():
    video_path = r'C:\\Users\\ZZQ\\Desktop\\2.mp4' # 修改你要生成字符画的视频路径
    video2image(video_path)
    play_dir = pic2str_V1(video_path)
    input('转换完成 按任意键开始播放')
    play_str(play_dir)

# 主函数V2
# 使用curses库
# 不会出现清屏的情况,是根据坐标进行刷新的
def main_V2():
    video_path = r'C:\\Users\\ZZQ\\Desktop\\1.mp4' # 修改你要生成字符画的视频路径
    video2image(video_path)
    play_dir = pic2str_V2(video_path)
    input('转换完成 按任意键开始播放')

    stdscr = curses.initscr()
    curses.noecho()   #不输出- -
    curses.cbreak()   #立刻读取:暂不清楚- -
    stdscr.keypad(1)  #开启keypad
    stdscr.box()
    stdscr.nodelay(True)

    width = os.get_terminal_size().columns
    height = os.get_terminal_size().lines
    c_y = height//2 - 1
    c_x = width//2 - 10
    stdscr.addstr(c_y,c_x,'Now press C to start...',curses.A_REVERSE)
    stdscr.addstr(c_y+1,c_x,'After movie start, D to end.',curses.A_REVERSE)
    while True:
        c = stdscr.getch()
        if c == ord('c') or c == ord('C'):
            break

    strs = os.listdir(play_dir)
    for str_txt in strs:
        real_dir = play_dir + '\\\\' + str_txt
        with open(real_dir, 'r') as f:
            ret = f.read()
        stdscr.addstr(0,0,ret) 
        stdscr.move(0,0)
        stdscr.refresh()
        time.sleep(0.005)
        c = stdscr.getch()
        if c == ord('d') or c == ord('D'):
            break

    curses.endwin()


if __name__ == '__main__':
    main_V2()


Last 参考文献

python 控制台单行刷新,多行刷新 - whyaza - 博客园
抖音流行的字符视频如何实现_追梦程序员的博客-CSDN博客
Python 把任意系统的路径转换成当前系统的格式(关于 / \\ 分隔符的)Ike Liu-CSDN博客
100行代码让你的代码跳起舞(视频转字符画)honestman_N的博客-CSDN博客
极乐净土跳舞代码源码[源码分享]视频转字符动画,50行代码搞定_weixin_39631301的博客-CSDN博客
Python命令行清屏方法_Andy的博客-CSDN博客_python 清屏
python windows curses库安装教程_Mikumiku339的博客-CSDN博客
python 控制台单行刷新,多行刷新 - 我的607_my607_IT资讯idcvps云服务器互联网等分享和工具
Python控制台输出时刷新当前行内容而不是输出新行_Dexter’s Laboratory-CSDN博客
python3 删除命令行中输出的内容_albert_fifth的博客-CSDN博客
python print 退格_学习python之基础print()函数_weixin_39559469的博客-CSDN博客
极乐净土跳舞代码源码[源码分享]视频转字符动画,50行代码搞定_weixin_39631301的博客-CSDN博客
以后可以参考的文献
【Python AsciiArt】利用命令行打印出字符图案_TomRen-CSDN博客
Python3 上实现命令行环境的多行独立进度条 - V2EX

以上是关于python代码实现:视频转换终端字符,输出字符代码视频(python实现输出多行刷新)的主要内容,如果未能解决你的问题,请参考以下文章

Python生成字符视频

Python生成字符视频

详细讲解用Python把动漫视频生成字符的完整步骤

Python 的一万种用法:生成字符视频

python终端如何输出彩色字体

JS对象如何转为json格式字符串