본문 바로가기

개발/웹개발

프리미어 프로가 너무 비싸 동영상 편집기를 JS로 만들어보자...

2023 전국 B모듈 첫 번째 동영상 편집 및 미리보기 개발 구현



주요 기능은 

  • 동영상 스냅샷,  속성값 저장, 드래그 앤 드롭, 편집 미리보기 등이 있다

 

해당 문제를 풀기 위해 알아야 할 지식

해당 문제를 풀기 위해 알아야 할  JS 문법은 
기본 VIDEO API 구조 및 드래그 앤 드롭에 대한 이해도가 있어야 하며
비디오마다 속성들을 저장하고 활용해야 한다면 클래스나 객체를 이용해야 한다 
메인 비디오의 currentBar를 이동할 때 JS의 버블링과 dispatchEvent를 알아야 
유연하게 개발할 수 있다.

 

 

JS 개발 코드

const file = document.querySelector("#file");
const video = document.querySelector("#video");

const nowPage = document.querySelector("#nowPage");
const endPage = document.querySelector("#endPage");
const nowTime = document.querySelector("#nowTime");
const endTime = document.querySelector("#endTime");
const title = document.querySelector("#title");

const videoNav = document.querySelector("#videoNav");
const editVideo = document.querySelector("#editVideo");
const snapshotNav = document.querySelector("#snapshotNav");
const snapSelect = document.querySelector("#snapSelect");
const currentBar = document.querySelector("#currentBar");
const videoEditWrap = document.querySelector("#videoEditWrap");
const videosWrap = document.querySelector("#videosWrap");

const leftV = document.querySelector("#left");
const rightV = document.querySelector("#right");

const btns = document.querySelectorAll(".btns");


let Data = [], page = 1, snapshot = 10;




let selected = false;

currentBar.addEventListener("mousedown",(e)=>{
    selected = true;
})

addEventListener("mouseup",(e)=>{
    selected = false;
})

currentBar.addEventListener("mousemove",(e)=>{
    e.stopPropagation();

    videoEditWrap.dispatchEvent(new MouseEvent("mousemove",e))
})

btns.forEach(element => {
    element.addEventListener("mousemove",(e)=>{
        e.stopPropagation();
    
        videoEditWrap.dispatchEvent(new MouseEvent("mousemove",e))
    })
});

btns.forEach(element => {
    element.addEventListener("click",(e)=>{
        e.stopPropagation();
    
        // videoEditWrap.dispatchEvent(new MouseEvent("click",e))
    })
});







currentBar.addEventListener("click",(e)=>{
    e.stopPropagation();

    videoEditWrap.dispatchEvent(new MouseEvent("click",e))
})

videoEditWrap.addEventListener("mousemove",(e)=>{
    if(selected && e.offsetX <= 1000) {
        currentBar.style.left = e.offsetX+"px";
        currentBar.className = e.offsetX;

        

        video.currentTime = e.offsetX / 1000 * video.duration;
        nowTime.textContent = timeFormat(Math.ceil(video.currentTime));

        Data[page-1].currentBar = e.offsetX+"px";
        Data[page-1].currentTimes = Math.ceil( video.currentTime);
        
        localStorage.setItem("filePath", JSON.stringify(Data));
    }
})

videoEditWrap.addEventListener("click",(e)=>{
    currentBar.style.left = e.offsetX+"px";
    currentBar.className = e.offsetX;

    video.currentTime = e.offsetX / 1000 * video.duration;
    nowTime.textContent = timeFormat(Math.ceil(video.currentTime));

    Data[page-1].currentBar = e.offsetX+"px";
    Data[page-1].currentTimes = Math.ceil(video.currentTime);

    localStorage.setItem("filePath", JSON.stringify(Data));
})

addEventListener("keydown",(e) => {
    if(e.key == "f"){
        file.click();
    }
    if(e.key == "i"){
      
        if((Number(currentBar.className) + Number(rightV.className)) <= 1000){
            leftV.style.width =  currentBar.className+"px";
            leftV.className = (currentBar.className);
            Data[page-1].left = (currentBar.className);
            Data[page-1].start = (currentBar.className) / 1000 * video.duration;
            editVideo.currentTime =  (currentBar.className) / 1000 * video.duration;
            localStorage.setItem("filePath", JSON.stringify(Data));
            
        }   
        
    }
    if(e.key == "o"){
        
        if((Number(leftV.className) + Number(1000-currentBar.className)) <= 1000){
            rightV.style.width =  (1000-currentBar.className)+"px";
            rightV.className = (1000-currentBar.className);
            Data[page-1].right = (1000-currentBar.className);
            Data[page-1].end = (currentBar.className) / 1000 * video.duration;
            localStorage.setItem("filePath", JSON.stringify(Data));
        }
       
    }

    if(e.key == "r") {
        leftV.className = "0";
        rightV.className = "0";
        leftV.style.width = "0px";
        rightV.style.width = "0px";
        Data[page-1].left = (currentBar.className);
        Data[page-1].right = (1000-currentBar.className);
        Data[page-1].start = 0;
        Data[page-1].end = video.duration;

        
        localStorage.setItem("filePath", JSON.stringify(Data));
    }

    if(e.key == "d"){
        let small = [];
        for(let i = 0; i < Data.length; i++){
            if(i != page - 1){
                small.push(Data[i])
            }
        }

        if(page > small.length){
            page--;
        }
        

        console.log(small);
        Data = small;
        videoDraw();    
    }
    if(e.keyCode == 32){ // 스페이스바
        if(editVideo.paused && Data[page-1].end != 0){ 
            editVideo.play();
           
        }
        else {
            editVideo.pause();
        }
    }
})



