Kotlin, Amazon Elastic Transcoder로 원본 동영상에서 오디오만 추출하기
by Taehyeong Lee
개요
- 대용량의 원본 비디오 파일을 다양한 네트워크 대역폭과 디바이스에 대응하기 위해 다양한 프리셋으로 변환하는 요건은 리얼 월드에서 빈번하게 발생한다. 이 경우 오픈 소스에서 보편적으로 사용되는 툴은
FFmpeg,HandBrake CLI가 대표적이다. 이번 글에서는 오픈 소스를 사용하지 않고 AWS의 트랜스코더 상품인Amazon Elastic Transcoder를 사용하여 원본 비디오 파일에서 오디오 파일을 추출하는 방법을 정리했다. (프리셋만 변경하면 오디오 뿐만 아니라 다양한 형식의 비디오로 변환할 수 있다.)
build.gradle.kts
- 프로젝트의 /build.gradle.kts 파일에 아래 내용을 추가한다.
dependencies {
implementation("software.amazon.awssdk:elastictranscoder:2.21.37")
}
ElasticTranscoderClient 생성
- 트랜스코더 사용을 위한 첫 단계로 ElasticTranscoderClient 오브젝트를 아래와 같이 생성한다.
val transcoderClient = ElasticTranscoderClient
.builder()
// 대한민국에서 가장 가까운 도쿄 리전으로 설정
.region(Region.AP_NORTHEAST_1)
.build()
- Amazon Elastic Transcoder는 서울 리전을 제공하지 않는다. 그래서 대한민국에서 가장 가까운 도쿄 리전을 사용했다.
시스템 프리셋 목록 조회
- 트랜스코딩을 하려면 트랜스코딩을 어떻게 할지를 정의한 Preset ID를 지정해야 한다. 트랜스코딩을 위한 커스텀 프리셋을 직접 생성할 수도 있고, 사전에 제공되는 시스템 프리셋을 사용할 수도 있다.
// 현재 사용 가능한 프리셋 목록을 조회
transcoderClient.listPresets().presets().forEach {
// Id=1351620000001-100141, Arn=arn:aws:elastictranscoder:ap-northeast-1:776875668468:preset/1351620000001-100141, Name=System preset: Audio AAC - 64k, Description=System preset: Audio AAC - 64 kilobits/second, Container=mp4, Audio=AudioParameters(Codec=AAC, SampleRate=44100, BitRate=64, Channels=1, CodecOptions=AudioCodecOptions(Profile=auto)), Type=System
println("$it")
}
- 본 예제에서는 AAC로 트랜스코딩할 것이라 AAC 관련 시스템 프리셋 목록을 추리면 아래와 같다.
Id=1351620000001-100110, Arn=arn:aws:elastictranscoder:ap-northeast-1:776875668468:preset/1351620000001-100110, Name=System preset: Audio AAC - 256k, Description=System preset: Audio AAC - 256 kilobits/second, Container=mp4, Audio=AudioParameters(Codec=AAC, SampleRate=44100, BitRate=256, Channels=2, CodecOptions=AudioCodecOptions(Profile=AAC-LC)), Type=System
Id=1351620000001-100120, Arn=arn:aws:elastictranscoder:ap-northeast-1:776875668468:preset/1351620000001-100120, Name=System preset: Audio AAC - 160k, Description=System preset: Audio AAC - 160 kilobits/second, Container=mp4, Audio=AudioParameters(Codec=AAC, SampleRate=44100, BitRate=160, Channels=2, CodecOptions=AudioCodecOptions(Profile=AAC-LC)), Type=System
Id=1351620000001-100130, Arn=arn:aws:elastictranscoder:ap-northeast-1:776875668468:preset/1351620000001-100130, Name=System preset: Audio AAC - 128k, Description=System preset: Audio AAC - 128 kilobits/second, Container=mp4, Audio=AudioParameters(Codec=AAC, SampleRate=44100, BitRate=128, Channels=2, CodecOptions=AudioCodecOptions(Profile=AAC-LC)), Type=System
Id=1351620000001-100141, Arn=arn:aws:elastictranscoder:ap-northeast-1:776875668468:preset/1351620000001-100141, Name=System preset: Audio AAC - 64k, Description=System preset: Audio AAC - 64 kilobits/second, Container=mp4, Audio=AudioParameters(Codec=AAC, SampleRate=44100, BitRate=64, Channels=1, CodecOptions=AudioCodecOptions(Profile=auto)), Type=System
트랜스코딩 Job 요청 및 결과 확인
- 아래는 S3에 업로드된 원본 비디오 파일에서 오디오만 AAC로 트랜스코딩하여 추출하여 S3에 결과물을 업로드하는 예제이다.
// 트랜스코딩을 요청할 파이프라인 ID
val pipelineId = "{elastic-transcoder-pipeline-id}"
// 원본 파일의 S3 Key를 지정
// S3 Bucket은 파이프라인 생성 시점에 사전 지정
val inputFileKey = "{input-s3-key}"
// 대상 파일의 S3 Key를 지정
// S3 Bucket은 파이프라인 생성 시점에 사전 지정
val outputFileKey = "{output-s3-key}"
// [Audio AAC - 128k] 시스템 프리셋을 지정
val presetId = "1351620000001-100130"
val jobInput = JobInput.builder()
.key(inputFileKey)
.aspectRatio("auto")
.container("auto")
.frameRate("auto")
.resolution("auto")
.build()
val createJobOutput = CreateJobOutput
.builder()
.key(outputFileKey)
.presetId(presetId)
.build()
val createJobRequest = CreateJobRequest
.builder()
.pipelineId(pipelineId)
.input(jobInput)
.output(createJobOutput)
.build()
// 트랜스코딩 Job을 파이프라인에 요청
val createJobResponse = transcoderClient.createJob(createJobRequest)
- 존재하지 않는 Preset ID를 지정하면 아래와 같이
ResourceNotFoundException예외가 발생함에 유의한다.
ResourceNotFoundException: The specified preset was not found: account={account}, presetId={preset-id}
- 요청된 Job의 실행 결과를 아래와 같이 확인할 수 있다.
repeat((1..1000).count()) {
// 10초마다 트랜스코딩 Job 처리 결과 확인
// JVM 19 이상에서는 Virtual Thread를 사용
Thread.sleep(1000 * 10)
val readJobResponse = transcoderClient
.readJob(
ReadJobRequest
.builder()
.id(createJobResponse.job().id())
.build()
)
when (readJobResponse.job().status().uppercase()) {
// 트랜스코딩 완료 결과를 처리
"COMPLETE" -> {
val queuedAt: Instant = Instant.ofEpochMilli(readJobResponse.job().timing().submitTimeMillis())
val startedAt: Instant = Instant.ofEpochMilli(readJobResponse.job().timing().startTimeMillis())
val endedAt: Instant = Instant.ofEpochMilli(readJobResponse.job().timing().finishTimeMillis())
val outputFileSize: Long = readJobResponse.job().output().fileSize()
}
// 트랜스코딩 에러 결과를 처리
"ERROR" -> {}
}
}