我生成了一个蔡徐坤 唱、跳、RAP的字符视频,链接为:
使用python将一个视频转换为字符视频
详细视频教程,总共8分钟。
文字教程见下文:
首先需要安装python
然后安装所需要的包:cv2、PIL
具体安装方法:
安装cv2
pip install opencv-python
安装PIL
pip install pillow
在安装过程中经常遇到安装时间过长、安装失败等问题。
可以在官网下载对应版本的whl文件,采用本地安装的方法。
下载时需要注意自己的电脑是32位还是64位,
需要注意自己电脑上装的python是多少版本的,
选择opencv_python和pillow时,要对应上才行。
此外还需要下载ffmpeg包,用来抽取视频中的声音,以及将声音添加到生成的字符视频中。
如果不配置ffmpeg的话,生成的视频是没有声音的。
下载时同样要注意自己的电脑是32位还是64位。
下载好ffmpeg包之后还需要配置环境变量。
等一切准备工作就绪之后,就可以运行python代码生成字符视频了,python代码如下。
import argparse
import os
import cv2
import subprocess
from cv2 import VideoWriter_fourcc
from PIL import Image, ImageFont, ImageDraw
class Video2CodeVideo:
def __init__(self):
self.config_dict = {
# 原视频文件
"input_file": "test.mp4",
# 中间文件存放目录
"cache_dir": "cache",
# 是否保留过程文件。True--保留,False--不保留
"save_cache_flag": True,
# 使用使用的字符集
"ascii_char_list": list("01B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:oa+>!:+. "),
}
# 第一步从函数,将像素转换为字符
# 调用栈:video_2_txt_jpg -> txt_2_image -> rgb_2_char
def rgb_2_char(self, r, g, b, alpha=256):
if alpha == 0:
return ''
length = len(self.config_dict["ascii_char_list"])
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0 + 1) / length
return self.config_dict["ascii_char_list"][int(gray / unit)]
# 第一步从函数,将txt转换为图片
# 调用栈:video_2_txt_jpg -> txt_2_image -> rgb_2_char
def txt_2_image(self, file_name):
im = Image.open(file_name).convert('RGB')
# gif拆分后的图像,需要转换,否则报错,由于gif分割后保存的是索引颜色
raw_width = im.width
raw_height = im.height
width = int(raw_width / 6)
height = int(raw_height / 15)
im = im.resize((width, height), Image.NEAREST)
txt = ""
colors = []
for i in range(height):
for j in range(width):
pixel = im.getpixel((j, i))
colors.append((pixel[0], pixel[1], pixel[2]))
if (len(pixel) == 4):
txt += self.rgb_2_char(pixel[0], pixel[1], pixel[2], pixel[3])
else:
txt += self.rgb_2_char(pixel[0], pixel[1], pixel[2])
txt += '\n'
colors.append((255, 255, 255))
im_txt = Image.new("RGB", (raw_width, raw_height), (255, 255, 255))
dr = ImageDraw.Draw(im_txt)
font = ImageFont.load_default().font
x = y = 0
# 获取字体的宽高
font_w, font_h = font.getsize(txt[1])
font_h *= 1.37 # 调整后更佳
# ImageDraw为每个ascii码进行上色
for i in range(len(txt)):
if (txt[i] == '\n'):
x += font_h
y = -font_w
dr.text((y, x), txt[i], fill=colors[i])
y += font_w
name = file_name
im_txt.save(name)
# 第一步,将原视频转成字符图片
# 调用栈:video_2_txt_jpg -> txt_2_image -> rgb_2_char
def video_2_txt_jpg(self, file_name):
vc = cv2.VideoCapture(file_name)
c = 1
if vc.isOpened():
r, frame = vc.read()
if not os.path.exists(self.config_dict["cache_dir"]):
os.mkdir(self.config_dict["cache_dir"])
os.chdir(self.config_dict["cache_dir"])
else:
r = False
while r:
cv2.imwrite(str(c) + '.jpg', frame)
self.txt_2_image(str(c) + '.jpg') # 同时转换为ascii图
r, frame = vc.read()
c += 1
os.chdir('..')
return vc
# 第二步,将字符图片合成新视频
def txt_jpg_2_video(self, outfile_name, fps):
fourcc = VideoWriter_fourcc(*"MJPG")
images = os.listdir(self.config_dict["cache_dir"])
im = Image.open(self.config_dict["cache_dir"] + '/' + images[0])
vw = cv2.VideoWriter(outfile_name + '.avi', fourcc, fps, im.size)
os.chdir(self.config_dict["cache_dir"])
for image in range(len(images)):
frame = cv2.imread(str(image + 1) + '.jpg')
vw.write(frame)
os.chdir('..')
vw.release()
# 第三步,从原视频中提取出背景音乐
def video_extract_mp3(self, file_name):
outfile_name = file_name.split('.')[0] + '.mp3'
subprocess.call('ffmpeg -i ' + file_name + ' -f mp3 -y ' + outfile_name, shell=True)
# 第四步,将背景音乐添加到新视频中
def video_add_mp3(self, file_name, mp3_file):
outfile_name = file_name.split('.')[0] + '-txt.mp4'
subprocess.call('ffmpeg -i ' + file_name + ' -i ' + mp3_file + ' -strict -2 -f mp4 -y ' + outfile_name, shell=True)
# 第五步,如果没配置保留则清除过程文件
def clean_cache_while_need(self):
# 为了清晰+代码比较短,直接写成内部函数
def remove_cache_dir(path):
if os.path.exists(path):
if os.path.isdir(path):
dirs = os.listdir(path)
for d in dirs:
if os.path.isdir(path + '/' + d):
remove_cache_dir(path + '/' + d)
elif os.path.isfile(path + '/' + d):
os.remove(path + '/' + d)
os.rmdir(path)
return
elif os.path.isfile(path):
os.remove(path)
return
# 为了清晰+代码比较短,直接写成内部函数
def delete_middle_media_file():
os.remove(self.config_dict["input_file"].split('.')[0] + '.mp3')
os.remove(self.config_dict["input_file"].split('.')[0] + '.avi')
# 如果没配置保留则清除过程文件
if not self.config_dict["save_cache_flag"]:
remove_cache_dir(self.config_dict["cache_dir"])
delete_middle_media_file()
# 程序主要逻辑
def main_logic(self):
# 第一步,将原视频转成字符图片
print("第一步,正在将原视频转成字符图片")
vc = self.video_2_txt_jpg(self.config_dict["input_file"])
# 获取原视频帧率
fps = vc.get(cv2.CAP_PROP_FPS)
print("获取原视频帧率:")
print(fps)
vc.release()
print("已将原视频转成字符图片\n")
# 第二步,将字符图片合成新视频
print("第二步,正在将字符图片合成新视频")
self.txt_jpg_2_video(self.config_dict["input_file"].split('.')[0], fps)
print(self.config_dict["input_file"], self.config_dict["input_file"].split('.')[0] + '.mp3')
print("已将字符图片合成新视频\n")
# 第三步,从原视频中提取出背景音乐
print("第三步, 正在从原视频中提取出背景音乐")
self.video_extract_mp3(self.config_dict["input_file"])
print("已从原视频中提取出背景音乐\n")
# 第四步,将背景音乐添加到新视频中
print("第四步, 正在将背景音乐添加到新视频中")
self.video_add_mp3(self.config_dict["input_file"].split('.')[0] + '.avi', self.config_dict["input_file"].split('.')[0] + '.mp3')
print("已将背景音乐添加到新视频中\n")
# 第五步,如果没配置保留则清除过程文件
self.clean_cache_while_need()
print("字符视频制作完毕\n字符视频为test-txt.mp4\n")
print("按任意键结束")
a=input()
if __name__ == '__main__':
obj = Video2CodeVideo()
obj.main_logic()
注意要将源视频和python代码放在一个目录下,将原视频命名为test.mp4,运行结束后,会生成一个test-txt.mp4文件,就是我们想要的字符视频。

有问题欢迎交流
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)