글쓰는 개발자

유튜브 다운로드 프로그램 웹 서빙 본문

IT 서비스 제작과정/Development

유튜브 다운로드 프로그램 웹 서빙

세가사 2024. 4. 30. 09:03
반응형

https://gran007.tistory.com/entry/%EC%9C%A0%ED%8A%9C%EB%B8%8C-%EC%98%81%EC%83%81-%EC%B6%94%EC%B6%9C-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EA%B0%9C%EB%B0%9C

 

유튜브 영상 추출 다운로드 프로그램 개발

pycharm을 실행해서 python 파일을 하나 만들고 샘플 코드를 작성하자.from pytube import YouTubedef download_video(video_url, download_path): yt = YouTube(video_url) yt.streams.filter(progressive=True, file_extension='mp4')\ .first()\ .do

gran007.tistory.com

위 글에서 유튜브 영상 추출 다운로드 프로그램을 파이썬으로 개발했다.

 

매번 파이썬 스크립트를 수정하거나 변수를 직접 입력해서 프로그램을 실행할수 있지만 사용의 편의성을 위해서 혹은 다수가 사용하기 위해서는 웹 API로 서빙을 할수 있게 하는게 훨씬 효율적이다.

 

앞에서 개발한 특정 시간대의 유튜브 영상을 웹에서 서빙하고 다운로드 받을수 있게 수정해보자.

 

파이썬에서 웹서빙을 하기 위해서 fast api를 설치해야 한다. fast api를 사용하기 위해서 이를 서빙하는 서버인 uvicorn도 함께 설치해준다.

pip install fastapi
pip install uvicorn

 

편집된 유튜브 영상을 다운받기 위해서는 3개의 파라미터가 필요하다.

1. youtube id

2. 영상 편집 시작시간

3. 영상 편집 종료시간

youtube id는 youtube에 v= 뒤에 들어오는 값이다.

https://www.youtube.com/watch?v=<YOUTUBE_ID>

 

이 값들을 받기 위해서 app.get 어노테이션에 받아올 파라미터들을 정의한다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/download/youtube/{youtube_id}/{start_time}/{end_time}")
async def download_youtube(youtube_id, start_time, end_time):
...

 

이전글에서 만든 pytube를 이용한 유튜브 다운로드 부분을 추출해서 video_download 함수를 다음과 같이 수정한다. 파라미터를 받아와서 파일을 다운받고 실제 다운받은 유튜브명과 임시로 만든 파일이름을 리턴해주도록 수정했다.

 

임시 이름을 사용하는 이유는 여려명이 같은 유튜브를 요청했을 때 이를 식별하기 위한 랜덤 식별 아이디를 부여하기 위해서이다.

 

from pytube import YouTube
import subprocess
import uuid
import os
...

path_dir = '<파일 다운로드위치>'
path_origin = f'{path_dir}/origin'
path_edit = f'{path_dir}/edit'
ffmpeg_file_path = '<ffmpeg 실행파일위치>'


def download_video(youtube_id, start_time, end_time):
    url = f'https://www.youtube.com/watch?v={youtube_id}'
    yt = YouTube(url)
    temp_file_name = f'{str(uuid.uuid4())}.mp4' # 요청별 임시파일 생성
    yt.streams.filter(progressive=True, file_extension='mp4').first().download(path_origin, filename=f'{temp_file_name}')
    filename = yt.streams.first().default_filename # 실제 유튜브 파일 이름

    subprocess.run(
            f'{ffmpeg_file_path} -i "{path_origin}/{temp_file_name}" -ss {start_time} -to {end_time} -vcodec copy -acodec copy "{path_edit}/{temp_file_name}"')
    os.remove(f'{path_origin}/{temp_file_name}')
    return filename, temp_file_name

 

아까 만든 api에서 download_video 함수를 호출하고 만들어진 편집 유튜브 영상을 요청한 사람에게 리턴해주는 기능을 완성한다.

from starlette.responses import FileResponse

...

@app.get("/download/youtube/{youtube_id}/{start_time}/{end_time}")
async def download_youtube(youtube_id, start_time, end_time):
    filename, temp_file_name = download_video(youtube_id, start_time, end_time)
    return FileResponse(f'{path_edit}/{temp_file_name}', filename=filename))

 

