YoutubeDL(ytdlp)をAPI化した
最近は需要自体をあまり聞きませんが、昔は Youtube 動画をダウンロードしたいというニーズがありました。実は今でもあったりしますが、サブスクサービスに多くがとってかわられました。
しかし、お気に入りのクリエイターが突如作品をすべて消してしまう[1]、そもそもサブスクにならないなどの理由で、やっぱりダウンロードしておかないとだめだよね。という状況はかなりあります。
ので、yt-dlp
というオープンソースの Web コンテンツダウンローダーを API 化してみました。
外部Webサービスのように、ダウンロードのたびにページを開く必要がなく、例えば次のようなiOSショートカットでボタンひとつで動画をダウンロードできるようになりました。
リポジトリについて
こちらになります。
HyperV
などでVMを立てることができたり、RaspberryPi
にUbuntuを入れて使っている方は、セットアップをしてgit clone
したdocker-compose.yml
で設定していただければと思います。
構築方法
端的に示すと、docker.io
、docker-compose
、cifs
をインストールして、docker-compose up -d
すれば動きます。
各種ソフトウェアをインストールして...
sudo apt update
sudo apt install docker.io cifs-utils
sudo gpasswd --add $USER docker
newgrp docker
sudo curl -SL https://github.com/docker/compose/releases/download/v2.4.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
設定を作成して
sudo mkdir -p /mnt/video
sudo tee "//<WindowsのIP>/<共有ディレクトリのパス> /mnt/video cifs nofail,_netdev,x-systemd.automount,user=<ユーザ名>,password=<パスワード>,file_mode=0664,dir_mode=0775 0 0" /etc/fstab
sudo mount -a
サービスを起動するだけです。
docker-compose up -d
これでもうサーバの5000番に対してHTTPリクエストを投げれば動画をダウンロードできます。
詳しいインストール方法はREADME.md
に詳しく書いていますが、単に使うだけなら上で十分です。
動機について
導入に書いた通りで、もう一つの目的として、以前、ラズパイ ×KEDA でサーバーレス入門という名目で作成したものを、より手軽に展開したいという部分でリファクターを行った形になります。
こちらが前プロジェクトです。今見るとかなり滑稽な景色が広がっておりますが...
仕組み
3種類のコンテナをDocker-compose
にて構築しています。
また、保存先としてWindows
でのファイル共有で共有されたパスをUbuntu
へマウントし、これに動画を保存するようにしました。
- ytdlpserver-api
- ユーザの http リクエストを受ける口
- redis
- リクエストを一時的に格納する DB
- ytdlpserver-worker
- ytdlp を実際に実行してファイルを保存する機構
- Windows マシン
- Windows の共有機能(Samba/Cifs と呼ばれるもの)で共有
技術仕様
- ytdlp は
Python
制のプログラムなので、プログラム自体もPython
で書いた - Python で API を展開するにあたって、一番使われているであろう
Flask
ライブラリを採用した- リリース展開も
waitress
のserve
に置き換えるだけでよいため、非常に簡単
- リリース展開も
- 可能な限り簡単にサービスを展開したかったため、コンテナ技術を用いた
- 今回は
Docker(Docker-compose)
を使うことにした - コンテナは可能な限り容量を減らしたかったため、
Alpine Linux
を用いた
- 今回は
- 非同期で処理をしたかったため、メッセージキューを実装する必要があった
- 元のプロジェクトではイベントドリブン(イベントが発火された際にのみリソースを確保する仕組み)を用いていた
-
Docker-compose
を使い、元プロジェクトではCNI
を使っていた内容をbridge
に簡略化した
- Python で非同期処理を簡単に実装できる
rq
ライブラリ(Redis Queue
)を用いた
インフラについて
構築について記載していきます。
構築するUbuntu環境
今回はWindows
上にHyperV
で仮想サーバを構築しました。
インターフェースは外部ネットワークに直に接続していて(NATではなく、仮想サーバ自体がホストマシンのNICを専有している状態)ベアメタル的な使い方をしています。[2]
ネットワークは以下のような感じです。Outboud Switch
という仮想スイッチを定義していて、これをマシンに設定しています。
これはRealtek PCIe GbE
(イーサネットアダプタ)に直接紐づいています。NICとスイッチが一対一で紐づいているため、仮想マシンにIPアドレスを割り当てることができます。
プライベートネットワークであればどんなデバイスからでもアクセスが可能で、iPhoneやiPadからリクエストを投げることができるのも、この設定のおかげです。
docker-compose.yml
docker compose は以下のようになっています。
version: '3'
services:
redis:
image: redis
api:
build:
dockerfile: ./Dockerfile.api
depends_on:
- redis
environment:
RQ_REDIS_URL: redis://redis
ports:
- 5000:5000
worker:
build:
dockerfile: ./Dockerfile.worker
depends_on:
- redis
environment:
RQ_REDIS_URL: redis://redis
volumes:
- /mnt/video:/download
working_dir: /download
- 外部からの情報を受けるのは
service: api
のみで、その他のコンテナについてはコンテナネットワーク(docker-compose の機能)でつながっているため、意識する必要がありません。- ポートは
5000
番を開けています。左辺に利用したいポートを設定してください。api: build: ...(省略)... ports: - 5000:5000
- ポートは
-
service: worker
ではダウンロードした動画を保存する先をマウントしています。この辺のコードですコードではワークディレクトリにダウンロードをするため、volumes: - /mnt/video:/download working_dir: /download
Docker-compose
でホストのディレクトリをバインドし、狙った箇所にダウンロードするようにしています。 - Redis へのアクセスは
redis://redis
で行います。本プログラムを書くために、参考した親記事がそうしていた[3]ためRQ_REDIS_URL
へ環境変数としていますが、埋め込みでも問題ないです。
プログラムについて
Pythonプログラムについて、軽く書いておきます。
動的型付け言語について
一般のプログラム、例えばTypescript
は静的型付け言語といい、基本的にはきちっとルールを決めて進めるタイプの言語です。
let a: number = 100;
a = a.toString() + "is 100"; // <--- 型が違うため、エディタ上でエラーしてくれる
a += 1;
型は常に定義に従っていることを強制されるため、コードを読む場合はこれをなぞるだけで問題ありません。
一方、Python は動的型付け言語といって、型情報が割と適当に設定できてしまう言語です。
a = 100 ## -> 勝手にintとして判断され、intになる
a = str(a) + "is 100" ## -> aはintからstrに変換されるため、エラーしない
a += 1 ## -> エディタ上ではエラーしないが、上の工程でaはstr型になっているため、エラーする
もちろんエラーとして表示されないだけで、実際の処理で矛盾が発生するなどした場合は処理が停止します。
このため、変数の状態は常に監視しておく必要があります。きも...
動的型付け言語であることを生かして、型によって処理を分岐などができれば Python マスターなのかもしれませんが、非常に難しい領域だと思います。
コーディングルールの設定
上記のように、Python は非常に難しい言語であるため、プロジェクトではruff
という Linter/Formatter を VSCode プラグインとして導入しました。
拡張をいれると Formatter が使えるようになるため、Shift+Alt+F
でコードを整頓してくれたり、読みやすいコードになるようにエディタが注意をしてくれます。
本プロジェクトでは、基本的にはすべてのルールに従い、便宜上不要そうな箇所についてのみignore
で無視する設定をいれるような形で記載しました。
[tool.ruff]
select = ["ALL"]
ignore = [
"D",
"T201",
"TRY301",
"BLE001",
"S104",
]
実際のコードについて
ひねったことはしていないので、あくまで概要レベルで記載しておきます
- main.py
- APIの受け口になるモジュール。
Flask
でHTTPメソッドを受け付けています。
- APIの受け口になるモジュール。
- function.py
- ytdlpで処理する内容を記載しているモジュール。
rq
が呼び出すために使います。
- ytdlpで処理する内容を記載しているモジュール。
- worker.py
-
rq
がredisと接続し、処理を実行するために記載されます。
-
- __init__.py
- モジュールの最初に読み込まれますが、特に何もしていません。
今後の課題
- セットアップ手順の前段として、Ubuntu Serverを実行する方法が記載されているべき
- そもそもUbuntu Server環境を持っている利用者しか利用ができない状況
- とはいえ外部サービスとして展開するのは、本プロダクトの趣旨から外れているため、あくまで環境構築までパッケージ化されている必要がある
- DockerDesktopでの利用方法も図式化するべきか?
- そもそもUbuntu Server環境を持っている利用者しか利用ができない状況
-
Docker
ではなくPodman
を使うべき[4]- Dockerの雲行きが怪しいため
- ただし、手軽さを重視するべきであるため必須ではない。
-
RaspberryPi
で気軽に使えるようなパッケージにしてもいいかもしれない- これはもともとのプロジェクト(https://github.com/nkte8/serverless )で実現したかった内容
- kubernetesよりはライトに実装できれば実用的であるが、現状は難しい。
所感
語るほどの内容でもありませんが、そこそこ便利なものが用意できたのかなと思います。
少なくとも、自身でパソコンを壊してしまった際には立て直しができそうです。
本記事は、せっかく対外的にも使えるものを作ったにも関わらず、物珍しいわけでもなく、おそらく埋もれてしまうと考え、自身の整理も兼ねて記事執筆をしてみました。
お読みの方にとって、なにかの足しになれば幸いです。
-
ハヤシダ(林田匠)さんのひみつのうた(KarenT)が AppleMusic から消えたときは本当に焦りました。楽曲購入派で本当に良かった。AppleMusic 上から消えていることで、アートワークの再取得ができなかった起因で発覚しました。 ↩︎
-
この構成、いろんなデバイスからアクセス可能な開発環境としてかなり優秀なので、おすすめです。VSCodeを使っている場合はRemote Developmentなんかを使うと、ほぼネイティブに使うことができます。 ↩︎
-
Python で分散タスクキュー (RQ 編)の内容そのままです。なんの工夫もせずキューイングができるの、めちゃくちゃすごい。 ↩︎
-
一応いろいろ試しはしました。コメント部分に形跡を残しています。CNI あたりがなかなか移行の障壁になりそうです。 ↩︎
Discussion
cifsよりsambaの方がいいかもです。最近知ったのですが「cifsはSamba v1の共通規格」っぽいので...
そういう点ではsambaはaptに配置されていて、確かこれがv2以降だったはずなので、動作実績は取っていませんがこちらを使うことをおすすめします。
ちょっと本筋とは異なる質問で恐縮ですが。
切り抜き動画系もこの類で、多分にこのニーズが現存していると思っているのですが。違うのでしょうか?
なるほど!盲点でした。ニーズあると思います。