콘텐츠로 이동

화면 속 화면(PiP)

This content is for v0.4. Switch to the latest version for up-to-date documentation.

이 레시피를 마치면 한 클립을 다른 클립 위에 작은 화면으로 띄울 수 있습니다. 크기를 줄여 구석에 놓고, 모서리를 둥글게 하고, 그림자를 드리웁니다. FFmpeg의 overlay와 scale을 문서 하나로 표현하는 셈입니다.

삽입 화면의 크기, 위치, 모서리

섹션 제목: “삽입 화면의 크기, 위치, 모서리”

시각 클립에는 모두 visual 객체가 있습니다. position, anchor, scale, rotation_degrees, opacity입니다. 클립의 masks는 클립이 보이는 영역을 제한합니다. 뒤에 오는 트랙이 위에 그려지므로 삽입 화면은 주 화면보다 위 트랙에 둡니다.

{
"schema_version": 1,
"id": "pip",
"name": "Picture in picture",
"settings": {
"width": 1280,
"height": 720,
"frame_rate": {
"num": 30,
"den": 1
},
"sample_rate": 48000
},
"sources": [
{
"id": "coast",
"locator": "asset://coast",
"kind": "image"
},
{
"id": "forest",
"locator": "asset://forest",
"kind": "image"
}
],
"timeline": {
"tracks": [
{
"id": "main",
"name": "Main",
"kind": "video",
"clips": [
{
"id": "main-clip",
"source_id": "coast",
"timeline_range": {
"start": 0,
"end": 480000
},
"source_range": {
"start": 0,
"end": 480000
}
}
],
"transitions": []
},
{
"id": "inset",
"name": "Inset",
"kind": "video",
"clips": [
{
"id": "inset-clip",
"source_id": "forest",
"timeline_range": {
"start": 60000,
"end": 480000
},
"source_range": {
"start": 0,
"end": 420000
},
"visual": {
"anchor": {
"x": 1,
"y": 1
},
"position": {
"mode": "constant",
"value": {
"x": 0.96,
"y": 0.93
}
},
"scale": {
"mode": "constant",
"value": {
"x": 0.32,
"y": 0.32
}
},
"opacity": {
"mode": "keyframes",
"keyframes": [
{
"time": 60000,
"value": 0,
"interpolation": {
"type": "cubic_bezier",
"x1": 0.2,
"y1": 0,
"x2": 0,
"y2": 1
}
},
{
"time": 108000,
"value": 1,
"interpolation": {
"type": "hold"
}
}
]
}
},
"masks": [
{
"shape": {
"type": "rectangle",
"center": {
"mode": "constant",
"value": {
"x": 0.5,
"y": 0.5
}
},
"size": {
"mode": "constant",
"value": {
"x": 1,
"y": 1
}
},
"corner_radius": {
"mode": "constant",
"value": 0.1
}
}
}
]
}
],
"transitions": []
}
]
}
}
  • 크기. 소스는 먼저 화면비를 유지한 채 캔버스 안에 맞춰집니다. scale은 거기에 곱해지므로, 16:9 캔버스에 16:9 사진을 0.32로 그리면 캔버스 너비의 32%가 됩니다.
  • 위치. anchor는 소스의 어느 점을 position이 가리키는 캔버스 위치에 놓을지 정하며, 둘 다 0~1로 정규화된 값입니다. anchor를 오른쪽 아래 모서리(1, 1)로, position을 (0.96, 0.93)으로 두면, 크기와 관계없이 삽입 화면의 모서리가 오른쪽에서 4%, 아래에서 7% 떨어진 곳에 놓입니다. 기본 앵커는 가운데입니다.
  • 둥근 모서리. 마스크는 소스 전체 크기(center 0.5, 0.5, size 1, 1)의 rectangle에 corner_radius를 준 것입니다. 마스크 도형은 변환 전 클립의 소스 공간 기준이므로 삽입 화면과 함께 크기가 바뀌고 움직입니다. corner_radius는 소스 짧은 변에 대한 비율입니다.
  • 페이드 인. opacity는 0.5초에 0, 0.9초에 1로 키를 잡았습니다. 클립의 visual은 타임라인 시간 기준이므로 이 키들은 0.5초에 시작하는 클립의 timeline_range 안에 있습니다.

