transcoder

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

{
  "functions": {
    "contents": {
      "transcoder": {
        ...
      }
    }
  }
}

Important

트랜스코딩 시스템 권장사항은 다음과 같다.

용도

Core

메모리(GiB)

비고

test

4

4

동작을 위해 트랜스코딩 성능 제약

stage

4

8

다른 함수와 동시 구동이 가능

production (SMB)

8

16

gif2video 서비스 가능

production (enterprise)

16

32

권장

How to use

명령어 리스트

# 비디오 분석정보를 JSON으로 제공한다.
https://example.com/video.mp4/xcdr/analyze/src
https://example.com/video.mp4/xcdr/analyze/srcd

# 원본-변환된 비디오 간 SSIM 값을 측정한다.
https://example.com/video.mp4/xcdr/preset/_720p/analyze/ssim

# /video.mp4를 사전 정의한 low 프리셋으로 트랜스코딩
https://example.com/video.mp4/xcdr/preset/low

# /video.mp4를 _1080p 프리셋 (built-in) 로 트랜스코딩
https://example.com/video.mp4/xcdr/preset/_1080p

# /video.mp4를 _1080p 프리셋 기반, 세부옵션만 조정하여 트랜스코딩
https://example.com/video.mp4/xcdr/preset/_1080p/options/audio.channels=1;video.frameRate=10

# /video.mp4를 구간/다구간 추출
https://example.com/video.mp4/xcdr/trim/01:20-01:30
https://example.com/video.mp4/xcdr/trim/01:20-01:30,03:57-04:28
https://example.com/video.mp4/xcdr/trim/45-50,11-13,36-43/faststart/true

# /video.mp4를 헤더만 앞으로 옮겨 전달
https://example.com/video.mp4/xcdr/faststart/true

# 화면을 캡쳐한다.
https://example.com/video.mp4/xcdr/capture/ts=10.3

# 화면을 캡쳐하여 avif로 추출한다.
https://example.com/video.mp4/xcdr/capture/ts=10.3;format=avif

# 화면을 캡쳐하여 animated 이미지로 제공한다.
https://example.com/video.mp4/xcdr/capture/ts=1,2,3,4,5

# 화면을 캡쳐하여 1s간격의 mp4 비디오로 제공한다.
https://example.com/video.mp4/xcdr/capture/ts=1,2,3,4,5;format=mp4

여러 명령어를 결합하거나 다른 함수와 연계되면 보다 확장성있는 서비스를 구현할 수 있다.

# /video.mp4의 10초~20초 구간추출하여 low 프리셋으로 트랜스코딩
https://example.com/video.mp4/xcdr/trim/00:00:10-00:00:20/preset/low
https://example.com/video.mp4/xcdr/trim/10-20/preset/low

# /video.mp4의 1시간 15분 30초 이후 구간추출하여 low 프리셋으로 트랜스코딩 후 헤더를 앞으로 이동
https://example.com/video.mp4/xcdr/trim/1:15:30-/preset/low/faststart/true

# /video.mp4를 시작부터 1분 30초 구간만 추출하여 low 프리셋으로 트랜스코딩 후 헤더를 앞으로 이동
https://example.com/video.mp4/xcdr/trim/-01:30/preset/low/faststart/true

# mp4hls 연계 - /video.mp4를 _1080p 프리셋으로 트랜스코딩한 다음 해당영상을 HLS로 전송
https://example.com/video.mp4/xcdr/preset/_1080p/mp4hls/index.m3u8

# mp4hls 연계 - /video.mp4 원본을 HLS로 분할한 뒤 전송과정에서 _1080p 프리셋으로 트랜스코딩
https://example.com/video.mp4/mp4hls/index.m3u8/xcdr/preset/_1080p

# mp4hls 연계 - /video.mp4 원본을 구간추출한 뒤 _1080p 프리셋으로 트랜스코딩, 이후 HLS로 전송
https://example.com/video.mp4/xcdr/trim/00:01:40-00:03:00/preset/_1080p/mp4hls/index.m3u8

