[Spring Boot] mp3 파일 정보 추출

2023. 9. 13. 18:55기술 창고/Spring

728x90
SMALL

음악 플랫폼을 만들어보고 싶은 생각이 들어서 음악 등록 api를 만들게 되었습니다.

내부 라이브러리를 사용하면 mp3 파일의 가사, 아티스트와 같은 정보들을 추출할 수 없기 때문에 외부의 라이브러리를 사용하게 되었습니다.

 

https://mvnrepository.com/artifact/net.jthink/jaudiotagger/3.0.1

 

다양한 라이브러리가 있겠지만 저는 jaudiotagger 를 사용해주었습니다.

maven repository 에서 jaudiotagger 를 가져와서 build.gradle에 넣어주었습니다.

 

이제 api를 구현하는 것부터 음악 파일을 넣는 과정까지 차례대로 정리해보겠습니다.

 

 

Music

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Entity
public class Music extends TimeStamped {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Long musicId;

    @Column(nullable = false)
    private String musicUuidName; // 난수화된 곡 이름

    @Column(nullable = false)
    private String musicRealName; // 곡 원래 이름

    @Column(nullable = false)
    private String musicUploadUrl; // 곡 업로드된 경로 url

    @Column(columnDefinition = "LONGTEXT")
    private String lyrics; // 가사

    @Column(nullable = false)
    private String genre; // 음악 장르

    @Column(nullable = false)
    private String artist; // 아티스트

    @Column(nullable = false)
    private String playTime; // 재생시간

    @Column(nullable = false)
    private String albumImg; // 앨범 이미지

    @Column(nullable = false)
    private String albumImgUploadUrl; // 업로드 된 이미지 경로 url

    @Column(nullable = false)
    private Long memberId; // 업로드한 유저 아이디
}

음악 정보를 저장하기 위한 Music 엔티티를 만들어줍니다.

필요한 정보는 uuid로 난수화된 곡 이름, 원래 곡 이름, 업로드된 파일의 경로, 가사, 음악 장르, 아티스트, 재생 시간, 앨범 이미지, 이미지 업로드 경로, 업로드한 유저 id 라고 가정하였습니다.

 

가사 정보는 매우 긴 텍스트로 이루어져 있기 때문에 Column 어노테이션에 columnDefinition 옵션을 LongText 로 지정하여 기존의 String 크기 범위를 넘어갈 수 있게끔하였습니다.

 

 

MusicRepository

@Repository
public interface MusicRepository extends JpaRepository<Music, Long> {
}

데이터를 저장할 MusicRepository를 생성합니다.

 

 

MusicController

// 음악 등록 api
@PostMapping("/upload")
public ResponseEntity<ResponseBody> musicUpload(
        HttpServletRequest request,
        @RequestPart MultipartFile music,
        @RequestPart MultipartFile thumbnailImg) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException {
    log.info("음악 등록 api");

    return musicService.musicUpload(request, music, thumbnailImg);
}

이제 controller를 만들어 줍니다.

Spring Security와 JWT를 적용했기 때문에 정상적인 토큰이 발급된 HttpServletRequest 가 필요하고, 음악 파일, 그리고 음악 썸네일 이미지 파일을 매개변수로 받습니다.

 

 

MusicUploadInterface / MusicUpload

@Component
public interface MusicUploadInterface {
    /** 업로드 될 경로 **/
    String getFullPath(String filename, String fileDir);

    /** 업로드 시킬 파일 **/
    HashMap<String, String> serverUploadFile(MultipartFile multipartFile, String fileDir) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException;

    /** 난수화한 업로드할 파일 이름 **/
    String createServerFileName(String originalFilename);

    /** 업로드 파일 확장자 정보 추출 **/
    String extractExt(String originalFilename);
}
@Component
public class MusicUpload implements MusicUploadInterface{
    // 업로드 될 경로
    @Override
    public String getFullPath(String filename, String fileDir) {
        // 파일명과 업로드될 경로 반환
        return fileDir + filename;
    }

