AWS

AWS MediaConvert SDK 사용 방법 (2) - Job 생성 코드

알쓸개잡 2023. 8. 18.

이전 포스팅에서는 MediaConvert 를 사용하기 위한 role 을 생성하는 방법에 대해서 알아 보았습니다. 이번 포스팅에서는 Spring Boot 프로젝트에 MediaConvert SDK 를 사용하여 Job 을 생성하는 샘플코드를 작성해 보겠습니다.

 

AWS MediaConvert 디펜던시

AWS MediaConvert SDK 를 사용하기 위해서는 software.amazon.awssdk:mediaconvert 디펜던시가 필요합니다.

샘플코드를 작성하기 이전에 트랜스코딩 대상 미디어 파일은 S3 에 업로드 되어 있다고 가정하겠습니다.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.20.87</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>io.awspring.cloud</groupId>
            <artifactId>spring-cloud-aws-dependencies</artifactId>
            <version>3.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
	...
	<dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>mediaconvert</artifactId>
    </dependency>
    <dependency>
        <groupId>io.awspring.cloud</groupId>
        <artifactId>spring-cloud-aws-starter-sns</artifactId>
    </dependency>
	<dependency>
        <groupId>io.awspring.cloud</groupId>
        <artifactId>spring-cloud-aws-autoconfigure</artifactId>
    </dependency>
    ...
</dependencies>

 

software.amazon.awssdk (AWS java SDK) 와 io.awspring.cloud 에 정의된 패키지들을 사용할 것입니다. 

io.awspring.cloud (spring cloud AWS) 는 AWS 관련 서비스를 사용하기 위한 인프라 관련 코드를 최대한 피할 수 있도록 Spring 에서 제공하는 Spring 기반 통합 모듈입니다. 즉, AWS 서비스를 사용하기 위한 코드를 보다 간단한 설정으로 사용할 수 있게 해줍니다.

io.awspring.cloud 관련 내용은 아래 링크를 참고하시면 도움이 됩니다.

https://docs.awspring.io/spring-cloud-aws/docs/2.4.1/reference/html/index.html#using-amazon-web-services

 

참고로 mediaconvert artifact 가 어떤 버전을 사용하는지 확인하려면

software.amazon.awssdk:bom 파일과 software.amazon.aswsdk:aws-sdk-java-pom 파일을 살펴보면 됩니다.

software.amazon.awssdk:bom 파일을 보면 아래와 같이 정의 되어 있습니다.

<parent>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>aws-sdk-java-pom</artifactId>
    <version>2.20.87</version>
    <relativePath>../pom.xml</relativePath>
</parent>

....

<dependencyManagement>
	<dependencies>
    	...
        ...
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>mediaconvert</artifactId>
            <version>${awsjavasdk.version}</version>
        </dependency>
        ...
        ...
	</dependencies>
</dependencyManagement>

software.amazon.awssdk:aws-sdk-java-pom 파일에 보면 awsjavasdk.version 을 확인할 수 있습니다.

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>aws-sdk-java-pom</artifactId>
    <version>2.20.87</version>
    <packaging>pom</packaging>
    <name>AWS Java SDK :: Parent</name>
    ...
    ...
    <properties>
        ...
        <awsjavasdk.version>${project.version}</awsjavasdk.version>
        ...
    </properties>
    ...
    ...
</project>

예시에서 사용된 software.amazon.awssdk 의 project.version 은 2.20.87 임을 알 수 있습니다.

 

application.yml 설정

...
spring:
  cloud:
    aws:
      credentials:
        access-key: ${AWS_ACCESS_KEY}
        secret-key: ${AWS_SECRET_KEY}
      region:
        static: ${AWS_REGION}
        
media-convert:
  role-arn: <MediaConvert role arn 정보를 입력합니다>
...

spring.cloud.aws.credentials 는 spring cloud AWS 모듈에서 참조하는 정보로써 AWS 계정 인증 정보를 입력합니다.

예시에서는 AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_REGION 을 시스템 환경변수로 등록하여 설정한 것입니다. 환경 변수로 등록하지 않고 직접 값을 입력해도 됩니다.

media-convert.role-arn 에는 앞선 AWS MediaConvert SDK 사용 방법 (1) - role 생성하기 에서 생성한 role 의 arn 정보를 입력합니다. 

 

 

MediaConvertClient Bean 생성

package global.voda.operator.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
import software.amazon.awssdk.services.mediaconvert.MediaConvertClient;
import software.amazon.awssdk.services.mediaconvert.model.DescribeEndpointsRequest;
import software.amazon.awssdk.services.mediaconvert.model.DescribeEndpointsResponse;

import java.net.URI;

