操作指南:上传(高级)

前言

可靠的资产上传是每个 C2C 集成的核心功能。 本指南提供了用于创建稳健、具有韧性且高效上传系统的高级技术和最佳做法,即使在充满挑战性的环境中也能表现出色。

前提条件

如果您还没有阅读,请在继续之前先查阅实施 C2C:设置指南。 您需要用到在身份验证和授权流程中获取的 access_token。 我们将继续使用基础上传指南中的同一个测试资产

高级资产参数

在 Frame.io 中创建资产时,您可以使用多个高级参数来自定义上传行为。 其中,offset 参数对于正确集成尤为重要。

Offset - 处理暂停的设备

提供准确的 offset 值至关重要。 此参数用于指定媒体文件的创建时间,并确保您的设备不会上传本不该分享的内容。 当设备在 Frame.io 中暂停时,表示用户希望暂停期间创建的媒体文件不被上传。 有关更多详情,请参阅我们关于暂停功能的指南

offset 参数的其他优势

offset 参数为在 Frame.io 中组织媒体文件提供了另一个显著优势。 当上传在较早日期拍摄的内容时(例如,用户在回放期间选择上周拍摄的一张照片),offset 参数能够确保该媒体文件出现在与其原始拍摄日期对应的文件夹中,而不是当前上传日期对应的文件夹中。 这种按时间顺序组织的机制,能够在 Frame.io 的项目结构中保持逻辑清晰的时间线。 如果没有 offset 参数,历史媒体文件会被错误地与今天的内容归类在一起,这可能会给剪辑师及其他协作者造成困惑。 您可以通过自己的界面为用户提供此方面的选择。 如果用户希望无论媒体文件何时拍摄,都按当前日期对所有上传内容进行归类,您只需省略 offset 参数即可,因为当未指定该参数时,其默认值为 0

我们的 API 设计使得您的设备无需跟踪暂停状态。 在上传文件时,您只需指明该文件是在多少秒之前创建的。 我们的服务器会将该信息与暂停窗口期进行比较,如果文件是在暂停期间创建的,则会拒绝上传。

为了演示此功能,请通过“C2C Connections”选项卡中的三点菜单暂停您的设备。

现在尝试上传一个资产:

{
curl -X POST https://api.frame.io/v2/devices/assets \
--header 'Authorization: Bearer [access_token]' \
--header 'Content-Type: application/json' \
--header 'x-client-version: 2.0.0' \
--data-binary @- <<'__JSON__'
{
"name": "C2C_TEST_CLIP.mp4",
"filetype": "video/mp4",
"filesize": 21136250,
"offset": 0
}
__JSON__
} | python -m json.tool
API 端点规范

/v2/devices/assets 的文档可以在此处找到

您将收到以下错误:

{
"code": 409,
"errors": [
{
"code": 409,
"detail": "The channel you're uploading from is currently paused.",
"status": 409,
"title": "Channel Paused"
}
],
"message": "Channel Paused"
}

如果您取消暂停设备并使用相同的请求重试,则会创建资产。

但是,如果该资产是在暂停窗口期内创建的,您需要设置 offset 参数以反映其实际创建时间:

{
curl -X POST https://api.frame.io/v2/devices/assets \
--header 'Authorization: Bearer [access_token]' \
--header 'Content-Type: application/json' \
--header 'x-client-version: 2.0.0' \
--data-binary @- <<'__JSON__'
{
"name": "C2C_TEST_CLIP.mp4",
"filetype": "video/mp4",
"filesize": 21136250,
"offset": 60
}
__JSON__
} | python -m json.tool

这告诉 Frame.io 该资产是在 60 秒前(暂停期间)创建的,从而正确触发通道已暂停错误。 准确的 offset 值至关重要,可防止违背用户意愿上传敏感内容,包括受保护的知识产权、敏感素材或其他受限制的材料。

偏移和重试

当重试一次失败的资产创建调用时,请记得更新 offset 值。 在较长的重试周期中,一个固定不变的 offset 值可能会偏离相关的暂停窗口期,从而可能导致本应被阻止的上传请求得以通过。

上传到指定通道

如果您的设备有多个通道,您可以指定要使用哪个通道:

{
curl -X POST https://api.frame.io/v2/devices/assets \
--header 'Authorization: Bearer [access_token]' \
--header 'Content-Type: application/json' \
--header 'x-client-version: 2.0.0' \
--data-binary @- <<'__JSON__'
{
"name": "C2C_TEST_CLIP.mp4",
"filetype": "video/mp4",
"filesize": 21136250,
"offset": -10,
"channel": 2
}
__JSON__
} | python -m json.tool

如果未指定,默认通道为 0。 大多数集成无需更改此值。