    // 업로드 시킬 파일
    @Override
    public HashMap<String, String> serverUploadFile(MultipartFile multipartFile, String fileDir) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException {
        // 업로드할 음악 파일의 진짜 이름 추출
        String originalFilename = multipartFile.getOriginalFilename();
        // 난수화된 파일 이름과 확장자를 합친 파일명 추출
        String serverUploadFileName = createServerFileName(originalFilename);

        // 업로드한 mp3 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
        File file = new File(getFullPath(serverUploadFileName, fileDir));

        // multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송)
        multipartFile.transferTo(file);

        // mp3 파일 정보들을 저장하여 전달하기 위한 HashMap 생성
        HashMap<String, String> musicinfo = new HashMap<>();

        if(extractExt(originalFilename).equals("mp3")){
            // 업로드한 mp3 파일을 jaudiotagger 에서 지원해주는 MP3File 객체 클래스로 읽어서 반영 생성
            MP3File mp3 = (MP3File) AudioFileIO.read(file);
            // mp3 파일 내부에 존재하는 기타 태그 정보들을 Tag 객체 클래스로 따로 생성
            Tag tag = mp3.getTag();

            String title = tag.getFirst(FieldKey.TITLE); // 곡 타이틀
            String artist = tag.getFirst(FieldKey.ARTIST); // 곡 아티스트
            String album = tag.getFirst(FieldKey.ALBUM); // 곡 앨범
            String genre = tag.getFirst(FieldKey.GENRE); // 곡 장르
            String lyrics = tag.getFirst(FieldKey.LYRICS); // 곡 가사

            // 추출한 정보들을 저장
            musicinfo.put("uuidName", serverUploadFileName);
            musicinfo.put("title", title);
            musicinfo.put("artist", artist);
            musicinfo.put("playTime", mp3.getMP3AudioHeader().getTrackLengthAsString());
            musicinfo.put("album", album);
            musicinfo.put("genre", genre);
            musicinfo.put("lyrics", lyrics);
        }

        // 이후 배포 시 mp3 파일과 썸네일 이미지 파일을 s3 와 같은 클라우드 플랫폼에 저장 후 발급된 url 정보도 같이 hashMap에 넣어서 전달해야됨.

        return musicinfo;
    }

    // 난수화한 업로드할 파일 이름
    @Override
    public String createServerFileName(String originalFilename) {
        // 원래 이름이 아닌 난수화한 uuid 이름 추출
        String uuid = UUID.randomUUID().toString();
        // 파일의 원래 이름 중에 . 기호 기준으로 확장자 추출
        String ext = extractExt(originalFilename);

        // 난수화된 이름과 확장자를 합쳐 난수화된 파일명 반환
        return uuid + "." + ext;
    }

    // 업로드 파일 확장자 정보 추출
    @Override
    public String extractExt(String originalFilename) {
        // 파일명의 . 기호가 몇번째에 존재하는지 인덱스 값 추출
        int pos = originalFilename.lastIndexOf(".");

        // 원래 이름에서 뽑은 인덱스값에 위치한 . 기호 다음 확장자 추출
        return originalFilename.substring(pos + 1);
    }
}

실질적으로 음악 파일을 업로드 시킬 인터페이스를 따로 만들어 주었습니다.

지금은 로컬 환경에서 업로드할 것이기 때문에 프로젝트 내부 경로에 저장되게 하였습니다.

이후에는 아마존의 s3 와 같은 기능을 연동하게 될 경우, 해당 업로드 경로를 적용시킬 것입니다.

 

인터페이스에는 총 네가지의 함수로 구성되어있습니다.

1. 파일명과 경로를 합친 업로드 경로를 뽑아낼 getFullPath 함수

파일 명과 업로드 경로를 매개변수로 받아 붙여서 최종 업로드 경로 반환합니다.

 

2. 파일을 업로드하고 파일 정보를 추출하여 반환할 serverUploadFile 함수

음악 파일과 업로드 경로를 매개변수로 받아서 transferTo 함수로 파일을 미리 프로젝트 내부 경로에 저장하고 jaudiotagger 에서 지원해주는 MP3File 클래스와 Tag 클래스를 사용하여 음악 파일 내부 정보 추출 후, HashMap을 사용하여 저장 후 반환

 

MP3File 객체 클래스로 변환한 음악 파일의 tag에 음악파일의 정보들이 저장되어있습니다.

Tag 객체 클래스로 해당 정보들을 가져오고, FieldKey.{태그명} 을 통해 가져올 정보들을 지정한뒤, tag의 getFirst함수로 실제로 정보들을 가져옵니다.

 

재생 시간은 MP3File 객체 클래스에서 지원하는 .getMP3AudioHeader().getTrackLengthAsString() 함수들을 통해 플레이 시간을 흔히 볼 수 있는 mm:ss 형식으로 추출해줍니다.

 

이제 추출한 모든 정보들을 HashMap에 담아 반환시켜줍니다.

 

3. 파일명을 난수화해서 확장자를 다시 붙여 재정의한 createServerFileName 함수

파일명을 매개변수로 받고 UUID.randomUUID 함수로 랜덤 파일명을 만든 뒤, 전달받은 파일명을 extractExt 함수에 넣어 확장자를 따로 뽑아서 난수화된 파일명과 확장자를 합친 파일명으로 추출합니다.

 

4. 확장자를 추출할 extractExt 함수

전달받은 파일명의 . 기호 기준으로 뒤에 있는 확장자를 따로 추출합니다.

 

 

MusicService

