方法:アップロード(リアルタイム)

はじめに

アップロードに関する基本的な知識に基づいて、アセットの作成中にリアルタイムでアップロードする方法を確認しましょう。この手法では、最終サイズが判明する前に、録画、レンダリング、またはストリーミング中にファイルをアップロードできます。

リアルタイムアップロード API を使用すると、録画完了からわずか数秒でアセットが Frame.io で再生可能となり、ワークフローの効率が大幅に向上します。

デモビデオ

この機能の手短なプレビューについては、動画デモをご覧ください。デモでは、Adobe Media Encoder からリアルタイムでレンダリングがアップロードされ、レンダリング完了後わずか 5 秒でビデオが Frame.io で再生可能となる様子を示しています。

前提条件

まだの場合は、C2C の実装:設定ガイドをご確認ください。

認証および承認プロセス中に、access_token の取得が必要になります。

例として、基本アップロードガイドと同じテスト用アセットを使用します。

これらの概念を基に説明していくため、基本アップロードガイドを精読しておくことをお勧めします。

リアルタイムアセットの作成

リアルタイムアップロードは、変更されたアセット作成プロセスから始まります。アセットの作成時には最終的なサイズが不明なため、is_realtime_uploadtrue に設定し、filesize パラメーターを省略(または null に設定)します。

${
>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",
> "is_realtime_upload": true
> }
>__JSON__
>} | python -m json.tool
API endpoint specification

Documentation for /v2/devices/assets can be found here.

Extension and filename

Real-time assets require a file extension. If the filename isn’t known when creating the asset, you can use the extension field instead (format: '.mp4'). This approach is preferred when you plan to update the asset name later.

リアルタイムアセットに対する応答は、標準的なアセット作成と比較すると簡略化されています。

1{
2 "id": "{asset_id}",
3 "name": "C2C_TEST_CLIP.mp4"
4}

upload_urls が存在しないことに注意してください。リアルタイムアップロードの場合、ファイルの作成時にオンデマンドでアップロード URL が生成されます。

アップロード URL の要求

以前の応答の asset_id を使用して、ファイルの前半(10,568,125 バイト)の URL を要求してみましょう。

${
>curl -X POST https://api.frame.io/v2/devices/assets/{asset_id}/realtime_upload/parts \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "parts": [
> {
> "number": 1,
> "size": 10568125,
> "is_final": false
> }
> ]
> }
>__JSON__
>} | python -m json.tool
API endpoint specification

Documentation for /v2/devices/assets/{asset_id}/realtime_upload/parts can be found here.

要求パラメーターについて:

  • parts:URL が必要なアップロードパートのリスト。1 回の呼び出しで複数の URL を要求すると、効率性が向上します。
    • number:1 から始まる連番のパート番号。数字はスキップでき、パーツは任意の順序でアップロードされますが、連続的に統合されます。10,000 を超えることはできません(AWS の制限)。
    • size:バイト単位のパートサイズ。AWS S3 マルチパートアップロードの制限事項に準拠している必要があります。
    • is_final:これが最後のファイルパートであるかどうかを示します。

応答には、要求されたアップロード URL が含まれます。

1{
2 "upload_urls": [
3 "https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part_01_path]"
4 ]
5}

upload_urls リストは parts 要求の順序に直接対応しています。

次に、基本アップロードガイドに従って、最初のチャンクをアップロードします。

$head -c 10568125 ~/Downloads/C2C_TEST_CLIP.mp4 | \
>curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part_01_path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @-

次に、2 番目と最後のパートの URL を要求します。

${
>curl -X POST https://api.frame.io/v2/devices/assets/{asset_id}/realtime_upload/parts \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "asset_filesize": 21136250,
> "parts": [
> {
> "number": 2,
> "size": 10568125,
> "is_final": true
> }
> ]
> }
>__JSON__
>} | python -m json.tool

