ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [1일차] MP3 Tools 웹앱 개발 시작
    프로젝트 개발/MP3 Tools 웹앱 (Drop) 2021. 5. 16. 17:13
    반응형

    한동안 관심을 가졌던 ffmpeg.wasm(https://ddochea.tistory.com/147) 으로 웹 서비스를 개발해보았다. 이번에는 광고를 붙이고 소소하게 수입이 들어오길 바라는 야망(?)을 품고 있기 때문에 어느정도 완성이 되면 URL을 공개할 것이다.

     

    첫 시작으론 mp3 파일에 cover image를 추가하는 기능이다. ffmpeg.wasm 을 사용해보았기 때문에 구현자체는 어렵지 않았다.

    Cover 이미지를 mp3에 추가하는 기능화면

    프로젝트 구성

    이번 프로젝트는 지난번 "뮤직플레이어 웹앱" 때와 마찬가지로 소스코드를 오픈해두었다. 다만 지난시간엔 App.ts 에 화면, 기능영역 구분없이 구현했었다. 이번엔 비즈니스 로직과 화면영역을 분리할 계획이다.

    각 메뉴별 화면(views)과 로직(FFmpegService) 분리

     

    아이콘 및 파비콘 디자인

    아이콘은 Figma를 통해 디자인하였다. 처음엔 어려웠지만 youtube에 figma로 검색하여 익혀가며 만들어보았다. 가운데 음표는 Google Fonts에서 검색하여 붙여넣었다.

     

    싱글톤(singleton) 패턴을 이용한 ffmpeg 객체 접근처리

    FFmpegService는 ffmpeg.wasm 객체를 감싸 각 화면별 필요기능을 구현해둔 웹 서비스이다. 화면별 공유 ffmpeg 객체를 사용해야 하기 때문에 싱글톤 구조로 구현했다.

    import { createFFmpeg, FFmpeg } from "@ffmpeg/ffmpeg";
    
    class FFmpegService {
      private static _instance: FFmpegService;
      private ffmpeg: FFmpeg;
    
      private constructor() {
        this.ffmpeg = createFFmpeg({ log: true });
        this.ffmpeg.load();
      }
    
      public static getInstance() {
        return this._instance || (this._instance = new FFmpegService());
      }
    
      public async importCoverImage(img: File, mp3: File) {
        if(!(img && mp3)) {
          throw new Error("file can not be null.");
        }
        const imgExt = img.getExtension();
        if(!["png", "jpg"].some(n => n === imgExt)) {
          throw new Error("not image file. check file extension (.png, .jpg)"); 
        }
        const musicExt = mp3.getExtension();
        if(!["mp3"].some(n => n === musicExt)) {
          throw new Error("not music file. check file extension (.mp3)"); 
        }
        const ffmpeg = this.ffmpeg;
        const coverName = `img.${imgExt}`
        const musicName = `music.${musicExt}`
          ffmpeg.FS(
            "writeFile",
            coverName,
            new Uint8Array(await img.arrayBuffer())
          );
          ffmpeg.FS(
            "writeFile",
            musicName,
            new Uint8Array(await mp3.arrayBuffer())
          );
          const args = [
            "-i",
            musicName,
            "-i",
            coverName,
            "-c:a",
            "copy",
            "-c:v",
            "copy",
            "-map",
            "0:0",
            "-map",
            "1:0",
            "-id3v2_version",
            "3",
            "output.mp3",
          ]; // must split
          await ffmpeg.run(...args);
        return ffmpeg.FS("readFile", "output.mp3"); // 파일명은 영어로 고정시켜서 써야 제대로 되는 듯 하다.
      }
    }
    // singleton 구조로 구현
    export default FFmpegService.getInstance();
    
    반응형

    댓글

Designed by Tistory.