-
[AWS] 유저가 업로드한 동영상에서 썸네일 이미지 추출하여 S3에 바로 저장하기 - JavaAWS/Cloud 2023. 2. 1. 23:31반응형
들어가기 전
저어어엉말 관련한 글들을 찾기가 어려워서 힘들었는데.. 결국 성공! 하여 누군가에게 도움이 되길 바라며 정리해놓는다.
[구현 하고 싶었던 기능]
1. 유저가 자신의 로컬 스토리지에서 영상을 업로드
2. 백에 MultipartFile 형태로 파일이 전달
3. 동영상을 바로 S3에 업로드
4. 동영상에서 썸네일 이미지만 추출하여 S3 업로드
[시도해본 것]
1. AWS MediaConvert
- 미디어 컨버트를 활용해 S3 버킷을 인풋용/아웃풋용으로 나누고 인풋 버킷에 영상 파일이 업로드 되면, 미디어 컨버트를 통해 썸네일로 변환하여 아웃풋 버킷에 저장하도록 함
- 해당 방법은 아웃풋 버킷에 무조건 full video도 함께 저장하여야 하고.. 미디어 컨버트는 영상의 분 단위로 비용 책정을 해서 비용이 너무 많이 발생함 => Drop2. ffmpeg
- 보통은 cmd로 명령어를 직접 날리는 코드를 작성하는 예시가 많은데 다양한 실행환경에서 호환이될까? 라는 의문에 최대한 자바 코드로 풀려고 하였으나 실패
- 또한 서버를 로컬에다 띄우지 않고 EC2 여러대에 띄울 예정이었는데, ffmpeg가 dependency만 추가하면 되는게 아니라 프로그램도 로컬에 설치하고 해야 하다보니 호환을 어떻게 할지 복잡하여 Drop3. Jcodec
- 그나마 gradle로 dependency만 추가하면 사용할 수 있을 것 같아서 가능성이 높아 보였음
- 예제 코드를 찾기 너무 어려웠음 => 하지만 chatGPT와 함께 성공!!!! (갓 GPT..)예제 코드
구현 환경
- Java 11
- Spring Boot 2.7.7
build.gradle
- 아래 의존성 추가
implementation group: 'org.jcodec', name: 'jcodec', version: '0.2.3' implementation group: 'org.jcodec', name: 'jcodec-javase', version: '0.2.5' implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'
Controller
@PostMapping public BaseResponse<?> uploadVideoFromLocal(@RequestPart MultipartFile newVideo) throws IOException, JCodecException { // S3 영상 저장 후 URL 얻어오기 // 영상 식별을 위해 파일 앞에 현재 시각 추가 String videoName = DateTime.now() + newVideo.getOriginalFilename(); String s3URL = s3Service.upload(newVideo, videoName); // 썸네일 이미지 생성하여 S3 저장 // videoName처럼 이름을 가져오게 될 경우, 확장자가 함께 와서 잘못 저장될 수 있음 // 이미지 convert를 할 확장자와 동일한 확장자로 이름을 붙여줘야 함!! String thumbnailName = "Thumb"+DateTime.now() + FilenameUtils.getBaseName(newVideo.getOriginalFilename()) + ".JPEG"; String thumbnailURL = s3Service.uploadThumbnail(newVideo, thumbnailName); return ... }
S3 Service
// S3 Client 생성을 위한 코드는 다른 블로그에 설명이 많으므로 생략 // s3Client를 생성해야 아래 코드들이 작동함 // Upload user's local video public String upload(MultipartFile file, String videoName) throws IOException { s3Client.putObject(new PutObjectRequest(bucket, videoName, file.getInputStream(), null) .withCannedAcl(CannedAccessControlList.PublicRead)); return s3Client.getUrl(bucket, videoName).toString(); } // Upload thumbnail from user's local video public String uploadThumbnail(MultipartFile videoFile, String thumbnailName) { File file = convertMultipartFileToFile(videoFile); String thumbnailURL = getThumbnailURL(thumbnailName, file); try { Files.delete(Path.of(file.getPath())); } catch (IOException e) { log.info("파일이 삭제되지 않았습니다."); throw new RuntimeException(e); } return thumbnailURL; } /** * S3에 썸네일 이미지를 저장하고 URL을 가져옴 * JCodec의 FrameGrab을 이용하였음 */ private String getThumbnailURL(String thumbnailName, File file) { // Get image from video try (FileChannelWrapper fileChannelWrapper = NIOUtils.readableChannel(file); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { FrameGrab grab = FrameGrab.createFrameGrab(fileChannelWrapper); Picture picture = grab.seekToSecondPrecise(1.0).getNativeFrame(); BufferedImage bufferedImage = AWTUtil.toBufferedImage(picture); // Convert the image to a JPEG and write it to a ByteArrayOutputStream int width = bufferedImage.getWidth(); int height = bufferedImage.getHeight(); BufferedImage outputImage = new BufferedImage(height, width, bufferedImage.getType()); Graphics2D g2d = outputImage.createGraphics(); AffineTransform at = new AffineTransform(); at.translate(height, 0); at.rotate(Math.PI / 2); g2d.setTransform(at); g2d.drawImage(bufferedImage, 0, 0, null); g2d.dispose(); ImageIO.write(outputImage, "JPEG", baos); baos.flush(); InputStream is = new ByteArrayInputStream(baos.toByteArray()); // Upload the object to S3 s3Client.putObject(new PutObjectRequest(bucket, thumbnailName, is, null)); return cloudFrontUrl + thumbnailName; } catch (JCodecException | IOException e) { throw new RuntimeException(e); } } // Convert MultipartFile to File private File convertMultipartFileToFile(MultipartFile file) throws IOException { File convFile = new File(file.getOriginalFilename()); convFile.createNewFile(); FileOutputStream fos = new FileOutputStream(convFile); fos.write(file.getBytes()); fos.close(); return convFile; }
반응형'AWS > Cloud' 카테고리의 다른 글
[AWS] S3 개념 정리 및 작동 방식 (0) 2023.01.21 [AWS] EC2 개념 원리 및 구성 요소 (0) 2023.01.21