본문 바로가기

Python

[Python] Youtube Downloader #1

What to do?

유튜브 다운로더 만들기


구현 내용

 

  • Python 서버를 실행
    • terminal에 python app.py 입력

 

  • 브라우져를 열고, localhost:5000 주소로 접속

 

  • 주소를 입력하고 다운로드 버튼을 누름
    • 다운로드 버튼disbaled 

 

  • 다운로드 완료
    • Detail Page에 제목, 조회수, 설명, 썸네일을 보여줌
    • 다운로드 버튼 다시 활성화

 

  • 다운로드 완료
    • downloaded폴더에 다운로드 됨


프로젝트 구조

 

  • .venv : 가상환경
  • downloaded : 다운로드한 파일이 저장되는 경로
  • templates : html 파일
  • app.py : flask로 만든 서버


가상환경 만들기

 

  • 가상환경 생성 (Window 기준)

가끔 보안오류 때문에 에러가 나는데, 구글링하면 Powershell에서 해결하는 방법 나옴

py -3 -m venv .venv

 

  • 가상환경 활성화
.venv/scripts/activate

 

  • 라이브러리 설치
    • pytube
    • flask


app.py

 

import json
from flask import Flask, jsonify, redirect, render_template, request
import logging
from pytube import YouTube 

app = Flask(__name__)

@app.route("/")
def index():
    return render_template('download.html')

@app.route('/detail')
def detail():
    return render_template('detail.html')
   
@app.route('/download', methods=['POST'])
def download():
    # parsing post request
    req = json.loads(request.get_data().decode())
    link = req.get('link')
    option = req.get('option')

    # create youtube instance
    yt = YouTube(link)
    if option.upper() == "AUDIO":
        stream = yt.streams.filter(only_audio=True)[0]
    elif option.upper() == "VIDEO":
        stream = yt.streams.get_highest_resolution()
    else:
        raise Exception("Option is not valid")

    # get meta data
    meta_data = {
        'title':yt.title,
        'length':yt.length,
        'thumbnail':yt.thumbnail_url,
        'numView':yt.views,
        'description':yt.description
    }

    try:
        stream.download("./downloaded")
        return {"isSuccess":True, **meta_data}
    except:
        return {"isSuccess":False}


if __name__=='__main__':
    app.debug = True    
    app.run(host="0.0.0.0")

Templates

 

detail.html

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Detail Page</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous"></head>
<body>  

    <div class="container mt-3">

        <div class="row mt-3 mb-3">
            <h1>Detail Page</h1>
        </div>         

        <hr/>

        <div class="row mt-3 mb-3">
            <div class="col-md-1">
                <span class="input-group-text">제목</span>
            </div>
            <div class="col">
                <p id="title"></p>
            </div>
        </div>


        <div class="row mt-3 mb-3">
            <div class="col-md-1">
                <span class="input-group-text">조회수</span>
            </div>
            <div class="col">
                <p id="numView"></p>
            </div>
        </div>

        <div class="row mt-3 mb-3">
            <div class="col-md-1">
                <span class="input-group-text">설명</span>
            </div>
            <div class="col">
                <p id="description"></p>
            </div>
        </div>

        <div class="row mt-3 mb-3">
            <div class="col-md-1">
                <span class="input-group-text">썸네일</span>
            </div>
            <div class="col">
                <img id="thumbnail"/>
            </div>
        </div>

    </div>
    <script>

        const titleEl = document.getElementById("title");
        const descriptonEl = document.getElementById("description");
        const numViewEl = document.getElementById("numView");
        const thumbnailEl = document.getElementById("thumbnail");
        titleEl.textContent
        window.addEventListener('message', (e) => {
            titleEl.textContent = e.data.title;
            numViewEl.textContent = e.data.numView;
            descriptonEl.textContent = e.data.description;
            thumbnailEl.src = e.data.thumbnail;
        });
        
    </script>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</html>

 

download.html

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>유튜브 다운로더</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous"></head>
<body>  

    <div class="container mt-3">

        <div class="row mt-3 mb-3">
            <h1>유튜브 다운로더</h1>
        </div>
    
        <div class="row mt-3 mb-3">
            <div class="col-md-1">
                <span class="input-group-text">링크</span>
            </div>
            <div class="dropdown col-md-2">
                <select class="form-select" id="option">
                    <option selected value="video">영상</option>
                    <option value="audio">음성</option>
                </select>       
            </div>
            <div class="col-md-6">
                <input id="link" type="text" class="form-control" placeholder="link..."/>
            </div>
            <div class="col-md-2">
                <button id="btn-download" type="button" class="btn btn-primary">다운로드</button>
            </div>
        </div>

        <hr/>

        <iframe id="detail" src="/detail" style="width:100%;height:500px"></iframe>
  
 
    </div>
    <script>
    
        let link = "";
        const linkEl = document.getElementById("link");
        linkEl.onchange = (e) => {link=e.target.value};
        
        let option = "video";
        const optionEl = document.getElementById("option");
        optionEl.onchange= (e) => {option=e.target.value};

        const detailEl = document.getElementById("detail");

        let isSuccess = true;
                
        const downloadBtn = document.getElementById("btn-download");
        downloadBtn.onclick = (e) =>{
            e.preventDefault();
            downloadBtn.disabled = true;
            downloadBtn.textContent = "다운로드 중..."
            fetch("/download", {
                method:'POST',
                body:JSON.stringify({link, option})
            }).then((res)=>{
                return res.json();
            }).then((data)=>{
                detailEl.contentWindow.postMessage(data);
                isSuccess = data.isSuccess;
                if (isSuccess === false){alert("다운로드 실패")}
            }).catch((err)=>{
                isSuccess = false;
                console.log(err);
            }).finally(()=>{
                downloadBtn.disabled = false;
                downloadBtn.textContent = "다운로드"
            })
        }    
    
    </script>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</html>