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

本文涉及的产品
函数计算FC,每月15万CU 3个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 在一些视频处理的场景求中,有对视频制作雪碧图需求,简单来说,就是对视频的按照一定的规律就行截帧,然后将截帧的图片组合成一张大的雪碧图。 本文基于对象存储服务 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']
相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
3月前
|
消息中间件 运维 Serverless
函数计算产品使用问题之如何部署Stable Diffusion Serverless API
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
25天前
|
机器学习/深度学习 监控 Serverless
无服务器架构(Serverless)
无服务器架构(Serverless)
|
1月前
|
存储 Java 开发工具
【三方服务集成】最新版 | 阿里云OSS对象存储服务使用教程(包含OSS工具类优化、自定义阿里云OSS服务starter)
阿里云OSS(Object Storage Service)是一种安全、可靠且成本低廉的云存储服务,支持海量数据存储。用户可通过网络轻松存储和访问各类文件,如文本、图片、音频和视频等。使用OSS后,项目中的文件上传业务无需在服务器本地磁盘存储文件,而是直接上传至OSS,由其管理和保障数据安全。此外,介绍了OSS服务的开通流程、Bucket创建、AccessKey配置及环境变量设置,并提供了Java SDK示例代码,帮助用户快速上手。最后,展示了如何通过自定义starter简化工具类集成,实现便捷的文件上传功能。
【三方服务集成】最新版 | 阿里云OSS对象存储服务使用教程(包含OSS工具类优化、自定义阿里云OSS服务starter)
|
2月前
|
Java API 对象存储
微服务魔法启动!Spring Cloud与Netflix OSS联手,零基础也能创造服务奇迹!
这段内容介绍了如何使用Spring Cloud和Netflix OSS构建微服务架构。首先,基于Spring Boot创建项目并添加Spring Cloud依赖项。接着配置Eureka服务器实现服务发现,然后创建REST控制器作为API入口。为提高服务稳定性,利用Hystrix实现断路器模式。最后,在启动类中启用Eureka客户端功能。此外,还可集成其他Netflix OSS组件以增强系统功能。通过这些步骤,开发者可以更高效地构建稳定且可扩展的微服务系统。
56 1
|
3月前
|
存储 运维 Serverless
Serverless 支撑赛事转播问题之利用函数计算实现图片处理的实时性和成本节约如何解决
Serverless 支撑赛事转播问题之利用函数计算实现图片处理的实时性和成本节约如何解决
|
3月前
|
存储 机器学习/深度学习 弹性计算
阿里云EMR数据湖文件系统问题之OSS-HDFS全托管服务的问题如何解决
阿里云EMR数据湖文件系统问题之OSS-HDFS全托管服务的问题如何解决
|
3月前
|
消息中间件 运维 Serverless
Serverless 支撑赛事转播问题之利用函数计算处理视频直播截帧服务如何解决
Serverless 支撑赛事转播问题之利用函数计算处理视频直播截帧服务如何解决
|
3月前
|
JavaScript Serverless
Serverless 架构问题之Midway FaaS开源框架的设计如何解决
Serverless 架构问题之Midway FaaS开源框架的设计如何解决
37 0
|
3月前
|
Serverless 数据安全/隐私保护 开发者
Serverless 架构问题之阿里云函数计算在事件生态层面如何解决
Serverless 架构问题之阿里云函数计算在事件生态层面如何解决
43 0
|
3月前
|
监控 Serverless API
Serverless 函数计算问题之环境变量不生效如何解决
在函数实例详情页面登录实例的方法是在“监控指标-实例指标”区域点击特定实例ID进入详情页,之后在右上方找到并点击“登录实例”按钮即可开始操作。使用Golang SDK调用`InstanceExec` API执行命令并通过回调处理输出的具体方式为:首先构建`InstanceExecInput`对象设置服务名、函数名、实例ID及命令
45 0

热门文章

最新文章

相关产品

  • 函数计算