神社 地域 にデジタルを

❝ユーチューブのダウンロード❞のコーディング

2023年05月30日 | 最終更新日:2023年08月18日 | コーディング |
Django4 python3.10 yt_dlp wsl bootstrap

Youtubeの動画をダウンロードする事例を探すと、その多くがPythubeを用いた例でした。

事例を参照して、Pythubeで動作させることはできましたが、動きが安定しないため断念。

youtube-dlの事例もたくさんありましたが、開発が活発でないとのことでこれもさけました。

採用したのは、動きが早いといわれているyt-dlpです。

容量の大きい動画をサーバーに保存したくなかったのと、軽快な動作になるようにさせたかったあたりに苦労しました。

どうにか思っていたものと近い形で動作するようになりました。概要を記します。

<更新:2023-6-22>
&list..., &t...など、youtubeのurl解析に不要な部分を除外するように改良しました。

<更新:2023-8-18>
yt-dlpのパッケージを更新(yt-dlp-2023.7.6)し、ダウンロードページが表示されない動作の不具合を解消しました。


事前準備(概略)

<Poetryで仮想環境を準備(例)>事前にPoetryをインストールしておく

仮想環境をフォルダ直下にする

poetry config virtualenvs.in-project true
~$ mkdir o_poetry_dj
~$ cd o_poetry_dj/
~/o_poetry_dj$ poetry init

以下の設問にEnter、yes、noを入力

This command will guide you through creating your pyproject.toml config.
Package name [o_poetry_dj]:※Enterキー
Version [0.1.0]:※Enterキー
Description []:※Enterキー
Author [user , n to skip]:※Enterキー
License []:※Enterキー
Compatible Python versions [^3.10]:※Enterキー
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file
[tool.poetry]
name = "o_poetry_dj"
version = "0.1.0"
description = ""
authors = ["user "]
[tool.poetry.dependencies]
python = "^3.10"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Do you confirm generation? (yes/no) [yes] yes
poetry shell

<venvの場合(例)>※venvがインストールされていることを確認

python3 -m venv app_o_ytdl
source app_o_ytdl/bin/activate

以後、仮想環境で作業

必要なパッケージをインストール

<poetryの場合>

(仮想名) poetry add yt-dlp 他

<pipの場合>

(仮想名) pip install yt-dlp 他

Djangoプロジェクトにアプリを作成

python manage.py startapp o_ytdl

スーパーユーザーを作成

python manage.py createsuperuser

settings.py(config/settings.py)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    "o_ytdl",
...省略... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / "templates"], ...省略... STATIC_URL = 'static/' STATICFILES_DIRS = [BASE_DIR / "static"] MEDIA_URL = 'media/' MEDIA_ROOT = BASE_DIR / 'media'

プロジェクトフォルダのsettings.pyに記述


