canon draft

함수 가이드

/usr/local/m2/setting.json 다음 영역에 대해 기술한다.

{
  "functions": {
    "contents": {
      "canon": {
        ...
      }
    }
  }
}

canon부적합 원본을 규격 적합 자산으로 정규화하는 것 으로 Conformant Asset Normalization Of Nonconformant sources 의 약어이다.

Note

쉽게 말해 웹 표준이 아닌 소스(예. WEBM · VP9 · MP3 )를 표준 규격(예. MP4 · AVC1 · AAC )으로 변환해 원본 스토리지에 적재하는 역할을 수행한다.

canon의 목적은 다음과 같다.

  • 파편화된 미디어 자산 정규화

  • 미디어 자산 호환성 강화

  • 저장용량 절감 및 재배치

  • 트랜스코딩 최소화

Important

이 함수의 프로파일profiles 를 의미하며, variant가 없는 단일 형식을 지칭한다. 산출물은 온전한 mp4 single container 다. 프로파일의 fmp4 rendition 은 코덱 · 해상도 · 프레임레이트 규격과 세그먼트 트랜스코딩 방식 을 제공하며, 세그먼트를 병합해 최종 mp4 로 만든다.

canon 은 원본을 처음부터 다시 인코딩하지 않는다. hive alpha 가 이미 트랜스코딩해 둔 세그먼트는 그대로 재사용하고, 누락된 세그먼트만 개별로 트랜스코딩한 뒤 전체를 하나의 mp4 로 병합한다.

이 방식에서 다음 성질이 따라온다.

  • 작업 단위가 세그먼트이므로 transcodertimeout세그먼트 1개 기준이다. 작업 전체 소요시간은 상태 메시지timestamps.normalize 로 확인한다.

  • 누락 세그먼트가 없으면 트랜스코딩 없이 병합만 수행하며 result.methodremux 가 된다.

  • 재생이 선행된 콘텐츠일수록 재사용할 세그먼트가 많아 비용이 낮다. mezzanineonIdle 위임이 이 성질을 이용한다.

How to use

트랜스코딩은 대표적인 LRO(Long Running Operation)다. 작업(job)을 등록하고 완료를 통지(callback)받는 메커니즘으로 동작한다.

Method

URL

정상응답

설명

POST

/vfly/v1/normalize/jobs

202

트랜스코딩 작업을 생성

GET

/vfly/v1/normalize/jobs/{id}

200

트랜스코딩 작업상태를 조회

DELETE

/vfly/v1/normalize/jobs/{id}

200

트랜스코딩 작업을 취소

실패 응답은 각 절의 에러 응답 항목에 정리한다.

작업 생성

본문에 원본 · 프로파일 · 적재 대상을 명시한다.

POST /vfly/v1/normalize/jobs HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "origin": "https://videofly.co.kr/dir/sample.mkv",
  "profile": "my_standard_profile",
  "dest": [ "canonarchive" ],
  "delete": [ "originstorage" ],
  "callback": "https://ops.example.com/hook/canon"
}
origin

정규화 대상 원본 URL

profile

정규화 프로파일 이름

dest

적재 대상. endpointsname 배열이다. 항목 수만큼 적재하며 생략할 수 없다. 암묵적인 적재 위치는 없다.

delete

삭제 대상. dest 와 같은 형식이며 생략할 수 있다. 모든 적재가 성공한 뒤에만 수행한다.

callback

완료 통보를 받을 URL. 생략하면 통보 없이 처리한다.

Important

트랜스코딩은 소요시간이 길기 때문에 canon 함수는 요청을 접수만 하고 202 Accepted 를 응답한다. 접수된 작업은 storgate 작업목록 에 적재되어 수행된다. 처리 결과는 callback 으로 통보한다.

요청을 수락하면 작업 ID 를 발급한다. Location 헤더가 상태 조회 URL 을 가리킨다. 응답 본문은 상태 메시지subset 이며, 접수 시점에 확정된 필드까지만 채워진다.