// 음악 등록 service
public ResponseEntity<ResponseBody> musicUpload(
        HttpServletRequest request,
        MultipartFile music, MultipartFile thumbnailImg) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException {

    // 발급된 jwt 토큰의 정합성이 옳바르지 않을 때 예외 처리
    if (tokenExceptionInterface.checkToken(request.getHeader("Refresh-Token"))) {
        new ResponseEntity<>(new ResponseBody(StatusCode.UNAUTHORIZE_TOKEN, null), HttpStatus.BAD_REQUEST);
    }

    // 로컬 환경 음악 파일 및 썸네일 이미지 파일 저장 경로 (이후 배포 시 s3를 연동하면서 경로를 따로 지정해주어야함.)
    String filePath = "C:" + File.separator + "MyProject" + File.separator + "MusicIsMyLife" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator;

    // 음악 파일 업로드 경로 지정 및 이름 추출
    HashMap<String, String> musicinfo = musicUploadInterface.serverUploadFile(music, filePath + "upload-music/");

    // 썸네일 이미지 파일 업로드 경로 지정 및 이름 추출
    musicUploadInterface.serverUploadFile(thumbnailImg, filePath + "upload-thumbnail/");

    Music uploadMusic = Music.builder()
            .musicUuidName(musicinfo.get("uuidName"))
            .musicRealName(music.getOriginalFilename())
            .musicUploadUrl(filePath + "upload-music/") // 이후 서버에 배포 시 s3 연동 후 url 정보로 대체
            .lyrics(musicinfo.get("lyrics"))
            .genre(musicinfo.get("genre"))
            .artist(musicinfo.get("artist"))
            .playTime(musicinfo.get("playTime"))
            .albumImg(thumbnailImg.getOriginalFilename())
            .albumImgUploadUrl(filePath + "upload-thumbnail/") // 이후 서버에 배포 시 s3 연동 후 url 정보로 대체
            .memberId(jwtTokenProvider.getMemberFromAuthentication().getMemberId())
            .build();

    // 음악 정보 DB 저장
    musicRepository.save(uploadMusic);

    // 결과 확인용 Dto 객체
    MusicUploadResponseDto responseDto = MusicUploadResponseDto.builder()
            .musicId(uploadMusic.getMusicId())
            .musicUuidName(uploadMusic.getMusicUuidName())
            .musicRealName(uploadMusic.getMusicRealName())
            .musicUploadUrl(uploadMusic.getMusicUploadUrl())
            .lyrics(uploadMusic.getLyrics())
            .genre(uploadMusic.getGenre())
            .artist(uploadMusic.getArtist())
            .playTime(uploadMusic.getPlayTime())
            .albumImg(uploadMusic.getAlbumImg())
            .albumImgUploadUrl(uploadMusic.getAlbumImgUploadUrl())
            .memberId(uploadMusic.getMemberId())
            .build();

    HashMap<String, Object> resultSet = new HashMap<>();
    resultSet.put("uploadData", responseDto);

    return new ResponseEntity<>(new ResponseBody(StatusCode.OK, resultSet), HttpStatus.OK);
}

구현한 코드의 시나리오는 이렇습니다.

 

(1)

발급된 유저의 토큰 정보가 옳바른지 처음에 확인합니다.

 

(2) 

File.separator 와 프로젝트 폴더 명을 합쳐서 업로드 경로 변수를 만듭니다.

 

(3)

MusicUploadInterface 의 음악 업로드 함수 serverUploadFile 에 음악 파일과 업로드 경로를 매개변수로 넘겨 추출받은 HashMap 음악 파일 정보들을 가져옵니다.

 

(4)

썸네일 용 음악 이미지 파일도 업로드 될 수 있게끔 MusicUploadInterface 의 음악 업로드 함수 serverUploadFile 에 이미지 파일과 업로드 경로를 매개변수로 넘겨 이미지 파일이 저장만 되게끔 합니다.

 

(5)

가져온 음악 정보 파일들 musicInfo 를 기반으로 Music 엔티티에 넣어줍니다.

 

(6)

MusicRepository 로 jpa save 함수를 통해 데이터 저장합니다.

 

(7)

저장된 결과를 확인하기 위해서 MusicUploadResponseDto 객체를 생성하여 결과 정보들 반영

그리고 결과 HashMap 에 저장합니다.

 

(8)

api 가 호출되면 저장된 MusicUploadResponseDto 를 반환합니다.

 

 

이제 Postman을 통해 api를 호출해봅시다.

 

 

Headers 에는 발급받은 JWT 토큰 정보(Authorization : 액세스 토큰, Refresh-Token : 리프레시 토큰) 을 넣어주고, Body에는 음악 파일과 썸네일 이미지를 넣어주었습니다.

 

 

정상적으로 데이터가 저장되고 추출되었습니다.

가사도 길지만 정상적으로 저장되었습니다.

 

무사히 저장 및 추출 완료했습니다!

728x90
반응형
LIST