# dims 연계 - /video.mp4 원본의 10.3초 구간을 이미지로 추출하여 800x600으로 리사이즈한다.
https://example.com/video.mp4/xcdr/capture/ts=10.3/dims/resize/800x600

명령어

파라미터

동작

analyze

src 또는 srcd

비디오 분석정보를 JSON 으로 제공한다.

analyse

src 또는 srcd

고~급스럽게 영국식 명령어를 제공한다.

analyze

ssim

원본-변환된 비디오 간 SSIM 값을 측정한다.

preset

{이름}

트랜스코딩할 프리셋을 지정한다.

options

키=값; 연속

preset 세부옵션

trim

hh:mm:ss 구간지정

트랜스코딩할 프리셋을 지정한다.

faststart

true 또는 false

동영상 헤더정보를 파일의 앞으로 이동시킨다.

capture

hh:mm:ss.SSS

화면을 캡쳐 지점. 단일 또는 콤마로 구분된 연속

비디오 분석정보

/analyze/src 명령어는 비디오 분석정보를 간략히 제공한다.

{
  "enable": true,
  "url": "/video.mp4",
  "result": {
    "source": {
      "size": 6238713,
      "format": "mp4",
      "duration": 15.062,
      "video": {
        "codec_name": "h264",
        "width": 1920,
        "height": 1080
      },
      "audio": {
        "codec_name": "aac"
      }
    },
    "elapsed": {
      "init": 2,
      "complete": 14
    },
    "function": {
      "keyword": "xcdr"
    }
  }
}
enable

트랜스코더 모듈에 적재/처리 가능하다면 true , 불가하다면 false

url

원본 URL

result

분석결과

source

미디어 정보

size  (단위: bytes)

용량

format

포맷 - flac, flv, fmp4, gif, mp2, mp3, mp4, mpg, mxf, oga, ogg, ts, wav, webm

duration (단위: 초)

재생시간 (소수점 3째자리까지 표시)

video

비디오 정보. 비디오가 없다면 null 이다.

codec_name

비디오 코덱

width (단위: px)

가로길이

height (단위: px)

세로길이

audio

오디오 정보. 오디오가 없다면 null 이다.

codec_name

비디오 코덱

elapsed

경과시간

init (단위: ms)

호출시점 ~ 원본이미지 초기화

complete (단위: ms)

호출시점 ~ 완료

function

트랜스코더 설정

keyword

호출 키워드

/analyze/srcd 명령어는 보다 상세한 분석정보를 제공한다.