请求自定义分片数量

默认情况下,Frame.io 的后端会将文件划分为大约 25 MB 大小的分片。 对于高拥堵的网络环境,您可能希望使用更小的分片。 您可以使用 parts 参数请求指定数量的分片:

{
curl -X POST https://api.frame.io/v2/devices/assets \
--header 'Authorization: Bearer [access_token]' \
--header 'Content-Type: application/json' \
--header 'x-client-version: 2.0.0' \
--data-binary @- <<'__JSON__'
{
"name": "C2C_TEST_CLIP.mp4",
"filetype": "video/mp4",
"filesize": 21136250,
"offset": 0,
"parts": 4
}
__JSON__
} | python -m json.tool

该响应将包含四个上传 URL:

{
...
"upload_urls": [
"https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-01-path]",
"https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-02-path]",
"https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-03-path]",
"https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-04-path]"
],
...
}

分片大小为:

Python
math.ceiling(float(21136250) / float(4))
# 5284063 bytes

最后一个分片将为 5,284,061 字节(计算方式:21136250 - 5284063 * 3)。 请求自定义分片数量时,请注意 AWS S3 分段上传限制

  • 每个分段大小必须至少为 5 MiB(5,242,880 字节),最后一个分段除外
  • 总分段数不得超过 10,000 个

如果您的请求违反了这些限制,您将收到 500:内部服务器错误

{
"code": 500,
"errors": [
{
"code": 500,
"detail": "There was a problem with your request",
"status": 500,
"title": "Something went wrong"
}
],
"message": "Something went wrong"
}

请务必确保您的自定义分段数量符合 S3 的要求。

高效上传

C2C 设备通常在充满挑战性的网络环境中运行,因此效率至关重要。 以下是一些最大化吞吐量的策略。

TCP 连接重用/连接池

建立加密连接需要大量的协商开销。 为了高效运行,在发出多次请求时可重复使用 TCP 连接。 大多数 HTTP 库都提供了客户端会话抽象,用于维护持久连接。

一个新的 HTTPS 连接的协商过程包括加密握手和证书验证。 通过重用连接,您只需承担一次此开销,而不是对每个请求都执行一次。

TCP 握手参考

有关 TLS 握手过程的技术详情,请参阅 Cloudflare 的说明

为了演示使用 curl 进行连接重用,首先按照基础上传指南中的描述在 Frame.io 中创建一个新资产。

接下来,将文件拆分为单独的分片进行测试:

head -c 10568125 ~/Downloads/C2C_TEST_CLIP.mp4 > "C2C_TEST_CLIP-Chunk01"
tail -c 10568125 ~/Downloads/C2C_TEST_CLIP.mp4 > "C2C_TEST_CLIP-Chunk02"

现在使用 curl 的 --next 参数,通过单个 TCP 连接上传两个分片:

curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-1-path] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk01 \
--next -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-2-path] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk02

将其与单独连接的方式进行比较:

curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-1-path] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk01 \
&& curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-2-path] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk02
重用分片 URL

您可以多次上传到同一个分片 URL,因此能在不同示例之间随意重用这些 URL。

在测试中,连接重用通常可将顺序上传的性能提高 15-20%。

并行上传

为了获得更高的吞吐量,可以同时上传多个分片:

curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-1-path] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk01 \
& \
curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-2-path]\
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk02 \
&

在带宽充足的情况下,并行上传的完成时间大约等于最慢的单个上传所需的时间。

为了实现最佳并行上传,一个实用的经验法则是:每个 CPU 核心对应 2 个并发上传。 超过这个比例可能会导致资源争用和收益递减。

并行上传速度

网络状况会显著影响并行上传的性能。 在某些环境中,顺序上传可能比并行上传表现更好。 高级实施可以监测吞吐量并动态调整并发上传数量。 请务必在实际生产环境中进行性能分析,而不是依赖示例时间。

结合两种方法

为了达到最高效率,可以将连接池与并行上传相结合。 创建多个进程,每个进程在其自己的上传序列中使用连接池:

curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset01-chunk01] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk01 \
--next -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset01-chunk02] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk02 \
& \
curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset02-chunk01] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk01 \
--next -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset02-chunk02] \
--include \
--header 'content-type: video/mp4' \
--header 'x-amz-acl: private' \
--data-binary @C2C_TEST_CLIP-Chunk02 \
&
HTTP 库功能

大多数 HTTP 库都提供了用于连接池和并行请求的抽象。 请尝试您所使用库的选项,以确定适合您环境的最佳配置。

跟踪上传进度

您的集成必须为用户提供基本的进度指示。 可以接受分片级别的粒度——对于一个包含三个分片的上传任务,随着每个分片完成,进度可能依次递增为:0% → 33% → 66% → 100%。