次の重要な追加事項に注意してください。

  • 最後のパートの is_finaltrue に設定されており、このチャンクの後にアップロードが完了することを示しています
  • asset_filesize は、パートに is_final: true がある場合の必要な合計ファイルサイズを示します

応答で URL を受信した後、次の手順に従います。

1{
2 "upload_urls": [
3 "https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part_02_path]"
4 ]
5}

最後のチャンクをアップロードします。

$tail -c 10568125 ~/Downloads/C2C_TEST_CLIP.mp4 | \
>curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part_02_path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @-
Final part handling

When the final part is uploaded, Frame.io begins assembling the complete file. This process includes a 60-second grace period for any remaining parts to complete. We recommend uploading the final part only after all other parts have been successfully uploaded.

以上です。Frame.io に移動して、正常にアップロードされたリアルタイムアセットを確認します。🎉

アセット名の管理

アセットの作成時にファイル名が不明な場合は name を指定せずに、extension フィールドを使用できます。

${
>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__'
> {
> "extension": ".mp4",
> "filetype": "video/mp4",
> "is_realtime_upload": true
> }
>__JSON__
>} | python -m json.tool

システムでデフォルトの名前が割り当てられます。

1{
2 "id": "{asset_id}",
3 "name": "[new file].mp4"
4}

アップロード URL を要求する際に asset_name フィールドを含めることで、この名前を更新できます。

${
>curl -X POST https://api.frame.io/v2/devices/assets/{asset_id}/realtime_upload/parts \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "asset_name": "C2C_TEST_CLIP.mp4",
> "asset_filesize": 21136250,
> "parts": [
> {
> "number": 2,
> "size": 10568125,
> "is_final": true
> }
> ]
> }
>__JSON__
>} | python -m json.tool

名前は、アセットがデフォルト名のままになっている場合にのみ更新されます。Frame.io UI で名前が変更されている場合、または以前に更新された場合、要求は無視されます。

URL 要求の最適化

効率性を向上するために、個別にではなく、現在データが利用可能なパートの数だけ URL を要求します。この方法は、アップロード速度がデータ生成よりも遅くなる可能性がある大容量ファイルに特に便利です。

メディアファイルヘッダーの処理

一部のメディア形式では、ファイル全体が完了するまで書き込まれないヘッダーがファイルの先頭に必要となります。この場合、ヘッダーが AWS の最小パートサイズである 5 MiB(5,242,880 バイト)よりも小さい場合に問題が発生します。

当社の推奨事項:

  1. メディアデータの最初の 5,242,880 バイトをアップロードしないでおきます
  2. part_number=2 で始まるパートのアップロードを開始します
  3. ファイルが完了したら、予約済みデータにヘッダーを追加します
  4. part_number=1 の URL を要求し、この結合されたチャンクをアップロードします

この方法により、最初のチャンクの最小サイズ要件を満たしながら、適切なファイル構造を維持できます。

最適なパフォーマンスのためのパートサイズの調整

AWS では、アップロード戦略に影響する次の制限が設けられています。

  • 最大ファイルサイズ:5 TiB(5,497,558,138,880 バイト)
  • パートの最大数:10,000
  • 最小パートサイズ:5 MiB(5,242,880 バイト)

サイズを固定すると、トレードオフが生じます。

  • 10,000 個のパートすべてに最小サイズ(5 MiB)を使用すると、合計ファイルサイズが 52.4 GB 以下に制限されます
  • 最大ファイルサイズを均等に分散するには、550 MB 以下のチャンクが必要となり、小さいファイルを効率的にストリーミングするには大きすぎます

これらの制約のバランスを取るための数式が必要です。レスポンシブアップロードの小さなパートから始め、必要に応じて非常に大きなファイルを処理できるようにします。

推奨されるパートサイズの式

Python で推奨される方法は次のとおりです。