{
  "enable": true,
  "url": "/video.mp4",
  "result": {
    "source": {
      "streams": [
        {
          "index": 0,
          "codec_name": "aac",
          "codec_long_name": "AAC (Advanced Audio Coding)",
          "profile": "LC",
          "codec_type": "audio",
          "codec_time_base": "1/48000",
          "codec_tag_string": "mp4a",
          "codec_tag": "0x6134706d",
          "sample_fmt": "fltp",
          "sample_rate": "48000",
          "channels": 2,
          "channel_layout": "stereo",
          "bits_per_sample": 0,
          "r_frame_rate": "0/0",
          "avg_frame_rate": "0/0",
          "time_base": "1/48000",
          "start_pts": -2048,
          "start_time": "-0.042667",
          "duration_ts": 722944,
          "duration": "15.061333",
          "bit_rate": "160002",
          "max_bit_rate": "160002",
          "nb_frames": "706",
          "disposition": {
            "default": 1,
            "dub": 0,
            "original": 0,
            "comment": 0,
            "lyrics": 0,
            "karaoke": 0,
            "forced": 0,
            "hearing_impaired": 0,
            "visual_impaired": 0,
            "clean_effects": 0,
            "attached_pic": 0
          },
          "tags": {
            "language": "kor",
            "handler_name": "SoundHandler"
          }
        },
        {
          "index": 1,
          "codec_name": "h264",
          "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
          "profile": "High",
          "codec_type": "video",
          "codec_time_base": "1/60",
          "codec_tag_string": "avc1",
          "codec_tag": "0x31637661",
          "width": 1920,
          "height": 1080,
          "coded_width": 1920,
          "coded_height": 1088,
          "has_b_frames": 2,
          "sample_aspect_ratio": "1:1",
          "display_aspect_ratio": "16:9",
          "pix_fmt": "yuv420p",
          "level": 41,
          "color_range": "tv",
          "color_space": "bt709",
          "color_transfer": "bt709",
          "color_primaries": "bt709",
          "chroma_location": "left",
          "refs": 4,
          "is_avc": "1",
          "nal_length_size": "4",
          "r_frame_rate": "30/1",
          "avg_frame_rate": "30/1",
          "time_base": "1/15360",
          "start_pts": 0,
          "start_time": "0.000000",
          "duration_ts": 230400,
          "duration": "15.000000",
          "bit_rate": "3157810",
          "bits_per_raw_sample": "8",
          "nb_frames": "450",
          "disposition": {
            "default": 1,
            "dub": 0,
            "original": 0,
            "comment": 0,
            "lyrics": 0,
            "karaoke": 0,
            "forced": 0,
            "hearing_impaired": 0,
            "visual_impaired": 0,
            "clean_effects": 0,
            "attached_pic": 0
          },
          "tags": {
            "language": "und",
            "handler_name": "VideoHandler"
          }
        }
      ],
      "format": {
        "filename": "http://rproxy.video.m2/http://127.0.0.1/mmv/media/monopoly.mp4",
        "nb_streams": 2,
        "nb_programs": 0,
        "format_name": "mov,mp4,m4a,3gp,3g2,mj2",
        "format_long_name": "QuickTime / MOV",
        "start_time": "-0.042667",
        "duration": "15.062000",
        "size": "6238713",
        "bit_rate": "3313617",
        "probe_score": 100,
        "tags": {
          "major_brand": "isom",
          "minor_version": "512",
          "compatible_brands": "isomiso2avc1mp41",
          "encoder": "Lavf57.71.100"
        }
      }
    },
    "elapsed": {
      "init": 2,
      "complete": 14
    },
    "function": {
      "keyword": "xcdr"
    }
  }
}

유사성 비교

/analyze/ssim 명령어는 원본과 변환된 비디오의 유사성을 SSIM(Structural Similarity Index Measure) 방식으로 비교한다.

{
  "enable": true,
  "url": "/example.mp4",
  "result": {
      "ssim": "0.971172",
      "elapsed": {
          "init": 5217,
          "complete": 9559
      }
  }
}
enable

SSIM 비교가 정상처리되었다. 실패라면 false 로 응답하다.

url

원본 URL

result

분석결과

ssim (범위: 0~1)

원본과 변환된 비디오의 유사성으로 완전히 동일하다면 1 이다.

meta

"meta" : {
  "enable" : false,
  "keyword": "xcdr",
  "tempdir": "xcdrtmp",
  "maxDuration": "3:00",
  "capacityPerCore": 52428800
}
enable (기본: false)

트랜스코딩 활성화

keyword (기본: xcdr)

트랜스코딩 키워드

tempdir (기본: xcdrtmp)

임시파일 I/O 디렉토리 경로 (상대경로인 경우 설치경로 하위에 생성된다.)

maxDuration (기본: 3:00)

트랜스코딩 가능한 소스 최대 시간으로 hh:mm:ss 표기를 사용한다. 소스가 trim 되었다면 trim 된 시간을 기준으로 한다.

Warning

이 값은 지나치게 무거운 워크로드로 인해 의도치 않은 서비스 지연을 방지하는 안전장치이다. 설정을 조정할 경우 서비스 흐름상에서 클라이언트가 응답에 대해 충분히 대기하여 처리 가능한지 검증되어야 한다.

capacityPerCore (기본: 50MB)

코어당 트랜스코딩 대기 가능한 최대 용량. 8코어 머신이라면 400MB까지 큐잉이 가능하다.

Note