@Slf4j
@Configuration
public class AwsMediaConvertConfig {
    @Bean
    public MediaConvertClient mediaConvertClient(AwsCredentialsProvider credentialsProvider, 
                                                 AwsRegionProvider regionProvider) {
        DescribeEndpointsResponse describeEndpointsResponse = MediaConvertClient.builder()
            .credentialsProvider(credentialsProvider)
            .region(regionProvider.getRegion())
            .build()
            .describeEndpoints(
            	//한번에 반환할 MediaConvert 서비스 endpoint 최대 개수 지정
            	DescribeEndpointsRequest.builder().maxResults(20).build()
            );

        if (describeEndpointsResponse.endpoints().size() == 0) {
            log.error("cannot find MediaConvert service endpoint URL");
            throw new RuntimeException("cannot find MediaConvert service endpoint URL");
        }

        String endPointUrl = describeEndpointsResponse.endpoints().get(0).url();
        if (log.isDebugEnabled()) {
            log.debug("MediaConvert endpoint URL: {}", endPointUrl);
        }

        //가져온 MediaConvert 서비스 endpoint URL 을 MediaConvertClient 에 셋팅하여 
        //MediaConvertClient Bean 을 생성
        return MediaConvertClient.builder()
            .region(regionProvider.getRegion())
            .endpointOverride(URI.create(endPointUrl))
            .build();
    }
}

AwsCredentialsProvider, AwsRegionProvider 는 spring.cloud.aws.credentials 설정을 기반으로 자동 주입됩니다.

 

 

MediaConvert Job 생성 코드

package com.example.mediaconvert.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.mediaconvert.MediaConvertClient;
import software.amazon.awssdk.services.mediaconvert.model.*;

@Service
@Slf4j
@RequiredArgsConstructor
public class MediaConvertServiceEx {
    @Value("${media-convert.role-arn:}")
    private String roleArn;

    private final MediaConvertClient mediaConvertClient;

    /**
     * 동영상을 트랜스 코딩하기 위해서 media convert job 을 생성하는 메소드
     *
     * @param s3InputPath 트랜스코딩 대상 영상 파일 S3 object 경로
     * @param s3OutputPath 트랜스코딩 결과 파일 저장하기 위한 S3 object 경로
     * @throws MediaConvertException MediaConvert 예외
     */
    public void videoConvert(String s3InputPath,
                             String s3OutputPath) throws MediaConvertException {
        Output.Builder outputBuilder = commonOutputBuilder();
        Output output = outputBuilder.videoDescription(
            VideoDescription.builder()
                .codecSettings(VideoCodecSettings.builder()
                    //mp4 형식 출력
                    .codec(VideoCodec.H_264)
                    .h264Settings(H264Settings.builder()
                        .maxBitrate(1000000)
                        .rateControlMode(H264RateControlMode.QVBR)
                        .sceneChangeDetect(H264SceneChangeDetect.TRANSITION_DETECTION)
                        .build())
                    .build())
                .build()
        ).build();

        OutputGroup.Builder outputGroupBuilder =
            commonOutputGroupBuilder(s3OutputPath);
        OutputGroup outputGroup = outputGroupBuilder
            .outputs(output)
            .build();

        Input input = Input.builder()
            .timecodeSource(InputTimecodeSource.ZEROBASED)
            .fileInput(s3InputPath)
            .build();

        executeMediaConvert(outputGroup, input);
    }

    /**
     * 이미지를 정지 영상으로 생성하기 위해서 media convert job 을 생성하는 메소드
     *
     * @param s3InputPath    이미지 파일 S3 object 경로
     * @param s3OutputPath   트랜스코딩 결과 파일 저장하기 위한 S3 object 경로
     * @param width          이미지 파일 가로 해상도
     * @param height         이미지 파일 세로 해상도
     * @param timeDurationMs 트랜스 코딩된 영상 재생시간 (MS)
     * @throws MediaConvertException MediaConvert 예외
     */
    public void imageConvert(String s3InputPath,
                             String s3OutputPath,
                             int width,
                             int height,
                             int timeDurationMs) throws MediaConvertException {
        int outputWidth = (int) (Math.ceil((double) width / 2) * 2);
        int outputHeight = (int) (Math.ceil((double) height / 2) * 2);
        int framerateNumerator = 30;
        int framerateDenominator = 1;

        Output.Builder outputBuilder = commonOutputBuilder();
        Output output = outputBuilder.videoDescription(
            VideoDescription.builder()
                .width(outputWidth)
                .height(outputHeight)
                .videoPreprocessors(VideoPreprocessor.builder()
                    .imageInserter(ImageInserter.builder()
                        .insertableImages(InsertableImage.builder()
                            .imageX(0)
                            .imageY(0)
                            .layer(1)
                            .imageInserterInput(s3InputPath)
                            .opacity(100)
                            .build())
                        .build())
                    .build())
                .codecSettings(VideoCodecSettings.builder()
                    .codec(VideoCodec.H_264)
                    .h264Settings(H264Settings.builder()
                        .maxBitrate(1000000)
                        .framerateDenominator(framerateDenominator)
                        .framerateControl(H264FramerateControl.SPECIFIED)
                        .framerateNumerator(framerateNumerator)
                        .rateControlMode(H264RateControlMode.QVBR)
                        .sceneChangeDetect(H264SceneChangeDetect.TRANSITION_DETECTION)
                        .build())
                    .build())
                .build()
        ).build();

        OutputGroup.Builder outputGroupBuilder =
            commonOutputGroupBuilder(s3OutputPath);
        OutputGroup outputGroup = outputGroupBuilder
            .outputs(output)
            .build();

        double oneFrameTime = (double) framerateDenominator / framerateNumerator;
        int oneFrameTimeMs = (int)(oneFrameTime * 1000);

        //마지막에 1 frame 시간이 재생시간에 추가되기 때문에 재생시간에서 1 frame 만큼의 시간을 빼준다.
        Input input = Input.builder()
            .videoGenerator(InputVideoGenerator.builder()
                .duration(timeDurationMs - oneFrameTimeMs)
                .build())
            .build();

        executeMediaConvert(outputGroup, input);
    }