HTTP/1.1 202 Accepted
Location: /vfly/v1/normalize/jobs/9f2c41ab7d0e5b38
Content-Type: application/json

{
  "meta": {
    "vhost": "example.com",
    "event": "canon",
    "profile": "my_standard_profile",
    "timestamps": {
      "accept": "2026-07-30T10:15:45.256Z"
    }
  },
  "result": {
    "id": "9f2c41ab7d0e5b38",
    "status": "accepted",
    "src": {
      "url": "https://videofly.co.kr/dir/sample.mkv"
    }
  }
}

필드 정의는 상태 메시지 를 따른다.

적재 경로

적재 대상은 dest명시 한다.

  • 원본 경로에 덮어쓰려면 원본 스토리지를 가리키는 엔드포인트 를 선언한다.

  • 정규화와 병합은 요청당 1회이고 적재만 N회이므로, 적재 대상을 늘려도 트랜스코딩 비용은 늘지 않는다.

  • delete모든 적재가 성공한 뒤에만 수행한다. 선언 순서와 무관하다. 적재가 하나라도 실패하면 삭제하지 않는다 — 원본이 유실되는 것을 막기 위한 규칙이다.

Note

  • container 가 바뀌어도 경로는 바뀌지 않는다. (고객사 레거시 규약 준수)

  • 원본 경로를 덮어쓰면 .mkv 경로에 MP4 콘텐츠가 담긴 상태가 되므로, 확장자를 맞추려면 path 규칙을 구성한다.

  • 최종 적재 결과는 상태 메시지result.dest 로 통보된다.

에러 응답

접수하지 못하면 본문 없이 실패를 응답한다.

응답

실패 원인

400 Bad Request

본문 형식 오류 · {profile} 미정의 · callback URL 형식 오류

403 Forbidden

metaexposefalse

500 Internal Error

transcoder 장애등 서비스 구동상 장애

Note

접수 단계의 실패는 위 응답 코드로만 전달된다. 접수 이후 처리 단계의 실패는 응답 코드가 아니라 상태 메시지failed 로 통보된다.

상태 조회 · 취소

발급된 ID 로 진행 상태를 조회하거나 작업을 취소한다.

# 상태 조회
GET https://videofly.co.kr/vfly/v1/normalize/jobs/9f2c41ab7d0e5b38

# 취소
DELETE https://videofly.co.kr/vfly/v1/normalize/jobs/9f2c41ab7d0e5b38

조회 · 취소의 응답 본문도 상태 메시지subset 이다. 진행 정도에 따라 채워지는 범위만 다르며, status 는 다음 값을 가진다.

상태

설명

accepted

접수되었으나 아직 시작하지 않았다.

running

정규화가 진행 중이다.

completed

정규화하여 적재했다.

skipped

정규화하지 않았다.

failed

정규화하지 못했다.

canceled

취소되었다.

정상 처리는 200 OK 로 응답한다.

Note

  • 취소된 작업은 callback 을 통보하지 않는다.

  • 작업목록은 storgate beta 가 유지한다. 노드가 재시작되어도 조회 · 취소가 유지되어야 한다면 database draft 모드를 권장한다.

에러 응답

응답

의미

404 Not Found

ID 가 없거나 작업 저장기간이 만료되었다.

409 Conflict

이미 종료된 작업이라 취소할 수 없다 ( DELETE 만 ).

callback

callback 은 접수 본문의 callback 필드로 등록하며, API 로 직접 접수한 요청 에만 해당한다. 트랜스코딩/정규화가 종료되면 ( 성공 · 실패 · 생략 모두 ) 이 URL 로 통보한다.

항목

규격

메서드

POST

Content-Type

application/json

성공 판정

2xx

Warning

callback 요청에는 서명 · 인증이 없다. 수신 엔드포인트는 접근 제어를 자체적으로 구성한다.

통보 본문은 상태 메시지 구조를 따른다.

재시도

수신측 응답

판정

동작

2xx

