From ee5bfbe8014bfba413b32725fa440d142ea77b3f Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Thu, 7 Mar 2024 18:04:24 +0100 Subject: Make url fetching async + add new commands for channels * Refactor: use url-queue-retrieve to retrieve urls * Refactor: new function yeetube-display-content-from-url to scrape an arbitrary url. * New variable yeetube-search-history to track search history separately from general minibuffer input. * New commands yeetube-channel-videos and yeetube-channel-search for exploring channels --- yeetube.el | 120 +++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/yeetube.el b/yeetube.el index c8ce30e..f73b19b 100644 --- a/yeetube.el +++ b/yeetube.el @@ -43,6 +43,7 @@ (require 'cl-lib) (require 'socks) (require 'iimage) +(require 'url-handlers) (require 'yeetube-mpv) @@ -178,6 +179,9 @@ on `temporary-file-directory'." (defvar yeetube-history nil "Stored urls & titles of recently played content.") +(defvar yeetube-search-history nil + "History of search terms.") + (defvar yeetube-url "https://youtube.com/watch?v=" "URL used to play videos from. @@ -344,27 +348,62 @@ This is used to download thumbnails from `yeetube-content'." (socks-server '("Default server" "127.0.0.1" 9050 5))) ,@body)) +(defun yeetube--callback (status) + "Yeetube callback handling STATUS." + (let ((url-buffer (current-buffer))) + (unwind-protect + (if-let ((err (plist-get :error status))) + (message "Error %s in retrieving yeetube results: %S" (car err) (cdr err)) + (with-temp-buffer + (set-buffer-multibyte t) + (url-insert url-buffer) + (decode-coding-region (point-min) (point-max) 'utf-8) + (goto-char (point-min)) + (yeetube-get-content) + (yeetube-get-thumbnails yeetube-content)) ;; download thumbnails + (pop-to-buffer-same-window "*yeetube*") + (yeetube-mode)) + (kill-buffer url-buffer)))) + +(defun yeetube-display-content-from-url (url) + "Display the video results from URL." + (let* ((url-request-extra-headers yeetube-request-headers)) + (if yeetube-enable-tor + (yeetube-with-tor-socks + (url-queue-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)) + (url-queue-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)))) + +(defun yeetube-read-query () + "Interactively read a search term." + (read-string "Yeetube Search: " nil 'yeetube-search-history)) + ;;;###autoload (defun yeetube-search (query) "Search for QUERY." - (interactive "sYeetube Search: ") - (let* ((url-request-extra-headers yeetube-request-headers) - (search-url (concat "https://youtube.com/search?q=" - (replace-regexp-in-string " " "+" query) - ;; Filter parameter to remove live videos. - "&sp=" - (yeetube-get-filter-code yeetube-filter)))) - (with-current-buffer - (if yeetube-enable-tor - (yeetube-with-tor-socks (url-retrieve-synchronously search-url 'silent 'inhibit-cookies 30)) - (url-retrieve-synchronously search-url 'silent 'inhibit-cookies 30)) - (decode-coding-region (point-min) (point-max) 'utf-8) - (goto-char (point-min)) - (toggle-enable-multibyte-characters) - (yeetube-get-content)) - (yeetube-get-thumbnails yeetube-content)) ;; download thumbnails - (pop-to-buffer-same-window "*yeetube*") - (yeetube-mode)) + (interactive (list (yeetube-read-query))) + (yeetube-display-content-from-url + (format "https://youtube.com/search?q=%s&sp=%s" + (url-hexify-string query) + (yeetube-get-filter-code yeetube-filter)))) + +(defun yeetube-channel-id-at-point () + "Return the channel name for the video at point." + (if-let ((entry (tabulated-list-get-entry))) + (get-text-property 0 :channel-id (aref entry 4)) + (error "No video at point"))) + +(defun yeetube-channel-videos (channel-id) + "View (some) videos for the channel with CHANNEL-ID." + (interactive (list (yeetube-channel-id-at-point))) + (yeetube-display-content-from-url (format "https://youtube.com/%s/videos" channel-id))) + +(defun yeetube-channel-search (channel-id query) + "Search channel with CHANNEL-ID for vidoes matching QUERY." + (interactive (list (yeetube-channel-id-at-point) (yeetube-read-query))) + (yeetube-display-content-from-url + (format "https://youtube.com/%s/search?query=%s" + channel-id + (url-hexify-string query)))) ;;;###autoload (defun yeetube-browse-url () @@ -410,30 +449,33 @@ SUBSTRING-END is the end of the string to return, interger." "Get content from youtube." (setf yeetube-content nil) (while (and (< (length yeetube-content) yeetube-results-limit) - (search-forward "videorenderer" nil t)) + (search-forward "videorenderer" nil t)) (search-forward "videoid") (let ((videoid (buffer-substring (+ (point) 3) - (- (search-forward ",") 2)))) + (- (search-forward ",") 2)))) (unless (member videoid (car yeetube-content)) - (let ((title (yeetube-scrape-item :item "title" :item-end ",\"" :substring-end 5)) - (view-count (yeetube-scrape-item :item "viewcounttext" :item-end " " :substring-end 0)) - (video-duration (yeetube-scrape-item :item "lengthtext" :item-end "}," :substring-end 3)) - (channel (yeetube-scrape-item :item "longbylinetext" :item-end "," :substring-end 2)) - (thumbnail (yeetube-scrape-item :item "thumbnail" :item-start "url" :item-end ".jpg" :substring-end 0)) - (date (yeetube-scrape-item :item "publishedtimetext" :item-end ",\"" :substring-end 4))) - (push (list :title title - :videoid videoid - :view-count (yeetube-view-count-format view-count) - :duration video-duration - :channel channel - :thumbnail (replace-regexp-in-string "hq720" "default" thumbnail) - :date (replace-regexp-in-string "Streamed " "" date) - :image (if yeetube-display-thumbnails - (format "[[%s.jpg]]" (expand-file-name - videoid - temporary-file-directory)) - "disabled")) - yeetube-content)))))) + (save-excursion + (let ((title (yeetube-scrape-item :item "title" :item-end ",\"" :substring-end 5)) + (view-count (yeetube-scrape-item :item "viewcounttext" :item-end " " :substring-end 0)) + (video-duration (yeetube-scrape-item :item "lengthtext" :item-end "}," :substring-end 3)) + (channel (yeetube-scrape-item :item "longbylinetext" :item-end "," :substring-end 2)) + (channel-id (yeetube-scrape-item :item "canonicalBaseUrl" :item-start "/" + :substring-start 0 :item-end "\"" :substring-end 1)) + (thumbnail (yeetube-scrape-item :item "thumbnail" :item-start "url" :item-end ".jpg" :substring-end 0)) + (date (yeetube-scrape-item :item "publishedtimetext" :item-end ",\"" :substring-end 4))) + (push (list :title title + :videoid videoid + :view-count (yeetube-view-count-format view-count) + :duration video-duration + :channel (propertize channel :channel-id channel-id) + :thumbnail (replace-regexp-in-string "hq720" "default" thumbnail) + :date (replace-regexp-in-string "Streamed " "" date) + :image (if yeetube-display-thumbnails + (format "[[%s.jpg]]" (expand-file-name + videoid + temporary-file-directory)) + "disabled")) + yeetube-content))))))) (add-variable-watcher 'yeetube-saved-videos #'yeetube-update-saved-videos-list) -- cgit v1.2.3 From 399fe26ba9fd1e7326fa0d11f7b3519abda4733b Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Thu, 7 Mar 2024 19:57:42 +0100 Subject: Use json parsing to properly parse json strings --- yeetube.el | 66 +++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/yeetube.el b/yeetube.el index f73b19b..a96e098 100644 --- a/yeetube.el +++ b/yeetube.el @@ -415,6 +415,19 @@ This is used to download thumbnails from `yeetube-content'." (nth invidious-instance yeetube-invidious-instances) (yeetube-get-url))))) +(defun yeetube--scrape-string (pos item &optional sub-item) + "Scrape string corresponding of SUB-ITEM of ITEM after POS." + (goto-char pos) + (search-forward item nil t) + (when sub-item + (search-forward sub-item nil t)) + (forward-char) + (search-forward "\"") + (backward-char) + (if (fboundp 'json-parse-buffer) + (json-parse-buffer) + (json-read))) + (cl-defun yeetube-scrape-item (&key item (item-start "text") item-end (substring-start 3) substring-end) "Scrape ITEM from YouTube.com. @@ -450,32 +463,33 @@ SUBSTRING-END is the end of the string to return, interger." (setf yeetube-content nil) (while (and (< (length yeetube-content) yeetube-results-limit) (search-forward "videorenderer" nil t)) - (search-forward "videoid") - (let ((videoid (buffer-substring (+ (point) 3) - (- (search-forward ",") 2)))) - (unless (member videoid (car yeetube-content)) - (save-excursion - (let ((title (yeetube-scrape-item :item "title" :item-end ",\"" :substring-end 5)) - (view-count (yeetube-scrape-item :item "viewcounttext" :item-end " " :substring-end 0)) - (video-duration (yeetube-scrape-item :item "lengthtext" :item-end "}," :substring-end 3)) - (channel (yeetube-scrape-item :item "longbylinetext" :item-end "," :substring-end 2)) - (channel-id (yeetube-scrape-item :item "canonicalBaseUrl" :item-start "/" - :substring-start 0 :item-end "\"" :substring-end 1)) - (thumbnail (yeetube-scrape-item :item "thumbnail" :item-start "url" :item-end ".jpg" :substring-end 0)) - (date (yeetube-scrape-item :item "publishedtimetext" :item-end ",\"" :substring-end 4))) - (push (list :title title - :videoid videoid - :view-count (yeetube-view-count-format view-count) - :duration video-duration - :channel (propertize channel :channel-id channel-id) - :thumbnail (replace-regexp-in-string "hq720" "default" thumbnail) - :date (replace-regexp-in-string "Streamed " "" date) - :image (if yeetube-display-thumbnails - (format "[[%s.jpg]]" (expand-file-name - videoid - temporary-file-directory)) - "disabled")) - yeetube-content))))))) + (let ((pos (point))) + (search-forward "videoid") + (let ((videoid (buffer-substring (+ (point) 3) + (- (search-forward ",") 2)))) + (unless (member videoid (car yeetube-content)) + (save-excursion + (let ((title (yeetube--scrape-string pos "title" "text")) + (view-count (yeetube--scrape-string pos "viewCountText" "simpleText")) + (video-duration (yeetube--scrape-string pos "lengthText" "simpleText")) + (channel (yeetube--scrape-string pos "longBylineText" "text")) + (channel-id (yeetube--scrape-string pos "canonicalBaseUrl")) + (thumbnail (yeetube--scrape-string pos "thumbnail" "url")) + (date (yeetube--scrape-string pos "publishedTimeText" "simpleText"))) + (setq thumbnail (substring thumbnail 0 (+ 4 (string-search ".jpg" thumbnail)))) + (push (list :title title + :videoid videoid + :view-count (yeetube-view-count-format view-count) + :duration video-duration + :channel (propertize channel :channel-id channel-id) + :thumbnail (replace-regexp-in-string "hq720" "default" thumbnail) + :date (replace-regexp-in-string "Streamed " "" date) + :image (if yeetube-display-thumbnails + (format "[[%s.jpg]]" (expand-file-name + videoid + temporary-file-directory)) + "disabled")) + yeetube-content)))))))) (add-variable-watcher 'yeetube-saved-videos #'yeetube-update-saved-videos-list) -- cgit v1.2.3 From 84e3173d785c3402ea14daa2b72f6f1438f7f328 Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Thu, 7 Mar 2024 19:58:28 +0100 Subject: Remove unused scraping function --- yeetube.el | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/yeetube.el b/yeetube.el index a96e098..1484777 100644 --- a/yeetube.el +++ b/yeetube.el @@ -428,24 +428,6 @@ This is used to download thumbnails from `yeetube-content'." (json-parse-buffer) (json-read))) -(cl-defun yeetube-scrape-item (&key item (item-start "text") item-end (substring-start 3) substring-end) - "Scrape ITEM from YouTube.com. - -Video result starts with videorenderer. -Search back to videorenderer (start of video results), -then for item. - -ITEM-START is the start of the information for item. -ITEM-END is the end of the item information. -SUBSTRING-START is the start of the string to return, integer. -SUBSTRING-END is the end of the string to return, interger." - (search-backward "videorenderer" nil t) - (search-forward item nil t) - (search-forward item-start nil t) - (let ((item (buffer-substring (+ (point) substring-start) - (- (search-forward item-end) substring-end)))) - item)) - (defun yeetube-view-count-format (string) "Add commas for STRING." (let* ((string (replace-regexp-in-string "[^0-9]" "" string)) -- cgit v1.2.3 From 33b51c57d01b30c5c2bfc6f45993520dcc325176 Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Thu, 7 Mar 2024 21:29:29 +0100 Subject: Use url-queue for getting thumbnails Also add a command to view related videos --- yeetube.el | 180 ++++++++++++++++++++++++++++--------------------------------- 1 file changed, 82 insertions(+), 98 deletions(-) diff --git a/yeetube.el b/yeetube.el index 1484777..bb51a25 100644 --- a/yeetube.el +++ b/yeetube.el @@ -42,8 +42,8 @@ (require 'tabulated-list) (require 'cl-lib) (require 'socks) -(require 'iimage) (require 'url-handlers) +(require 'mm-decode) (require 'yeetube-mpv) @@ -123,11 +123,7 @@ Valid options include: :group 'yeetube) (defcustom yeetube-display-thumbnails t - "When t, fetch & display thumbnails. - -Disabled by default, still an experimental feature that a user should -opt-in. Note that when enabled the thumbnail images will be downloaded -on `temporary-file-directory'." + "When t, fetch & display thumbnails." :type 'boolean :group 'yeetube) @@ -224,7 +220,7 @@ Keywords: (defun yeetube-replay () "Select entry from history to replay. -Select entry title from yeetube-history and play corresponding URL." +Select entry title from `yeetube-history' and play corresponding URL." (interactive) (let* ((titles (mapcar (lambda (entry) (cl-getf entry :title)) yeetube-history)) (selected (completing-read "Replay: " titles)) @@ -294,36 +290,6 @@ WHERE indicates where in the buffer the update should happen." (save-buffer) (kill-buffer))) -(defun yeetube--wget-thumbnail (torsocks url &optional output) - "Get thumbnail using `wget' from URL. - -If TORSOCKS is non-nil, use torsocks to download URL. -URL is the URL to download. -OUTPUT is the output file name." - (let ((wget-exec (executable-find "wget"))) - (unless wget-exec - (error "Please install `wget' to download videos")) - (let ((command (if torsocks - (format "%s %s %s -O %s.jpg" (executable-find "torsocks") wget-exec - (shell-quote-argument url) (shell-quote-argument output)) - (format "%s %s -O %s.jpg" wget-exec (shell-quote-argument url) - (shell-quote-argument output))))) - (call-process-shell-command command nil 0)))) - - -(cl-defun yeetube-get-thumbnails (content) - "Download thumbnails for CONTENT using `wget'. - -This is used to download thumbnails from `yeetube-content'." - (interactive) - (when yeetube-display-thumbnails - (let ((default-directory temporary-file-directory)) - (cl-loop for item in content - do (let ((thumbnail (plist-get item :thumbnail)) - (videoid (plist-get item :videoid))) - (unless (file-exists-p (expand-file-name (concat videoid ".jpg"))) - (yeetube--wget-thumbnail yeetube-enable-tor thumbnail videoid))))))) - (defvar yeetube-filter-code-alist '(("Relevance" . "EgIQAQ%253D%253D") ("Date" . "CAISAhAB") @@ -359,8 +325,7 @@ This is used to download thumbnails from `yeetube-content'." (url-insert url-buffer) (decode-coding-region (point-min) (point-max) 'utf-8) (goto-char (point-min)) - (yeetube-get-content) - (yeetube-get-thumbnails yeetube-content)) ;; download thumbnails + (yeetube-get-content)) (pop-to-buffer-same-window "*yeetube*") (yeetube-mode)) (kill-buffer url-buffer)))) @@ -373,6 +338,38 @@ This is used to download thumbnails from `yeetube-content'." (url-queue-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)) (url-queue-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)))) +(defun yeetube--image-callback (status entry buffer) + "Yeetube callback for thumbnail images handling STATUS. +Image is inserted in BUFFER for ENTRY." + (let ((url-buffer (current-buffer))) + (unwind-protect + (if-let ((err (plist-get :error status))) + (message "Error %s in retrieving a thumnail: %S" (car err) (cdr err)) + (if-let ((handle (mm-dissect-buffer t)) + (image (mm-get-image handle))) + (progn + (setf (image-property image :max-width) (car yeetube-thumbnail-size)) + (setf (image-property image :max-height) (cdr yeetube-thumbnail-size)) + (with-current-buffer buffer + (with-silent-modifications + (save-excursion + (goto-char (point-min)) + (search-forward (format "[[%s.jpg]]" (plist-get entry :videoid))) + (put-text-property (match-beginning 0) (match-end 0) 'display image) + (setf (aref (nth 0 (alist-get entry tabulated-list-entries)) 5) image))))) + (message "yeetube error: no image found"))) + (kill-buffer url-buffer)))) + +(defun yeetube--retrieve-thumnail (url str buffer) + "Retrieve thumbnail from URL and show it in place of STR in BUFFER." + (let* ((url-request-extra-headers yeetube-request-headers)) + (if yeetube-enable-tor + (yeetube-with-tor-socks + (url-queue-retrieve url #'yeetube--image-callback `(,str ,buffer) + 'silent 'inhibit-cookies)) + (url-queue-retrieve url #'yeetube--image-callback `(,str ,buffer) + 'silent 'inhibit-cookies)))) + (defun yeetube-read-query () "Interactively read a search term." (read-string "Yeetube Search: " nil 'yeetube-search-history)) @@ -405,6 +402,11 @@ This is used to download thumbnails from `yeetube-content'." channel-id (url-hexify-string query)))) +(defun yeetube-related (video-id) + "View videos found on page for VIDEO-ID." + (interactive (list (yeetube-get :videoid))) + (yeetube-display-content-from-url (concat yeetube-url video-id))) + ;;;###autoload (defun yeetube-browse-url () "Open URL for video at point, using an invidious instance." @@ -443,35 +445,41 @@ This is used to download thumbnails from `yeetube-content'." (defun yeetube-get-content () "Get content from youtube." (setf yeetube-content nil) - (while (and (< (length yeetube-content) yeetube-results-limit) - (search-forward "videorenderer" nil t)) - (let ((pos (point))) + (let (ids pos videoid) + (while (and (< (length yeetube-content) yeetube-results-limit) + (search-forward "videorenderer" nil t)) + (setq pos (point)) (search-forward "videoid") - (let ((videoid (buffer-substring (+ (point) 3) - (- (search-forward ",") 2)))) - (unless (member videoid (car yeetube-content)) - (save-excursion - (let ((title (yeetube--scrape-string pos "title" "text")) - (view-count (yeetube--scrape-string pos "viewCountText" "simpleText")) - (video-duration (yeetube--scrape-string pos "lengthText" "simpleText")) - (channel (yeetube--scrape-string pos "longBylineText" "text")) - (channel-id (yeetube--scrape-string pos "canonicalBaseUrl")) - (thumbnail (yeetube--scrape-string pos "thumbnail" "url")) - (date (yeetube--scrape-string pos "publishedTimeText" "simpleText"))) - (setq thumbnail (substring thumbnail 0 (+ 4 (string-search ".jpg" thumbnail)))) - (push (list :title title - :videoid videoid - :view-count (yeetube-view-count-format view-count) - :duration video-duration - :channel (propertize channel :channel-id channel-id) - :thumbnail (replace-regexp-in-string "hq720" "default" thumbnail) - :date (replace-regexp-in-string "Streamed " "" date) - :image (if yeetube-display-thumbnails - (format "[[%s.jpg]]" (expand-file-name - videoid - temporary-file-directory)) - "disabled")) - yeetube-content)))))))) + (setq videoid (buffer-substring (+ (point) 3) + (- (search-forward ",") 2))) + (unless (member videoid ids) + (push videoid ids) + (save-excursion + (let ((title (yeetube--scrape-string pos "title" "text")) + (view-count (yeetube--scrape-string pos "viewCountText" "simpleText")) + (video-duration (yeetube--scrape-string pos "lengthText" "simpleText")) + (channel (yeetube--scrape-string pos "longBylineText" "text")) + (channel-id (yeetube--scrape-string pos "canonicalBaseUrl")) + (thumbnail (yeetube--scrape-string pos "thumbnail" "url")) + (date (yeetube--scrape-string pos "publishedTimeText" "simpleText")) + (entry nil)) + (setq thumbnail (string-replace + "hq720" "default" + (substring thumbnail 0 (string-search "?" thumbnail)))) + (setq entry + (list :title title + :videoid videoid + :view-count (yeetube-view-count-format view-count) + :duration video-duration + :channel (propertize channel :channel-id channel-id) + :thumbnail thumbnail + :date (replace-regexp-in-string "Streamed " "" date) + :image (if yeetube-display-thumbnails + (format "[[%s.jpg]]" videoid) + "disabled"))) + (yeetube--retrieve-thumnail thumbnail entry "*yeetube*") + (push entry yeetube-content)))))) + (cl-callf nreverse yeetube-content)) (add-variable-watcher 'yeetube-saved-videos #'yeetube-update-saved-videos-list) @@ -619,29 +627,6 @@ A and B are vectors." (< (string-to-number (nth 0 split-a)) (string-to-number (nth 0 split-b))) (> units-a units-b)))) -;; Modified from iimage.el for hardcoded width/height -(defun yeetube-iimage-mode-buffer (arg) - "Display images if ARG is non-nil, undisplay them otherwise." - (let ((image-path (cons default-directory iimage-mode-image-search-path)) - file) - (with-silent-modifications - (save-excursion - (dolist (pair iimage-mode-image-regex-alist) - (goto-char (point-min)) - (while (re-search-forward (car pair) nil t) - (when (and (setq file (match-string (cdr pair))) - (setq file (locate-file file image-path))) - (if arg - (add-text-properties - (match-beginning 0) (match-end 0) - `(display - ,(create-image file nil nil - :max-width (car yeetube-thumbnail-size) - :max-height (cdr yeetube-thumbnail-size))) - (remove-list-of-text-properties - (match-beginning 0) (match-end 0) - '(display modification-hooks))))))))))) - (define-derived-mode yeetube-mode tabulated-list-mode "Yeetube" "Yeetube mode." :keymap yeetube-mode-map @@ -653,23 +638,22 @@ A and B are vectors." ("Channel" 12 t) ("Thumbnail" 0 t)] tabulated-list-entries - (cl-map 'list - (lambda (content) + (cl-map 'list + (lambda (content) (list content - (yeetube-propertize-vector content + (yeetube-propertize-vector content :title 'yeetube-face-title :view-count 'yeetube-face-view-count :duration 'yeetube-face-duration - :date 'yeetube-face-date + :date 'yeetube-face-date :channel 'yeetube-face-channel - :image nil))) - (reverse yeetube-content)) + :image nil))) + yeetube-content) tabulated-list-sort-key (cons yeetube-default-sort-column yeetube-default-sort-ascending)) (display-line-numbers-mode 0) (tabulated-list-init-header) - (tabulated-list-print) - (yeetube-iimage-mode-buffer t)) + (tabulated-list-print)) (provide 'yeetube) ;;; yeetube.el ends here -- cgit v1.2.3 From fe794975246f1910bfdc494e266817d33fd199df Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Sun, 10 Mar 2024 12:09:25 +0100 Subject: Optionally show status of mpv process on modeline --- yeetube-mpv.el | 24 +++++++++++++++++++----- yeetube.el | 9 ++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/yeetube-mpv.el b/yeetube-mpv.el index 2c925a4..04f684a 100644 --- a/yeetube-mpv.el +++ b/yeetube-mpv.el @@ -32,20 +32,34 @@ :group 'yeetube) (defcustom yeetube-mpv-enable-torsocks nil - "Enable torsocks.") + "Enable torsocks." + :type 'boolean + :group 'yeetube) + +(defcustom yeetube-mpv-show-status nil + "Show mpv status in mode-line." + :type 'boolean + :group 'yeetube) + +(setf (alist-get 'yeetube-mpv-show-status mode-line-misc-info nil t) + '(("" yeetube-mpv-status))) -(defvar yeetube-mpv-path (executable-find "mpv") - "Path for mpv executable.") +(defvar yeetube-mpv-command '("mpv" "--no-msg-color" "--term-status-msg=${?=audio==1:A}${?=video==1:V} ${?=pause==yes:Paused}${?=pause==no:Playing} (${percent-pos}%)") + "Cons of mpv command and list of args passed to it.") (defvar yeetube-mpv-torsocks (executable-find "torsocks") "Path to torsocks executable.") (defvar yeetube-mpv-video-quality "720" - "Video resolution/quality - + "Video resolution/quality. Accepted values include: 1080, 720, 480, 360, 240, 144") +(defvar yeetube-mpv-status nil + "Contains a brief status of the mpv process.") +(put 'yeetube-mpv-status 'risky-local-variable t) + (defun yeetube-mpv-change-video-quality () + "Change video quality." (interactive) (let ((new-value (completing-read (format "Set video quality (current value %s):" yeetube-mpv-video-quality) '("1080" "720" "480" "360" "240" "144") nil t))) diff --git a/yeetube.el b/yeetube.el index bb51a25..1b7bd34 100644 --- a/yeetube.el +++ b/yeetube.el @@ -210,9 +210,11 @@ Keywords: (defun yeetube-play () "Play video at point in *yeetube* buffer." (interactive) - (let ((video-url (yeetube-get-url)) - (video-title (yeetube-get :title))) - (funcall yeetube-play-function video-url) + (let* ((video-url (yeetube-get-url)) + (video-title (yeetube-get :title)) + (proc (funcall yeetube-play-function video-url))) + (when (processp proc) + (process-put proc :now-playing video-title)) (push (list :url video-url :title video-title) yeetube-history) (message "Playing: %s" video-title))) @@ -651,6 +653,7 @@ A and B are vectors." yeetube-content) tabulated-list-sort-key (cons yeetube-default-sort-column yeetube-default-sort-ascending)) + (setq-local yeetube-mpv-show-status t) (display-line-numbers-mode 0) (tabulated-list-init-header) (tabulated-list-print)) -- cgit v1.2.3 From 5c00a7bd546df8e87452edb18447851263aed2c2 Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Sun, 10 Mar 2024 12:10:26 +0100 Subject: Command to quit and go forward/backward --- yeetube-mpv.el | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/yeetube-mpv.el b/yeetube-mpv.el index 04f684a..77e28ca 100644 --- a/yeetube-mpv.el +++ b/yeetube-mpv.el @@ -123,7 +123,7 @@ to play local files." (message "yeetube: mpv disabled video"))) (defun yeetube-mpv-send-keypress (key) - "Send KEY to yeetube-mpv-process." + "Send KEY to `yeetube-mpv-process'." (interactive "sKey: ") (process-send-string "yeetube" key)) @@ -145,5 +145,21 @@ to play local files." (yeetube-mpv-send-keypress "_") (message "yeetube: toggle video")) +(defun yeetube-mpv-forward () + "Forward video." + (interactive) + (yeetube-mpv-send-keypress "")) + +(defun yeetube-mpv-backward () + "Go backwards in video." + (interactive) + (yeetube-mpv-send-keypress "")) + +(defun yeetube-mpv-quit () + "Quit mpv." + (interactive) + (yeetube-mpv-send-keypress "q") + (message "yeetube: quit")) + (provide 'yeetube-mpv) ;;; yeetube-mpv.el ends here -- cgit v1.2.3 From 6d19d98a69eef898ab1ab2c982d4d814315633b6 Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Sun, 10 Mar 2024 12:16:12 +0100 Subject: Also scrape playlist --- yeetube.el | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/yeetube.el b/yeetube.el index 1b7bd34..f585157 100644 --- a/yeetube.el +++ b/yeetube.el @@ -447,37 +447,38 @@ Image is inserted in BUFFER for ENTRY." (defun yeetube-get-content () "Get content from youtube." (setf yeetube-content nil) - (let (ids pos videoid) + (let (id ids videop pos) (while (and (< (length yeetube-content) yeetube-results-limit) - (search-forward "videorenderer" nil t)) + (re-search-forward (rx (or "\"videoRenderer\"" "\"playlistRenderer\"")) nil t)) (setq pos (point)) - (search-forward "videoid") - (setq videoid (buffer-substring (+ (point) 3) - (- (search-forward ",") 2))) - (unless (member videoid ids) - (push videoid ids) + (setq videop (equal (match-string 0) "\"videoRenderer\"")) + (setq id (yeetube--scrape-string pos (if videop "videoId" "playlistId"))) + (unless (member id ids) + (push id ids) (save-excursion - (let ((title (yeetube--scrape-string pos "title" "text")) - (view-count (yeetube--scrape-string pos "viewCountText" "simpleText")) - (video-duration (yeetube--scrape-string pos "lengthText" "simpleText")) + (let ((title (yeetube--scrape-string pos "title" (if videop "text" "simpleText"))) + (view-count (when videop (yeetube--scrape-string pos "viewCountText" "simpleText"))) + (duration (if videop + (yeetube--scrape-string pos "lengthText" "simpleText") + (format "%s videos" (yeetube--scrape-string pos "videoCount")))) (channel (yeetube--scrape-string pos "longBylineText" "text")) (channel-id (yeetube--scrape-string pos "canonicalBaseUrl")) (thumbnail (yeetube--scrape-string pos "thumbnail" "url")) - (date (yeetube--scrape-string pos "publishedTimeText" "simpleText")) + (date (when videop (yeetube--scrape-string pos "publishedTimeText" "simpleText"))) (entry nil)) (setq thumbnail (string-replace "hq720" "default" (substring thumbnail 0 (string-search "?" thumbnail)))) (setq entry - (list :title title - :videoid videoid - :view-count (yeetube-view-count-format view-count) - :duration video-duration + (list :title (if videop title (concat "Playlist: " title)) + :videoid id + :view-count (yeetube-view-count-format (or view-count "")) + :duration duration :channel (propertize channel :channel-id channel-id) :thumbnail thumbnail - :date (replace-regexp-in-string "Streamed " "" date) + :date (string-replace "Streamed " "" (or date "")) :image (if yeetube-display-thumbnails - (format "[[%s.jpg]]" videoid) + (format "[[%s.jpg]]" id) "disabled"))) (yeetube--retrieve-thumnail thumbnail entry "*yeetube*") (push entry yeetube-content)))))) -- cgit v1.2.3 From 7b7e2c36ace7eff561675ae9dfaa8582e6c99dec Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Sun, 10 Mar 2024 14:23:35 +0100 Subject: Integrate playlist functionality --- yeetube.el | 53 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/yeetube.el b/yeetube.el index f585157..b3658c7 100644 --- a/yeetube.el +++ b/yeetube.el @@ -178,13 +178,20 @@ Valid options include: (defvar yeetube-search-history nil "History of search terms.") -(defvar yeetube-url "https://youtube.com/watch?v=" +(defvar yeetube-video-url "https://youtube.com/watch?v=" "URL used to play videos from. You can change this value to an invidious instance. Although yeetube will still query youtube, `yeetube-play' will use the above url to play videos from.") +(defvar yeetube-playlist-url "https://youtube.com/playlist?list=" + "URL used to play playlists from. + +You can change this value to an invidious instance. Although yeetube +will still query youtube, `yeetube-play' will use the above url to play +videos from.") + (defun yeetube-get (keyword) "Retrieve KEYWORD value for entry at point. @@ -193,7 +200,8 @@ Retrieve keyword value for entry at point, from `yeetube-content', in Keywords: - :title -- :videoid +- :id +- :type - :view-count - :duration - :channel" @@ -202,9 +210,12 @@ Keywords: (cl-getf (tabulated-list-get-id) keyword)) (defun yeetube-get-url () - "Get video url." - (let ((video-url (concat yeetube-url (yeetube-get :videoid)))) - video-url)) + "Get video or playlist url." + (format "%s%s" + (if (eq (yeetube-get :type) 'video) + yeetube-video-url + yeetube-playlist-url) + (yeetube-get :id))) ;;;###autoload (defun yeetube-play () @@ -326,7 +337,6 @@ WHERE indicates where in the buffer the update should happen." (set-buffer-multibyte t) (url-insert url-buffer) (decode-coding-region (point-min) (point-max) 'utf-8) - (goto-char (point-min)) (yeetube-get-content)) (pop-to-buffer-same-window "*yeetube*") (yeetube-mode)) @@ -356,7 +366,7 @@ Image is inserted in BUFFER for ENTRY." (with-silent-modifications (save-excursion (goto-char (point-min)) - (search-forward (format "[[%s.jpg]]" (plist-get entry :videoid))) + (search-forward (format "[[%s.jpg]]" (plist-get entry :id))) (put-text-property (match-beginning 0) (match-end 0) 'display image) (setf (aref (nth 0 (alist-get entry tabulated-list-entries)) 5) image))))) (message "yeetube error: no image found"))) @@ -381,9 +391,11 @@ Image is inserted in BUFFER for ENTRY." "Search for QUERY." (interactive (list (yeetube-read-query))) (yeetube-display-content-from-url - (format "https://youtube.com/search?q=%s&sp=%s" + (format "https://youtube.com/search?q=%s%s" (url-hexify-string query) - (yeetube-get-filter-code yeetube-filter)))) + (if yeetube-filter + (format "&sp=%s" (yeetube-get-filter-code yeetube-filter)) + "")))) (defun yeetube-channel-id-at-point () "Return the channel name for the video at point." @@ -404,10 +416,10 @@ Image is inserted in BUFFER for ENTRY." channel-id (url-hexify-string query)))) -(defun yeetube-related (video-id) - "View videos found on page for VIDEO-ID." - (interactive (list (yeetube-get :videoid))) - (yeetube-display-content-from-url (concat yeetube-url video-id))) +(defun yeetube-video-or-playlist-page () + "View videos in playlist or those found on the video page." + (interactive) + (yeetube-display-content-from-url (yeetube-get-url))) ;;;###autoload (defun yeetube-browse-url () @@ -447,11 +459,15 @@ Image is inserted in BUFFER for ENTRY." (defun yeetube-get-content () "Get content from youtube." (setf yeetube-content nil) - (let (id ids videop pos) - (while (and (< (length yeetube-content) yeetube-results-limit) - (re-search-forward (rx (or "\"videoRenderer\"" "\"playlistRenderer\"")) nil t)) + (goto-char (point-min)) + (let ((count 0) + (result-rx (rx "\"" (or "video" (and (or "playlist" "compact") (? "Video"))) "Renderer\"")) + id ids videop pos) + (while (and (< count yeetube-results-limit) + (re-search-forward result-rx nil t)) + (cl-incf count) (setq pos (point)) - (setq videop (equal (match-string 0) "\"videoRenderer\"")) + (setq videop (not (equal (match-string 0) "\"playlistRenderer\""))) (setq id (yeetube--scrape-string pos (if videop "videoId" "playlistId"))) (unless (member id ids) (push id ids) @@ -471,7 +487,8 @@ Image is inserted in BUFFER for ENTRY." (substring thumbnail 0 (string-search "?" thumbnail)))) (setq entry (list :title (if videop title (concat "Playlist: " title)) - :videoid id + :type (if videop 'video 'playlist) + :id id :view-count (yeetube-view-count-format (or view-count "")) :duration duration :channel (propertize channel :channel-id channel-id) -- cgit v1.2.3 From 55e435fb7a3074cb756a41179d5c737ce2c69f3d Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Sat, 16 Mar 2024 08:57:44 +0100 Subject: Use url-retrieve for youtube urls (for headers) --- yeetube.el | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/yeetube.el b/yeetube.el index b3658c7..181dc66 100644 --- a/yeetube.el +++ b/yeetube.el @@ -344,11 +344,11 @@ WHERE indicates where in the buffer the update should happen." (defun yeetube-display-content-from-url (url) "Display the video results from URL." - (let* ((url-request-extra-headers yeetube-request-headers)) + (let ((url-request-extra-headers yeetube-request-headers)) (if yeetube-enable-tor (yeetube-with-tor-socks - (url-queue-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)) - (url-queue-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)))) + (url-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)) + (url-retrieve url #'yeetube--callback nil 'silent 'inhibit-cookies)))) (defun yeetube--image-callback (status entry buffer) "Yeetube callback for thumbnail images handling STATUS. @@ -374,7 +374,7 @@ Image is inserted in BUFFER for ENTRY." (defun yeetube--retrieve-thumnail (url str buffer) "Retrieve thumbnail from URL and show it in place of STR in BUFFER." - (let* ((url-request-extra-headers yeetube-request-headers)) + (let ((url-request-extra-headers yeetube-request-headers)) (if yeetube-enable-tor (yeetube-with-tor-socks (url-queue-retrieve url #'yeetube--image-callback `(,str ,buffer) @@ -394,8 +394,7 @@ Image is inserted in BUFFER for ENTRY." (format "https://youtube.com/search?q=%s%s" (url-hexify-string query) (if yeetube-filter - (format "&sp=%s" (yeetube-get-filter-code yeetube-filter)) - "")))) + (format "&sp=%s" (yeetube-get-filter-code yeetube-filter)) "")))) (defun yeetube-channel-id-at-point () "Return the channel name for the video at point." -- cgit v1.2.3 From 3916a2332a79c0640b5f9167bf67865493264937 Mon Sep 17 00:00:00 2001 From: Rahguzar Date: Sat, 16 Mar 2024 08:58:42 +0100 Subject: Fix status command and pass process to quit-process --- yeetube-mpv.el | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/yeetube-mpv.el b/yeetube-mpv.el index 77e28ca..39f8881 100644 --- a/yeetube-mpv.el +++ b/yeetube-mpv.el @@ -101,17 +101,18 @@ Accepted values include: 1080, 720, 480, 360, 240, 144") This function is not specific to just playing urls. Feel free to use to play local files." - (yeetube-mpv-process - (concat (when yeetube-mpv-enable-torsocks - (concat yeetube-mpv-torsocks " ")) - yeetube-mpv-path " --ytdl-format=" - (yeetube-mpv-ytdl-format-video-quality yeetube-mpv-video-quality) - " " - (shell-quote-argument input) - (when yeetube-mpv-disable-video " --no-video"))) - (message (if yeetube-mpv-enable-torsocks - "yeetube: Starting mpv process (using torsocks)" - "yeetube: Starting mpv process"))) + (let ((yeetube-mpv-path (executable-find "mpv"))) + (yeetube-mpv-process + (concat (when yeetube-mpv-enable-torsocks + (concat yeetube-mpv-torsocks " ")) + yeetube-mpv-path " --ytdl-format=" + (yeetube-mpv-ytdl-format-video-quality yeetube-mpv-video-quality) + " " + (shell-quote-argument input) + (when yeetube-mpv-disable-video " --no-video"))) + (message (if yeetube-mpv-enable-torsocks + "yeetube: Starting mpv process (using torsocks)" + "yeetube: Starting mpv process")))) (defun yeetube-mpv-toggle-no-video-flag () "Toggle no video flag for mpv player." -- cgit v1.2.3 From 70ea7e5e7d41577b63dce632cd0b7d5d3bdb7c15 Mon Sep 17 00:00:00 2001 From: Thanos Apollo Date: Mon, 18 Mar 2024 13:47:00 +0200 Subject: yeetube-mpv: Remove mpv-show-status If such a feature will be implemented, should be done as yeetube-modeline-mode, similarly to emms-mode-line-mode. --- yeetube-mpv.el | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/yeetube-mpv.el b/yeetube-mpv.el index 39f8881..5070fd5 100644 --- a/yeetube-mpv.el +++ b/yeetube-mpv.el @@ -36,17 +36,6 @@ :type 'boolean :group 'yeetube) -(defcustom yeetube-mpv-show-status nil - "Show mpv status in mode-line." - :type 'boolean - :group 'yeetube) - -(setf (alist-get 'yeetube-mpv-show-status mode-line-misc-info nil t) - '(("" yeetube-mpv-status))) - -(defvar yeetube-mpv-command '("mpv" "--no-msg-color" "--term-status-msg=${?=audio==1:A}${?=video==1:V} ${?=pause==yes:Paused}${?=pause==no:Playing} (${percent-pos}%)") - "Cons of mpv command and list of args passed to it.") - (defvar yeetube-mpv-torsocks (executable-find "torsocks") "Path to torsocks executable.") @@ -54,10 +43,6 @@ "Video resolution/quality. Accepted values include: 1080, 720, 480, 360, 240, 144") -(defvar yeetube-mpv-status nil - "Contains a brief status of the mpv process.") -(put 'yeetube-mpv-status 'risky-local-variable t) - (defun yeetube-mpv-change-video-quality () "Change video quality." (interactive) -- cgit v1.2.3 From 0afab2f072dc1cebdd8ffa4a7b05366de70a73eb Mon Sep 17 00:00:00 2001 From: Thanos Apollo Date: Mon, 18 Mar 2024 14:06:14 +0200 Subject: yeetube-mode: Disable sorting for thumbnails --- yeetube.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yeetube.el b/yeetube.el index 181dc66..131c26c 100644 --- a/yeetube.el +++ b/yeetube.el @@ -655,7 +655,7 @@ A and B are vectors." ("Duration" 9 yeetube--sort-duration) ("Date" 13 yeetube--sort-date) ("Channel" 12 t) - ("Thumbnail" 0 t)] + ("Thumbnail" 0 nil)] tabulated-list-entries (cl-map 'list (lambda (content) -- cgit v1.2.3 From 14266c178eb06bf7da77243af17d782be0c3ae1d Mon Sep 17 00:00:00 2001 From: Thanos Apollo Date: Mon, 18 Mar 2024 14:06:31 +0200 Subject: yeetube.el: Style & Add comments --- yeetube.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/yeetube.el b/yeetube.el index 131c26c..6b658d2 100644 --- a/yeetube.el +++ b/yeetube.el @@ -393,8 +393,7 @@ Image is inserted in BUFFER for ENTRY." (yeetube-display-content-from-url (format "https://youtube.com/search?q=%s%s" (url-hexify-string query) - (if yeetube-filter - (format "&sp=%s" (yeetube-get-filter-code yeetube-filter)) "")))) + (if yeetube-filter (format "&sp=%s" (yeetube-get-filter-code yeetube-filter)) "")))) (defun yeetube-channel-id-at-point () "Return the channel name for the video at point." @@ -412,8 +411,7 @@ Image is inserted in BUFFER for ENTRY." (interactive (list (yeetube-channel-id-at-point) (yeetube-read-query))) (yeetube-display-content-from-url (format "https://youtube.com/%s/search?query=%s" - channel-id - (url-hexify-string query)))) + channel-id (url-hexify-string query)))) (defun yeetube-video-or-playlist-page () "View videos in playlist or those found on the video page." @@ -462,8 +460,10 @@ Image is inserted in BUFFER for ENTRY." (let ((count 0) (result-rx (rx "\"" (or "video" (and (or "playlist" "compact") (? "Video"))) "Renderer\"")) id ids videop pos) + ;; Keep scraping while there are results and the limit is not reached (while (and (< count yeetube-results-limit) (re-search-forward result-rx nil t)) + ;; Increment count (cl-incf count) (setq pos (point)) (setq videop (not (equal (match-string 0) "\"playlistRenderer\""))) @@ -471,6 +471,7 @@ Image is inserted in BUFFER for ENTRY." (unless (member id ids) (push id ids) (save-excursion + ;; Scrape necessary data and push to list of contents (let ((title (yeetube--scrape-string pos "title" (if videop "text" "simpleText"))) (view-count (when videop (yeetube--scrape-string pos "viewCountText" "simpleText"))) (duration (if videop @@ -498,6 +499,7 @@ Image is inserted in BUFFER for ENTRY." "disabled"))) (yeetube--retrieve-thumnail thumbnail entry "*yeetube*") (push entry yeetube-content)))))) + ;; Reverse the list of entries before returning (cl-callf nreverse yeetube-content)) (add-variable-watcher 'yeetube-saved-videos #'yeetube-update-saved-videos-list) -- cgit v1.2.3 From fc91fd70b997958b1391b0914354fc7d3c8ee682 Mon Sep 17 00:00:00 2001 From: Thanos Apollo Date: Mon, 18 Mar 2024 18:17:11 +0200 Subject: yeetube-mode-map: Add yeetube-video-or-playlist --- yeetube.el | 1 + 1 file changed, 1 insertion(+) diff --git a/yeetube.el b/yeetube.el index 6b658d2..0162476 100644 --- a/yeetube.el +++ b/yeetube.el @@ -597,6 +597,7 @@ FIELDS-FACE-PAIRS is a list of fields and faces." :doc "Keymap for yeetube commands" "RET" #'yeetube-play "M-RET" #'yeetube-search + "C-" #'yeetube-video-or-playlist-page "b" #'yeetube-browse-url "d" #'yeetube-download-video "D" #'yeetube-download-change-directory -- cgit v1.2.3 From 59635b779cb7890d2cb2b6af75014d2a29b35efa Mon Sep 17 00:00:00 2001 From: Thanos Apollo Date: Mon, 18 Mar 2024 21:05:25 +0200 Subject: yeetube-mode: Add emojify-mode --- yeetube.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yeetube.el b/yeetube.el index 0162476..1d88ed1 100644 --- a/yeetube.el +++ b/yeetube.el @@ -676,7 +676,9 @@ A and B are vectors." (setq-local yeetube-mpv-show-status t) (display-line-numbers-mode 0) (tabulated-list-init-header) - (tabulated-list-print)) + (tabulated-list-print) + (when (fboundp 'emojify-mode) + (emojify-mode 1))) (provide 'yeetube) ;;; yeetube.el ends here -- cgit v1.2.3 From bb38d60adcc12b002917fe7a141d74fe1945ae3f Mon Sep 17 00:00:00 2001 From: Thanos Apollo Date: Tue, 19 Mar 2024 22:02:52 +0200 Subject: Version bump & fix indentation --- yeetube.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yeetube.el b/yeetube.el index 1d88ed1..765e04d 100644 --- a/yeetube.el +++ b/yeetube.el @@ -5,7 +5,7 @@ ;; Author: Thanos Apollo ;; Keywords: extensions youtube videos ;; URL: https://thanosapollo.org/projects/yeetube/ -;; Version: 2.1.4 +;; Version: 2.1.5 ;; Package-Requires: ((emacs "27.2") (compat "29.1.4.2")) @@ -222,8 +222,8 @@ Keywords: "Play video at point in *yeetube* buffer." (interactive) (let* ((video-url (yeetube-get-url)) - (video-title (yeetube-get :title)) - (proc (funcall yeetube-play-function video-url))) + (video-title (yeetube-get :title)) + (proc (funcall yeetube-play-function video-url))) (when (processp proc) (process-put proc :now-playing video-title)) (push (list :url video-url :title video-title) yeetube-history) @@ -433,7 +433,7 @@ Image is inserted in BUFFER for ENTRY." (goto-char pos) (search-forward item nil t) (when sub-item - (search-forward sub-item nil t)) + (search-forward sub-item nil t)) (forward-char) (search-forward "\"") (backward-char) @@ -646,7 +646,7 @@ A and B are vectors." (units-a (length (member (nth 1 split-a) intervals))) (units-b (length (member (nth 1 split-b) intervals)))) (if (= units-a units-b) - (< (string-to-number (nth 0 split-a)) (string-to-number (nth 0 split-b))) + (< (string-to-number (nth 0 split-a)) (string-to-number (nth 0 split-b))) (> units-a units-b)))) (define-derived-mode yeetube-mode tabulated-list-mode "Yeetube" -- cgit v1.2.3