更细粒度的进度报告取决于您所使用的 HTTP 库的能力。 如果您需要关于实施更详细的进度跟踪的指导,请联系我们的团队。

可靠上传

为了实现稳健的错误处理,请查阅我们的错误指南。 以下各节假设您已实施了该指南中描述的错误处理策略。

创建一个可用于生产环境的上传程序,除了处理单个请求的错误之外,还需要考虑更多因素。

创建上传队列

在现实场景中,您的设备生成媒体文件的速度可能会超过其上传速度,或者可能遇到长时间的网络连接中断。 实施队列系统可以将媒体创建与上传管理分离。

考虑双队列架构:

  1. 一个媒体队列,用于在 Frame.io 中注册本地文件
  2. 一个分片队列,用于上传各个文件块

下面是一个简化的实施示例:

Python
# Where we are going to queue new files.
FILE_QUEUE = Queue()
# Where we are going to queue new chunks.
CHUNK_QUEUE = Queue()
# The http session that will handle TCP
# connection pooling for us.
HTTP_SESSION = http.Session()
def take_picture():
"""Snaps a picture for the user."""
image = MY_DEVICE.capture()
file_path = MY_DEVICE.write_image(image)
FILE_QUEUE.add(file_path)
def task_register_assets():
"""
Pulls snapped pictures from the FILE_QUEUE, registers
with Frame.io, and adds the chunks to the CHUNK_QUEUE.
"""
while True:
# Get the latest file added to the queue and register
# a C2C Asset for it.
new_file = FILE_QUEUE.get()
asset = c2c.crete_asset_for_file(HTTP_SESSION, new_file)
# Calculate the size for each chunk
chunk_size = c2c.calculate_chunk_size(asset, new_file)
# Create a message for each chunk with it's parameters
# and add it to the
# queue.
chunk_start = 0
for chunk_url in asset.upload_urls:
message = {
"file_path": new_file,
"chunk_url": chunk_url,
"chunk_start": chunk_start,
"chunk_size": chunk_size,
}
# Put the message in the queue and
CHUNK_QUEUE.put(message)
chunk_start += chunk_size
def task_upload_chunk():
"""Takes a chunks and uploads them."""
while True:
info = CHUNK_QUEUE.get()
c2c.upload_chunk(HTTP_SESSION, info)
def launch_upload_tasks():
"""Lauches our Frame.io upload tasks."""
# Create a list to hold all of our tasks.
tasks = list()
# Create one task for registering assets.
asset_task = run_task_in_thread(task_register_assets)
tasks.append(asset_task)
# Create 2 tasks per CPU core for uploading chunks.
for _ in range(0, GET_CPU_COUNT() * 2):
chunk_task = run_task_in_thread(task_upload_chunk)
tasks.append(chunk_task)
# Run these tasks until shutdown
run_forever(tasks)
错误处理

在上述示例中,我们假设为 C2C 调用而调用的函数正按照错误指南中讨论的方式处理错误。

跨电源周期的持久队列

内存中的队列方法在设备保持通电状态下运行良好,但如果在上传完成前断电会怎样? 为了创建一个真正具有韧性的集成,我们需要确保设备在重启后能够从中断处恢复。

这需要在电源周期之间将队列状态持久保存到存储中。 像 SQLite 这样的嵌入式数据库为此功能提供了绝佳的基础。

您的持久队列实施应支持以下关键操作:

  • 将新创建的文件添加到上传队列
  • 跟踪资产在 Frame.io 中成功创建的时间
  • 记录资产因错误而创建失败的情况
  • 存储上传任务的文件分片信息
  • 检索下一个要上传的分片
  • 将分片标记为已成功上传
  • 记录分片上传失败
  • 提供文件状态信息以向用户展示

以下是我们如何调整之前的示例以使用持久存储系统:

