diff options
Diffstat (limited to 'lisp/mastodon-media.el')
-rw-r--r-- | lisp/mastodon-media.el | 232 |
1 files changed, 40 insertions, 192 deletions
diff --git a/lisp/mastodon-media.el b/lisp/mastodon-media.el index d1ec871..23fbc79 100644 --- a/lisp/mastodon-media.el +++ b/lisp/mastodon-media.el @@ -38,212 +38,60 @@ :prefix "mastodon-media-" :group 'mastodon) -(defvar mastodon-media-show-avatars-p - (image-type-available-p 'imagemagick) - "A boolean value stating whether to show avatars in timelines.") - -(defvar mastodon-media--generic-avatar-data - (base64-decode-string - "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") - "The PNG data for a generic 100x100 avatar") - -(defvar mastodon-media--generic-broken-image-data - (base64-decode-string - "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") - "The PNG data for a generic 200x200 'broken image' view") - -(defun mastodon-media--process-image-response (status-plist marker image-options region-length image-url) - "Callback function processing the url retrieve response for URL. - -STATUS-PLIST is the usual plist of status events as per `url-retrieve'. -IMAGE-OPTIONS are the precomputed options to apply to the image. -MARKER is the marker to where the response should be visible. -REGION-LENGTH is the length of the region that should be replaced with the image. -IMAGE-URL is the URL that was retrieved. -" - (let ((url-buffer (current-buffer)) - (is-error-response-p (eq :error (car status-plist)))) +(defun mastodon-media--image-from-url (url) + "Takes a URL and return an image." + (let ((buffer (url-retrieve-synchronously url))) (unwind-protect - (let* ((data (unless is-error-response-p - (goto-char (point-min)) - (search-forward "\n\n") - (buffer-substring (point) (point-max)))) - (image (when data - (apply #'create-image data (when image-options 'imagemagick) - t image-options)))) - (switch-to-buffer (marker-buffer marker)) - ;; Save narrowing in our buffer - (let ((inhibit-read-only t)) - (save-restriction - (widen) - (put-text-property marker (+ marker region-length) 'media-state 'loaded) - (when image - ;; We only set the image to display if we could load - ;; it; we already have set a default image when we - ;; added the tag. - (put-text-property marker (+ marker region-length) - 'display image)) - ;; We are done with the marker; release it: - (set-marker marker nil))) - (kill-buffer url-buffer))))) - -(defun mastodon-media--load-image-from-url (url media-type start region-length) - "Takes a URL and MEDIA-TYPE and load the image asynchronously. - -MEDIA-TYPE is a symbol and either 'avatar or 'media-link." - ;; TODO: Cache the avatars - (let ((image-options (when (image-type-available-p 'imagemagick) - (cond - ((eq media-type 'avatar) - `(:height ,mastodon-avatar-height)) - ((eq media-type 'media-link) - `(:max-height ,mastodon-preview-max-height)))))) - (url-retrieve url - #'mastodon-media--process-image-response - (list (copy-marker start) image-options region-length url)))) + (let ((data (with-current-buffer buffer + (goto-char (point-min)) + (search-forward "\n\n") + (buffer-substring (point) (point-max))))) + (insert "\n") + (insert-image (create-image data nil t))) + (kill-buffer buffer)))) (defun mastodon-media--select-next-media-line () - "Find coordinates of the next media to load. - -Returns the list of (`start' . `end', `media-symbol') points of -that line and string found or nil no more media links were -found." - (let ((next-pos (point))) - (while (and (setq next-pos (next-single-property-change next-pos 'media-state)) - (or (not (eq 'needs-loading (get-text-property next-pos 'media-state))) - (null (get-text-property next-pos 'media-url)) - (null (get-text-property next-pos 'media-type)))) - ;; do nothing - the loop will proceed - ) - (when next-pos - (let ((media-type (get-text-property next-pos 'media-type))) - (cond - ;; Avatars are just one character in the buffer - ((eq media-type 'avatar) - (list next-pos (+ next-pos 1) 'avatar)) - ;; Media links are 5 character ("[img]") - ((eq media-type 'media-link) - (list next-pos (+ next-pos 5) 'media-link))))))) + "Find coordinates of a line that contains `Media_Links::' + +Returns the cons of (`start' . `end') points of that line or nil no +more media links were found." + (let ((foundp (search-forward-regexp "Media_Link::" nil t))) + (when foundp + (let ((start (progn (move-beginning-of-line nil) (point))) + (end (progn (move-end-of-line nil) (point)))) + (cons start end))))) (defun mastodon-media--valid-link-p (link) "Checks to make sure that the missing string has not been returned." (let ((missing "/files/small/missing.png")) - (and link - (not (equal link missing))))) + (not (equal link missing)))) + +(defun mastodon-media--line-to-link (line-points) + "Returns the url of the media link given at the given point. + +`LINE-POINTS' is a cons of (`start' . `end') positions of the line with +the `Media_Link:: <url>' text." + (replace-regexp-in-string "Media_Link:: " "" + (buffer-substring + (car line-points) + (cdr line-points)))) + +(defun mastodon-media--delete-line (line) + "Deletes the current media line" + (delete-region (car line) (cdr line))) (defun mastodon-media--inline-images () "Find all `Media_Links:' in the buffer replacing them with the referenced image." (interactive) (goto-char (point-min)) - (let (line-details) - (while (setq line-details (mastodon-media--select-next-media-line)) - (let* ((start (car line-details)) - (end (cadr line-details)) - (media-type (cadr (cdr line-details))) - (image-url (get-text-property start 'media-url))) - (if (not (mastodon-media--valid-link-p image-url)) - ;; mark it at least as not needing loading any more - (put-text-property start end 'media-state 'invalid-url) - ;; proceed to load this image asynchronously - (put-text-property start end 'media-state 'loading) - (mastodon-media--load-image-from-url image-url media-type start (- end start))))))) - -(defun mastodon-media--get-avatar-rendering (avatar-url) - "Returns the string to be written that renders the avatar at AVATAR-URL." - ;; We use just an empty space as the textual representation. - ;; This is what a user will see on a non-graphical display - ;; where not showing an avatar at all is preferable. - (let ((image-options (when (image-type-available-p 'imagemagick) - `(:height ,mastodon-avatar-height)))) - (concat - (propertize " " - 'media-url avatar-url - 'media-state 'needs-loading - 'media-type 'avatar - 'display (apply #'create-image mastodon-media--generic-avatar-data - (when image-options 'imagemagick) - t image-options)) - " "))) - -(defun mastodon-media--get-media-link-rendering (media-url) - "Returns the string to be written that renders the image at MEDIA-URL." - (concat - (propertize "[img]" - 'media-url media-url - 'media-state 'needs-loading - 'media-type 'media-link - 'display (create-image mastodon-media--generic-broken-image-data nil t)) - " ")) + (let (line-coordinates) + (while (setq line-coordinates (mastodon-media--select-next-media-line)) + (let ((link (mastodon-media--line-to-link line-coordinates))) + (when (mastodon-media--valid-link-p link) + (mastodon-media--image-from-url link) + (mastodon-media--delete-line line-coordinates)))))) (provide 'mastodon-media) ;;; mastodon-media.el ends here |