簡単に入手できるパッケージのみを使用して、新しい Raspberry Pi 2 (Raspbian を実行) を使用して、コマンド ラインから YouTube ビデオを視聴する非常に簡単な方法を発見した後、次のようになります。
omxplayer -o local $(youtube-dl -g {videoURL})
YouTube のプレイリスト全体をそのように見る方法がすぐに欲しかったのです。だから私はこれを、 Common Lispでソリューションをハックする完璧な言い訳だと思った:)
私の解決策 (想像力で RpiTube と呼ばれています) は、YouTube プレイリストの URL を指定すると、ページの HTML ソースを検索し、そこに含まれるビデオの URL を抽出するスクリプトです。次に、これらの URL を Bash スクリプトに渡すことができます。Bash スクリプトは、最終的に各ビデオに対して個別に上記のコマンドを次々と呼び出します。Common Lisp スクリプト自体は完全で動作しますが、URL をコマンド ライン引数として使用して呼び出すのは困難です。これは主に、私が Quicklisp や Lisp パッケージ、Common Lisp コードから実行可能ファイルを作成することにまだ慣れていないためです。
私はClozure Common Lisp (CCL) を Quicklisp ( Rainer Joswig の指示に従ってインストール) で実行しています。以下に完全なコードを含めました。少し効率が悪いかもしれませんが、驚いたことに、Raspberry Pi でもかなり高速に動作します。(提案された改善は高く評価されます。)
;rpitube.lisp
;Given the URL of a YouTube playlist's overview page, return a list of the URLs of videos in said playlist.
(load "/home/pi/quicklisp/setup.lisp")
(ql:quickload :drakma)
(ql:quickload "cl-html-parse")
(ql:quickload "split-sequence")
(defun flatten (x)
"Paul Graham's utility function from On Lisp."
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
(defun parse-page-source (url)
"Generate lisp list of a page's html source."
(cl-html-parse:parse-html (drakma:http-request url)))
(defun occurences (e l)
"Returns the number of occurences of an element in a list. Note: not fully tail recursive."
(cond
((null l) 0)
((equal e (car l)) (1+ (occurences e (cdr l))))
(t (occurences e (cdr l)))))
(defun extract-url-stubs (flatlist unique-atom url-retrieval-fn)
"In a playlist's overview page the title of each video is represented in HTML as a link,
whose href entry is part of the video's actual URL (referred to here as a stub).
Within the link's tag there is also an entry that doesn't occur anywhere else in the
page source. This is the unique-atom (a string) that we will use to locate the link's tag
within the flattened list of the page source, from which we can then extract the video's URL
stub using a simple url-retrieval-fn (see comments below this function). This function is iterative, not
recursive, because the latter approach was too confusing."
(let* ((tail (member unique-atom flatlist :test #'equal))
(n (occurences unique-atom tail))
(urls nil))
(loop for x in tail with i = 0
while (< (length urls) n) do
(if (string= x unique-atom)
(setf urls (cons (funcall url-retrieval-fn tail i) urls)))
(incf i))
(reverse urls)))
;Example HTML tag:
;<a class="pl-video-title-link yt-uix-tile-link yt-uix-sessionlink spf-link " data-sessionlink="verylongirrelevantinfo" href="/watch?v=uniquevideocode&index=numberofvideoinplaylist&list=uniqueplaylistcode" dir="ltr"></a>
;Example tag when parsed and flattened:
;(:A :CLASS "pl-video-title-link yt-uix-tile-link yt-uix-sessionlink spf-link " :DATA-SESSIONLINK "verylongirrelevantinfo" :HREF "/watch?v=uniquevideocode&index=numberofvideoinplaylist&list=uniqueplaylistcode" :DIR "ltr")
;The URL stub is the fourth list element after unique-atom ("pl-video-title..."), so the url-retreival-fn is:
;(lambda (l i) (elt l (+ i 4))), where i is the index of unique-atom.
(defun get-vid-urls (url)
"Extracts the URL stubs, turns them into full URLs, and returns them in a list."
(mapcar (lambda (s)
(concatenate 'string
"https://www.youtube.com"
(car (split-sequence:split-sequence #\& s))))
(extract-url-stubs (flatten (parse-page-source url))
"pl-video-title-link yt-uix-tile-link yt-uix-sessionlink spf-link "
(lambda (l i) (elt l (+ i 4))))))
(let ((args #+clozure *unprocessed-command-line-arguments*))
(if (and (= (length args) 1)
(stringp (car args)))
(loop for url in (get-vid-urls (car args)) do
(format t "~a " url))
(error "Usage: rpitube <URL of youtube playlist>
where URL is of the form:
'https://www.youtube.com/playlist?list=uniqueplaylistcode'")))
まず、次の行をスクリプトに追加してみました
#!/home/pi/ccl/armcl
そして実行中
$ chmod +x rpitube.lisp
$ ./rpitube.lisp {playlistURL}
これは次を与えます:
Unrecognized non-option arguments: (./rpitube.lisp {playlistURL})
./rpitube.lisp がこの認識されない引数のリストに存在しないことを少なくとも期待していたとき. Clozure CL では、コマンド ライン引数をそのままREPLセッションに渡すには、次のように 2 つのハイフンで他の引数と区切る必要があることを私は知っています。
~/ccl/armcl -l rpitube.lisp -- {playlistURL}
しかし、このようにスクリプトを呼び出すと、スクリプトの実行後に REPL が発生することは明らかです。これは望ましくありません。さらに、Quicklisp の読み込み情報と進行状況バーが端末に出力されますが、これも望ましくありません。(ちなみに、Rainer が示唆したように、私は CCL init ファイルに Quicklisp を追加していません。なぜなら、私は追加のオーバーヘッド、つまり Raspberry Pi での数秒の読み込み時間は一般的に望んでいないからです。それが関連しているかどうかはわかりません)。
次に、実行してスタンドアロンの実行可能ファイルを作成することにしました (上記のコードが読み込まれたら)。
(ccl:save-application "rpitube" :prepend-kernel t)
そして、次のようにシェルから呼び出します。
$ ./rpitube {playlistURL}
これは次を与えます:
Unrecognized non-option arguments: ({playlistURL})
これは改善されているようですが、私はまだ何か間違ったことをしています。drakma、cl-html-extract、および split-sequence を必要とする独自の asdf パッケージを作成し、それをin-package
などでロードして、Quicklisp 関連のコードを置き換える必要がありますか? 別のプロジェクトで以前に独自のパッケージを作成しました-具体的には、コードを複数のファイルに分割したかったためです-動作しているように見えますがql:quickload
、in-package
後者は動作しないように見えたので(おそらくそれについては別の質問として尋ねるべきです)。ここで、rpitube.lisp のコードは非常に短いので、quickproject 全体を作成してパッケージ化する必要はないように思われます。
では、スクリプト (またはその呼び出し) を変更して、URL をコマンドライン引数として受け入れ、非対話的に実行できるように (つまり、REPL を開かず)、必要な出力のみを出力するようにするにはどうすればよいですか?ターミナル - スペースで区切られた URL のリスト - Quicklisp ロード情報なし?