성공

종료한다.

408 · 429 · 5xx

일시 실패

지수 백오프로 재시도한다.

그 외 4xx

영구 실패

재시도하지 않고 로그에 기록한다.

무응답 · 연결 실패

일시 실패

지수 백오프로 재시도한다.

Note

callback 전달 실패는 정규화 실패가 아니다. 정본은 이미 적재되어 있으므로 재생에는 영향이 없다. 통보를 놓친 대상은 onBatch 로 재확인한다.

상태 메시지

작업의 상태를 표현하는 단일 메시지 구조 다. 작업 생성 · 상태 조회 · 취소 의 응답 본문과 callback 의 통보 본문은 모두 이 구조의 subset 이며, 시점에 따라 채워지는 범위만 다르다.

Warning

draft 개발 단계에서 가능한 상세한 정보를 보강한다.

아래는 정규화하여 적재한 경우다.

{
  "meta": {
    "vhost": "example.com",
    "event": "canon",
    "profile": "my_standard_profile",
    "timestamps": {
      "accept": "2026-07-30T10:15:45.256Z",
      "normalize": {
        "start": "2026-07-30T10:15:46.001Z",
        "end": "2026-07-30T10:18:12.874Z"
      }
    }
  },
  "result": {
    "id": "9f2c41ab7d0e5b38",
    "status": "completed",
    "method": "transcode",
    "src": {
      "url": "https://videofly.co.kr/video.mkv",
      "videoId": "c81d4e2ebf3a",
      "container": "mkv",
      "video": { "codec": "vp9", "resolution": "1920x1080", "frame_rate": 29.97 },
      "audio": { "codec": "opus", "channels": 2 },
      "duration": 372.44,
      "size": 284917263
    },
    "asset": {
      "container": "mp4",
      "video": { "codec": "h264", "resolution": "1920x1080", "frame_rate": 29.97 },
      "audio": { "codec": "aac", "channels": 2 },
      "size": 198432110
    },
    "dest": [
      {
        "url": "s3://s3-canon-archive/test/123/sample.mp4",
        "storage": "canonarchive"
      },
      {
        "url": "s3://s3-canon-backup/test/123/sample.mp4",
        "storage": "canonbackup"
      }
    ],
    "deleted": [
      {
        "url": "s3://origin-bucket/dir/sample.mkv",
        "storage": "originstorage"
      }
    ]
  }
}
meta

요청 컨텍스트. 웹훅 개발가이드 와 같은 구조를 따른다.

  • timestamps.accept 수락 시각

  • timestamps.normalize 정규화 시작 · 종료 시각. statusfailedend 는 실패를 확정한 시각이다. skipped 면 항목 자체가 생략된다.

result.id

작업 생성 에서 발급한 작업 ID

result.status

종료 상태. 상태 조회 · 취소status 와 같은 값 집합이며, 종료 시점에 통보되는 값은 아래 세 가지다.

상태

설명

completed

정규화하여 모든 적재 · 삭제에 성공했다. asset · dest 가 포함된다.

skipped

정규화하지 않았다. reason 이 포함된다.

failed

정규화하지 못했거나 적재 · 삭제 중 하나 이상이 실패했다. error 가 포함된다.

Note

대상이 여러 곳일 때 하나라도 실패하면 ``failed`` 다. 부분 성공 상태는 없다. 성공한 작업은 dest · deleted 에 그대로 남으므로 어디까지 처리됐는지는 항목별로 확인한다.

result.method

사용한 수단. skipped · failednull 이다.

  • remux 누락 세그먼트가 없어 병합만 수행했다.

  • transcode 누락 세그먼트를 하나 이상 트랜스코딩했다.

result.src

정규화 입력. 세 상태 모두 포함되지만 채워지는 범위가 다르다.

  • url · videoId 는 항상 포함된다.

  • container · video · audio · duration · size원본 분석에 성공한 경우에만 포함된다. error.phasefetch · probe 면 분석 이전 단계의 실패이므로 이 필드들이 없다.