Python
# Our persistence layer for queuing uploads, potentially using SQLite
# or another embedded database
C2C_UPLOAD_STORE = NewC2CUploadStore()
# HTTP session for connection pooling
HTTP_SESSION = http.Session()
def take_picture():
"""Captures an image and adds it to the upload queue."""
image = MY_DEVICE.capture()
file_path = MY_DEVICE.write_image(image)
# Register the file with our persistent store
C2C_UPLOAD_STORE.add_file(file_path)
def task_register_assets():
"""
Processes files from persistent storage and registers
them with Frame.io for upload.
"""
while True:
# Get the next available file from our store
file_record = C2C_UPLOAD_STORE.get_file()
try:
# Register the asset with Frame.io
asset = c2c.create_asset_for_file(HTTP_SESSION, file_record)
chunk_size = c2c.calculate_chunk_size(asset, file_record)
# Create entries for each chunk in our persistent store
chunk_start = 0
for chunk_url in asset.upload_urls:
message = {
"file_path": file_record,
"chunk_url": chunk_url,
"chunk_start": chunk_start,
"chunk_size": chunk_size,
}
C2C_UPLOAD_STORE.new_chunk(message)
chunk_start += chunk_size
except BaseException as error:
# Record the error in our persistent store
C2C_UPLOAD_STORE.file_asset_create_error(file_record, error)
else:
# Mark the asset as successfully created
C2C_UPLOAD_STORE.file_asset_created(file_record)
def task_upload_chunk():
"""Uploads individual file chunks from the persistent queue."""
while True:
# Get the next chunk, marking it as "in progress" to prevent
# other tasks from processing it simultaneously
chunk_record = C2C_UPLOAD_STORE.get_chunk()
try:
c2c.upload_chunk(HTTP_SESSION, chunk_record)
except BaseException as error:
# Record the error for potential retry
C2C_UPLOAD_STORE.chunk_error(chunk_record, error)
else:
# Mark successful completion
C2C_UPLOAD_STORE.chunk_success(chunk_record)
def launch_upload_tasks():
"""Launches Frame.io upload processing tasks."""
tasks = []
# Asset registration task
asset_task = run_task_in_thread(task_register_assets)
tasks.append(asset_task)
# Multiple parallel chunk upload tasks
worker_count = GET_CPU_COUNT() * 2
for _ in range(worker_count):
chunk_task = run_task_in_thread(task_upload_chunk)
tasks.append(chunk_task)
# Run indefinitely
run_forever(tasks)

采用这种持久存储方法后,您的集成将能够抵御电源中断的影响。 当设备重新启动时,只需从其上次保存的状态继续处理。 此架构也为实施更高级的功能(如错误跟踪和检测上传停滞)奠定了基础。

跟踪上传错误

一个稳健的上传系统必须仔细跟踪错误。 使用错误指南中的策略重试某个操作后,请将这些失败记录到您的持久化存储中。 这样,您的系统就可以:

  1. 降低有问题上传的优先级,防止它们阻塞整个队列
  2. 向用户提供准确的状态信息
  3. 针对持续存在的问题启用管理干预

当发生致命错误时,请标记该项,以避免不必要的重试尝试。

管理停滞的上传

实施防护措施,防止上传无限期停滞。 设置最长持续时间(例如 30 分钟),超过该时间后应终止并重新启动分片上传任务。 这样可以防止所有上传工作进程都被无响应操作阻塞的情况发生。

从静默故障中恢复

系统崩溃、断电或进程终止可能会阻止正常的错误报告。 从队列中检索项目时,请记录检出时间。 如果某个项目在超过合理阈值(例如 30 分钟)后仍处于“正在处理”状态,且未报告成功或失败,请自动将其返回到可用池中,以便由另一个工作进程处理。

缓解有毒上传

由于数据或环境本身的问题,“有毒”队列项目会持续失败。 如果这些项目不断重新排队,它们实际上可能会阻塞整个上传系统。 请考虑使用以下策略来处理此类情况:

  • 经过多次失败后,降低项目的优先级,以便可以继续处理较新的内容
  • 同时追踪显式错误和处理尝试次数
  • 遵循连接和授权的最佳做法,以区分临时环境问题与文件本身的内在问题
  • 实施递进式重试限制(例如,每个作业尝试内对单个操作重试 10 次,共尝试 3 个作业,总计 30 次尝试)
  • 提供一个用户界面,以便在环境问题解决后手动重置有问题的上传

“有毒”上传可能由以下原因导致:

  • 损坏的文件数据导致 I/O 错误
  • 灾难性进程故障阻止错误报告
  • 原本可重试的错误因永久性的潜在条件而被触发

系统重启后重试

在永久放弃有问题的上传之前,标记它们,以便在下次系统重启后进行最后一次重试。 这可以应对因内存、驱动程序或资源分配等临时系统状态问题而导致上传失败的情况。 如果某个上传在彻底重启后仍然失败,则可以更有把握地将其标记为永久性问题。

清空您的队列

请记得从队列中移除不可用的文件。 当媒体被物理移除或文件被删除时,请从上传队列中清除相应条目,以防止不必要的错误。

重要的是,连接到新项目时必须清空上传队列。 为一个项目排队的媒体绝不应出现在另一个项目中。 当用户将设备与不同项目进行配对时,请验证项目是否已更改;如果是,请完全清空现有队列。

后续步骤

我们鼓励您就任何疑问联系我们的团队,并继续参阅高级上传指南。 我们期待为您的集成进度提供支持。