ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [AWS] 유저가 업로드한 동영상에서 썸네일 이미지 추출하여 S3에 바로 저장하기 - Java
    AWS/Cloud 2023. 2. 1. 23:31
    반응형

    들어가기 전

    저어어엉말 관련한 글들을 찾기가 어려워서 힘들었는데.. 결국 성공! 하여 누군가에게 도움이 되길 바라며 정리해놓는다.

    [구현 하고 싶었던 기능]

    1. 유저가 자신의 로컬 스토리지에서 영상을 업로드 

    2. 백에 MultipartFile 형태로 파일이 전달

    3. 동영상을 바로 S3에 업로드

    4. 동영상에서 썸네일 이미지만 추출하여 S3 업로드

    [시도해본 것]

    1. AWS MediaConvert
    - 미디어 컨버트를 활용해 S3 버킷을 인풋용/아웃풋용으로 나누고 인풋 버킷에 영상 파일이 업로드 되면, 미디어 컨버트를 통해 썸네일로 변환하여 아웃풋 버킷에 저장하도록 함
    - 해당 방법은 아웃풋 버킷에 무조건 full video도 함께 저장하여야 하고.. 미디어 컨버트는 영상의 분 단위로 비용 책정을 해서 비용이 너무 많이 발생함 => Drop

    2. ffmpeg
    - 보통은 cmd로 명령어를 직접 날리는 코드를 작성하는 예시가 많은데 다양한 실행환경에서 호환이될까? 라는 의문에 최대한 자바 코드로 풀려고 하였으나 실패
    - 또한 서버를 로컬에다 띄우지 않고 EC2 여러대에 띄울 예정이었는데, ffmpeg가 dependency만 추가하면 되는게 아니라 프로그램도 로컬에 설치하고 해야 하다보니 호환을 어떻게 할지 복잡하여 Drop

    3. 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

    댓글

Designed by Tistory.