video · audio 필드명은 video 규격을 따른다.

result.asset

정규화 출력의 규격. 산출물은 요청당 1개이므로 단일 객체다. container · video · audio · size 를 가지며 적재를 시도한 경우 포함된다.

result.dest

적재 위치 배열. 접수 본문의 dest 에 선언한 수만큼 항목이 생긴다. 적재를 시도한 경우 포함되므로 statusfailed 여도 성공분이 남는다.

url

적재 위치. 스킴을 포함한 절대 표현이며 endpointsdomain 에 따라 형식이 갈린다.

domain

url

null ( AWS S3 )

s3://{bucket}/{path}

지정됨 ( S3 호환 )

https://{domain}/{bucket}/{path}

Warning

적재 위치의 식별 표현이며 접근 가능한 URL 이 아니다. 버킷이 비공개면 이 주소로 조회할 수 없다. 자격증명 · 쿼리스트링은 포함하지 않는다.

storage

적용된 endpointsname

error

이 항목의 적재가 실패한 경우에만 포함된다. reason · message 를 가진다. 항목별 실패이므로 phase 는 갖지 않는다 — 작업 전체의 단계는 result.error.phasestore 로 표시한다.

result.deleted

삭제 위치 배열. 접수 본문의 delete 에 선언한 수만큼 항목이 생기고, 선언이 없으면 필드 자체가 없다. 항목 구성은 dest 와 같다 ( url · storage , 실패 시 error ).

적재가 하나라도 실패하면 삭제를 수행하지 않으므로 이 필드도 없다.

정규화하지 않은 경우 asset · dest 대신 reason 이 포함된다.

{
  "meta": {
    "vhost": "example.com",
    "event": "canon",
    "profile": "my_standard_profile",
    "timestamps": {
      "accept": "2026-07-30T10:15:45.256Z"
    }
  },
  "result": {
    "id": "3a7e02c9d4f1b865",
    "status": "skipped",
    "method": null,
    "src": {
      "url": "https://videofly.co.kr/video.mp4",
      "videoId": "a1b2c3d4e5f6",
      "container": "mp4",
      "video": { "codec": "h264", "resolution": "1280x720", "frame_rate": 30 },
      "audio": { "codec": "aac", "channels": 2 },
      "duration": 128.5,
      "size": 41230776
    },
    "reason": "already_conformant"
  }
}
result.reason

skipped 사유.

사유

설명

already_conformant

원본이 이미 프로파일 규격을 만족한다.

Note

판정은 경로의 확장자가 아니라 원본을 분석한 결과 로 한다. 원본 경로에 덮어쓴 경우 확장자와 내용이 어긋나 있으므로( .mkv 경로에 MP4 콘텐츠 ) 확장자를 기준으로 삼으면 이미 정규화된 자산을 계속 다시 정규화한다.

실패한 경우 error 가 포함된다. 아래는 정규화 단계에서 실패해 적재를 시도하지 못한 예다. error.phasestore 라면 asset · dest 도 함께 포함되며 실패한 항목에 dest[].error 가 담긴다.

{
  "meta": {
    "vhost": "example.com",
    "event": "canon",
    "profile": "my_standard_profile",
    "timestamps": {
      "accept": "2026-07-30T10:15:45.256Z",
      "normalize": {
        "start": "2026-07-30T10:15:46.001Z",
        "end": "2026-07-30T10:25:46.113Z"
      }
    }
  },
  "result": {
    "id": "9f2c41ab7d0e5b38",
    "status": "failed",
    "method": null,
    "src": { ... },
    "error": {
      "phase": "normalize",
      "reason": "transcoder_timeout",
      "message": "no response in 600s"
    }
  }
}
error.phase

실패 단계

단계

상세

fetch

원본 획득 실패

probe

원본 분석 실패

normalize

대상 정규화 실패

store

대상 적재 실패

delete

대상 삭제 실패

Note

