几个月前,我开始为一个专注于科技领域的客户合作一个关于人工智能生成内容的项目。我的职责主要是使用 WordPress 设置 SSG 作为 Headless CMS 用于 Nuxt 前端。
客户过去每周写几次关于影响该行业的不同趋势或情况的文章,为了增加网站的流量和文章的输出,他决定使用人工智能为他生成文章。
一段时间后,在正确的提示下,客户得到的信息与人类撰写的文章几乎完全匹配,很难发现它们是机器制作的。
在我开始研究不同的功能后,我会不断被问到一件特定的事情。
哎,你能更新一下这篇文章的特色图片吗?
经过两周的每日更新帖子后,我突然灵光一现。
为什么我不使用人工智能自动为这些文章生成特色图像?
我们已经自动化撰写帖子,为什么不自动化精选图片?
在空闲时间,我在计算机上尝试生成法学硕士,因此我或多或少对如何解决这个支线任务有了一个扎实的想法。我向客户发送了一条消息,详细说明了问题是什么、我想要做什么以及优势是什么,无需令人信服,我就获得了使用此功能的绿灯,并立即同意了我的第一步。
1. 设计解决方案的外观。
鉴于我接触过在本地运行模型,我立即知道自行托管这些模型是不可行的。放弃这个之后,我开始尝试根据文本提示生成图像的 API。
特色图片由两部分组成:主要组成的图形和吸引人的标语。
组成的图形将是与文章相关的一些元素,以良好的方式排列,然后应用一些颜色和纹理,并应用一些混合模式来实现品牌之后的一些奇特效果。
标语是简短的 8-12 个单词的句子,下面有一个简单的阴影。
根据我的测试,我意识到追求图像生成的人工智能路线是不切实际的。图像质量没有达到预期,而且过程太耗时,无法证明其使用的合理性。考虑到这将作为 AWS Lambda 函数运行,其中执行时间直接影响成本。
放弃了这一点,我选择了 B 计划:使用 JavaScript 的 Canvas API 将图像和设计资源混合在一起。
深入观察,我们主要有 5 种风格的简单帖子,以及大约 4 种类型的纹理,其中 3 种使用相同的文本对齐方式、样式和位置。做了一些数学计算后,我想:
嗯,如果我拍摄这 3 个图像,抓取 8 个纹理并使用混合模式,我就可以解决 24 种变化
鉴于这 3 种类型的帖子具有相同的文本样式,它实际上是一个模板。
解决了这个问题后,我转向了标语生成器。我想根据文章的内容和标题创建一个口号。鉴于公司已经支付了费用,我决定使用 ChatGPT 的 API,经过一些实验和提示调整后,我的口号生成器有了一个非常好的 MVP。
弄清楚任务中最困难的 2 个部分后,我花了一些时间在 Figma 中整理出我服务的最终架构的图表。
2.编写我的 lambda
计划创建一个 Lambda 函数,能够分析帖子内容、生成标语并组装特色图像 - 所有这些都与 WordPress 无缝集成。
我将提供一些代码,但足以向ke传达总体想法。
分析内容
Lambda 函数首先从传入事件负载中提取必要的参数:
const { title: request_title, content, backend, app_password} = JSON.parse(event.body);
- 标题和内容:这些提供了文章的上下文。
- 后端: 用于图像上传的 WordPress 后端 URL。
- app_password: 我将使用 WordPress Rest API 以我的用户身份上传的身份验证令牌。
生成标语
该函数的第一个主要任务是使用analyzeContent函数生成标语,该函数使用OpenAI的API根据文章的标题和内容制作值得点击的标语。
我们的函数获取帖子标题和内容,但返回标语、帖子情绪以了解帖子是正面、负面还是中性意见,以及来自标准普尔指数公司的可选公司符号。
const { 口号、情感、公司 } =等待analyzeContent({ title: request_title, content });
这一步很关键,因为口号直接影响图片的美感。
创建特色图像
接下来,generateImage函数开始运行:
let buffer; buffer = await generateImage({ title: tagline, company_logo: company_logo, sentiment: sentiment, });
该函数处理:
- 设计构图。
- 分层纹理、颜色和品牌元素。
- 应用效果并创建标题。
以下是其工作原理的分步分析:
generateImage 函数首先设置一个空白画布,定义其尺寸,并准备好处理所有设计元素。
let buffer; buffer = await generateImage({ title: tagline, company_logo: company_logo, sentiment: sentiment, });
从那里,从预定义的资源集合中加载随机背景图像。这些图像经过精心设计,以适应以技术为导向的品牌,同时允许帖子之间有足够的多样性。背景图像是根据其情绪随机选择的。
为了确保每个背景图像看起来都很棒,我根据宽高比动态计算其尺寸。这样可以避免扭曲,同时保持视觉平衡完好。
添加标语
标语很短,但根据一些规则,这个有影响力的句子被分成可管理的部分,并动态设计样式以确保它始终可读,无论长度或画布大小如何(基于行的字数、字长等) .
const COLOURS = { BLUE: "#33b8e1", BLACK: "#000000", } const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const images_path = path.join(__dirname, 'images/'); const files_length = fs.readdirSync(images_path).length; const images_folder = process.env.ENVIRONMENT === "local" ? "./images/" : "/var/task/images/"; registerFont("/var/task/fonts/open-sans.bold.ttf", { family: "OpenSansBold" }); registerFont("/var/task/fonts/open-sans.regular.ttf", { family: "OpenSans" }); console.log("1. Created canvas"); const canvas = createCanvas(1118, 806); let image = await loadImage(`${images_folder}/${Math.floor(Math.random() * (files_length - 1 + 1)) + 1}.jpg`); let textBlockHeight = 0; console.log("2. Image loaded"); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const aspectRatio = image.width / image.height; console.log("3. Defined ASPECT RATIO",) let drawWidth, drawHeight; if (image.width > image.height) { // Landscape orientation: fit by width drawWidth = canvasWidth; drawHeight = canvasWidth / aspectRatio; } else { // Portrait orientation: fit by height drawHeight = canvasHeight; drawWidth = canvasHeight * aspectRatio; } // Center the image const x = (canvasWidth - drawWidth) / 2; const y = (canvasHeight - drawHeight) / 2; const ctx = canvas.getContext("2d"); console.log("4. Centered Image") ctx.drawImage(image, x, y, drawWidth, drawHeight);
最后,画布被转换为 PNG 缓冲区。
console.log("4.1 Text splitting"); if (splitText.length === 1) { const isItWiderThanHalf = ctx.measureText(splitText[0]).width > ((canvasWidth / 2) + 160); const wordCount = splitText[0].split(" ").length; if (isItWiderThanHalf && wordCount > 4) { const refactored_line = splitText[0].split(" ").reduce((acc, curr, i) => { if (i % 3 === 0) { acc.push([curr]); } else { acc[acc.length - 1].push(curr); } return acc; }, []).map((item) => item.join(" ")); refactored_line[1] = "[s]" + refactored_line[1] + "[s]"; splitText = refactored_line } } let tagline = splitText.filter(item => item !== '' && item !== '[br]' && item !== '[s]' && item !== '[/s]' && item !== '[s]'); let headlineSentences = []; let lineCounter = { total: 0, reduced_line_counter: 0, reduced_lines_indexes: [] } console.log("4.2 Tagline Preparation", tagline); for (let i = 0; i item !== '' && item !== '[s]' && item !== '[/s]'); const lineWidth = ctx.measureText(finalLine[0]).width const halfOfWidth = canvasWidth / 2; if (lineWidth > halfOfWidth && finalLine[0]) { let splitted_text = finalLine[0].split(" ").reduce((acc, curr, i) => { const modulus = finalLine[0].split(" ").length >= 5 ? 3 : 2; if (i % modulus === 0) { acc.push([curr]); } else { acc[acc.length - 1].push(curr); } return acc; }, []); let splitted_text_arr = [] splitted_text.forEach((item, _) => { let lineText = item.join(" "); item = lineText splitted_text_arr.push(item) }) headlineSentences[i] = splitted_text_arr[0] + '/s/' if (splitted_text_arr[1]) { headlineSentences.splice(i + 1, 0, splitted_text_arr[1] + '/s/') } } else { headlineSentences.push("/s/" + finalLine[0] + "/s/") } } else { headlineSentences.push(line) } } console.log("5. Drawing text on canvas", headlineSentences); const headlineSentencesLength = headlineSentences.length; let textHeightAccumulator = 0; for (let i = 0; i item !== '/s/'); const nextLine = headlineSentences[i + 1]; if (nextLine && /^\s*$/.test(nextLine)) { headlineSentences.splice(i + 1, 1); } let line = headlineSentences[i]; if (!line) continue; let lineText = line.trim(); let textY; ctx.font = " 72px OpenSans"; const cleanedUpLine = lineText.includes('/s/') ? lineText.replace(/\s+/g, ' ') : lineText; const lineWidth = ctx.measureText(cleanedUpLine).width const halfOfWidth = canvasWidth / 2; lineCounter.total += 1 const isLineTooLong = lineWidth > (halfOfWidth + 50); if (isLineTooLong) { if (lineText.includes(':')) { const split_line_arr = lineText.split(":") if (split_line_arr.length > 1) { lineText = split_line_arr[0] + ":"; if (split_line_arr[1]) { headlineSentences.splice(i + 1, 0, split_line_arr[1]) } } } ctx.font = "52px OpenSans"; lineCounter.reduced_line_counter += 1 if (i === 0 && headlineSentencesLength === 2) { is2LinesAndPreviewsWasReduced = true } lineCounter.reduced_lines_indexes.push(i) } else { if (i === 0 && headlineSentencesLength === 2) { is2LinesAndPreviewsWasReduced = false } } if (lineText.includes("/s/")) { lineText = lineText.replace(/\/s\//g, ""); if (headlineSentencesLength > (i + 1) && i (canvasWidth / 2.35)) { ctx.font = "84px OpenSansBold"; assignedSize = 80 } else { ctx.font = "84px OpenSansBold"; assignedSize = 84 } } else { if (i === headlineSentencesLength - 1 && lineWidth (canvasWidth / 2) + 120) { if (assignedSize === 84) { ctx.font = "72px OpenSansBold"; } else if (assignedSize === 80) { ctx.font = "64px OpenSansBold"; textHeightAccumulator += 8 } else { ctx.font = "52px OpenSansBold"; } } } else { const textWidth = ctx.measureText(lineText).width if (textWidth > (canvasWidth / 2)) { ctx.font = "44px OpenSans"; textHeightAccumulator += 12 } else if (i === headlineSentencesLength - 1) { textHeightAccumulator += 12 } } ctx.fillStyle = "white"; ctx.textAlign = "center"; const textHeight = ctx.measureText(lineText).emHeightAscent; textHeightAccumulator += textHeight; if (headlineSentencesLength == 3) { textY = (canvasHeight / 3) } else if (headlineSentencesLength == 4) { textY = (canvasHeight / 3.5) } else { textY = 300 } textY += textHeightAccumulator; const words = lineText.split(' '); console.log("words", words, lineText, headlineSentences) const capitalizedWords = words.map(word => { if (word.length > 0) return word[0].toUpperCase() + word.slice(1) return word }); const capitalizedLineText = capitalizedWords.join(' '); ctx.fillText(capitalizedLineText, canvasWidth / 2, textY); }
最后!!!将图像上传到 WordPress
成功生成图像缓冲区后,调用 uploadImageToWordpress 函数。
此函数通过对 WordPress 图像进行编码,处理使用其 REST API 将图像发送到 WordPress 的繁重工作。
该函数首先通过清理空格和特殊字符来准备用作文件名的标语:
const buffer = canvas.toBuffer("image/png"); return buffer;
然后将图像缓冲区转换为 Blob 对象,以使其与 WordPress API 兼容:
const file = new Blob([buffer], { type: "image/png" });
准备 API 请求 使用编码的图像和标语,该函数构建一个 FormData 对象,并添加可选的元数据,例如用于可访问性的 alt_text 和用于上下文的标题。
const createSlug = (string) => { return string.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, ''); }; const image_name = createSlug(tagline);
为了进行身份验证,用户名和应用程序密码以 Base64 进行编码并包含在请求标头中:
formData.append("file", file, image_name + ".png"); formData.append("alt_text", `${tagline} image`); formData.append("caption", "Uploaded via API");
发送图像使用准备好的数据和标头向 WordPress 媒体端点发出 POST 请求,并在等待响应后验证成功或错误。
const credentials = `${username}:${app_password}`; const base64Encoded = Buffer.from(credentials).toString("base64");
如果成功,我会在 lambda 中返回相同的媒体响应。
这就是我的 lambda 最终的样子。
const response = await fetch(`${wordpress_url}wp-json/wp/v2/media`, { method: "POST", headers: { Authorization: "Basic " + base64Encoded, contentType: "multipart/form-data", }, body: formData, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Error uploading image: ${response.statusText}, Details: ${errorText}`); }
这是我的脚本生成的示例图像。它没有在生产中使用,只是使用本示例的通用资源创建。
后果
一段时间过去了,每个人都很高兴我们不再有劣质或空洞的无图像文章,图像与设计师制作的图像非常匹配,设计师很高兴他只专注于为整个公司的其他营销活动进行设计。
但随后出现了一个新问题:有时客户不喜欢生成的图像,他会要求我启动脚本为特定帖子生成新图像。
这给我带来了下一个支线任务:Wordpress 插件,使用人工智能为特定帖子手动生成特色图像
以上是AWS JavaScript WordPress = 使用人工智能的有趣内容自动化策略的详细内容。更多信息请关注PHP中文网其他相关文章!

JavaScript核心数据类型在浏览器和Node.js中一致,但处理方式和额外类型有所不同。1)全局对象在浏览器中为window,在Node.js中为global。2)Node.js独有Buffer对象,用于处理二进制数据。3)性能和时间处理在两者间也有差异,需根据环境调整代码。

JavaScriptusestwotypesofcomments:single-line(//)andmulti-line(//).1)Use//forquicknotesorsingle-lineexplanations.2)Use//forlongerexplanationsorcommentingoutblocksofcode.Commentsshouldexplainthe'why',notthe'what',andbeplacedabovetherelevantcodeforclari

Python和JavaScript的主要区别在于类型系统和应用场景。1.Python使用动态类型,适合科学计算和数据分析。2.JavaScript采用弱类型,广泛用于前端和全栈开发。两者在异步编程和性能优化上各有优势,选择时应根据项目需求决定。

选择Python还是JavaScript取决于项目类型:1)数据科学和自动化任务选择Python;2)前端和全栈开发选择JavaScript。Python因其在数据处理和自动化方面的强大库而备受青睐,而JavaScript则因其在网页交互和全栈开发中的优势而不可或缺。

Python和JavaScript各有优势,选择取决于项目需求和个人偏好。1.Python易学,语法简洁,适用于数据科学和后端开发,但执行速度较慢。2.JavaScript在前端开发中无处不在,异步编程能力强,Node.js使其适用于全栈开发,但语法可能复杂且易出错。

javascriptisnotbuiltoncorc; saninterpretedlanguagethatrunsonenginesoftenwritteninc.1)javascriptwasdesignedAsalightweight,解释edganguageforwebbrowsers.2)Enginesevolvedfromsimpleterterterpretpreterterterpretertestojitcompilerers,典型地提示。

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

Dreamweaver CS6
视觉化网页开发工具