uvicorn이 fast api를 서빙할수 있게 메인을 작성한 뒤 파이썬을 실행한다.

import uvicorn

...

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=3000)

 

 

이제 크롬창에 다음 url을 호출해보자.

http://localhost:3000/download/youtube/<유튜브ID>/<편집시작시간>/<편집종료시간>

파일이 잘 다운로드 된다면 여기서 종료해도 되지만 이런식으로 호출이 계속 일어나면 다운로드된 파일이 파이썬 edit 폴더에도 계속해서 쌓이게 된다. 따라서 파일을 전달한 뒤로는 파일을 삭제해야 한다. cleanup 함수를 작성해서 FileResponse 함수에 background에 추가해주자.

 

from starlette.background import BackgroundTask

...

def cleanup(temp_file_name):
    os.remove(f'{path_edit}/{temp_file_name}')

....
return FileResponse(f'{path_edit}/{temp_file_name}', filename=filename,
                        background=BackgroundTask(cleanup, f'{temp_file_name}'))

 

이제 유튜브 영상을 다운받고 편집하고 전달한 뒤에는 전달된 파일이 삭제될 것이다.

 

마지막으로 매번 url에 api 주소를 입력하는 불편함을 해소하기 위해 간단한 html을 하나 만들어서 파일을 다운로드 받을 수 있는 인터페이스를 구성해보자.

<!DOCTYPE html>
<html>
<body>
<div>
    youtube_id
    <input type="text" id="youtube_id" value="" />
</div>
<div>
    start_time
    <input type="text" id="start_time" value=""/>
</div>
<div>
    end_time
    <input type="text" id="end_time" value=""/>
</div>
    <input type="button" id="download" value="download"/>
    <script type="text/javascript">
        var download = document.getElementById('download');
        download.addEventListener('click', function(){
        var youtube_id = document.getElementById('youtube_id').value;
        var start_time = document.getElementById('start_time').value;
        var end_time = document.getElementById('end_time').value;
        var url = 'http://localhost:3000/download/youtube/'+youtube_id+'/'+start_time+'/'+end_time;
        const a = document.createElement('a')
        a.href = url;
        a.download = url.split('/').pop();
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
        });
    </script>
</body>
</html>

 

youtube id와  시작시간, 끝시간을 입력하고 download 버튼을 누르면 편집된 유튜브를 다운로드 폴더에 받아볼수 있다.

 

python full source는 다음과 같다.

from pytube import YouTube
import subprocess
from fastapi import FastAPI
from starlette.responses import FileResponse
from starlette.background import BackgroundTask
import uuid
import os

app = FastAPI()

path_dir = '<파일 다운로드위치>'
path_origin = f'{path_dir}/origin'
path_edit = f'{path_dir}/edit'
ffmpeg_file_path = '<ffmpeg 실행파일위치>'


def download_video(youtube_id, start_time, end_time):
    url = f'https://www.youtube.com/watch?v={youtube_id}'
    yt = YouTube(url)
    temp_file_name = f'{str(uuid.uuid4())}.mp4'
    yt.streams.filter(progressive=True, file_extension='mp4').first().download(path_origin, filename=f'{temp_file_name}')
    filename = yt.streams.first().default_filename

    subprocess.run(
            f'{ffmpeg_file_path} -i "{path_origin}/{temp_file_name}" -ss {start_time} -to {end_time} -vcodec copy -acodec copy "{path_edit}/{temp_file_name}"')
    os.remove(f'{path_origin}/{temp_file_name}')
    return filename, temp_file_name


def cleanup(temp_file_name):
    os.remove(f'{path_edit}/{temp_file_name}')


@app.get("/download/youtube/{youtube_id}/{start_time}/{end_time}")
async def download_youtube(youtube_id, start_time, end_time):
    filename, temp_file_name = download_video(youtube_id, start_time, end_time)
    return FileResponse(f'{path_edit}/{temp_file_name}', filename=filename,
                        background=BackgroundTask(cleanup, f'{temp_file_name}'))
반응형