fetch · probe 는 분석 이전 단계의 실패이므로 skipped 가 아니라 failed 로 통보한다. skipped 는 분석에 성공했고 그 결과가 “정규화 불필요” 인 경우다.

error.reason

실패 사유 식별자

error.message

실패 사유 설명

meta

"meta": {
  "enable": false,
  "keyword": "vfly/v1/normalize",
  "expose": false
}
enable (기본: false)

활성화

keyword (기본: vfly/v1/normalize)

Normalize 작업 명령을 인식하는 URL 키워드. 이 키워드 이후 경로가 canon 명령으로 해석된다.

expose (기본: false)

외부 인터페이스 노출 여부. false 인 경우 403 Forbidden 으로 응답한다.

Note

내부적으로 발견된 파일을 처리하는 경우가 많다.

이 경로들은 API 를 경유하지 않고 같은 가상호스트 안에서 inline 으로 연결되므로 expose 값과 무관하게 동작하며, callback 도 사용하지 않는다.

transcoder

트랜스코더를 구성한다.

"transcoder": {
  "type": "local",
  "timeout": 60
}
type (기본: local)

트랜스코더 타입.

  • local 개발목적의 동시성 1의 트랜스코더로, GPU/CPU 모드를 자동으로 설정한다.

  • hlseg-transcoder={name} 사용할 hlseg-transcodername 을 연결한다.

    "transcoder": {
      "type": "hlseg-transcoder=batch",
      "timeout": 60
    }
    

    videofly 가 비활성화되어 있거나 hlseg-transcoder 연결이 유효하지 않다면 요청이 실패한다.

timeout (기본: 60)

세그먼트 1개 의 요청전송 후 완료 응답을 기다리는 최대 시간(초). 작업 전체의 제한시간이 아니다.

Note

  • hlseg-transcoder 는 전송·취합(aggregation) 담당이며, 응답 대기·대응 정책은 본 가상호스트 설정이 소유한다.

로그

설정예제

원본 /dir/sample.mkv ( WEBM 계열 · VP9 · Opus ) 를 mp4 ( AVC1 · AAC ) 로 정규화하는 다섯 가지 경우다. 뒤로 갈수록 앞의 설정에 하나씩 더한다.

  • 모든 예제는 aws_s3 엔드포인트를 함께 구성한다.

  • 적재 대상은 항상 접수 본문의 dest 로 명시한다. 암묵적인 적재 위치는 없다.

모든 예제는 프로파일 my_standard_profile 을 사용한다. 프로파일은 profiles 에 정의하며 fmp4 rendition 이 기준 규격이 된다.

Note

예제는 기본값과 같은 항목을 생략 했다. 실제 설정에서도 적지 않아도 된다. 생략된 항목의 기본값은 meta · transcoder · endpoints 를 참고한다.

See also

여기서는 URL 로 개별 자산을 정규화한다. 저장소를 순회하며 일괄 정규화 하는 구성은 비디오 정규화 배치 를 참고한다. 이 경우 적재는 storgate betasavePath 가 담당한다.

1. 원본 경로에 덮어쓴다

가장 단순한 형태다. 산출물이 원본을 대체하고 경로는 그대로 유지된다.

설정 예제는 아래와 같다.

# functions.contents.canon
"canon": {
  "meta": {
    "enable": true,

    # API 로 직접 호출하므로 노출을 허용한다. 기본값은 false 이며 403 을 응답한다.
    "expose": true
  },
  "transcoder": {
    # 기본값 local 은 개발 목적이므로 운영은 task 를 연결한다.
    "type": "hlseg-transcoder=batch"
  }
}

# functions.backend.aws_s3
# 원본 스토리지다. path 를 지정하지 않으면 입력된 경로 그대로 올린다. 즉 원본을 덮어쓴다.
"endpoints": [
  {
    "name": "originstorage",
    "bucket": "origin-bucket",
    "region": "ap-northeast-2"
  }
]

접수 요청은 다음과 같다. 덮어쓰기도 dest 로 명시한다.