    private void executeMediaConvert(OutputGroup outputGroup, Input input) {
        try {
            JobSettings jobSettings = JobSettings.builder()
                .timecodeConfig(TimecodeConfig.builder()
                    .source(TimecodeSource.ZEROBASED)
                    .build())
                .inputs(input)
                .outputGroups(outputGroup)
                .build();

            CreateJobRequest createJobRequest = CreateJobRequest.builder()
                .role(roleArn)
                .settings(jobSettings)
                .build();
            CreateJobResponse jobResponse = mediaConvertClient.createJob(createJobRequest);
            log.info("success to create image media convert job, id={}", jobResponse.job().id());
        } catch (MediaConvertException e) {
            log.error("failure create image media convert job, {}", e.getMessage());
            throw e;
        }
    }

    private OutputGroup.Builder commonOutputGroupBuilder(String s3OutputPath) {
        return OutputGroup.builder()
            .name("File Group")
            .outputGroupSettings(OutputGroupSettings.builder()
                .type(OutputGroupType.FILE_GROUP_SETTINGS)
                .fileGroupSettings(FileGroupSettings.builder()
                    .destination(s3OutputPath)
                    .build())
                .build());
    }

    private Output.Builder commonOutputBuilder() {
        return Output.builder()
            .containerSettings(ContainerSettings.builder()
                .container(ContainerType.MP4)
                .build());
    }
}

샘플 코드에는 동영상 파일을 트랜스코딩 하는 메소드와 이미지 파일을 정지된 동영상 파일로 트랜스코딩 하는 메소드가 있습니다.

이미지 파일 하나를 정지된 동영상 파일로 만드는 경우에는 MediaConvert Job Input 에 재생하고자 하는 시간 길이의 black video 를 생성하고 Output 에는 Encoding settings 에서 Image inserter 에 input 이미지를 지정해서 만듭니다.

 

 

MediaConvert Job 생성 샘플 코드

package com.example.mediaconvert;

import com.example.mediaconvert.service.MediaConvertServiceEx;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@RequiredArgsConstructor
public class MediaconvertApplication implements CommandLineRunner {

	private final MediaConvertServiceEx mediaConvertServiceEx;

	public static void main(String[] args) {
		SpringApplication.run(MediaconvertApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		//이미지 해상도 600 * 120
		String input = "s3://my-media-convert-source/600x120.png";
		String output = "s3://my-media-convert-target/600x120";
        
		//출력 영상 재생시간은 15초
		mediaConvertServiceEx.imageConvert(input, output, 600, 120, 15000);
	}
}

참고로 이미지의 경우 해상도 정보는 tika 라이브러리를 통해서 이미지 해상도 정보를 얻어 올 수 있습니다.

위와 같이 실행해 봤을 때 생성된 MediaConvert Job id 는 아래와 같습니다.

AWS 관리 콘솔 상에서 Job 이 정상적으로 생성이 되었는지 확인해 봅니다.

MediaConvert Job 생성을 위한 셋팅 요소들이 많기 때문에 JOB 생성을 위한 SDK 를 사용하기 전에 AWS 관리 콘솔에서 JOB 을 직접 생성한 후에 Job 목록에서 해당 Job ID 의 상세 정보 페이지에서 [View JSON] 으로 Job 에 대한 명세를 보고 SDK 를 사용하면 많은 도움이 됩니다.

 

지금까지 MediaConvert SDK 를 이용하여 Job 을 생성하는 샘플코드를 작성해 봤습니다.

다음 포스팅에서는 AWS SNS 를 통해 요청된 Job 의 완료 이벤트를 REST API 기반으로 수신하는 방법에 대해서 알아 보겠습니다.

댓글

💲 추천 글