十分钟上线 - FC&OSS构建serverless视频截取雪碧图服务

简介: 在一些视频处理的场景求中,有对视频制作雪碧图需求,简单来说,就是对视频的按照一定的规律就行截帧,然后将截帧的图片组合成一张大的雪碧图。 本文基于对象存储服务 OSS 和函数计算 FC, 实现弹性高可用的海量视频的存储和雪碧图制作处理服务。

前言

CSS Sprite,多个图片被整合到一个精灵图中,用户不需要下载多个文件,而是只需要下载单个文件,当需要特定的图像时,CSS引用这张雪碧图,通过偏移和定义尺寸来达到目的。CSS Sprite 具有如下优点:

  • 更流畅的用户体验,因为一旦雪碧图被下载,所有使用雪碧图上面的图片的地方都会得到渲染,而不是一个文件一个文件的加载。
  • 减少HTTP请求,将原本需要的多个请求合并为一个,较少服务器压力,从而较少网络堵塞。
  • 减少图片的字节。多次比较,三张图片合并成一张图片之后的字节总是小于这三长图片的总和。
  • 更换风格方便,只需要在一张或少张图片上修改图片的颜色或样式,维护起来更加方便。

在一些视频处理的场景求中,有对视频制作雪碧图需求,简单来说,就是对视频的按照一定的规律就行截帧,然后将截帧的图片组合成一张大的雪碧图。 本文基于对象存储服务 OSS 和函数计算 FC, 实现弹性高可用的海量视频的存储和雪碧图制作处理服务。

体验入口地址

  • 10 * 10 的雪碧图

http://fcdemo.mofangdegisn.cn/vedio-process?srcdir=video&video=test_hd.mp4&w=144&h=128&offset=100&interval=20&sprite=10*10&saveas=picture&capture_num=100

  • 4 * 4 竖屏视频雪碧图(不变形处理)

http://fcdemo.mofangdegisn.cn/vedio-process?srcdir=video&video=shupin.mp4&w=144&h=128&offset=100&interval=20&sprite=4*4&saveas=picture&capture_num=16&autofill=1&oringin_ratio=0.5625

该体验地址采用的函数计算的http trigger, 其中:

返回值

截图的zip包和雪碧图的url

参数意义:

  • srcdir: oss bucket 中上传视频的目录
  • video :视频的名字
  • w: 视频截帧的宽度
  • h: 视频截帧的高度
  • offset: 视频截帧从起始处,单位是秒
  • interval: 每多少秒截一次
  • sprite:雪碧图的组成,列数*行数
  • capture_num: 需要截图的总数
  • saveas: 截图zip包和雪碧图在oss 上保存的目录
  • autofill: 和oringin_ratio(原始视频的 宽度/高度 比)一起使用,当值为1时, 竖屏处理

架构设计图

image

利用oss的存储、单帧截图能力与函数计算的自定义处理能力,可以实现各种自定义的视频截图与图片组合操作,再也不用担心各家厂商提供的视频截图功能无法满足自定义功能的情况,同时,OSS 的海量高可靠 和 FC 的弹性高可用相结合,整体服务也就具有了按需付费、弹性扩容、数据高可靠,计算高可用,免运维等一系列优点。

代码

# -*- coding: utf-8 -*-
from wand.image import Image
from wand.color import Color
from threading import Thread
import oss2
from urllib import parse
import math
import zipfile
import io

bucket_name = 'xbt-video'
oss_region = "cn-hangzhou"
images = []

# 获取oss 视频截帧的返回图片
def video_capture(bucket, srcdir, vedio_name, t, process):
  cap_pic = vedio_name + '_' + str(t) + '.png'
  r = bucket.get_object(srcdir + "/" + vedio_name, process=process)
  content = r.read()
  images.append(content)

