有人上传一张照片,等大约4分钟,就能拿到一段约13秒的赛车恶搞视频,还配着原创背景音乐。这个叫 MemeMotion 的小站只做一件事,没有提示词输入框,用户选好照片付钱,剩下的全部固定在服务器上。
![]()
作者把整个搭建过程写成了日志,包括管线怎么跑、哪些环节比看起来难、以及哪些错误让他不得不重跑。他声明没有跑分数据,只有一个数据点的地方会直接说明。MemeMotion 是非官方的,与 TikTok、原视频创作者以及 Crazy Frog 版权方均无关联,动作参考来自 @wdmowski 的原帖,原片版权归其创作者所有。
先定约束,再写代码
动手之前,作者给自己划了几条线:一个模板、一个按钮,没有提示词框;按视频付费,不做订阅,一个积分换一条视频,套餐为 7.99美元1条、14.99美元2条、27.99美元4条,一次性付款;生成前必须用谷歌账号登录,任务、积分和下载都归属账户;成品视频和源照片在任务完成7天后删除;绝不为做不成的事收费,先检查照片再扣钱,生成失败自动退款。
整条管线跑在一个 Cloudflare Worker 里:D1 存用户、任务和积分账本,R2 存照片、参考素材和输出,一个队列的生产者/消费者处理任务,两个定时触发器负责清扫和清理。
扣钱之前先看脸
模型需要一张清晰的正脸。如果传的是合影或侧脸照,照样会扣费,结果还很差。所以第一件事是在动任何积分或预算之前跑一个便宜的检查:画面里恰好一张、大致正面的脸。
Worker 没法在进程内跑人脸关键点模型,作者也不想为这个再引入一家供应商,于是检查用的是同一家供应商上的一个小视觉模型。代码注释写得很直白:扣费前的照片检查,恰好一张、大致正面的脸,在任何积分或预算被动用之前运行。
两个细节在实践中很关键:太小的脸框通常是海报或背景里的人脸,所以小于画面 1.5% 的框会被忽略;如果检查服务本身报错,请求按失败关闭处理,返回 503 且不扣任何费用。悄悄放行意味着为大概率失败的任务收费。
参考片段本身就是产品。这是一个视频到视频的任务:固定的动作参考加上用户的照片。把参考素材弄对,占了大部分工作量。参考素材私密存放在 R2,通过短时效签名链接交给模型,绝不作为公开素材出现在站内,页面只展示用虚构人物做出的成品,并标注原帖出处。
作者在原型阶段换过不止一个模型,代码里至今留着三家供应商的开关,默认是一个重铸风格的模型,保留参考素材的运镜、节奏和姿态,把照片里的人换进去。它按输出秒数计费,输出长度等于参考长度,这就引出了整个项目里最有用的一个 bug。
那个毁掉身份的近距离镜头
最早的成品在大部分片段里看着都对,但最后1到2秒,脸会从上传的照片漂移回模板素材里的那个人。原因是参考素材的结尾:镜头快速近距离掠过,带着严重的运动模糊。在那个镜头里,模型几乎没有清晰的东西可以锚定新脸,于是退回到了它在参考里能看到的那个人。
修复不需要任何付费重跑。作者把参考素材剪掉,让那个镜头消失,代码里把重铸参考时长定为 12.8秒,并收紧提示词,要求照片里的人“在每一个镜头里,包括任何镜头近距离掠过时”都保持是骑手。
一个副作用:计费跟着输出长度走,参考变短也让每条视频更便宜。这也是站点写“约13秒”而不是15秒的原因。
因为管线假设输出长度等于参考长度,运行器会检查这一点,容差故意设得很小。同一个模块还会在需要时把 MP4 的时间表重写为恒定帧率,并有一个名为“接受小的编码器漂移,拒绝被缩短或拉长的片段”的测试。短了一秒的片段会破坏下一步的音乐同步,所以最好在这里就拦住。
不管返回什么音频都不能发出去。参考素材在准备时已经去掉了音频,但作者不单独信任这一点,所以有一个小的 MP4 盒子遍历器,从结果里丢掉音轨并把孤立的采样字节清零。注释写明:参考片段的音频不得随成品发布,如果文件不是能证明静音的 MP4,调用方应拒绝存储。
音乐是为站点生成的原创背景音,不是任何现有歌曲,私密存在 R2,通过一个托管的 ffmpeg 合并端点合入。省掉所有滤镜工作的技巧是:音轨被剪到 13.0秒,带一个在 12.8秒结束的0.5秒淡出,正好是片段长度,合并永远不会在淡出中途切断音乐。混流是尽力而为,失败的话任务仍以静音版本成功。用户两个版本都拿到:默认带音乐,另有一份静音拷贝,因为有些平台希望你在应用内自己加声音。
钱、队列和退款
这是作者建议任何做付费生成器的人最先设计好的部分。每个用户同时只有一个活跃任务,靠 D1 里活跃任务的唯一索引强制。如果有人在一条视频还在跑时又付了钱,会被扣费并停为等待名额,等前一个任务结束或定时清扫时启动;如果永远无法启动,积分只退一次。
还有站点级的每日支出上限。生成按输出秒数花真金白银,所以有供应商支出的每日上限,外加每用户每日限额。到达上限时,新任务停为等待预算,不扣积分也不预留预算,定时任务在预算释放后按最旧优先提升停驻任务。支出越过阈值时,每天发一次管理员告警。
失败会被分类,而不是盲目重试:供应商的审核拒绝是终局,绝不重新提交,退还积分,给用户一条平实的消息;其他失败最多自动重跑一次,用新的幂等键,避免供应商把它去重到失败请求上,然后退款;退款按任务去重,重试风暴不会退两次。提交也绝不意外做两次:提交时的网络失败不代表任务没被创建,在第二次提交之前,运行器会查找该任务已有的请求,状态检查失败绝不触发提交。
任务完成后,用户收到一封“就绪”邮件或一封“失败”邮件,视频在账户里。分享面板链接到站点而不是视频,视频在用户自己发布文件之前保持私密;在手机上,TikTok 和 Instagram 走原生分享面板,带下载兜底。留存由代码强制而非政策文本:任务完成时设置 7天后过期,清理任务从 R2 删除成品和原始照片,失败或从未完成的任务在创建7天后清理。
作者列了几条会改的做法:调提示词之前逐镜头看参考素材,最大的质量修复就是去掉参考里的一个镜头,而且没花钱;从第一天起就把输出长度做成服务器常量,文案里写了一阵子“15秒”,参考一变就得改遍所有地方;先写退款路径再写正常路径,停驻、一次重跑和按任务去重,当成状态机来推理比打补丁容易得多;保持单一事实来源,价格放在一个 JSON 目录里,Stripe 和界面都读它,文案和结账就不会漂移。
能站得住的数字只有三个:输出约13秒,参考是12.80秒;完成时间约4分钟,这来自第一个线上任务,是单个数据点而非平均值;留存7天。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.