POST /vfly/v1/normalize/jobs HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "origin": "https://videofly.co.kr/dir/sample.mkv",
  "profile": "my_standard_profile",
  "dest": [ "originstorage" ],
  "callback": "https://ops.example.com/hook/canon"
}

결과는 다음과 같다.

# callback 상태 메시지의 result ( 발췌 )
"result": {
  "status": "completed",
  "method": "transcode",
  "asset": {
    "container": "mp4",
    "video": { "codec": "h264", "resolution": "1920x1080", "frame_rate": 29.97 },
    "audio": { "codec": "aac", "channels": 2 },
    "size": 198432110
  },
  "dest": [
    {
      # 원본 경로 그대로다.
      "url": "s3://origin-bucket/dir/sample.mkv",
      "storage": "originstorage"
    }
  ]
}

Warning

경로가 .mkv 인데 내용은 MP4 다. 확장자와 내용이 어긋나도 되는 환경에서만 쓴다. 확장자를 맞추려면 2번을 따른다.

2. 같은 디렉터리에 mp4 로 저장한다

확장자를 .mp4 로 바꿔 저장한다. 원본 /dir/sample.mkv 는 그대로 남는다.

경로를 바꾸려면 aws_s3 엔드포인트에 경로 규칙을 정의한다. 1번 설정에 다음을 추가한다.

# functions.backend.aws_s3
"aws_s3": {
  "meta": {
    "enable": true
  },
  "endpoints": [
    {
      "name": "samebucket",

      # 원본과 같은 버킷이다.
      "bucket": "origin-bucket",
      "region": "ap-northeast-2",

      # /dir/sample.mkv 를 /dir/sample.mp4 로 바꾼다.
      # #1 은 pattern 의 첫 캡처그룹( sample )이다.
      "path": {
        "dest": {
          "type": "rewrite",
          "urlRewrites": [
            { "pattern": "/dir/(.*)\\.mkv", "replace": "/dir/#1.mp4" }
          ]
        }
      },

      # 지정하지 않으면 S3 에서 application/octet-stream 으로 내려간다.
      # inheritContentType 으로 승계하면 원본의 mkv 타입이 붙으므로 static 으로 고정한다.
      "metadata": [
        { "key": "Content-Type", "type": "static", "value": "video/mp4" }
      ]
    }
  ]
}

접수 요청은 다음과 같다.

POST /vfly/v1/normalize/jobs

{
  "origin": "https://videofly.co.kr/dir/sample.mkv",
  "profile": "my_standard_profile",
  "dest": [ "samebucket" ]
}

결과는 다음과 같다.

"result": {
  "status": "completed",
  "method": "transcode",
  "asset": { "container": "mp4", "size": 198432110 },
  "dest": [
    {
      # path.dest 규칙이 적용된 경로다.
      "url": "s3://origin-bucket/dir/sample.mp4",

      # 적용된 엔드포인트 이름이다.
      "storage": "samebucket"
    }
  ]
}

Note

원본과 산출물이 모두 남으므로 저장용량이 늘어난다. 원본을 정리하려면 5번을 따른다.

3. 다른 저장소에 저장한다

버킷과 경로를 모두 바꿔 /test/123/sample.mp4 로 저장한다. 2번과 달라지는 것은 엔드포인트의 bucketreplace 뿐이다.

# functions.backend.aws_s3
"endpoints": [
  {
    "name": "canonarchive",

    # 다른 버킷이다.
    # AWS 가 아닌 S3 호환 스토리지라면 domain 을, 다른 계정이라면 accessKey · secretKey 를 더한다.
    "bucket": "s3-canon-archive",
    "region": "ap-northeast-2",

    # /dir/sample.mkv 를 /test/123/sample.mp4 로 바꾼다.
    "path": {
      "dest": {
        "type": "rewrite",
        "urlRewrites": [
          { "pattern": "/dir/(.*)\\.mkv", "replace": "/test/123/#1.mp4" }
        ]
      }
    },
    "metadata": [
      { "key": "Content-Type", "type": "static", "value": "video/mp4" }
    ]
  }
]