def handler(environ, start_response):
  global images
  images = []

  context = environ['fc.context']
  creds = context.credentials
  # Required by OSS sdk
  auth=oss2.StsAuth(
    creds.access_key_id,
    creds.access_key_secret,
    creds.security_token)
    
  endpoint = 'oss-{}-internal.aliyuncs.com'.format(oss_region)
  bucket = oss2.Bucket(auth, endpoint, bucket_name)
  
  # 解析出对应的请求参数
  try:
    query_string = environ.get('QUERY_STRING', '')
    params = parse.parse_qs(query_string)
    params = {k: v[0] for k, v in params.items()}
  except Exception as e:
    print(str(e))

  srcdir = params['srcdir']
  video = params['video']
  saveas = params['saveas']

  width, height, offset, interval = int(params['w']), int(params['h']), int(params.get('offset',0)), int(params.get('interval', 1))
  sprite = params.get('sprite', "1*1")
  rows, cols = sprite.split("*")
  rows, cols = int(rows), int(cols)
  
  capture_num = params.get('capture_num')
  if not capture_num:
    capture_num = rows*cols
  
  capture_num = int(capture_num)
  cap_width , cap_height = width , height
  # autofill 可选参数的处理
  autofill = params.get('autofill')
  if autofill and int(autofill) == 1:
    oringin_ratio = float(params['oringin_ratio'])
    cap_width = int(height * oringin_ratio)

  print("cap_info = ",  cap_width , cap_height)
  # 多线程调用oss视频截帧服务
  ts = []
  for i in range(capture_num):
    t = (offset + i* interval) * 1000
    process = "video/snapshot,t_{0},f_png,w_{1},h_{2},m_fast".format(t, cap_width, cap_height)
    t = Thread(target=video_capture, args=(bucket, srcdir, video, t, process,))
    t.start()
    ts.append(t)
    
  for t in ts:
    t.join()
    
  image_len = len(images)
  print("image length = {}".format(image_len))
  ret = []
  zip_buffer = io.BytesIO()
  with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zf:
    for i, content in enumerate(images):
      zf.writestr(video + "_{}.png".format(i),  content)
      
  zip_file = "{}/{}.zip".format(saveas, video)
  bucket.put_object(zip_file, zip_buffer.getvalue()) 
  
  # 生成截图的zip放入返回结果
  zip_url = "https://{}.oss-{}.aliyuncs.com/".format(bucket_name, oss_region) + zip_file + "\n"
  ret.append(bytes(zip_url, encoding = "utf8"))

  # 雪碧图之间的间隙
  cell_gap = 2
  # BATCH_NUM 数量的图片生成一张 rows * cols 雪碧图
  # 可以生成多张,如果capture_num > rows * cols
  BATCH_NUM = rows*cols
  xbt_num = int(math.ceil(image_len/float(BATCH_NUM)))
  for x in range(xbt_num):
    img = Image()
    img.blank((width+cell_gap)*rows, (height+cell_gap)*cols, Color('black'))
    begin = x * BATCH_NUM
    end = begin + BATCH_NUM if (begin + BATCH_NUM) < image_len else image_len
    sub_images = images[begin:end]
    for i, content in enumerate(sub_images):
      with Image(blob=content)  as sub_img:
        r = i % rows
        j = int(i / rows)
        if cap_width == width:
          img.composite(image=sub_img,left=r*(width + cell_gap), top=j*(height + cell_gap))
        else: # autofill为1时的特殊处理
          jz_img = Image()
          jz_img.blank(width, height, Color('blue'))
          jz_img.composite(image=sub_img,left=int((width - cap_width)/2), top=0)
          img.composite(image=jz_img,left=r*(width + cell_gap), top=j*(height + cell_gap))
    
    pic_file = "{}/xbt_{}_{}.png".format(saveas, video, x)
    bucket.put_object(pic_file, img.make_blob(format='png')) 
    pic_url = "https://{}.oss-{}.aliyuncs.com/".format(bucket_name, oss_region) + pic_file + "\n"
    # 生成的雪碧图的url地址放入返回值
    ret.append(bytes(pic_url, encoding = "utf8"))
  
  status = '200 OK'
  response_headers = [('Content-type', 'text/plain')]
  start_response(status, response_headers)
  
  return ret

fun 部署的template.yml

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  xbt-demo-pro:
    Type: 'Aliyun::Serverless::Log'
    Properties:
      Description: 'image process log pro'
    fc-log:
      Type: 'Aliyun::Serverless::Log::Logstore'
      Properties:
        TTL: 362
        ShardCount: 1

  xbt-service:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'image process demo'
      Policies:
        - AliyunOSSFullAccess
      LogConfig:
        Project: 'xbt-demo-pro'
        Logstore: 'fc-log'
    xbt-func:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        CodeUri: './'
        Description: 'xbt-process http function'
        Runtime: python3
        Timeout: 60
        MemorySize: 512
      Events:
        http-trigger:
          Type: HTTP
          Properties:
            AuthType: ANONYMOUS
            Methods: ['GET', 'POST']
