神社 地域 にデジタルを
❝ユーチューブのダウンロード❞のコーディング
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に一覧表示させます。
投稿内容 ランダム表示
❝ユーチューブのダウンロード❞のコーディング
最終更新:2023年08月18日
豊中市Graphの基データの説明
最終更新:2023年07月14日
❝ワードクラウド ジェネレーター❞のコーディング
最終更新:2023年07月14日
HTMLメールを活用してみた
最終更新:2023年07月14日
python3.10 Django4 wsl bootstrap javascript
カテゴリ
私の願い
私は神社の宮司です。神社や地域を担う次世代の人々に対し、何かを残してお役に立ててもらいたいとの願いが、強く芽生えました。個業としての神社や、小規模な地域社会に、恩恵が届くのが遅くなりそうな「デジタル」の分野。門外漢として奮闘した実体験から得た経験則を、わずかずつでも残し未来につなぎたいと願うばかりです。
最近の投稿
- 簡易で安価なカメラで防犯・外出対策を
最終更新:2023年09月08日
- 神社のオリジナルTシャツを作ってみた
最終更新:2023年08月07日
- HTMLメールを活用してみた
最終更新:2023年07月14日
- 豊中市Graphの基データの説明
最終更新:2023年07月14日