Python
1import math
2from typing import Callable
3
4# Constants
5MINIMUM_PART_SIZE = 5_242_880
6MAXIMUM_PART_COUNT = 10_000
7MAXIMUM_FILE_SIZE = 5_497_558_138_880
8
9# Maximum uniform data rate that allows for 10,000 parts
10MAXIMUM_DATA_RATE = MAXIMUM_FILE_SIZE // MAXIMUM_PART_COUNT
11
12def part_size(part_number: int, format_bytes_per_second: int) -> int:
13 """
14 Returns the payload size for a specific part number given the file's
15 expected data rate.
16 """
17 if part_number < 1:
18 raise ValueError("part_number must be greater than 0")
19
20 if part_number > 10_000:
21 raise ValueError("part_number must be less than 10,000")
22
23 # Make sure we never go above the maximum data rate or fall below the
24 # minimum part size, even if the data rate is lower.
25 data_rate = min(format_bytes_per_second, MAXIMUM_DATA_RATE)
26 data_rate = max(data_rate, MINIMUM_PART_SIZE)
27
28 # Calculate a scalar given our data rate. We will explain this step
29 # futher on in the guides.
30 scalar = -(2 * (125 * data_rate - 68_719_476_736)) / 8_334_583_375
31 part_size = math.floor(scalar * pow(part_number, 2)) + data_rate
32
33 return part_size

part_number110_000 で、format_bytes_per_second はファイルが 1 秒あたりに消費すると予想される平均バイト数です。この式の計算方法については、後で詳しく説明します。

Scalar value

The scalar variable and calculation might be a little perplexing at first glance, but it is a mathematical tool that ensures no matter what value we use for format_bytes_per_second, if we feed all allowed part_number values from 1 to 10_0000 into the function, we will receive a set of values that totals to exactly our 5 TiB filesize limit — well, as exactly as possible. We show our work further in on how we came to this formula.

Floor Rounding

By using floor rounding, we leave some bytes on the table, but ensure that regular rounding over 10,000 part does not accidentally cause us to exceed our maximum allowed filesize. At most, 10,000 bytes, or 10 KB will be left on the table this way, an acceptable tradeoff.

この式の重要な特徴は次のとおりです。

  • 10,000 のパートをアップロードする場合、アップロードされるデータの合計量は、5 TiB のファイルサイズ制限の 10 KB 以内になります。
  • 最初のうちはペイロードを効率的な小さめに最適化し、短いクリップや中程度の長さのクリップでの応答性を向上させます。
  • 非常に長いクリップでは、ファイルの最後のパートが書き込まれてからフレーム内で再生可能になるまでの応答性が低下します。

ほとんどのクリップは 3 番目が影響を及ぼし始めるサイズに達しないことから、2 番目を取ることによるトレードオフは軽減されます。ごく少数のファイルの応答性の低下と引き換えに、大部分のファイルの応答性の向上を達成しています。 **

この式のより高度で効率的なバージョン(静的スカラーとデータレートを事前計算して組み込んだ匿名 part_size_calculator 関数を生成)は、次のようになります。

Python
1def create_part_size_calculator(format_bytes_per_second: int) -> Callable[[int], int]:
2 """
3 Returns a function that takes in a `part_number` and returns a
4 `part_size` based on `data_rate`.
5 """
6
7 # Make sure we never go above the maximum data rate or fall below the
8 # minimum part size, even if the data rate is lower.
9 data_rate = min(format_bytes_per_second, MAXIMUM_DATA_RATE)
10 data_rate = max(data_rate, MINIMUM_PART_SIZE)
11
12 static_scalar = -(2 * (125 * data_rate - 68_719_476_736)) / 8_334_583_375
13
14 def part_size_calculator(part_number: int) -> int:
15 """Calculates size in bytes of upload for `part_number`."""
16 if part_number < 1:
17 raise ValueError("part_number must be greater than 0")
18
19 if part_number > 10_000:
20 raise ValueError("part_number must be less than 10,000")
21
22 return math.floor(static_scalar * pow(part_number, 2) + data_rate)
23
24 return part_size_calculator

