B站动态转图片
前言
因为重写QQ机器人,空间动态转发动态的功能需要阉割,所以我考虑用其他的方式来进行一定程度补救。
想到的办法就是将内容转发到QQ群中,但要尽可能小的减少对正常群聊的影响,于是便打算将B站动态转为图片发送到群聊中。
环境准备
-
Python 3.10 (您可以使用自己的环境)
-
pip install pillow pip install numpy pip install opencv-python pip install requests
实现思路
大致过程如下:
- 获取B站动态并解析对应内容
- 计算高度动态创建底层图片
- 填充文字
- 填充图片
首先就是要获取B站动态,可以通过api
请求后解析获得。
https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid=uid
将整个图片的宽度设置为 1200 像素,高度需要进行响应计算获得。
在解析动态时,可以获取对应的图片列表,但我当然不想将图片下载下来在进行计算。
于是使用 numpy
将内存中的二进制图片转为数组,再转为image
。
通过 image.shape
就可以获得对应的宽高参数
在填充文字的时候,cv2.inputText
不能填充中文,所以需要将其转为PIL
使用。
OpenCV
转PIL
def OpenCVtoPIL(opencvImage):
return Image.fromarray(cv2.cvtColor(opencvImage, cv2.COLOR_BGR2RGB))
PIL
转OpenCV
def PILtoOpenCV(pilImage):
return cv2.cvtColor(numpy.asarray(pilImage), cv2.COLOR_RGB2BGR)
填充文字时,还需要考虑字符问题,以便实现换行。
使用正则表达式,[^\x00-\xff]
筛选出所有双字节字符。
最后使用image.paste
添加所有图片。
全部代码
import json
import os
import re
import time
import cv2
import numpy
import requests
from PIL import Image, ImageDraw, ImageFont
reg = "[^\x00-\xff]"
class Blog:
def __init__(self, uid):
self.blog = getBlog(uid)
self.blogTitle = self.blog["data"]["name"] + self.blog["data"]["type"]
self.blogTime = self.blog["data"]["time"]
self.blogContent = self.blog["data"]["content"].replace("\r", "")
self.blogImage = self.blog["data"]["image"]
self.lineCount = 0
self.content = ""
self.imageList = []
self.getContent()
if not len(self.blogImage) == 0:
self.getImageList()
def getContent(self):
contentList = self.blogContent.split("\n")
self.content = ""
for i in contentList:
lineWidth = 0
for j in i:
if lineWidth >= 1075:
self.content += "\n"
self.lineCount += 1
lineWidth = 0
if isChinese(j):
lineWidth += 50
self.content += j
else:
lineWidth += 25
self.content += j
self.content += "\n"
self.lineCount += 1
def getImageList(self):
for i in self.blogImage:
# 获取图片
data = requests.get(i)
# 将图片转为矩阵
blogImage = cv2.imdecode(numpy.asarray(bytearray(data.content), dtype="uint8"), cv2.IMREAD_COLOR)
# 获取原图片宽高
height, width = blogImage.shape[:2]
# 重新计算比例
newHeight = 1100 * height // width
# 修改图片宽高
blogImage = cv2.resize(blogImage, (1100, newHeight))
print(blogImage.shape)
self.imageList.append(blogImage)
def getBlog(uid):
url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid=' + str(uid)
result = requests.get(url).json()
data = result['data']['cards'][0]
blogName = data['desc']['user_profile']['info']['uname']
blogType = data['desc']['type']
blogTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data['desc']['timestamp']))
if blogType == 1 or blogType == 4:
blogTypeName = '[转发]'
blogContent = json.loads(data['card'])['item']['content']
blogUrl = 'https://t.bilibili.com/' + data['desc']['dynamic_id_str']
blogImage = []
try:
imageUrl = json.loads(json.loads(data['card'])['origin'])['pic']
blogImage.append(imageUrl)
except:
pass
ret = {
'name': blogName,
'type': blogTypeName,
'time': blogTime,
'content': blogContent,
'url': blogUrl,
'image': blogImage
}
retJson = {
'code': 200,
'msg': '成功',
'data': ret
}
return retJson
elif blogType == 2:
blogTypeName = '[动态]'
blogContent = json.loads(data['card'])['item']['description']
blogUrl = 'https://t.bilibili.com/' + data['desc']['dynamic_id_str']
blogImage = []
try:
imageList = json.loads(data['card'])['item']['pictures']
for i in imageList:
blogImage.append(i['img_src'])
except:
pass
ret = {
'name': blogName,
'type': blogTypeName,
'time': blogTime,
'content': blogContent,
'url': blogUrl,
'image': blogImage
}
retJson = {
'code': 200,
'msg': '成功',
'data': ret
}
return retJson
elif blogType == 8:
blogTypeName = '[视频]'
blogContent = json.loads(data['card'])['title'] + '\n\n' + json.loads(data['card'])['dynamic'] + '\n' + \
json.loads(data['card'])['desc']
blogImage = [json.loads(data['card'])['pic']]
blogUrl = json.loads(data['card'])['short_link']
ret = {
'name': blogName,
'type': blogTypeName,
'time': blogTime,
'content': blogContent,
'url': blogUrl,
'image': blogImage
}
retJson = {
'code': 200,
'msg': '成功',
'data': ret
}
return retJson
def isChinese(text):
chLen = re.findall(reg, text)
if len(chLen) == 0:
return False
else:
return True
def PILtoOpenCV(pilImage):
return cv2.cvtColor(numpy.asarray(pilImage), cv2.COLOR_RGB2BGR)
def OpenCVtoPIL(opencvImage):
return Image.fromarray(cv2.cvtColor(opencvImage, cv2.COLOR_BGR2RGB))
def setTitle():
titleFont = ImageFont.truetype(os.path.join('', 'simkai.ttf'), 100)
draw.text((50, 50), blog.blogTitle, font=titleFont, fill="#000000")
def setContent():
contentFont = ImageFont.truetype(os.path.join('', 'simkai.ttf'), 50)
draw.text((50, 170), blog.content, font=contentFont, fill="#000000")
def getHeight():
getTextHeight = 170 + blog.lineCount * 50 + 20
getAllHeight = getTextHeight
for i in blog.imageList:
height, width = i.shape[:2]
getAllHeight += height + 20
getAllHeight += 50
return getTextHeight, getAllHeight
def setImage():
imageHeight = 0
for i in blog.imageList:
height, width = i.shape[:2]
img = OpenCVtoPIL(i)
image.paste(img, (50, imageHeight + textHeight))
imageHeight += height + 20
if __name__ == "__main__":
blog = Blog(36081646)
textHeight, allHeight = getHeight()
image = numpy.zeros((allHeight, 1200, 3), numpy.uint8)
image.fill(255)
image = OpenCVtoPIL(image)
draw = ImageDraw.Draw(image)
setTitle()
setContent()
setImage()
image = PILtoOpenCV(image)
cv2.imwrite("image.jpg", image)