2021-06-29 20:38:02 +00:00
|
|
|
|
;;; ytplay.el --- Play YouTube videos in mpv -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
|
|
;; Author: Lucien Cartier-Tilet <lucien@phundrak.com>
|
|
|
|
|
;; Maintainer: Lucien Cartier-Tilet <lucien@phundrak.com>
|
|
|
|
|
;; Version: 0.1.0
|
|
|
|
|
;; Package-Requires: ((emacs "25.1") (s "1"))
|
|
|
|
|
;; Homepage: https://labs.phundrak.com/phundrak/ytplay.el
|
|
|
|
|
;; Keywords: convenience
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; This file is not part of GNU Emacs
|
|
|
|
|
|
|
|
|
|
;; This program is free software: you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; commentary
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(require 'cl-lib)
|
|
|
|
|
(require 's)
|
|
|
|
|
(require 'seq)
|
|
|
|
|
|
2021-07-01 09:37:16 +00:00
|
|
|
|
(defgroup ytplay ()
|
2021-06-29 20:38:02 +00:00
|
|
|
|
"Play youtube videos in a video player through Emacs."
|
|
|
|
|
:prefix "ytplay-"
|
|
|
|
|
:link '(url-link :tag "Gitea" "https://labs.phundrak.com/phundrak/ytplay.el"))
|
|
|
|
|
|
|
|
|
|
(defcustom ytplay-video-player "mpv"
|
|
|
|
|
"Executable for playing videos."
|
|
|
|
|
:group 'ytplay
|
|
|
|
|
:type 'executable
|
|
|
|
|
:version "0.1.0")
|
|
|
|
|
|
|
|
|
|
(defmacro ytplay--rx-extra (&rest body-forms)
|
|
|
|
|
"Extra keywords for `rx' that can be used in BODY-FORMS."
|
|
|
|
|
`(rx-let ((resolution (seq (+ digit) "p" (0+ digit)))
|
|
|
|
|
(size (seq (+ digit) (opt "." (* digit)) (or "K" "M" "G") "iB")))
|
|
|
|
|
,@body-forms))
|
|
|
|
|
|
|
|
|
|
(defun ytplay--get-all-formats-raw (video)
|
|
|
|
|
"Get the raw lines describing available formats for VIDEO."
|
|
|
|
|
(cl-remove-if-not
|
|
|
|
|
(lambda (line)
|
|
|
|
|
(string-match (ytplay--rx-extra (rx (one-or-more digit) "x" (one-or-more digit)
|
|
|
|
|
(+ blank)
|
|
|
|
|
resolution
|
|
|
|
|
(+ ascii)
|
|
|
|
|
size))
|
|
|
|
|
line))
|
|
|
|
|
(s-lines (with-temp-buffer
|
|
|
|
|
(call-process "youtube-dl" nil t nil "--list-formats" video)
|
|
|
|
|
(buffer-string)))))
|
|
|
|
|
|
|
|
|
|
(cl-defstruct ytplay--format
|
|
|
|
|
"Representation of an available format for a video."
|
|
|
|
|
format-code extension resolution total-size)
|
|
|
|
|
|
|
|
|
|
(defun ytplay--raw-format-to-struct (formats)
|
|
|
|
|
"Get normalized formats from a video’s raw FORMATS."
|
|
|
|
|
(cl-remove-if #'null
|
|
|
|
|
(mapcar (lambda (format)
|
|
|
|
|
(save-match-data
|
|
|
|
|
(string-match (ytplay--rx-extra
|
|
|
|
|
(rx
|
|
|
|
|
(group (+ digit))
|
|
|
|
|
(one-or-more space)
|
|
|
|
|
(group (+ alnum))
|
|
|
|
|
(one-or-more space)
|
|
|
|
|
(+ alnum)
|
|
|
|
|
(one-or-more space)
|
|
|
|
|
(group resolution)
|
|
|
|
|
(one-or-more ascii)
|
|
|
|
|
(one-or-more space)
|
|
|
|
|
(group size)))
|
|
|
|
|
format)
|
|
|
|
|
(let ((format-code (match-string-no-properties 1 format))
|
|
|
|
|
(extension (match-string-no-properties 2 format))
|
|
|
|
|
(resolution (match-string-no-properties 3 format))
|
|
|
|
|
(total-size (match-string-no-properties 4 format)))
|
|
|
|
|
(when (and format-code extension resolution total-size)
|
|
|
|
|
(make-ytplay--format :format-code format-code
|
|
|
|
|
:extension extension
|
|
|
|
|
:resolution resolution
|
|
|
|
|
:total-size total-size)))))
|
|
|
|
|
formats)))
|
|
|
|
|
|
|
|
|
|
(defun ytplay--chose-resolution (formats)
|
|
|
|
|
"FORMATS."
|
|
|
|
|
(let* ((resolutions (seq-uniq (mapcar (lambda (format)
|
|
|
|
|
(ytplay--format-resolution format))
|
|
|
|
|
formats)))
|
|
|
|
|
(user-choice (completing-read "Resolution: " resolutions)))
|
|
|
|
|
(cl-remove-if-not (lambda (format)
|
|
|
|
|
(equal (ytplay--format-resolution format)
|
|
|
|
|
user-choice))
|
|
|
|
|
formats)))
|
|
|
|
|
|
|
|
|
|
(defun ytplay--chose-size (formats)
|
|
|
|
|
"FORMATS."
|
|
|
|
|
(let ((formats (mapcar (lambda (format)
|
|
|
|
|
`(,(ytplay--format-total-size format) . ,format))
|
|
|
|
|
formats)))
|
|
|
|
|
(cdr (assoc (completing-read "File: " formats) formats))))
|
|
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun ytplay (video)
|
|
|
|
|
"Play in mpv an internet VIDEO."
|
|
|
|
|
(interactive "sVideo url: ")
|
|
|
|
|
(let ((code (ytplay--format-format-code
|
|
|
|
|
(ytplay--chose-size
|
|
|
|
|
(ytplay--chose-resolution
|
|
|
|
|
(ytplay--raw-format-to-struct
|
|
|
|
|
(ytplay--get-all-formats-raw video)))))))
|
|
|
|
|
(with-temp-buffer
|
2022-02-05 12:42:40 +00:00
|
|
|
|
(async-shell-command (format "%s --ytdl-format=%s+bestaudio/best \"%s\""
|
|
|
|
|
ytplay-video-player
|
|
|
|
|
code
|
|
|
|
|
video)))))
|
2021-06-29 20:38:02 +00:00
|
|
|
|
|
|
|
|
|
(provide 'ytplay)
|
|
|
|
|
|
|
|
|
|
;;; ytplay.el ends here
|