上一章:OTT端技术赋能之前端收单能力建设 | 《优酷OTT互联网大屏前端技术实践》第四章>>>
下一章:不一样的烟火:记OTT端半屏互动能力建设 | 《优酷OTT互联网大屏前端技术实践》第六章>>>
作者| 阿里巴巴文娱技术 罄天
一、背景
图片作为网页中的重要组成元素,广泛存在于各种站点中,有些站点中的图片内容已经远远超过了其他网页内容总和。如何高效的、快速的制作业务图片就被广泛的提出来。阿里有很多自动化图片生产平台,如海棠,鲁班等。
谈到自动化制图,主要有两种模式:
一是自动化模式:依赖于服务化能力包装,将核心制图能力进行抽取,任何三方通过直接调用服务能力即可完成图片的合成,此种模式完全自动化,无需任何人工干预即可制作出符合指定条件的业务图片;
二是半自动化模式:主要依赖于业务共性的提取与升华,将繁琐的重复的业务流程通过统一的范式来解决,或多或少的需要人工干预。人工干预一方面需要人力投入,另一方面意味着可以发挥人的主观创造性。成品图除了满足指定的共性外,也可以保证输出个性,这种个性与共性并存的方式不失为一种好的折中。
二、OTT自动化制图
1、OTT自动化制图的起源
以前,优酷OTT侧有自动化制图的雏形,主要依据前端提供制图的工具平台,并将OTT主要合作厂商坑位图配置信息进行固化,然后运营在工具平台上做相应的坑位图合成。此模式在一定程度上满足了运营的合图需求。
在2019年,OTT侧开始尝试自动化制图。深入自动化制图的目的也很明显,主要基于以下痛点:
1)内容源与成品沉淀
我们观察到,阿里集团开始大面积的做自动化制图的尝试,有影响力的包括鲁班系统。从OTT的业务场景出发,这些平台存在一些不足:比如,所生产物料的最终落地形式是一次性的,忽略了物料的源和产物最核心的内容价值。从渠道维度来讲,OTT侧坑位图具有高度的同质性,不仅包括内容源如节目,而且从产物的角度也如此。例如《爱我就别想太多》,某电视厂商对资源位有明确的要求,而且优酷等平台方也需要这一内容源,内容源的价值就应该被放大。另外,优酷需要将同一个成品图,投放到不同渠道上,如果能从产物角度做沉淀,成品图才能发挥最大价值。
2)场景单一
制图工具解决的问题非常有限,主要是节目图的制作。前文提到,从内容源到成品图都缺少相应沉淀,这使得自动化制图服务的场景单一,和当前平台能力相比有明显差距。
现阶段OTT自动化制图涵盖了节目图、轮播、专题、动图等常见的制图场景,并结合自动化与半自动化双轨模式。半自动化场景下可以充分发挥运营创造性,基于原材料做创新性尝试;在自动化场景下,直接对接专题轮播系统,使得依赖于特定模板的制图无需人工参与。更近一步,在大数据推荐上也有相应场景,比如依赖于客户端唯一标识实现个性化的专题类推荐,这使得自动化制图一改之前"自动化"包装的外表。
3)分发效率
平台建立之初,从素材源到成品的完整链路都在本地环境完成,依赖于工具平台下载的成品图对接给运营、第三方进行投放。严重依赖于运营的人力投入,分发效率低,其原因是从内容源到成品库,到最终的分发链路缺乏一致性,所以使得整个系统没有“活”起来。
所以从平台建立开始,我们就在探索一条"活"的完整链路。通过链路的升级,不仅能完成内容源、成品库的沉淀,也包括最终分发链路的升级,从而摆脱传统工具平台面临的点状而非面状链路。
4)TOB厂商
"巧妇难为无米之炊",在自动化制图场景下,所谓的“米”就是模板,模板的格式包括但不限于PSD、SKETCH等,也包括如SVG,比如海棠。“米”是最核心的内容,如果不能从特定的场景中提取内容共性,那么自动化制图领域下的效率提升就非常有限,因此共性才是最应该被重视的特征。
并且,这种共性不仅存在于单个内容提供商,也存在于提供商之间。
2、OTT自动化制图的模式
自动化制图的优势非常明显:
第一是链路的升级:通过自动化制图的流程,可将原来"线下讨论-UED设计-效果确认-开发使用图片"的一次性长链路,转化为"UED设计模板-运营制图-自动/半自动分发"的非一次性完整流程,缩短沟通成本。
第二,内容共享红利:基于系统设计的高质量毛料库,产品库可进一步沉淀,打通多方,减少任何三方重复设计的可能性,将内容价值从原来的一方扩展到整个合作方生态,形成完整的内容回流链路;
第三,分发链路的建设:基于系统设计的成品图无需任何人工对接,可直接输出到渠道三方或者对接任何系统。现有可行的尝试,如专题系统自动化,大数据的个性化推荐场景等,完成从内容生产到内容自动分发的一体化能力建设。
制图效率环节:在半自动化制图场景,站外投放时间可从原来周级缩短为小时级,计件的时间成本减低50%~60%。而在自动化制图场景,无需任何人工干预的方式,使得自动化制图能真正发挥价值。
二、OTT自建自动化制图平台
可能会有人问,阿里已经有鲁班系统等自动化制图平台,为什么优酷OTT要自建?
1、为什么考虑自建?
与鲁班系统服务的(侧重商品化属性)场景不同, OTT场景更侧重媒资属性。图片主体维度的偏差,使得很多原本在商品制图场景下的规则被打破。比如一个简单的元素缩放操作,常规是按照等比加移动的方式解决。但是在以人物为中心的场景,这可能并不合理。因为在以人为主体的场景中,所有模板的指定需要考虑“人物”,而非人物所在元素图片的缩放,简单的说就是人脸,否则可能面临着人物缩放不满足模板要求的场景。因此,我们需要更进一步引入算法或人工打标的新思路。而且,除了元素缩放的特有场景,其实这种区别还很多。比如特定场景的模糊效果、动图绘制、自动化能力输出等等,这种基于特定业务特性的需求海棠确实难以做到定制。
2、商品图使用海棠
除了生产媒资海报图以外,OTT业务也需要生产商品图。比如天猫魔盒:
在没有自建系统之前,所有天猫魔盒的图片生产全部依赖于海棠。设计师从海棠后台传入模板,然后基于此模板在海棠上做相应的坑位图。但自建系统后也慢慢尝试切回到自有系统,主要原因有两个:
第一,内容回流: OTT业务本身是一闭环系统,媒资图和商品图缺一不可。这使得内容隔离渐渐不被接受。内容回流到自有系统后可以做统一管理、分发,享受现有系统提供的能力;
第二,定制化能力:设计师的需求往往依据业务的迭代渐渐变化,渐进增强成为一个常态。因此,当自己的述求不被满足或者不会被满足后可能会渐渐的丧失信心,进而转向新的平台。而自建系统正好提供了这样一个契机。下图提供了一个基于多模板拼接的述求。任何模板可以随意拖进工作区,然后对模板元素加工并设置模板特性,比如模板间距等。
三、OTT自动化制图的流程
关于自动化制图我们是如何实现的?整体流程可以简化为下图:
1、模板解析与数据格式化
自动化制图的前提是内容的共性,共性点更近一步的提取,就是模板,模板格式包括但不限于PSD、SKETCH、SVG等。任意一张成品图都可以看做是基于某一种模板生产出来的,这张成品图由不同图层构成,这和Photoshop中的图层是同一个道理。比如以下示例图就包含了角标、文案卖点、主题、主角、蒙版,背景图等诸多图层。通过对不同图层的*组合、编辑,得到最终的成品图。
上面讲到模板是由不同的图层所描述的,更形象的,模板就是解析后得到的格式化数据,描述了模板中的元素信息,如位置、尺寸、数量等等。而成品图可以看做是基于这些约定(也可以看做是图纸)生产出来的最终元素。
上图就是对PSD的解析后获取到的模板缩略图。当然,正如前文所讲,解析后图片的存在形式也不再是图片本身,而是格式化的数据,一般是对应的JSON。该JSON包含了模板指定的所有元素信息:
const templateInfo = {
"modelBase": {
// 包括模板本身的信息,如尺寸等
},
"psdLayerInfoList": null,
"person": [
// 模板包含的主体个数,主体可以是人或者其他元素
// 也包括主体的人脸信息等
],
"bgPic": {
//背景图
},
"themeGradientMask": [{
//主题渐变
}],
"showNameH": [{
}],
"showNameV": [{
}],
"mMask": [{}],
"mAppLogo": [{
}],
"mFontAppName": [{}]
}
2、前端依据模板绘制流程
服务端做了统一的数据格式化后,给到前端的是格式化的数据,数据指定了模板包含的所有元素信息,前端基于该元素信息进行绘制。
1)分层绘制
HTML5页面中图层的概念大家应该已经很熟悉了。讲到HTML5的网页分层就需要深入理解Chrome渲染原理中的RenderObject,、RenderLayer、Graphiclayer等几棵树。
除了网页会分层以外,Canvas中绘制的元素也可以分层,分层绘制有很多优势。比如在游戏场景中,很多背景类的图层需要重绘的可能性远比动态元素,如障碍物低得多。因此,在每一帧的绘制行为中,可以绕过相应背景图的绘制,直接绘制当前场景变化的图层即可,这与Chrome网页分层要解决的问题是一样的。
class SmallMultiLayerCanvas {
constructor(id) {
this.id = id;
this.canvas = document.getElementById(id);
this.ctx2d = this.canvas.getContext('2d');
this.layers = [];
}
static extend = function (defaults, options) {
var extended = {}, prop;
for (prop in defaults) {
if (Object.prototype.hasOwnProperty.call(defaults, prop))
extended[prop] = defaults[prop];
}
for (prop in options) {
if (Object.prototype.hasOwnProperty.call(options, prop))
extended[prop] = options[prop];
}
return extended;
};
// 添加图层
addLayer(obj) {
const layer = SmallCanvas.extend({
id: Math.random().toString(36).substr(2, 5),
show: true,
render: function (canvas, ctx) { }
}, obj);
if (this.getLayer(layer.id) !== false) {
return false;
}
this.layers.push(layer);
return this;
};
//渲染所有图层
render() {
var canvas = this.canvas;
var ctx = this.ctx2d;
this.layers.forEach(function (item, index, array) {
if (item.show)
item.render(canvas, ctx);
});
};
//获取一个图层
getLayer(id) {
var length = this.layers.length;
for (var i = 0; i < length; i++) {
if (this.layers[i].id === id)
return this.layers[i];
}
return false;
};
// 移除一个图层
removeLayer(id) {
var length = this.layers.length;
for (var i = 0; i < length; i++) {
if (this.layers[i].id === id) {
removed = this.layers[i];
this.layers.splice(i, 1);
return removed;
}
}
return false;
};
}
以上是一个简单的分层绘制的类,通过该类可以随意新增、移除、获取、渲染任意的图层。这也是很多复杂的多图层绘制框架的最核心思想,比如fabric.js或者konvajs。有了这样的图层管理框架,就可以根据服务端下发的格式化数据来绘制模板中指定的任意元素,以模板为图纸,生产出符合设计规定的核心产品。例如:
<canvas id="theCanvas" width="512" height="512"></canvas>
在该Canvas上,我们绘制出几个指定的图形看看效果如何:
const myCanvas = new SmallMultiLayerCanvas("theCanvas");
myCanvas.addLayer({
id: 'background',
render: function (canvas, ctx) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
})
.addLayer({
id: 'squares',
render: function (canvas, ctx) {
ctx.fillStyle = "#E5E059";
ctx.fillRect(50, 50, 150, 150);
ctx.fillStyle = "#BDD358";
ctx.fillRect(350, 75, 150, 150);
ctx.fillStyle = "#E5625E";
ctx.fillRect(50, 250, 100, 250);
}
})
.addLayer({
id: 'circles',
render: function (canvas, ctx) {
ctx.fillStyle = "#558B6E";
ctx.beginPath();
ctx.arc(75, 75, 80, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#88A09E";
ctx.arc(275, 275, 150, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#704C5E";
ctx.arc(450, 450, 50, 0, 2 * Math.PI);
ctx.fill();
}
})
.addLayer({
id: 'triangles',
render: function (canvas, ctx) {
ctx.fillStyle = "#DAF7A6";
ctx.beginPath();
ctx.moveTo(120, 400);
ctx.lineTo(250, 300);
ctx.lineTo(300, 500);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#FFC300";
ctx.beginPath();
ctx.moveTo(400, 100);
ctx.lineTo(350, 300);
ctx.lineTo(230, 200);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#C70039";
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(100, 300);
ctx.lineTo(300, 300);
ctx.closePath();
ctx.fill();
}
});
myCanvas.render();
上面的代码通过链式调用在Canvas中添加了4个对象,id分别为background、squares、circles、triangles。而且每一个对象都有相应的render方法,该方法指定了元素本身在Canvas的上下文是如何绘制的。基于以上代码可以看到在Canvas中绘制的效果:
2)单量与批量模式实时渲染
本小节将讲述首轮绘制后如何基于用户输入做相应的更新。其实所有的半自动化绘图场景,运营不可能只根据某一个模板进行绘制,换句话说,多模板同时绘制的场景必须加以考量。
比如,大多数场景下,运营需要同时基于模板来绘制海信、康佳、歌华、LG的所有坑位图然后导出或者投放,进而摆脱每次只能单独绘制导出单模板的低效模式。因此基于多模板实时绘制渲染的方式就亟待解决。基于此,在半自动化绘制场景中,天生支持多模板实时渲染绘制的模式,正如下面的动图所示:
上图展示的是多模板实时修改的场景,也就是所谓的联动模式。但在未开启联动模式的场景下,所有的修改只针对单模板生效。因此在满足成品图共性的同时又保证了模板的个性。
3、数据统一存储在服务端
前面讲过,工具化平台的设计思路没法做到链路的完整串联,进而完成内容的回流,这在平台化的思路下是行不通的。平台化解决问题的思路是:从内容生产到内容消费的完整链路串联。
基于此,从输入到输出,到最终的分发都需要流动起来,这一切的前提都基于数据的存储,从输入源到输出结果。基于存储的结果,完成了从自动化生产,个性化等*推荐。
四、OTT蜂鸟制图场景的主要输出与输入
下图展示了制图平台在业务上所做的尝试,也总结了在支撑业务的同时,如何在技术上做范式的探索。
从1.0到2.0,是整个系统架构的升级。系统现有支持能力在OTT场景下已经逐渐完善起来。
1、技术侧主要输出
1)服务化的能力
服务化的能力是上面所述的解决问题范式的具体形式,而制图平台服务化的能力已经涵盖了包括:通用模板解析能力,图片合成服务化能力,素材服务化能力,统一分发能力等等。
a) 通用模板解析能力,不仅适用于制图场景的数据格式化,在智能化领域也有涉及;
b) 图片合成服务化能力,不仅可用于制图场景,对于动画合成场景也有渗透;
c) 至于素材的服务化,分发能力的联合在业务赋能的同时,也能谋求业务发展方向的新思路。
2)半自动化合图尝试
服务化能力将制图的触角做了极大的延展,但在半自动化的制图场景,依然需要探究新的制图范式。基于此,我们产出了自有的Canvas合图尝试,这与鲁班、海棠的模式有极大的差异。这条路没有太多的参考,很多问题需要自己去挖坑和填坑。
2、无人工自动化制图
自动化制图具有极大的吸引力,无需运营任何手动制图干预,直接在系统中选择相应的内容集合即可。因此,这种业务赋能尝试也被极大的重视起来。在OTT业务范围内比较成功的案例就是专题系统:
通过指定内容集合(scgid)以及相应的封面图生成规则就可生成相应的专题推荐内容,极大的节省了人力成本。