- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
在发现一种使用我的新 Raspberry Pi 2(运行 Raspbian)的命令行从命令行观看 YouTube 视频的非常简单的方法后,仅使用易于获得的软件包,即:
omxplayer -o local $(youtube-dl -g {videoURL})
我立即想要一种以这种方式观看整个 YouTube 播放列表的方法。所以我认为这是在 Common Lisp 中拼凑解决方案的完美借口。 :)
我的解决方案(富有想象力地称为 RpiTube)是一个脚本,当给定 YouTube 播放列表的 URL 时,它会搜索页面的 HTML 源并提取其中包含的视频的 URL。那我可以将这些 URL 传递给 Bash 脚本,该脚本最终会一个接一个地为每个视频单独调用上述命令。 Common Lisp 脚本本身是完整的并且可以工作,但是我很难用 URL 作为命令行参数来调用它。这主要是因为我对 Quicklisp、Lisp 包和从 Common Lisp 代码创建可执行文件还很陌生。
我在跑 Clozure Common Lisp (CCL) 和 Quicklisp(根据 Rainer Joswig's instructions 安装)。我在下面包含了完整的代码。它可能有点低效,但令我惊讶的是它甚至在 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 session 不变,我必须用双连字符将它们与其他参数分开,如下所示:
~/ccl/armcl -l rpitube.lisp -- {playlistURL}
但是像这样调用脚本显然会在脚本运行后让我进入 REPL,这是我不想要的。此外,Quicklisp 加载信息和进度条会打印到终端,这也是我不想要的。 (顺便说一下,正如 Rainer 所建议的,我没有将 Quicklisp 添加到我的 CCL 初始化文件中,因为我通常不想要额外的开销,即 Raspberry Pi 上几秒钟的加载时间。我不确定这是否相关)。
然后我决定尝试通过运行(加载上述代码后)来创建一个独立的可执行文件:
(ccl:save-application "rpitube" :prepend-kernel t)
然后像这样从 shell 中调用它:
$ ./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 加载信息?
最佳答案
好的,我已经设法根据上面用户@m-n 链接的建议改编了一个解决方案。 RpiTube 现在似乎适用于我尝试过的大多数播放列表,除了一些音乐播放列表,这些播放列表是不可靠的,因为我住在德国,而且许多音乐视频在这个国家出于法律原因被屏蔽。巨大的播放列表、非常高质量(或非常长)的视频可能不可靠。
BASH 脚本:
#! /bin/bash
#Calls rpitube.lisp to retrieve the URLs of the videos in the provided
#playlist, and then plays them in order using omxplayer, optionally
#starting from the nth video instead of the first.
CCL_PATH='/home/pi/ccl/armcl'
RPITUBE_PATH='/home/pi/lisp/rpitube.lisp'
N=0
USAGE='
Usage: ./rpitube [-h help] [-n start at nth video] <playlist URL>
where URL is of the form: https://www.youtube.com/playlist?list=uniqueplaylistcode
******** Be sure to surround the URL with single quotes! *********'
play()
{
if `omxplayer -o local $(youtube-dl -g "$1") > /dev/null`; then
return 0
else
echo "An error occured while playing $1."
exit 1
fi
}
while getopts ":n:h" opt; do
case $opt in
n ) N=$((OPTARG - 1)) ;;
h ) echo "$USAGE"
exit 1 ;;
\? ) echo "Invalid option."
echo "$USAGE"
exit 1 ;;
esac
done
shift $(($OPTIND - 1))
if [[ "$#" -ne 1 ]]; then
echo "Invalid number of arguments."
echo "$USAGE"
exit 1
elif [[ "$1" != *'https://www.youtube.com/playlist?list='* ]]; then
echo "URL is of the wrong form."
echo "$USAGE"
exit 1
else
echo 'Welcome to RpiTube!'
echo 'Fetching video URLs... (may take a moment, especially for large playlists)'
urls="$(exec $CCL_PATH -b -e '(progn (load "'$RPITUBE_PATH'") (main "'$1'") (ccl::quit))')"
echo 'Starting video... press Q to skip to next video, left/right arrow keys to rewind/fast-forward, Ctrl-C to quit.'
count=0
for u in $urls; do #do NOT quote $urls here
[[ $count -lt $N ]] && count=$((count + 1)) && continue
play "$u"
echo 'Loading next video...'
done
echo 'Reached end of playlist. Hope you enjoyed it! :)'
fi
我对 CL 脚本进行了以下更改:将 :silent
选项添加到 ql:quickload
调用中;用内置的 count
(:test #'equal
) 替换我自己的 occurrences
函数;最重要的是,脚本末尾的代码实际上调用了 URL 获取函数。首先,我将它包装在一个接受一个参数的 main
函数中,即播放列表 URL,并删除了对 *command-line-argument-list*
等的引用。重要的部分:我没有使用 URL 作为 CCL 的命令行参数来调用整个 rpitube.lisp
脚本,而是在没有参数的情况下调用它,而是将 URL 传递给 main
直接函数(在对 exec
的调用中)。见下文:
(defun main (url)
(if (stringp url)
(loop for u in (get-vid-urls url) do
(format t "~a " u))
(error "Usage: rpitube <URL of youtube playlist>
where URL is of the form:
'https://www.youtube.com/playlist?list=uniqueplaylistcode'")))
这种方法可以广泛应用并且效果很好,但如果没有更好的方法,我会感到惊讶。如果我可以在“顶层”功能 + 可执行想法方面取得任何进展,我将编辑此答案。
一个工作调用示例,在短视频的小型播放列表上运行,从第 3 个视频开始播放:
$ ./rpitube -n 3 'https://www.youtube.com/playlist?list=PLVPJ1jbg0CaE9eZCTWS4KxOWi3NWv_oXL'
非常感谢。
关于raspberry-pi - 使用命令行参数调用 CCL + Quicklisp 脚本作为可执行文件并实现所需的输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30789161/
我正在浏览 CCL 代码示例以及 oneapi 工具包。在下面的 DPC++(SYCL) 代码中,最初 sendbuf 缓冲区在 cpu 端创建并且未初始化,并且在卸载到目标设备的部分发生了 dev_
? (run-program "ls" '() :output *standard-output*) money.lisp ssqHitNum.txt ssqNum.txt # ? (run-prog
似乎没有 ANSI 标准方法来执行外部程序并获取其输出,如下面的 SBCL 特殊代码所做的那样: (defmacro with-input-from-program ((stream program
考虑以下几点: (directory (make-pathname :directory '(:absolute "Users" "gazonk" "foo" "*")
我正在使用 CCL Lisp 并行运行批量实验。在我的机器上,一切运行良好。但是,我想在服务器上使用它。当我在服务器上执行此操作时,我总是收到以下错误消息: > Error: on # : >
我正在使用 CCL 学习 Common Lisp。当我在本地使用全局变量时收到警告。为什么 CCL 提供这个功能?这样做的目的是什么? (setf n 75) ;;;This function wor
我是 ccl 的新手,想知道如何在 *.nib 文件和 ccl 中的对象之间进行“通信”?我浏览了“Apple's Currency Converter in Lisp”教程,但我不确定这两者如何“交
我使用 quicklisp 安装 linedit,http://www.cliki.net/Linedit说“应该在 Lispworks 和 OpenMCL/CCL 上工作。” $HOME/.ccl-
当我尝试按如下方式发送消息时: (let* ((temp-buffer message) (out-vector (make-array (length temp-buffer)
在 mac os x 10.9 上的 ccl 上,我在创建可执行文件时遇到问题。 (save-application "/full/path/to/saved-app":prepend-kernel
在发现一种使用我的新 Raspberry Pi 2(运行 Raspbian)的命令行从命令行观看 YouTube 视频的非常简单的方法后,仅使用易于获得的软件包,即: omxplayer -o loc
我正在尝试运行 Clozure 为 CCL-Cocoa 桥提供的“hello world”脚本: https://trac.clozure.com/ccl/wiki/CocoaBridge Load
在使用 Emacs、SLIME 和 Clozure CL 时,我有一个小问题:aref 的函数签名(我还没有看到任何其他实例)仅显示为 (aref a)。 当我转到源代码时,有问题的代码以 (defu
我用 CCL 调用了 (load "code.lisp"),然后不小心删除了 code.lisp。有什么办法可以找回源码吗? CCL 是否将其存储在任何地方? 最佳答案 这是一个非常特殊的功能。这里仅
我正在使用 hunchentoot session 值使我的服务器代码可重入。问题是,根据定义, session 值在 session 期间保留,即从同一浏览器的一个调用到下一个,而我真正要寻找的是线
大家 我正在尝试做一些计算并绘制结果,但这些对于 Maxima 来说似乎太重了。当我尝试计算 N1 和 N2 时,当参数 j 太高或尝试绘制它们时,程序崩溃,程序显示以下错误消息:“堆耗尽,游戏结束。
我是一名优秀的程序员,十分优秀!