式の性能

いくつかの一般的なファイル形式について、上式の出力の特徴を調べてみましょう。

例 1:Web 形式

約 5.3 MB/秒以下の web 再生可能な形式(ほとんどの H.264/H.265/HEVC ファイル)の場合、ペイロードサイズの増加は次のようになります。

合計パート数ペイロードバイト数ペイロード MB合計ファイルバイト数合計ファイル GB
15,242,8965.2 MB5,242,8960.0 GB
1,00021,575,81721.6 MB10,695,361,35710.7 GB
5,000413,566,329413.6 MB706,957,655,928707.0 GB
10,0001,638,536,6791,638.5 MB5,497,558,133,9215,497.6 GB

テーブル列キー

  • Total Parts:AWS にアップロードされたファイルパートの合計数。
  • Payload Bytespart_numberTotal Parts に等しい場合の AWS PUT ペイロードのサイズ。
  • Payload MBPayload Bytes はメガバイト単位で表示されます。
  • Total File BytesTotal Parts 順次パートがアップロードされたときに、ファイルでアップロードされた合計バイト数。
  • Total File GBTotal File Bytes は GB 単位で表示されます。

これらの値は、リアルタイムアップロード、特に H.264 などの web 再生コーデックの場合に適切にバランスがとれています。ほとんどは 10.7 GB 未満であるため、1,000 パート以内で完了します。ペイロードサイズは 21.6 MB を超えることはありません。

パートの半分まで進んだとしても、ペイロードサイズは 413.5 MB を超えることはありません。アップロードの合計は 707 GB で、多くの web ファイルに十分な容量です。

許容されるパート数の終わりに近づくと、ファイルサイズが急増し始めます。ただし、1.7 GB を超えることはなく、AWS の制限であるパートあたり 5 GiB を大幅に下回っています。

例 2: Prores 422 LT

ProRes 422 LT のデータレートは 102 Mbps で、次のようなテーブルを生成します。

合計パート数ペイロードバイト数ペイロード MB合計ファイルバイト数合計ファイル GB
112,750,01612.8 MB12,750,0160.0 GB
1,00028,857,75828.9 MB18,127,308,78318.1 GB
5,000415,443,954415.4 MB735,107,948,432735.1 GB
10,0001,623,525,8171,623.5 MB5,497,558,133,9585,497.6 GB

このテーブルは、web 用に最適化された数式と比較した有用な特性を示しています。最初の 1,000 個のパート内で、8 GB のファイルをアップロードできます。初期ペイロードが大きいほど、最初に URL を要求する必要がなくなり、データレートが高くなるほどアップロードの効率性が向上します。アップロードプロセスの最終段階におけるペイロードサイズは引き続き大きくなります。

例 2:Camera Raw

最後に、280MB/s のデータレートの Camera Raw 形式を試してみましょう。データがこれほど高速になると、最初に 5 MiB チャンクでアップロードしようとするのは意味を成さなくなります。

合計パート数ペイロードバイト数ペイロード MB合計ファイルバイト数合計ファイル GB
1280,000,008280.0 MB280,000,0080.3 GB
1,000288,091,460288.1 MB282,701,200,139282.7 GB
5,000482,286,516482.3 MB1,737,245,341,5421,737.2 GB
10,0001,089,146,0651,089.1 MB5,497,558,133,8705,497.6 GB

初期のペイロードがより効率的になるだけでなく、上位設定では 0.5 ギガ以上を節約しているため、ネットワーク呼び出しがネットワークイベントの悪影響を受けにくくなります。

仕事の成果

サンプルアップローダーにすべてをまとめる前に、まずどのようにしてこの数式にたどり着いたのかを確認しましょう。