단, 현재 트랜스코딩되는 크기는 포함되지 않는다. 최대 큐잉크기를 넘어선다면 503 Service Unavailable 을 응답한다.

presets

AWS Elastic Transcoder 와 동일한 인터페이스와 기능을 제공한다.

"presets" : {
  "list": [
    {
      "name": "low",
      "video":{
        "bitRate":"900",
        "maxWidth":"640",
        "maxHeight":"480"
      },
      "watermarks":[
        {
          "maxWidth": "20%",
          "maxHeight": "20%",
          "sizingPolicy": "ShrinkToFit",
          "horizontalAlign": "Right",
          "horizontalOffset": "10px",
          "verticalAlign": "Bottom",
          "verticalOffset": "10px",
          "opacity": "55.5",
          "target": "Content"
        }
      ]
    }
  ]
}
list

프리셋 정의

preset 세부옵션

설정옵션

container

flac, flv, fmp4, gif, mp2, mp3, mp4, mpg, mxf, oga, ogg, ts, wav, webm

speed

ultrafast , superfast , veryfast , faster , fast , medium (기본) , slow , slower , veryslow

audio.enable

true (기본) , false

audio.codec

AAC, flac, mp2, mp3, pcm, vorbis

audio.codecOptions.profile

auto, AAC-LC, HE-AAC, HE-AACv2

audio.bitDepth

8, 16, 24, 32

audio.sampleRate

auto, 22050, 32000, 44100, 48000, 96000

audio.bitRate

출력 파일의 오디오 비트 전송률(KB/초)

audio.bitRateForce

false (기본) 라면 bitRate 가 원본을 초과하지 않도록 제한한다. true 라면 강제로 조정한다.

audio.channels

auto, 0, 1, 2

video.enable

true (기본) , false

video.codec

gif, H.264, mpeg2, vp8, vp9

video.codecOptions.profile

baseline, main, high, 0, 1, 2, 3

video.codecOptions.level

1, 1b, 1.1, 1.2, 1.3, 2, 2.1, 2.2, 3, 3.1, 3.2, 4, 4.1

video.codecOptions.maxReferenceFrames

최대 참조 프레임 수

video.codecOptions.maxBitRate

최대 비트 전송률

video.codecOptions.interlacedMode

Progressive, TopFirst, BottomFirst, Auto

video.codecOptions.colorSpaceConversion

None, Bt709ToBt601, Bt601ToBt709, Auto

video.codecOptions.chromaSubsampling

yuv420p, yuv422p

video.keyframesMaxDist

키 프레임 사이의 최대 프레임

video.fixedGOP

true, false

video.bitRate

auto , 출력 파일의 비디오 비트 전송률(KB/초)

video.bitRateForce

false (기본) 라면 bitRate 가 원본을 초과하지 않도록 제한한다. true 라면 강제로 조정한다.

video.frameRate

auto, 10, 15, 23.97, 24, 25, 29.97, 30, 50, 60

video.maxFrameRate

10, 15, 23.97, 24, 25, 29.97, 30, 50, 60

video.maxWidth

auto, [128,4096]

video.maxHeight

auto, [96,3072]

video.sizingPolicy

Fit, Fill, Stretch, Keep, ShrinkToFit, ShrinkToFill

video.paddingPolicy

Pad, NoPad

video.displayAspectRatio

auto, 1:1, 4:3, 3:2, 16:9

watermarks.maxWidth

최소 16 ~ video.maxWidth , [0,100]%

watermarks.maxHeight

최소 16 ~ video.maxHeight , [0,100]%

watermarks.sizingPolicy

Fit , Stretch , ShrinkToFit

watermarks.horizontalAlign

Left , Right , Center

watermarks.horizontalOffset

[0,100]% 또는 [0,video:maxWidth]px

watermarks.verticalAlign

Top , Bottom , Center

watermarks.verticalOffset

[0,100]% , [0,video:maxHeight]px

watermarks.opacity

[0,100]

watermarks.target

Content , Frame

built-in

별도 설정없이도 동작하는 프리셋을 지원한다. 빌트인 프리셋은 _ 로 시작하는 규칙을 가진다.