접수 요청은 다음과 같다.

POST /vfly/v1/normalize/jobs

{
  "origin": "https://videofly.co.kr/dir/sample.mkv",
  "profile": "my_standard_profile",
  "dest": [ "canonarchive" ]
}

결과는 다음과 같다.

"result": {
  "status": "completed",
  "dest": [
    {
      "url": "s3://s3-canon-archive/test/123/sample.mp4",
      "storage": "canonarchive"
    }
  ]
}

4. 원본 경로와 다른 저장소에 동시에 저장한다

dest 에 두 개를 선언하면 두 곳에 적재한다. 원본 경로에도 적재하려면 원본 스토리지를 가리키는 엔드포인트를 따로 정의 해야 한다.

엔드포인트를 두 개 구성한다.

# functions.backend.aws_s3
"endpoints": [
  {
    # 원본 스토리지다. path 를 지정하지 않으면 입력된 경로 그대로 올린다.
    # 즉 /dir/sample.mkv 를 덮어쓴다.
    "name": "originstorage",
    "bucket": "origin-bucket",
    "region": "ap-northeast-2"
  },
  {
    # 3번과 같은 엔드포인트다.
    "name": "canonarchive",
    "bucket": "s3-canon-archive",
    "region": "ap-northeast-2",
    "path": {
      "dest": {
        "type": "rewrite",
        "urlRewrites": [
          { "pattern": "/dir/(.*)\\.mkv", "replace": "/test/123/#1.mp4" }
        ]
      }
    },
    "metadata": [
      { "key": "Content-Type", "type": "static", "value": "video/mp4" }
    ]
  }
]

접수 요청은 다음과 같다.

POST /vfly/v1/normalize/jobs

{
  "origin": "https://videofly.co.kr/dir/sample.mkv",
  "profile": "my_standard_profile",
  "dest": [ "originstorage", "canonarchive" ]
}

결과는 다음과 같다. dest 항목이 두 개다.

"result": {
  "status": "completed",
  "asset": { "container": "mp4", "size": 198432110 },
  "dest": [
    {
      "url": "s3://origin-bucket/dir/sample.mkv",
      "storage": "originstorage"
    },
    {
      "url": "s3://s3-canon-archive/test/123/sample.mp4",
      "storage": "canonarchive"
    }
  ]
}

Important

정규화와 병합은 요청당 1회다. 적재 대상을 늘려도 트랜스코딩 비용은 늘지 않는다. 둘 중 하나라도 실패하면 statusfailed 이며, 실패한 항목에 dest[].error 가 담긴다.

5. 다른 저장소로 옮기고 원본을 삭제한다

3번에 delete 를 더한 형태다. 산출물을 다른 저장소에 적재하고 원본을 정리한다.

설정은 4번과 같다. originstorage 는 적재 대상이 아니라 삭제 대상 으로 쓴다. 접수 요청은 다음과 같다.

POST /vfly/v1/normalize/jobs

{
  "origin": "https://videofly.co.kr/dir/sample.mkv",
  "profile": "my_standard_profile",
  "dest": [ "canonarchive" ],
  "delete": [ "originstorage" ]
}

결과는 다음과 같다.

"result": {
  "status": "completed",
  "asset": { "container": "mp4", "size": 198432110 },
  "dest": [
    {
      "url": "s3://s3-canon-archive/test/123/sample.mp4",
      "storage": "canonarchive"
    }
  ],
  "deleted": [
    {
      "url": "s3://origin-bucket/dir/sample.mkv",
      "storage": "originstorage"
    }
  ]
}

Warning

삭제는 적재가 모두 성공한 뒤에만 수행한다. 선언 순서와 무관하다. 적재가 하나라도 실패하면 삭제하지 않으며 deleted 필드도 생기지 않는다. 원본이 유실되는 것을 막기 위한 규칙이다.