ここで必要なのは、許可されたパートの最後に大きく重いペイロード(ほとんどのアップロードで到達不可能)を引き換えに、最初の部分で軽量かつ効率的なペイロードを取得するため数式を考え出すことでした。この計算式では、すべてのアップロードでメリットが得られます。同時に、アルゴリズムで約 5 TiB のファイルサイズ制限(パート番号は **10,000)を達成したいと考えました。

微積分を解き明かす時が来ました。

グラフを飛躍的に成長させるための数式は次のようになります。

n2n^2

n はパート番号です。各パートが少なくとも数式のデータレート(r と呼びます)であることも確認します。

n2+rn^2 + r

ここで、最初の **10,000 個の自然数(1,2,3,…)に対するこの式の和を示す式を見つける必要があります。シグマ Σ 記号は和を表します。これを数式に追加しましょう。

Σxn2+rΣxn^2 + r

1~10,000 の間の一連の自然数として n を再定義します。

この方程式はこの時点では、まだあまり役立ちません。直観的ですが、n=10,000r=5,242,880 に設定すると、次のような結果385,812,135,000(385 GB)が出ます。結果はファイルサイズの上限である 5 TiB をはるかに下回るだけでなく、式を操作してこの結果を出す方法もありません。

それでは解いていきましょう。

Σxn2+rΣxn^2 + r

x は、結果として 5 TiB を得るために解くことができるスカラーです。次に方程式をファイルサイズ制限に等しく設定し、x を解くことができます。

Σxn2+r=5,497,558,138,880Σxn^2 + r = 5,497,558,138,880

多くの場合、和は for または while ループのように反復的に解く必要があります。しかし結果として、完璧な公式があることがわかりました。これは、最初の n 自然数の二乗の和を簡単に計算する方法として知られています

Σn2=n(n+1)(2n+1)/6Σn^2 = n(n+1)(2n+1)/6

多項式として再配置すると、分かりやすくなります。

Σn2=(2n3+3n2+n)/6Σn^2 = (2n^3 + 3n^2 + n)/6

変数の xr を次のように両側に追加できます。

Σxn2+r=x(2n3+3n2+n)/6+rnΣxn^2 + r = x(2n^3 + 3n^2 + n)/6 + rn

最後に、新しい数式を 5 TiB と等しくなるように設定します。

x(2n3+3n2+n)/6+rn=5,497,558,138,880x(2n^3 + 3n^2 + n)/6 + rn = 5,497,558,138,880

後は、合計パート数を n=10,000 に設定してx を解くだけです。これにより、指定されたデータレートの静的スカラーを計算できます。

これを手作業で行う代わりに、Wolfram Alpha に接続してみましょう。

x=(2(125r68719476736))/8334583375x = -(2 (125 r - 68719476736)) / 8334583375

ゴールに近くなってきました。データレートが最小パートサイズ(5 MiB)の場合、静的スカラーは次のようになります

136,128,233,472/8,334,583,375136,128,233,472 / 8,334,583,375

計算の世界では、これは 16.33293799427617 の float64 値を表します。この例では、パートサイズを決定する数式は次のようになります。

s=16.33293799427617n2+5,242,880s = 16.33293799427617n^2 + 5,242,880

ここでは、s はパートサイズを意味します。

まだ問題が 1 つあります。実環境では、非整数バイトのペイロードを持つことはできません。各値を四捨五入する必要があります。Python を使用して、以下のように切り捨てます。

Python
1math.floor(16.33293799427617 * pow(part_number, 2)) + 5_242_880

このガイドに記載されている元の関数の具体的な例にたどり着きました。

基本的なアップローダーの構築

このガイドのすべての学習内容を活用して、リアルタイムでレンダリングされるファイルをアップロードするための、Python のような簡単な疑似コードをいくつか見てみましょう。