name

container

video

audio

width

height

bitrate

_1080p

mp4

H.264

AAC

1920

1080

5400 kbps

_720p

mp4

H.264

AAC

1280

720

2400 kbps

_480p

mp4

H.264

AAC

854

480

1200 kbps

_360p

mp4

H.264

AAC

640

360

720 kbps

_gif2mp4

mp4

H.264

N.A.

auto

auto

3000 kbps

_mp42webp

webp

webp

N.A.

auto

auto

360 kbps

_mp42avif

avif

AV1

N.A.

auto

auto

360 kbps

_gif2webp

webp

webp

N.A.

auto

auto

360 kbps

_gif2avif

avif

AV1

N.A.

auto

auto

360 kbps

_gif2webm

webm

vp9

opus

auto

auto

auto

_mp42webm

webm

AV1

opus

auto

auto

auto

Warning

  • 원본 동영상이 container 포맷과 다르다면, 원본으로 리디렉션한다.

  • presets.list[].name 이름을 _1080p 로 build-in과 동일하게 지정하여 오버라이딩 가능하다. 하지만 혼란스러움을 감당할만큼 가치있는 일이 아니다.

{
  "list": [
    {
      "name":"_1080p",
      "container":"mp4",
      "audio":{
        "enable": true,
        "codec":"AAC",
        "codecOptions":{
            "profile":"AAC-LC",
            "bitDepth":"8"
        },
        "sampleRate":"44100",
        "bitRate":"128",
        "channels":"2"
      },
      "video":{
        "enable": true,
        "codec":"H.264",
        "speed": "medium",
        "codecOptions":{
          "profile":"high",
          "level":"4",
          "maxReferenceFrames": "3",
          "interlacedMode":"progressive",
          "colorSpaceConversionMode":"none",
          "chromaSubsampling":"yuv420p"
        },
        "keyframesMaxDist": "24",
        "fixedGOP": false,
        "bitRate": "5400",
        "frameRate":"23.97",
        "maxFrameRate":"30",
        "maxWidth":"1920",
        "maxHeight":"1080",
        "sizingPolicy":"ShrinkToFit",
        "paddingPolicy":"NoPad",
        "displayAspectRatio":"auto"
      }
    },
    {
      "name":"_720p",
      "container":"mp4",
      "audio":{
        "enable": true,
        "codec":"AAC",
        "codecOptions":{
            "profile":"AAC-LC",
            "bitDepth":"8"
        },
        "sampleRate":"44100",
        "bitRate":"128",
        "channels":"2"
      },
      "video":{
        "enable": true,
        "codec":"H.264",
        "speed": "medium",
        "codecOptions":{
          "profile":"high",
          "level":"3.1",
          "maxReferenceFrames": "3",
          "interlacedMode":"progressive",
          "colorSpaceConversionMode":"none",
          "chromaSubsampling":"yuv420p"
        },
        "keyframesMaxDist": "24",
        "fixedGOP": false,
        "bitRate": "2400",
        "frameRate":"23.97",
        "maxFrameRate":"30",
        "maxWidth":"1280",
        "maxHeight":"720",
        "sizingPolicy":"ShrinkToFit",
        "paddingPolicy":"NoPad",
        "displayAspectRatio":"auto"
      }
    },
    {
      "name":"_480p",
      "container":"mp4",
      "audio":{
        "enable": true,
        "codec":"AAC",
        "codecOptions":{
            "profile":"AAC-LC",
            "bitDepth":"8"
        },
        "sampleRate":"44100",
        "bitRate":"128",
        "channels":"2"
      },
      "video":{
        "enable": true,
        "codec":"H.264",
        "speed": "medium",
        "codecOptions":{
          "profile":"main",
          "level":"3",
          "maxReferenceFrames": "3",
          "interlacedMode":"progressive",
          "colorSpaceConversionMode":"none",
          "chromaSubsampling":"yuv420p"
        },
        "keyframesMaxDist": "240",
        "fixedGOP": false,
        "bitRate": "1200",
        "frameRate":"23.97",
        "maxFrameRate":"30",
        "maxWidth":"854",
        "maxHeight":"480",
        "sizingPolicy":"ShrinkToFit",
        "paddingPolicy":"NoPad",
        "displayAspectRatio":"auto"
      }
    },
    {
      "name":"_360p",
      "container":"mp4",
      "audio":{
        "enable": true,
        "codec":"AAC",
        "codecOptions":{
            "profile":"AAC-LC",
            "bitDepth":"8"
        },
        "sampleRate":"44100",
        "bitRate":"128",
        "channels":"2"
      },
      "video":{
        "enable": true,
        "codec":"H.264",
        "speed": "medium",
        "codecOptions":{
          "profile":"Baseline",
          "level":"3",
          "maxReferenceFrames": "1",
          "interlacedMode":"progressive",
          "colorSpaceConversionMode":"none",
          "chromaSubsampling":"yuv420p"
        },
        "keyframesMaxDist": "24",
        "fixedGOP": false,
        "bitRate": "720",
        "frameRate":"23.97",
        "maxFrameRate":"30",
        "maxWidth":"640",
        "maxHeight":"360",
        "sizingPolicy":"ShrinkToFit",
        "paddingPolicy":"NoPad",
        "displayAspectRatio":"auto"
      }
    },
    {
      "name": "_gif2mp4",
      "container": "mp4",
      "audio": {
        "enable": false
      },
      "video": {
        "enable": true,
        "codec": "H.264",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuv420p"
        },
        "fixedGOP": false,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    },
    {
      "name": "_mp42webp",
      "container": "webp",
      "audio": {
        "enable": false
      },
      "video": {
        "enable": true,
        "codec": "webp",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuv420p"
        },
        "fixedGOP": false,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    },
    {
      "name": "_mp42avif",
      "container": "avif",
      "audio": {
        "enable": false
      },
      "video": {
        "enable": true,
        "codec": "AV1",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuv420p"
        },
        "fixedGOP": false,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    },
    {
      "name": "_gif2webp",
      "container": "webp",
      "audio": {
        "enable": false
      },
      "video": {
        "enable": true,
        "codec": "webp",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuv420p"
        },
        "fixedGOP": false,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    },
    {
      "name": "_gif2avif",
      "container": "avif",
      "audio": {
        "enable": false
      },
      "video": {
        "enable": true,
        "codec": "AV1",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuv420p"
        },
        "fixedGOP": false,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    },
    {
      "name": "_gif2webm",
      "container": "webm",
      "audio": {
        "enable": true,
        "codec": "opus",
        "bitRateForce": false
      },
      "video": {
        "enable": true,
        "codec": "vp9",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuva420p"
        },
        "bitRateForce": true,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    },
    {
      "name": "_mp42webm",
      "container": "webm",
      "audio": {
        "enable": true,
        "codec": "opus",
        "bitRateForce": false
      },
      "video": {
        "enable": true,
        "codec": "SVT_AV1",
        "codecOptions": {
          "profile": "main",
          "chromaSubsampling": "yuva420p"
        },
        "bitRateForce": true,
        "sizingPolicy": "ShrinkToFit",
        "paddingPolicy": "NoPad"
      }
    }
  ]
}

capture

capture 명령어로 추출되는 이미지 출력형태를 설정한다.

"capture" : {
  "format" : "webp",
  "interval" : "1s"
}
format (기본: webp)

출력 포맷 webp , jpg , png , gif , avif , mp4 를 지원한다.

Note

jpg , png 와 같이 animated를 지원하지 않는 포맷에서 다구간을 추출하는 경우라면 첫 장만 추출된다.

interval (기본: 1s)

다구간 animated 이미지 추출시 화면전환 간격

Warning

비디오 가로, 세로 길이가 고정되면 비율을 유지하지 않는다. 추출된 이미지에 대한 가공은 hyperdims 를 연계하는 것을 권장한다.

/video.mp4/xcdr/capture/ts=10/hdims/resize/640