file.addEventListener("change",()=>{

  

    for(let i = 0; i < file.files.length; i++) {

        file.files[i].left = 0;
        file.files[i].right = 0;
        file.files[i].currentBar = "0px";
        file.files[i].currentTimes = 0;
        file.files[i].start = 0;
        file.files[i].end = 0;
            
        Data.push(file.files[i])
        Data[i].names = file.files[i].name;
        console.log(Data);
    }


    for(let i = 0; i < file.files.length; i++) {
      
        if(localStorage.getItem("filePath") != null){
            let small = JSON.parse(localStorage.getItem("filePath"));
            

        
            small.forEach(element => {
                if(element.names == file.files[i].name){
                    Data[i].left = element.left;
                    Data[i].right = element.right;
                    Data[i].currentBar = element.currentBar;
                    Data[i].currentTimes = element.currentTimes;
                    Data[i].start = element.start;
                    Data[i].end = element.end;
                    Data[i].names = element.names;
                }
            });
        }
    }






    videoNav.style.display = "flex";



    videoDraw();

})

snapSelect.addEventListener("change",(e)=>{
    snapshot = e.target.value;
    snapDraw();
})

function snapDraw() {
    snapshotNav.innerHTML = "";

    let time = video.duration /  (snapshot - 1)
  
    for(let i = 0; i < snapshot; i++){
     
        const videos = document.createElement("video");

        videos.style.width = 90 / snapshot +"%";

        videos.src = URL.createObjectURL(Data[page-1]);
        
    
        videos.currentTime = time * i;
      

        videos.addEventListener("click",(e)=>{
            video.currentTime = time * i;

            currentBar.style.left =  (time * i) / video.duration  * 1000 +"px";
            currentBar.className = (time * i) / video.duration  * 1000;

            e.stopPropagation();
            nowTime.textContent = timeFormat(Math.ceil(video.currentTime));

            Data[page-1].currentBar = (time * i) / video.duration * 100+"%";
            Data[page-1].currentTimes = Math.ceil(time * i);

        })

        videos.addEventListener("mousemove",(e)=>{
            e.stopPropagation();
            videoEditWrap.dispatchEvent(new MouseEvent("mousemove",e))
        })

        snapshotNav.appendChild(videos);
    }
}


function left() {
    if(page != 1) {
        page--;
        videoDraw()
    }

}

function right() {
    if(page != Data.length){
        page++;
        videoDraw();
    }
}

function timeFormat(n) {
    let hour = Math.floor(n / 3600);
    let minute = Math.floor(n % 3600 / 60);
    let second = n % 60;

    if(hour < 10){
        hour = "0"+hour;
    }
    if(minute < 10){
        minute = "0"+minute;
    }
    if(second < 10) {
        second = "0"+second;
    }

    return `${hour}:${minute}:${second}`;
}

function videoDraw() {

 
    endPage.textContent = Data.length;

    videosWrap.innerHTML = "";
    
    video.src = URL.createObjectURL(Data[page-1]);
    editVideo.src = URL.createObjectURL(Data[page-1]);
    
    nowPage.textContent = page;
    console.log(Data[page-1].currentTimes);
    nowTime.textContent = timeFormat(Data[page-1].currentTimes);
    title.textContent = Data[page-1].names;
    currentBar.style.left = Data[page-1].currentBar;
    currentBar.className = (Data[page-1].currentBar).replace(/[^0-9]/g,'');
    leftV.className = Data[page-1].left
    rightV.className = Data[page-1].right
    editVideo.currentTime = Data[page-1].start;

    if(Data[page-1].end == -1) {
        setTimeout(() => {
            Data[page-1].end = video.duration;
        }, 100);
    }

    setTimeout(() => {
        endTime.textContent =  timeFormat(Math.ceil(video.duration));
        snapDraw();
    }, 100);

    leftV.style.width = Data[page-1].left+"px";
    rightV.style.width = Data[page-1].right+"px";


    let temp, start, end;

    for(let i = 0; i <  Data.length; i++){
        let videos = document.createElement("video");
        videos.src = URL.createObjectURL(Data[i]);
        videos.classList.add("videos");
        videos.id = i + 1;
        videos.draggable = true;

        
        videos.addEventListener("dragstart",(e)=>{
            start = e.target.id - 1;    
        });


        videos.addEventListener("dragover", (e) => {
            e.preventDefault();
          });


        videos.addEventListener("drop",(e)=>{
            e.preventDefault();
            end = e.target.id - 1;

            if(page -1 == start){
                page = end+1;
            }
            else if(page -1 == end){
                page = start+1;
            }

            temp = Data[start];
            Data[start] = Data[end];
            Data[end] = temp;

            videoDraw();

        })

        if(i == page-1) {
            videos.classList.add("red");
        }

        setTimeout(() => {
            videos.currentTime = videos.duration / 9; 
            videosWrap.appendChild(videos);
        }, 100);

    
    }


    setTimeout(() => {
        const videos = document.querySelectorAll(".videos");

        videos.forEach(element => {
            element.addEventListener("click",()=>{
                videos.forEach(element => {
                    element.classList.remove("red");
                });
    
                page = element.id;
                videoDraw();
                element.classList.add("red");
                
            })
        });
    }, 100);

    localStorage.setItem("filePath", JSON.stringify(Data));
}



editVideo.addEventListener("timeupdate",()=>{
    if(editVideo.currentTime >= Data[page-1].end && Data[page-1].end != 0){
        editVideo.pause();
        editVideo.currentTime = 0;
    }
})