그림자는 삽입 화면 바깥에 드리워야 하므로, 삽입 화면을 배치한 다음에 그려야 합니다. 클립의 visual과 masks는 맨 마지막에 적용되므로, 이 작업은 클립의 그림을 만드는 그래프인 컴포지션에서 합니다. 아래 컴포지션은 클립에 마스크를 씌우고, transform 노드로 배치한 뒤, 결과에 alpha2.effect.drop-shadow를 적용합니다.

{
"schema_version": 1,
"id": "pip-shadow",
"name": "Picture in picture with a shadow",
"settings": {
"width": 1280,
"height": 720,
"frame_rate": {
"num": 30,
"den": 1
},
"sample_rate": 48000
},
"sources": [
{
"id": "coast",
"locator": "asset://coast",
"kind": "image"
},
{
"id": "forest",
"locator": "asset://forest",
"kind": "image"
}
],
"compositions": [
{
"id": "inset",
"output": "shadow",
"nodes": [
{
"id": "clip",
"op": {
"type": "clip"
}
},
{
"id": "rounded",
"op": {
"type": "shape_mask",
"input": "clip",
"masks": [
{
"shape": {
"type": "rectangle",
"center": {
"mode": "constant",
"value": {
"x": 0.5,
"y": 0.5
}
},
"size": {
"mode": "constant",
"value": {
"x": 1,
"y": 1
}
},
"corner_radius": {
"mode": "constant",
"value": 0.1
}
}
}
]
}
},
{
"id": "placed",
"op": {
"type": "transform",
"input": "rounded",
"transform": {
"anchor": {
"x": 1,
"y": 1
},
"position": {
"mode": "constant",
"value": {
"x": 0.96,
"y": 0.93
}
},
"scale": {
"mode": "constant",
"value": {
"x": 0.32,
"y": 0.32
}
}
}
}
},
{
"id": "shadow",
"op": {
"type": "effect",
"input": "placed",
"effect": {
"id": "alpha2.effect.drop-shadow",
"version": 1,
"params": {
"opacity": {
"mode": "constant",
"value": {
"type": "float",
"value": 0.8
}
},
"angle": {
"mode": "constant",
"value": {
"type": "float",
"value": -90
}
},
"distance": {
"mode": "constant",
"value": {
"type": "float",
"value": 0.02
}
},
"softness": {
"mode": "constant",
"value": {
"type": "float",
"value": 0.04
}
}
}
}
}
}
]
}
],
"timeline": {
"tracks": [
{
"id": "main",
"name": "Main",
"kind": "video",
"clips": [
{
"id": "main-clip",
"source_id": "coast",
"timeline_range": {
"start": 0,
"end": 480000
},
"source_range": {
"start": 0,
"end": 480000
}
}
],
"transitions": []
},
{
"id": "inset",
"name": "Inset",
"kind": "video",
"clips": [
{
"id": "inset-clip",
"source_id": "forest",
"timeline_range": {
"start": 0,
"end": 480000
},
"source_range": {
"start": 0,
"end": 480000
},
"composition": {
"id": "inset"
}
}
],
"transitions": []
}
]
}
}

클립에서 출력까지의 그래프는 다음과 같습니다.

  1. clip은 삽입 클립 자체의 그림입니다.
  2. rounded는 같은 둥근 사각형을 쓰는 shape_mask 노드입니다. 도형은 입력의 정규화 공간 기준입니다.
  3. placed는 transform 노드입니다. 클립의 visual과 같은 필드를 받으므로 크기와 위치는 앞의 예와 같습니다.
  4. shadow는 배치된 그림에 그림자를 적용하므로, 그림자가 둥근 윤곽을 따라갑니다. angle은 도 단위이고, distance와 softness는 정규화된 길이입니다(카탈로그 기본값은 0.01).

삽입 클립은 "composition": { "id": "inset" }으로 이 컴포지션을 쓰고, visual은 기본값으로 둡니다. 이펙트 파라미터는 타입이 붙은 값({ "type": "float", "value": 0.8 })입니다. 생략한 파라미터는 기본값을 따르며, 여기서는 검은색 color가 그렇습니다. 컴포지션의 키프레임은 클립 시작에서 0이 되는 컴포지션 시간 기준입니다.

소스가 coast와 forest이므로 export에는 각각의 파일이 필요합니다.

터미널 창
alpha2-host export --project pip.json \
--asset coast=coast.jpg --asset forest=forest.jpg \
--out pip.mp4

사진 대신 영상 파일을 쓰려면 소스의 kind를 video로 바꾸고 --asset에 영상 파일을 지정하십시오. 그러면 클립의 source_range가 각 영상의 어느 부분을 재생할지 정합니다.