;;; ytplay.el --- Play YouTube videos in mpv -*- lexical-binding: t -*- ;; Author: Lucien Cartier-Tilet ;; Maintainer: Lucien Cartier-Tilet ;; 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 . ;;; Commentary: ;; commentary ;;; Code: (require 'cl-lib) (require 's) (require 'seq) (defgroup ytplay () "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 (async-shell-command (format "%s --ytdl-format=%s+bestaudio/best \"%s\"" ytplay-video-player code video))))) (provide 'ytplay) ;;; ytplay.el ends here