Python
1import math
2from datetime import datetime, timezone
3from typing import Callable
4
5# The minimum size, in bytes, for a single, non-final part upload.
6MINIMUM_PART_SIZE = 5_242_880
7# The maximum filesize in
8MAXIMUM_PART_COUNT = 10_000
9# The maximum size, in bytes, for an AWS upload.
10MAXIMUM_FILE_SIZE = 5_497_558_138_880
11
12# The data rate at which every part is an equal size, and could not
13# be any uniformly larger without violating the maximum total file
14# size if 10_000 parts were to be uploaded. it works out to
15# ~549.8 MB per payload. By enforcing this we actually never need
16# to check if a part exceeds the maximum allowed part size, as our
17# parts will never exceed ~549.8 MB.
18MAXIMUM_DATA_RATE = MAXIMUM_FILE_SIZE // MAXIMUM_PART_COUNT
19
20def create_part_size_calculator(format_bytes_per_second: int) -> Callable[[int], int]:
21 """
22 Returns a function that takes in a `part_number` and returns a
23 `part_size` based on `data_rate`.
24 """
25 ...
26
27def upload_render(data_stream: DataStream, channel: int = 0) -> None:
28 """
29 Uploads an asset for data_stream, which is a custom IO class that pulls remaining
30 upload data from an internal buffer or file, depending on how well the upload is
31 keeping pace with the render.
32
33 Uploads to `channel`
34 """
35
36 asset = c2c.asset_create(
37 extension=data_stream.extension,
38 filetype=data_stream.mimetpye,
39 channel=channel,
40 offset=datetime.now(timezone.utc) - data_stream.created_at()
41 )
42
43 calculate_part_size = create_part_size_calculator(data_stream.data_rate())
44 next_part_number = 0
45
46 while True:
47 next_payload_size = calculate_part_size(next_part_number)
48
49 # Waits until one or more chunks worth of data is ready for upload. Cache
50 # whether our data stream has completed writing the file, and the current
51 # number of bytes we have remaining to upload at this time.
52 available_bytes, stream_complete = data_stream.wait_for_available_data(
53 minimum_bytes=next_payload_size
54 )
55
56 # Build the list of parts to request based on our available data.
57 parts = []
58 while available_bytes > 0:
59 payload_size = calculate_part_size(next_part_number)
60
61 if available_bytes < payload_size and not stream_complete:
62 break
63
64 payload_size = min(payload_size, available_bytes)
65
66 parts.append(
67 c2c.RealtimeUploadPart(
68 part_number=next_part_number,
69 part_size=payload_size,
70 is_final=False
71 )
72 )
73
74 available_bytes -= payload_size
75 next_part_number += 1
76
77 # If our stream is done writing, mark the last part as final.
78 if stream_complete:
79 parts[-1].is_final = True
80
81 # Create the part URLs using the C2C endpoint.
82 response = c2c.create_realtime_parts(
83 asset_id=asset.id,
84 asset_name=None if not stream_complete else data_stream.filename,
85 asset_filesize=None if not stream_complete else data_stream.size(),
86 parts=parts
87 )
88
89 # Upload each part to its URL.
90 for part, part_url in zip(parts, response.upload_urls):
91 part_data = data_stream.read(bytes=part.size)
92 c2c.upload_chunk(part_data, part_url, data_stream.mimetype)
93
94 if stream_complete:
95 break
Advanced uploading

The code above only demonstrates the basic flow of uploading a file in real time. In reality, this logic will need to be enhanced with error handling and advanced upload techniques.

次のステップ

リアルタイムアップロードにより、統合の応答性を可能な限り高めることができます。アセットは、記録が完了してから数秒後に Frame.io で再生可能になります。後述のガイドでは、高度なアップロードテクニックと要件について説明します。ガイドの説明は基本アップロードを念頭に置いて書かれていますが、ガイドの大部分はリアルタイムアップロードにも適用できます。

まだの場合は、当社チームにお問い合わせください。そのうえで、次のガイドに進んでください。ご連絡をお待ちしております。