├── .gitignore ├── helm-spotify.png ├── LICENSE.txt ├── README.org └── helm-spotify.el /.gitignore: -------------------------------------------------------------------------------- 1 | /flycheck-helm-spotify.el 2 | /helm-spotify-autoloads.el 3 | -------------------------------------------------------------------------------- /helm-spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krisajenkins/helm-spotify/HEAD/helm-spotify.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Kris Jenkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Helm Spotify 2 | 3 | A simple Emacs interface for Spotify that makes good use of helm. 4 | 5 | [[helm-spotify.png]] 6 | 7 | * Video 8 | 9 | See how [[http://www.youtube.com/watch?v=XjKtkEMUYGc&feature=youtu.be][the first version was coded in 16 minutes]]. 10 | 11 | ** Installation 12 | 13 | If you're hooked up to [[http://melpa.milkbox.net/][MELPA]]: 14 | 15 | #+BEGIN_EXAMPLE 16 | M-x package-refresh-contents 17 | M-x package-install RET helm-spotify 18 | #+END_EXAMPLE 19 | 20 | Alternatively just grab the single =helm-spotify.el= file and 21 | install that in your preferred way. 22 | 23 | * Usage 24 | 25 | =M-x helm-spotify= and type a search string. 26 | 27 | (The search begins after you've typed at least 2 characters. You can 28 | use space-separated terms for psuedo-fuzzy matching.) 29 | 30 | *** Keys 31 | 32 | | =C-n= | Next item. | 33 | | =C-p= | Previous item. | 34 | | =RET= | Play this track. | 35 | | =C-z= | Play this album. | 36 | | =TAB= | More options. | 37 | | =C-h m= | Full list of keyboard shortcuts. | 38 | 39 | * Status 40 | 41 | Ready to use. 42 | 43 | Currently OSX & Linux only. Windows support is available, but 44 | partial. Please contribute the code for your platform, if you can! 45 | 46 | * Supporting Other Platforms 47 | 48 | Find out what emacs says your =system-type= is. (=C-h v system-type=). 49 | Let's say it shows the symbol =ms-dos=. Then you need to write this function: 50 | 51 | #+BEGIN_SRC emacs-lisp 52 | (defmulti-method spotify-play-href 'ms-dos 53 | (href) 54 | ... 55 | ... href is a string that's something like "spotify:track:5Yt80fWRB8JG73XlPjrrKP" 56 | ... 57 | ... here, you write any code that will cause Spotify to play that href. 58 | ... 59 | ) 60 | #+END_SRC 61 | 62 | Then submit a pull request! 63 | 64 | ** Credits 65 | 66 | Thanks to [[https://github.com/aes][Anders Eurenius]] for supplying the Linux portion of the code. 67 | Thanks to [[https://github.com/Kungsgeten][Kungsgeten]] for supplying the Windows portion of the code. 68 | 69 | I tip my hat to the team behind [[https://github.com/emacs-helm/helm][Helm]], to [[https://github.com/purcell][Steve Purcell]] (for [[https://github.com/milkypostman/melpa][Melpa]]), 70 | and to [[https://github.com/kurisuwhyte][Christina Whyte]] (for [[https://github.com/kurisuwhyte/emacs-multi][Emacs Multimethods]]). 71 | 72 | -------------------------------------------------------------------------------- /helm-spotify.el: -------------------------------------------------------------------------------- 1 | ;;; helm-spotify.el --- Control Spotify with Helm. 2 | ;; Copyright 2013 Kris Jenkins 3 | ;; 4 | ;; Author: Kris Jenkins 5 | ;; Maintainer: Kris Jenkins 6 | ;; Keywords: helm spotify 7 | ;; URL: https://github.com/krisajenkins/helm-spotify 8 | ;; Created: 14th October 2013 9 | ;; Version: 0.1.1 10 | ;; Package-Requires: ((helm "0.0.0") (multi "2.0.0")) 11 | 12 | ;;; Commentary: 13 | ;; 14 | ;; A search & play interface for Spotify. 15 | ;; 16 | ;; Currently supports OSX, Linux & Windows. 17 | ;; 18 | ;; (Want support for another platform? There's a guide in the github README.) 19 | 20 | ;;; Code: 21 | 22 | ;;; API Reference: https://developer.spotify.com/technologies/web-api/ 23 | (require 'url) 24 | (require 'json) 25 | (require 'helm) 26 | (require 'multi) 27 | 28 | (defun alist-get (symbols alist) 29 | "Look up the value for the chain of SYMBOLS in ALIST." 30 | (if symbols 31 | (alist-get (cdr symbols) 32 | (assoc (car symbols) alist)) 33 | (cdr alist))) 34 | 35 | (defmulti spotify-play-href (href) 36 | "Get the Spotify app to play the object with the given HREF." 37 | system-type) 38 | 39 | (defmulti-method spotify-play-href 'darwin 40 | (href) 41 | (shell-command (format "osascript -e 'tell application %S to play track %S'" 42 | "Spotify" 43 | href))) 44 | 45 | (defmulti-method spotify-play-href 'gnu/linux 46 | (href) 47 | (shell-command "dbus-send --print-reply --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Pause") 48 | (shell-command (format "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.OpenUri \"string:%s\"" 49 | href))) 50 | 51 | (defmulti-method spotify-play-href 'windows-nt 52 | (href) 53 | (shell-command (format "explorer %S" href))) 54 | 55 | (defmulti-method-fallback spotify-play-href 56 | (href) 57 | (message "Sorry, helm-spotify does not support playing tracks on %S." system-type)) 58 | 59 | (defun spotify-play-track (track) 60 | "Get the Spotify app to play the TRACK." 61 | (spotify-play-href (alist-get '(uri) track))) 62 | 63 | (defun spotify-get-track (album-href) 64 | (let ((response (with-current-buffer 65 | (url-retrieve-synchronously album-href) 66 | (goto-char url-http-end-of-headers) 67 | (json-read)))) 68 | (aref (alist-get '(tracks items) response) 0))) 69 | 70 | (defun spotify-play-album (track) 71 | "Get the Spotify app to play the album for this TRACK." 72 | (let ((first-track (spotify-get-track (alist-get '(album href) track)))) 73 | (spotify-play-href (alist-get '(uri) first-track)))) 74 | 75 | 76 | (defun spotify-search (search-term) 77 | "Search spotify for SEARCH-TERM, returning the results as a Lisp structure." 78 | (let ((a-url (format "https://api.spotify.com/v1/search?q=%s&type=track" search-term))) 79 | (with-current-buffer 80 | (url-retrieve-synchronously a-url) 81 | (goto-char url-http-end-of-headers) 82 | (json-read)))) 83 | 84 | (defun spotify-format-track (track) 85 | "Given a TRACK, return a a formatted string suitable for display." 86 | (let ((track-name (alist-get '(name) track)) 87 | (track-length (/ (alist-get '(duration_ms) track) 1000)) 88 | (album-name (alist-get '(album name) track)) 89 | (artist-names (mapcar (lambda (artist) 90 | (alist-get '(name) artist)) 91 | (alist-get '(artists) track)))) 92 | (format "%s (%dm%0.2ds)\n%s - %s" 93 | track-name 94 | (/ track-length 60) (mod track-length 60) 95 | (mapconcat 'identity artist-names "/") 96 | album-name))) 97 | 98 | (defun spotify-search-formatted (search-term) 99 | (mapcar (lambda (track) 100 | (cons (spotify-format-track track) track)) 101 | (alist-get '(tracks items) (spotify-search search-term)))) 102 | 103 | 104 | (defun helm-spotify-search () 105 | (spotify-search-formatted helm-pattern)) 106 | 107 | (defun helm-spotify-actions-for-track (actions track) 108 | "Return a list of helm ACTIONS available for this TRACK." 109 | `((,(format "Play Track - %s" (alist-get '(name) track)) . spotify-play-track) 110 | (,(format "Play Album - %s" (alist-get '(album name) track)) . spotify-play-album) 111 | ("Show Track Metadata" . pp))) 112 | 113 | ;;;###autoload 114 | (defvar helm-source-spotify-track-search 115 | '((name . "Spotify") 116 | (volatile) 117 | (delayed) 118 | (multiline) 119 | (requires-pattern . 2) 120 | (candidates-process . helm-spotify-search) 121 | (action-transformer . helm-spotify-actions-for-track))) 122 | 123 | ;;;###autoload 124 | (defun helm-spotify () 125 | "Bring up a Spotify search interface in helm." 126 | (interactive) 127 | (helm :sources '(helm-source-spotify-track-search) 128 | :buffer "*helm-spotify*")) 129 | 130 | (provide 'helm-spotify) 131 | ;;; helm-spotify.el ends here 132 | --------------------------------------------------------------------------------