相关实践学习
【AI破次元壁合照】少年白马醉春风,函数计算一键部署AI绘画平台
本次实验基于阿里云函数计算产品能力开发AI绘画平台,可让您实现“破次元壁”与角色合照,为角色换背景效果,用AI绘图技术绘出属于自己的少年江湖。
从 0 入门函数计算
在函数计算的架构中,开发者只需要编写业务代码,并监控业务运行情况就可以了。这将开发者从繁重的运维工作中解放出来,将精力投入到更有意义的开发任务上。
目录
相关文章
数据采集 Web App开发 人工智能
246 0
|
4月前
|
数据采集 Web App开发 人工智能
基于函数计算FC构建 Browser Tool Sandbox 详解与实操
本文介绍了如何基于阿里云函数计算(FC)构建一个安全、可控的 Browser Tool Sandbox,支持多种浏览器自动化操作方式,包括 Playwright、Puppeteer、Browser Use 和 REST API 等,为 AI Agent 安全高效地操作浏览器提供了完整的技术方案。
|
5月前
|
编解码 数据处理 API
如何用阿里云OSS对图片和视频进行数据处理?
本文介绍了如何利用阿里云对象存储OSS进行图片和视频处理。OSS提供了丰富的功能,如图片的缩放、裁剪、旋转和水印添加等,用户只需在图片URL后附加处理参数即可实现自动化处理。同时,OSS还支持自定义样式模板,便于批量操作。对于视频处理,OSS支持转码、截图、拼接等功能,满足多终端播放需求。通过OSS的API和SDK,开发者可以方便地集成这些功能,提升数据管理效率。
|
6月前
|
分布式计算 Serverless OLAP
实时数仓Hologres V3.1版本发布,Serverless型实例从零开始构建OLAP系统
Hologres推出Serverless型实例,支持按需计费、无需独享资源,适合新业务探索分析。高性能查询内表及MaxCompute/OSS外表,弹性扩展至512CU,性能媲美主流开源产品。新增Dynamic Table升级、直读架构优化及ChatBI解决方案,助力高效数据分析。
实时数仓Hologres V3.1版本发布,Serverless型实例从零开始构建OLAP系统
|
7月前
|
存储 编解码 Serverless
Serverless架构下的OSS应用:函数计算FC自动处理图片/视频转码(演示水印添加+缩略图生成流水线)
本文介绍基于阿里云函数计算(FC)和对象存储(OSS)构建Serverless媒体处理流水线,解决传统方案资源利用率低、运维复杂、成本高等问题。通过事件驱动机制实现图片水印添加、多规格缩略图生成及视频转码优化,支持毫秒级弹性伸缩与精确计费,提升处理效率并降低成本,适用于高并发媒体处理场景。
396 0
|
7月前
|
编解码 监控 算法
CDN+OSS边缘加速实践:动态压缩+智能路由降低30%视频流量成本(含带宽峰值监控与告警配置)
本方案通过动态压缩、智能路由及CDN与OSS集成优化,实现视频业务带宽成本下降31%,首帧时间缩短50%,错误率降低53%。结合实测数据分析与架构创新,有效解决冷启动延迟、跨区域传输及设备适配性问题,具备快速投入回收能力。
487 0
|
8月前
|
JSON 安全 Serverless
MCP Server 之旅第 2 站: 从 0 到 1 - MCP Server 市场构建与存量 OpenAPI 转 MCP Server
本文聚焦MCP协议在企业应用中的两大核心痛点:如何将社区主流STDIO MCP Server一键转为可插拔Remote MCP Server,以及如何实现存量OpenAPI向MCP Server的智能化转型。文章通过具体示例,展示了基于函数计算和协议转译Adapter的解决方案,支持npm/pip生态,实现零改造一键迁移,大幅降低成本。
|
8月前
|
JSON 安全 Serverless
MCP Server On FC之旅2: 从0到1-MCP Server市场构建与存量OpenAPI转MCP Server
本文介绍了将社区主流STDIO MCP Server一键转为企业内可插拔Remote MCP Server的方法,以及存量API智能化重生的解决方案。通过FunctionAI平台模板实现STDIO MCP Server到SSE MCP Server的快速部署,并可通过“npx”或“uvx”命令调试。同时,文章还探讨了如何将OpenAPI规范数据转化为MCP Server实例,支持API Key、HTTP Basic和OAuth 2.0三种鉴权配置。该方案联合阿里云百练、魔搭社区等平台,提供低成本、高效率的企业级MCP Server服务化路径,助力AI应用生态繁荣。
1202 40
|
8月前
|
人工智能 运维 安全
阿里云 Serverless 助力海牙湾构建弹性、高效、智能的 AI 数字化平台
海牙湾(G-Town)是一家以“供应链+场景+技术+AI”为核心驱动力的科技公司,致力于为各行业提供数字化转型解决方案。通过采用阿里云Serverless架构,解决了弹性能力不足、资源浪费与运维低效的问题。SAE全托管特性降低了技术复杂度,并计划进一步探索Serverless与AI结合,推动智能数字化发展。海牙湾业务覆盖金融、美妆、能源等领域,与多家知名企业建立战略合作,持续优化用户体验和供应链决策能力,保障信息安全并创造可量化的商业价值。未来,公司将深化云原生技术应用,助力更多行业实现高效数字化转型。
634 19
|
9月前
|
自然语言处理 安全 数据挖掘
Hologres+函数计算+Qwen3,对接MCP构建企业级数据分析 Agent
本文介绍了通过阿里云Hologres、函数计算FC和通义千问Qwen3构建企业级数据分析Agent的解决方案。大模型在数据分析中潜力巨大,但面临实时数据接入与跨系统整合等挑战。MCP(模型上下文协议)提供标准化接口,实现AI模型与外部资源解耦。方案利用SSE模式连接,具备高实时性、良好解耦性和轻量级特性。Hologres作为高性能实时数仓,支持多源数据毫秒级接入与分析;函数计算FC以Serverless模式部署,弹性扩缩降低成本;Qwen3则具备强大的推理与多语言能力。用户可通过ModelScope的MCP Playground快速体验,结合TPC-H样例数据完成复杂查询任务。

相关产品

  • 函数计算