前段时间刷到一篇分享 liruifengv/daily-poetry-image:用Bing-AI
的文生图功能结合诗词api生成对应图片。联想到之前fork的bing-wallpaper项目,就来简单改造下。
bing-wallpaper
bing-wallpaper项目比较简单,就是利用GitHub-action跑定时任务爬取Bing每日壁纸链接,随后更新README.md文档。
这个项目是用java写的,当时直接fork的niumoo/bing-wallpape,修改下ci的配置就能跑了。目前已经稳定运行快两年了,每天会跑两次防止更新遗漏,会有一次异常部署是因为当时还没更新今日壁纸。
改造的思路就是利用Bing-AI来代替Bing每日壁纸,每天都是AI-Generate图片。
bing-ai-poetry
Github 地址 Mulander-J/bing-ai-poetry
前提准备,需要有edge浏览器+账号,以及科学上网。能正常访问 https://bing.com/create
,并生成一次图片,记录cookie
后面会用到。
文生图的文字部分由今日诗词api随机生成。
bash
curl https://v1.jinrishici.com/all
{
"content" : "正蹇驴吟影,茶烟灶冷,酒亭门闭。",
"origin" : "无闷·催雪",
"author" : "吴文英",
"category" : "古诗文-食物-写茶"
}
在liruifengv/daily-poetry-image 的基础上,砍掉原有的前端项目(不需要前端渲染,自带README.md足够。)
diff
- website
稍微修改入口文件 bin/cli.ts
。
之前在本地测试的时候,因为网络原因图片经常只能下到一两张,且只有一半的数据。修改的部分主要是将主函数套上async,然后就可以在内部await图片下载和读写事务,确保进程退出时图片不会下到一半。
另外下载好图片的同时会同步修改README.md文件,只需要append新内容就行了。
核心的getImageBySentence
随机诗词文生图方法原项目都封装好了。
ts
import { getImageBySentence } from "../src/get-up";
import type { Response } from "../src/types";
import path from "path";
import fs from "fs";
import stream from "stream";
import { promisify } from "util";
const pipeline = promisify(stream.pipeline);
async function init() {
const cwd = process.cwd();
const argv = require("minimist")(process.argv.slice(2));
if (argv.cookie && typeof argv.cookie !== 'boolean') { // 此处兼容 --cookie="xx" 和 --cookie "xx"
try {
// const res = {
// images: [
// 'https://tse1.mm.bing.net/th/id/OIG.0lT30AZgF0Y9TGnssoUu',
// 'https://tse2.mm.bing.net/th/id/OIG.AYKgb1ftqrAfxCfwGjCs',
// 'https://tse1.mm.bing.net/th/id/OIG.GcNrrihRgfhlHOwiRtR.'
// ],
// content: '关山别荡子,风月守空闺。',
// origin: '昔昔盐',
// author: '薛道衡',
// category: '古诗文-抒情-离别'
// }
const res: Response = await getImageBySentence(argv.cookie);
console.log("Create Successful: ", res);
const imagesPath = path.join(cwd, "images");
if (!fs.existsSync(imagesPath)) {
fs.mkdirSync(imagesPath);
}
// 在 images 目录下,创建一个以时间戳命名的文件夹,将图片放入其中
const imagesFolderName = Date.now().toString();
const imagesFolderPath = path.join(imagesPath, imagesFolderName);
if (!fs.existsSync(imagesFolderPath)) {
fs.mkdirSync(imagesFolderPath);
}
const images = res.images
let imgPaths:any[] = []
// 将图片放入 images 目录下的文件夹中
for(let i = 0; i < images.length; i++){
// images 中是网络url,请求图片,将图片保存到 images 目录下的文件夹中
const imageFileName = `${i}.jpg`;
const imageFilePath = path.join(imagesFolderPath, imageFileName);
const imageDocPath = `./images/${imagesFolderName}/${imageFileName}`
imgPaths.push(`![${imagesFolderName}_${imageFileName}](${imageDocPath})[${imageFileName}](${images[i]})`)
// 下载图片
console.log('fetching...', images[i])
const res:any = await fetch(images[i])
if (!res.ok) throw new Error(`unexpected response ${res.statusText}`);
await pipeline(res.body, fs.createWriteStream(imageFilePath)).catch((e) => {
console.error("Something went wrong while saving the image", e);
});
console.log('>fetched', images[i])
}
console.log('Fetch Images Ended')
const options = { timeZone: "Asia/Shanghai", hour12: false };
const outputData = {
...res,
date: new Date().toLocaleString("zh-CN", options),
localImagesPath: imagesFolderName,
};
const contentFile = path.join(imagesFolderPath, 'index.json');
fs.writeFileSync(contentFile, JSON.stringify(outputData));
const rowHead = `> ${res.content} ------ ${res.author}《${res.origin}》\n`
let appendCtx = `|${imgPaths.map(_=>' ').join('|')}|\n`
appendCtx += `|${imgPaths.map(_=>' :----: ').join('|')}|\n`
appendCtx += `|${imgPaths.join('|')}|`
fs.appendFileSync('./README.md', [
'',
rowHead,
appendCtx,
''
].join('\n'))
// 缓冲等待,确保未捕获的异步任务都能跑完
console.log('Wating Sec...')
setTimeout(() => {
process.exit(0);
}, 5 * 1000);
} catch (e) {
console.error(e);
process.exit(1);
}
} else {
throw new Error("Please provide a cookie using the --cookie argument");
}
}
init().catch((e) => {
console.error(e);
});
以及,处理Bing接口返回时,获取图片数据是采用正则/src="([^"]*)"/g
全局匹配的,偶尔会跑出来几张https://r.bing.com/rp/xxx
的乱入图片需要剔除。
ts
// /src/bing-image-creator.ts
parseResult(result: string) {
console.log("Parsing result...");
// Use regex to search for src=""
const regex = /src="([^"]*)"/g;
const matches = [...result.matchAll(regex)].map((match) => match[1]);
// Remove Bad Images(https://r.bing.com/rp/xxx)
const safe_image_links = matches.filter(link => !/r.bing.com\/rp/i.test(link))
// Remove size limit
const normal_image_links = safe_image_links.map(link => (link.split("?w=")[0]));
// Remove duplicates
const unique_image_links = [...new Set(normal_image_links)];
// No images
if (unique_image_links.length === 0) {
throw new Error("error_no_images");
}
return unique_image_links;
}
完工。接下来就是actions定时跑任务了, 一切正常。