見出し画像

Django_YouTube動画のURLを入力してmp3にコンバートしてダウンロードするアプリ #296日目

【一言日記】
今日は熟練営業マンの凄さを痛感しました。池袋久しぶりに行ったなー。


PythonでYouTubeから動画をダウンロードする方法の一つである「yt-dlp」を用いて、Djangoでmp3コンバーターを作成してます。

yt-dlp自体に違法性はないと判断されているらしいですが、動画は著作権やYouTube側の権利など色々あると思うので、以下の内容を扱われる際はくれぐれも著作者への配慮をもって、自己責任で行ってください。

今回はDjangoの実装なので、Pythonでyt-dlpを動作させる部分はこちらをご参照ください。


ダウンロードまでの大まかな流れは以下です。
①FormViewからURLの入力を受け取ってバリデーションチェック
②URLをyt-dlpに渡す
(mp3に変換、保存先はDjangoのMEDIAファイル)
④ViewからGETリクエストを送信してファイルダウンロード

↓こんなアプリが出来上がります(見た目は無視してください)

2パターンのダウンロード方法を試している


MEDIAファイルへのパスを有効にする

MEDIAファイルはユーザーの入力によって動的にファイルを作り、それを保存しておきたい時に使うものです。

基本設定は以下です。これで「media_root」という名前のフォルダが作られます。

[config/settings.py]
MEDIA_URL = '/media_root/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media_root')

加えてアプリ側のurlに設定する必要があります。
一番下のところです。

[app/urls.py]
 
urlpatterns = [
    path('', YoutubeConverter.as_view(), name='converter'),
    path('download/<str:file_name>/', FileDownload, name='download')
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

これでmedia_rootにアクセスできるようになりました。
本来ならば、後はmodelの中でFileFieldやImageFieldを使ってファイルを管理することができます。

ただ、今回厄介なのは、ファイルを生成するのはユーザーではなくyt-dlpである点です。フロント側からの入力がないので、うまくmodelに入れて管理していくことができません。

一先ず進めます。


formを定義する

URLを入力するためのformを定義します。URL形式になっているか、空欄になっていないか等をバリデーションチェックします。最終的にURLのリストを返しています。

[forms.py]
 
from django import forms
from django.core.exceptions import ValidationError
import re


class EnterURL(forms.Form):
    urls = forms.CharField(widget=forms.Textarea(attrs={'placeholder': '複数のURLは改行して入力'}))

    def clean(self):
        data = super().clean()
        if not data:
            raise ValidationError("URLを入力してください")

        urls = data['urls'].split()
        for url in urls:
            if not re.match("https?://[\w/:%#\$&\?\(\)~\.=\+\-]+", url):
                raise ValidationError("URLではない入力値が存在しています")

        if len(urls) > 10:
            raise ValidationError("同時に処理できるURLは10個までです")
        return urls


Viewを定義する

formを扱うためのViewを定義します。コンバーターとダウンロード機能の2つが定義されています。yt-dlpの保存先パスにはMEDIA_ROOTを用いています。

[views.py]
 
from yt_dlp import YoutubeDL
import os, glob

from django.http import HttpResponse
from config.settings import MEDIA_ROOT
from django.views.generic.edit import FormView
from django.http import FileResponse

from .forms import EnterURL


class YoutubeConverter(FormView):
    form_class = EnterURL
    template_name = 'youtube-converter.html'

    def form_valid(self, form) -> HttpResponse:
        urls = form.cleaned_data

        ydl_opts = {
            'format': 'bestaudio/best',
            'outtmpl': MEDIA_ROOT + '/%(title)s.%(ext)s',
            'postprocessors': [
                {'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192'},
                {'key': 'FFmpegMetadata'},
            ],
        }
        with YoutubeDL(ydl_opts) as ydl:
            ydl.download(urls)

        file_names = os.listdir(MEDIA_ROOT)
        ctxt = self.get_context_data(form=form, file_names=file_names)
        
        return self.render_to_response(ctxt)


def FileDownload(request, file_name):
    file_path = f'media_root/{file_name}'
    return FileResponse(open(file_path, "rb"), as_attachment=True)

FileDownload()は別ファイルで定義してもいいかもしれません。
urls.pyは冒頭のものをご参照ください。


テンプレートを定義する

formに特別なことはしていません。ダウンロード部分は2パターンを試しています。formタグでhttpリクエストを出すパターンと、aタグでリンクを貼ってリクエストを出すパターンです。どちらでもダウンロードできます。

[youtube-converter.html]

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
</head>
<body>
    <h1>Youtube Converter</h1>
    <div>
        <h3>YouTube動画のURLを入力</h3>
        <form method="post">
            {% csrf_token %}
            {% for field in form %}
            <p>
                {{ field }}
            </p>
            {% endfor %}
            <input type="submit" value="mp3へ変換" />
        </form>
    </div>
    <div>
        <h3>↓(変換後)クリックしてダウンロード</h3>
        {% for file_name in file_names %}
        {{ file_name }}
        <form action="{% url 'download' file_name %}" method="get">
            <input type="submit" value="ダウンロード">
        </form>
        <br>
        <a href="{% url 'download' file_name %}">{{ file_name }}</a>
        <br>
        {% endfor %}
    </div>
    <div>
        {% for error in form.non_field_errors %}
        {{ error }}
        {% endfor %}
    </div>
</body>


これで冒頭のようなアプリが完成します。
ここまでお読みいただきありがとうございました!


参考


この記事が気に入ったらサポートをしてみませんか?