urls.py(config/urls.py)

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [ path('admin/', admin.site.urls), path("y_download/", include("o_ytdl.urls")), ...省略... if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

プロジェクトフォルダのurls.pyに記述


フォルダ構成


├── o_ytdl/
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── __init__.py
│   ├── migrations/
│   ├── models.py
│   ├── __pycache__/
│   ├── tests.py
│   ├── urls.py
│   └── views.py

forms.py

youtubeのurlを貼り付けるためのフォームを用意します。


from django import forms


class DownloadForm(forms.Form):
    url = forms.CharField(
        widget=forms.TextInput(attrs={"placeholder": "URLを入力してください"}), label="Input")

urls.py

urls.pyにviewを指定


from django.urls import path
from .views import download_video

app_name = "o_ytdl"

urlpatterns = [
    path("", download_video, name="y_download"),
]

views.py


from django.shortcuts import render

from django.http import HttpResponse
from yt_dlp import YoutubeDL
from .forms import DownloadForm
import re

# YoutubeのURLを解析可能な状態に整形
def url_shape(s):
    
    if "&list" in s:
        idx = s.find("&list")
        return s[:idx]
    elif "&t" in s:
        idx = s.find("&t")
        return s[:idx]
    else:
        return s

def download_video(request):
    global context
    form = DownloadForm(request.POST or None)
    
    if form.is_valid():
        video_url = form.cleaned_data.get("url")
        video_url = url_shape(video_url)
        regex = r'^(http(s)?:\/\/)?((w){3}.)?youtu(be|.be)?(\.com)?\/.+'
        
        print(video_url)
        if not re.match(regex,video_url):
            return HttpResponse("YoutubeのURLを正しく入力してください")
        
        # if '&' in video_url or 'channel' in video_url:
        #    return HttpResponse("URLを見直して入力してください ※listやチャンネルのアドレスは対応できません")
        
        ydl_opts = {'format': 'best'}
        
        with YoutubeDL(ydl_opts) as ydl:
            meta = ydl.extract_info(
                video_url, download=False
            )
            # print(meta["filesize_approx"])
        video_audio_streams = []
        for m in meta["formats"]:            
                
            # file_size = m["filesize_approx"]
            # if file_size is not None:
            #     file_size = f'{round(int(file_size) / 1000000,2)} mb'
                
            # resolution = "Audio"
            # if m["height"] is not None:
            #     resolution = f"{m['height']}x{m['width']}"
            if m["ext"] == "mhtml":
                continue
            
            try:
                m["acodec"] is None
            except:
                continue
            
            video_audio_streams.append({
                "resolution": m["resolution"],
                "extension": m["ext"],
                # "file_size": file_size,
                "acodec": m["acodec"],
                "video_url": m["url"]
            })
        video_audio_streams = video_audio_streams[::-1]
        context = {
            "form": form,
            "title": meta["title"],
            "streams": video_audio_streams,
            "description": meta["description"],
            # "likes": meta["like_count"],
            # "dislikes": meta["dislike_count"],
            "thumb": meta["thumbnails"][3]['url'],
            "duration": round(int(meta["duration"])/60, 2),
            "views": f'{int(meta["view_count"]):,}'
        }
        return render(request, "o_ytdl/home.html", context)
    return render(request, "o_ytdl/home.html", {"form": form})
  • urlがリストやチャンネルの場合は弾くようにしています。22行目
  • youtube-dlのオプションを参考に設定しましたが、
    ytdlpとは一致しないものもあるため、その箇所はコメントアウトしています。43,53~54行目
  • video_audio_streamsは、動画の情報を取得するためのコード。47行目

templates/base.html

※雛形のイメージhtmlです 用途に合わせて手直してください


{% load static %}
<!doctype html>
<html lanh=ja>
    <head>
        <!-- Require meta tags -->
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0", shrink-to-fit=none>
        
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
        
        <!-- custom css & js -->
        <link rel="stylesheet" href="{% static 'style.css' %}">
        <script src="{% static 'main.js' %}" defer></script>
        
        
        <title>Spiner | {% block title %}{% endblock title %}</title>
    </head>
    <body>
        <div class="container mt-3">
            {% block content %}
            {% endblock content %}
        </div>
        <!-- Optional Javascript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS-->
        <script
        src="https://code.jquery.com/jquery-3.6.3.min.js"
        integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU="
        crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
    </body>
</html>

templates/o_ytdl/home.html

base.htmlを継承した表示用のhtmlを作成します。


{% extends 'base.html' %}
{% load static %}
{% load widget_tweaks %}

{% block title %}Video Download{% endblock title %}


{% block contents %}
    <div class="row">
        <div class="col">
            <h3 class="mb-3">ユーチューブのダウンロード</h3>
            <form action="" method="post">
                {% csrf_token %}
                {% for field in form %}
                    <div class="form-group">
                        {{ field.label_tag }}
                        {% render_field field class="form-control" %}
                        {% if field.help_text %}
                            <small class="form-text text-muted">
                                {{ field.help_text }}
                            </small>
                        {% endif %}
                    </div>
                {% endfor %}
                <button type="submit" class="btn btn-dark rounded-capsule">解析する</button>
            </form>            
           
        </div>
    </div>

    {% if title %}
        <div class="col text-center mt-4">
            <img class="img-responsive" src="{{ thumb }}" alt="">
        
            <h5 class="mt-3">{{ title }}</h5>
        </div>


        <p>Duration: {{ duration }}</p>
        <p>Views: {{ views }}</p>
        <P class="text-right"><small>ACODECに値のあるものが音声付き</small></P>

        <table class="table mb-5">
            <tr>
                <th>Resolution</th>
                <th>Extension</th>
                <th>ACODEC</th>
                <th>Download</th>
            </tr>
            {% for stream in streams %}
                <tr>
                    <td>{{ stream.resolution }}</td>
                    <td>{{ stream.extension }}</td>
                    <td>{{ stream.acodec }}</td>
                    <td><a href="{{ stream.video_url}}" style="text-decoration:none"
                        download="{{ title }}.{{ extension }}" target="_blank">Download</a></td>
                </tr>
            {% endfor %}
        </table>        

    {% endif %}
    <p class="text-center text-danger mt-3">※単体動画のアドレスを入力<br>listやチャンネルのアドレスは対応できません<br>
        &list=xxx...など<u>❝&❞以降の文字を削除</u>して解析してください</p> 
{% endblock contents %}

views.py側で記述した、video_audio_streamsの情報を、Tableに一覧表示させます。

私の願い

私は神社の宮司です。神社や地域を担う次世代の人々に対し、何かを残してお役に立ててもらいたいとの願いが、強く芽生えました。個業としての神社や、小規模な地域社会に、恩恵が届くのが遅くなりそうな「デジタル」の分野。門外漢として奮闘した実体験から得た経験則を、わずかずつでも残し未来につなぎたいと願うばかりです。

More

若宮 住吉神社

サイトへ

最近の投稿

Copyright ©All rights reserved | by omo fun

Made withby Toshio Omote