<?xmlversion="1.0" encoding="utf-8"?><xsl:stylesheetversion="3.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:dc="http://purl.org/dc/elements/1.1/"xmlns:atom="http://www.w3.org/2005/Atom"><xsl:outputmethod="html"version="1.0"encoding="UTF-8"indent="yes"/><xsl:templatematch="/"><htmlxmlns="http://www.w3.org/1999/xhtml"lang="en"><head><title> RSS Feed | <xsl:value-ofselect="/rss/channel/title"/></title><linkrel="stylesheet"href="/assets/style.css"/></head><body><h1style="margin-bottom:0">Recent posts: <xsl:value-ofselect="/rss/channel/title"/></h1><p> This is an RSS feed. You can subscribe to <ahref=" {/rss/channel/link}"><xsl:value-ofselect="/rss/channel/link"/></a> in a feed reader such as <ahref="https://github.com/skeeto/elfeed">Elfeed</a> for Emacs, <ahref="https://www.inoreader.com/">Inoreader</a>, or <ahref="https://newsblur.com/">NewsBlur</a>, or you can use tools like <ahref="https://github.com/rss2email/rss2email">rss2email</a>. The feed includes the full blog posts.You can also view the posts on the website at<ahref="{/rss/channel/atom:link[contains(@rel,'alternate')]/@href}"><xsl:value-ofselect="/rss/channel/atom:link[contains(@rel,'alternate')]/@href"/></a> .</p><xsl:for-eachselect="/rss/channel/item"><divstyle="margin-bottom:20px"><div><xsl:value-ofselect="pubDate"/></div><div><a><xsl:attributename="href"><xsl:value-ofselect="link/@href"/></xsl:attribute><xsl:value-ofselect="title"/></a></div></div></xsl:for-each></body></html></xsl:template></xsl:stylesheet>
atom.xsl
<?xmlversion="1.0" encoding="utf-8"?><xsl:stylesheetversion="3.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:atom="http://www.w3.org/2005/Atom"><xsl:outputmethod="html"version="1.0"encoding="UTF-8"indent="yes"/><xsl:templatematch="/"><htmlxmlns="http://www.w3.org/1999/xhtml"lang="en"><head><title> Atom Feed | <xsl:value-ofselect="/atom:feed/atom:title"/></title><linkrel="stylesheet"href="/assets/style.css"/></head><body><h1style="margin-bottom:0">Recent posts: <xsl:value-ofselect="/atom:feed/atom:title"/></h1><p> This is an Atom feed. You can subscribe to <ahref=" {/atom:feed/atom:link/@href}"><xsl:value-ofselect="/atom:feed/atom:link/@href"/></a> in a feed reader such as <ahref="https://github.com/skeeto/elfeed">Elfeed</a> for Emacs, <ahref="https://www.inoreader.com/">Inoreader</a>, or <ahref="https://newsblur.com/">NewsBlur</a>, or you can use tools like <ahref="https://github.com/rss2email/rss2email">rss2email</a>. The feed includes the full blog posts.You can also view the posts on the website at<ahref="{/atom:feed/atom:link[contains(@rel,'alternate')]/@href}"><xsl:value-ofselect="/atom:feed/atom:link[contains(@rel,'alternate')]/@href"/></a> .</p><xsl:for-eachselect="/atom:feed/atom:entry"><divstyle="margin-bottom:20px"><div><xsl:value-ofselect="substring(atom:updated, 0, 11)"/></div><div><a><xsl:attributename="href"><xsl:value-ofselect="atom:link/@href"/></xsl:attribute><xsl:value-ofselect="atom:title"/></a></div></div></xsl:for-each></body></html></xsl:template></xsl:stylesheet>
tldr (2167 words): I can make animating presentation maps easier by
writing my own functions for the Emacs text editor. In this post, I
show how I can animate an SVG element by element. I can also add IDs
to the path and use CSS to build up an SVG with temporary highlighting
in a Reveal.js presentation.
Text from the sketch
PNG: Inkscape: trace
Supernote (e-ink)
iPad: Adobe Fresco
Convert PDF to SVG with Inkscape (Cairo option) or pdftocairo)
PNG / Supernote PDF: Combined shapes. Process
Break apart, fracture overlaps
Recombine
Set IDs
Sort paths -> Animation style 1
Adobe Fresco: individual elements in order; landscape feels natural
Animation styles
Animation style 1: Display elements one after another
Animation style 2: Display elements one after another, and also show/hide highlights
Table: slide ID, IDs to add, temporary highlights -> Reveal.js: CSS with transitions
Ideas for next steps:
Explore graphviz & other diagramming tools
Frame-by-frame SVGs
on include
write to files
FFmpeg crossfade
Recording Reveal.js presentations
Use OCR results?
I often have a hard time organizing my thoughts into a linear
sequence. Sketches are nice because they let me jump around and still
show the connections between ideas. For presentations, I'd like to
walk people through these sketches by highlighting different areas.
For example, I might highlight the current topic or show the previous
topics that are connected to the current one. Of course, this is
something Emacs can help with. Before we dive into it, here are quick
previews of the kinds of animation I'm talking about:
Getting the sketches: PDFs are not all the same
Let's start with getting the sketches. I usually export my sketches as
PNGs from my Supernote A5X. But if I know that I'm going to animate a
sketch, I can export it as a PDF. I've recently been experimenting
with Adobe Fresco on the iPad, which can also export to PDF. The PDF I
get from Fresco is easier to animate, but I prefer to draw on the
Supernote because it's an e-ink device (and because the kiddo usually
uses the iPad).
If I start with a PNG, I could use Inkscape to trace the PNG and turn
it into an SVG. I think Inkscape uses autotrace behind the scenes. I
don't usually put my highlights on a separate layer, so autotrace will
make odd shapes.
It's a lot easier if you start off with vector graphics in the first
place. I can export a vector PDF from the SuperNote A5X and either
import it into Inkscape using the Cairo option or use the command-line
pdftocairo tool.
I've been looking into using Adobe Fresco, which is a free app
available for the iPad. Fresco's PDF export can be converted to an SVG
using Inkscape or PDF to Cairo. What I like about the output of this
app is that it gives me individual elements as their own paths and
they're listed in order of drawing. This makes it really easy to
animate by just going through the paths in order.
Animation style 1: displaying paths in order
Here's a sample SVG file that pdfcairo creates from an Adobe Fresco
PDF export:
Adobe Fresco also includes built-in time-lapse, but since I often like
to move things around or tidy things up, it's easier to just work with
the final image, export it as a PDF, and convert it to an SVG.
I can make a very simple animation by setting the opacity of all the
paths to 0, then looping through the elements to set the opacity back
to 1 and write that version of the SVG to a separate file.
From how-can-i-generate-png-frames-that-step-through-the-highlights:
my-animate-svg-paths: Add one path at a time. Save the resulting SVGs to OUTPUT-DIR.
Neither Supernote nor Adobe Fresco give me the original stroke
information. These are filled shapes, so I can't animate something
drawing it. But having different elements appear in sequence is fine
for my purposes. If you happen to know how to get stroke information
out of Supernote .note files or of an iPad app that exports nice
single-line SVGs that have stroke direction, I would love to hear
about it.
Identifying paths from Supernote sketches
When I export a PDF from Supernote and convert it to an SVG, each
color is a combined shape with all the elements. If I want to animate
parts of the image, I have to break it up and recombine selected
elements (Inkscape's Ctrl-k shortcut) so that the holes in shapes are
properly handled. This is a bit of a tedious process and it usually
ends up with elements in a pretty random order. Since I have to
reorder elements by hand, I don't really want to animate the sketch
letter-by-letter. Instead, I combine them into larger chunks like
topics or paragraphs.
The following code takes the PDF, converts it to an SVG, recolours
highlights, and then breaks up paths into elements:
my-sketch-convert-pdf-and-break-up-paths: Convert PDF to SVG and break up paths.
(defunmy-sketch-convert-pdf-and-break-up-paths (pdf-file &optional rotate)
"Convert PDF to SVG and break up paths."
(interactive (list (read-file-name
(format "PDF (%s): "
(my-latest-file "~/Dropbox/Supernote/EXPORT/""pdf"))
"~/Dropbox/Supernote/EXPORT/"
(my-latest-file "~/Dropbox/Supernote/EXPORT/""pdf")
t
nil
(lambda (s) (string-match "pdf" s)))))
(unless (file-exists-p (concat (file-name-sans-extension pdf-file) ".svg"))
(call-process "pdftocairo" nil nil nil "-svg" (expand-file-name pdf-file)
(expand-file-name (concat (file-name-sans-extension pdf-file) ".svg"))))
(let ((dom (xml-parse-file (expand-file-name (concat (file-name-sans-extension pdf-file) ".svg"))))
highlights)
(setq highlights (dom-node 'g'((id . "highlights"))))
(dom-append-child dom highlights)
(dolist (path (dom-by-tag dom 'path))
;; recolor and move
(unless (string-match (regexp-quote "rgb(0%,0%,0%)") (or (dom-attr path 'style) ""))
(dom-remove-node dom path)
(dom-append-child highlights path)
(dom-set-attribute
path 'style
(replace-regexp-in-string
(regexp-quote "rgb(78.822327%,78.822327%,78.822327%)")
"#f6f396"
(or (dom-attr path 'style) ""))))
(let ((parent (dom-parent dom path)))
;; break apart
(when (dom-attr path 'd)
(dolist (part (split-string (dom-attr path 'd) "M " t " +"))
(dom-append-child
parent
(dom-node 'path`((style . ,(dom-attr path 'style))
(d . ,(concat "M " part))))))
(dom-remove-node dom path))))
;; remove the use
(dolist (use (dom-by-tag dom 'use))
(dom-remove-node dom use))
(dolist (use (dom-by-tag dom 'image))
(dom-remove-node dom use))
;; move the first g down
(let ((g (car (dom-by-id dom "surface1"))))
(setf (cddar dom)
(seq-remove (lambda (o)
(and (listp o) (string= (dom-attr o 'id) "surface1")))
(dom-children dom)))
(dom-append-child dom g)
(when rotate
(let* ((old-width (dom-attr dom 'width))
(old-height (dom-attr dom 'height))
(view-box (mapcar 'string-to-number (split-string (dom-attr dom 'viewBox))))
(rotate (format "rotate(90) translate(0 %s)" (- (elt view-box 3)))))
(dom-set-attribute dom 'width old-height)
(dom-set-attribute dom 'height old-width)
(dom-set-attribute dom 'viewBox (format "0 0 %d %d" (elt view-box 3) (elt view-box 2)))
(dom-set-attribute highlights 'transform rotate)
(dom-set-attribute g 'transform rotate))))
(with-temp-file (expand-file-name (concat (file-name-sans-extension pdf-file) "-split.svg"))
(svg-print (car dom)))))
You can see how the spaces inside letters like "o" end up being black.
Selecting and combining those paths fixes that.
If there were shapes that were touching, then I need to draw lines and
fracture the shapes in order to break them apart.
The end result should be an SVG with the different chunks that I might
want to animate, but I need to identify the paths first. You can
assign object IDs in Inkscape, but this is a bit of an annoying
process since I haven't figured out a keyboard-friendly way to set
object IDs. I usually find it easier to just set up an Autokey
shortcut (or AutoHotkey in Windows) to click on the ID text box so
that I can type something in.
Autokey script for clicking
import time
x, y= mouse.get_location()
# Use the coordinates of the ID text field on your screen; xev can help
mouse.click_absolute(3152, 639, 1)
time.sleep(1)
keyboard.send_keys("<ctrl>+a")
mouse.move_cursor(x, y)
Then I can select each element, press the shortcut key, and type an ID
into the textbox. I might use "t-…" to indicate the text for a map
section, "h-…" to indicate a highlight, and arrows by specifying
their start and end.
To simplify things, I wrote a function in Emacs that will go through
the different groups that I've made, show each path in a different
color and with a reasonable guess at a bounding box, and prompt me for
an ID. This way, I can quickly assign IDs to all of the paths. The
completion is mostly there to make sure I don't accidentally reuse an
ID, although it can try to combine paths if I specify the ID. It saves
the paths after each change so that I can start and stop as needed.
Identifying paths in Emacs is usually much nicer than identifying them
in Inkscape.
my-svg-identify-paths: Prompt for IDs for each path in FILENAME.
(defunmy-svg-identify-paths (filename)
"Prompt for IDs for each path in FILENAME."
(interactive (list (read-file-name "SVG: " nil nil
(lambda (f) (string-match "\\.svg$" f)))))
(let* ((dom (car (xml-parse-file filename)))
(paths (dom-by-tag dom 'path))
(vertico-count 3)
(ids (seq-keep (lambda (path)
(unless (string-match "path[0-9]+" (or (dom-attr path 'id) "path0"))
(dom-attr path 'id)))
paths))
(edges (window-inside-pixel-edges (get-buffer-window)))
id)
(my-svg-display "*image*" dom nil t)
(dolist (path paths)
(when (string-match "path[0-9]+" (or (dom-attr path 'id) "path0"))
;; display the image with an outline
(unwind-protect
(progn
(my-svg-display "*image*" dom (dom-attr path 'id) t)
(setq id (completing-read
(format "ID (%s): " (dom-attr path 'id))
ids))
;; already exists, merge with existing element
(if-let ((old (dom-by-id dom id)))
(progn
(dom-set-attribute
old
'd
(concat (dom-attr (dom-by-id dom id) 'd)
" ";; change relative to absolute
(replace-regexp-in-string "^m""M"
(dom-attr path 'd))))
(dom-remove-node dom path)
(setq id nil))
(dom-set-attribute path 'id id)
(add-to-list 'ids id))))
;; save the image just in case we get interrupted halfway through
(with-temp-file filename
(svg-print dom))))))
Then I can animate SVGs by specifying the IDs. I can reorder the paths
in the SVG itself so that I can animate it group by group, like the
way that the Adobe Fresco SVGs were animated element by element.
The way it works is that the my-svg-reorder-paths function removes
and readds elements following the list of IDs specified, so
everything's ready to go for step-by-step animation. Here's the code:
Animation style 2: Building up a map with temporary highlights
I can also use CSS rules to transition between opacity values for more
complex animations. For my EmacsConf 2023 presentation, I wanted to
make a self-paced, narrated presentation so that people could follow
hyperlinks, read the source code, and explore. I wanted to include a
map so that I could try to make sense of everything. For this map, I
wanted to highlight the previous sections that were connected to the
topic for the current section.
I used a custom Org link to include the full contents of the SVG
instead of just including it with an img tag.
#+ATTR_HTML: :class r-stretchmy-include:~/proj/emacsconf-2023-emacsconf/map.svg?wrap=export html
my-include-export: Export PATH to FORMAT using the specified wrap parameter.
I wanted to be able to specify the entire sequence using a table in
the Org Mode source for my presentation. Each row had the slide ID, a
list of highlights in the form prev1,prev2;current, and a
comma-separated list of elements to add to the full-opacity view.
Reveal.js adds a "current" class to the slide, so I can use that as a
trigger for the transition. I have a bit of Emacs Lisp code that
generates some very messy CSS, in which I specify the ID of the slide,
followed by all of the elements that need their opacity set to 1, and
also specifying the highlights that will be shown in an animated way.
my-reveal-svg-progression-css: Make the CSS.
(defunmy-reveal-svg-progression-css (map-progression &optional highlight-duration)
"Make the CSS.map-progression should be a list of lists with the following format:((\"slide-id\" \"prev1,prev2;cur1\" \"id-to-add1,id-to-add2\") ...)."
(setq highlight-duration (or highlight-duration 2))
(let (full)
(format
"<style>%s</style>"
(mapconcat
(lambda (slide)
(setq full (append (split-string (elt slide 2) ",") full))
(format "#slide-%s.present path { opacity: 0.2 }%s { opacity: 1 !important }%s"
(car slide)
(mapconcat (lambda (id) (format "#slide-%s.present #%s" (car slide) id))
full
", ")
(my-reveal-svg-highlight-different-colors slide)))
map-progression
"\n"))))
Since it's automatically generated, I don't have to worry about it
once I've gotten it to work. It's all hidden in a
results drawer. So this CSS highlights specific parts of the SVG with
a transition, and the highlight changes over the course of a second or
two. It highlights the previous names and then the current one. The
topics I'd already discussed would be in black, and the topics that I
had yet to discuss would be in very light gray. This could give people
a sense of the progress through the presentation.
As a result, as I go through my presentation, the image appears to
build up incrementally, which is the effect that I was going for.
I can test this by exporting only my map slides:
Graphviz, mermaid-js, and other diagramming tools can make SVGs. I
should be able to adapt my code to animate those diagrams by adding
other elements in addition to path. Then I'll be able to make
diagrams even more easily.
Since SVGs can contain CSS, I could make an SVG equivalent of the
CSS rules I used for the presentation, maybe calling a function with
a Lisp expression that specifies the operations (ex:
("frame-001.svg" "h-foo" opacity 1)). Then I could write frames to
SVGs.
FFmpeg has a crossfade filter. With a little bit of figuring out, I
should be able to make the same kind of animation in a webm form
that I can include in my regular videos instead of using Reveal.js
and CSS transitions.
I've also been thinking about automating the recording of my
Reveal.js presentations. For my EmacsConf talk, I opened my
presentation, started the recording with the system audio and the
screen, and then let it autoplay the presentation. I checked on it
periodically to avoid the screensaver/energy saving things from
kicking in and so that I could stop the recording when it's
finished. If I want to make this take less work, one option is to
use ffmpeg's "-t" argument to specify the expected duration of the
presentation so that I don't have to manually stop it. I'm also
thinking about using Puppeteer to open the presentation, check when
it's fully loaded, and start the process to record it - maybe even
polling to see whether it's finished. I haven't gotten around to it
yet. Anyhow, those are some ideas to explore next time.
As for animation, I'm still curious about the possibility of
finding a way to access the raw stroke information if it's even
available from my Supernote A5X (difficult because it's a
proprietary data format) or finding an app for the iPad that exports
single line SVGs that use stroke information instead of fill. That
would only be if I wanted to do those even fancier animations that
look like the whole thing is being drawn for you. I was trying to
figure out if I could green screen the Adobe Fresco timelapse videos
so that even if I have a pre-sketch to figure out spacing and remind
me what to draw, I can just export the finished elements. But
there's too much anti-aliasing and I haven't figured out how to do
it cleanly yet. Maybe some other day.
I use Google Cloud Vision's text detection engine to convert my
handwriting to text. It can give me bounding polygons for words or
paragraphs. I might be able to figure out which curves are entirely
within a word's bounding polygon and combine those automatically.
It would be pretty cool if I could combine the words recognized by
Google Cloud Vision with the word-level timestamps from speech
recognition so that I could get word-synced sketchnote animations
with maybe a little manual intervention.
Anyway, those are some workflows for animating sketches with Inkscape
and Emacs. Yay Emacs!
Inspired by Arne Bab (who mentioned being inspired by my sketches)
I've been drawing daily moments since 2023-03-20. Nothing fancy, just
a quick reminder of our day.
I draw while the kiddo watches a bedtime video. Sometimes she suggests
a moment to draw, or flips through the pages and laughs at the
memories.
I also have my text journal (occasionally with photos) and my time
tracker. It doesn't take a lot of time to update them, and I like what
they let me do.
I like this. It makes the path visible. I'm looking forward to seeing
what this is like after years
I used to draw and write monthly reviews. I'd like to get back to
those. They help with the annual reviews, too.
phone: review sketches, jot keywords on phone
computer: draw sketch, braindump, blog
Right now I put 12 days on one A5.
Week? nah, not really needed
More details? longer to review, though. Redirect drawing to monthly notes
Still working on shaping the day/week more proactively. A+ likes to
take the lead, so maybe it's more like strewing.
If you're viewing this on my blog, you might be able to click on the
links below to open them in a viewer and then swipe or use arrow keys
to navigate.
What can I learn from thinking about the flow rate?
input > output, and that's okay
Parts:
idea: agenda/review?
capture: refile to tags
toot: use this more, get stuff out
braindump: use transcripts or outline
sketch: bedtime
post: cut off earlier, can follow up
video: workflow tweaks
Thoughts:
more input is not always better; already plenty, not limiting factor
prioritize, review
overflow: add notes and pass it along, if poss.
can add things later (results, sketches, posts, videos)
manage expectations; minimize commitments
favour small things that flow easily
collect things in a container
tags, outlines
posts, videos
minimize filing, but still find related notes
become more efficient and effective
The heap:
Org dates have been working for time-sensitive/urgent things
Lots of discretionary things get lost in the shuffle
waste info collected but forgotten
half-finished posts that have gone stale
redoing things
late replies to conversations
things that are just in my config - some people still find them, so that's fine
Next: toot more experiment with braindumping, video
I come up with way more ideas than I can work on, and that's okay.
That's good. It means I can always skim the top for interesting
things, and it's fine if things overflow as long as the important
stuff stays in the funnel. I'm experimenting with more ways to keep
things flowing.
I usually come up with lots of ideas and then revisit my priorities to
see if I can figure out 1-3 things I'd like to work on for my next
focused time sessions. These priorities are actually pretty stable for
the most part, but sometimes an idea jumps the queue and that's okay.
There's a loose net of projects/tasks that I'm currently working on
and things I'm currently interested in, so I want to connect ideas and
resources to those if I can. If they aren't connected, or if they're
low-priority and I probably won't get to them any time soon, it can
make a lot of sense to add quick notes and pass it along.
For things I want to think about some more, my audio braindumping
workflow seems to be working out as a way to capture lots of text
even when I'm away from my computer. I also have a bit more time to
sketch while waiting for the kiddo to get ready for bed. I can use the
sketchnotes as outlines to talk through while I braindump, and I can
take my braindumps and distill them into sketches. Then I can take
those and put them into blog posts. Instead of getting tempted to add
more and more to a blog post (just one more idea, really!), I can try
wrapping up earlier since I can always add a follow-up post. For some
things, making a video might be worthwhile, so smoothing out my
workflow for creating a video could be useful. I don't want to spend a
lot of time filing but I still want to be able to find related notes,
so automatically refiling based on tags (or possibly suggesting refile
targets based on vector similarity?) might help me shift things out of
my inbox.
I'm generally not bothered by the waste of coming up with ideas that I
don't get around to, since it's more like daydreaming or fun. I
sometimes get a little frustrated when I want to find an interesting
resource I remember coming across some time ago and I can't find it
with the words I'm looking for. Building more of a habit of capturing
interesting resources in my Org files and using my own words in the
notes will help while I wait for personal search engines to get
better. I'm a little slow when it comes to e-mails because I tend to
wait until I'm at my computer–and then when I'm at my computer, I
prefer to tinker or write. I occasionally redo things because I didn't
have notes from the previous solution or I couldn't find my notes.
That's fine too. I can get better at taking notes and finding them.
So I think some next steps for me are:
Post more toots on @sachac@emacs.ch; might be useful as a firehose for ideas. Share them back to my Org file so I have a link to the discussion (if any). Could be a quick way to see if anyone already knows of related packages/code or if anyone might have the same itch.
See if I can improve my braindumping/sketch workflow so that I can flesh out more ideas
Tweak my video process gradually so that I can include more screenshots and maybe eventually longer explanations
I received a Google Open Source Peer Bonus for my contributions to GNU
Emacs, which was a pleasant surprise. Thanks!
I'm thinking of ways to reinvest the ~USD 250 award into Emacs and the
community to see what a little money earmarked for that could do.
People have already donated enough to EmacsConf to cover hosting
costs, so that's all sorted out. People have also already sent me more
than enough to cover my hosting costs using my ancient
pay-what-you-want resources. I wonder how I could use the money to
help me make more blog posts and videos.
Speech recognition: Paying for cloud usage will let me do tiny
experiments without upgrading my X230T1 for now. I could start
with speech recognition as a way of fleshing out ideas and getting
them into text faster. Deepgram charges USD 0.0048/min for
batch-processing with Whisper Large and USD 0.0059/min for streaming
with their Nova-2 model, so that's… umm… ~860+ hours I could
process. Over the past couple of weeks of experimenting with this
idea, I've recorded about 1-2 hours of audio braindumps a day, so
that's still well over a year of being able to play around with this.
(And actually I still have USD ~187 of free trial credits with them,
so…)
AI: I might also be able to use AI for outlining/summarizing/cleaning up
my audio braindumps. I just have to figure out the right prompts for
ChatGPT. Here's one I've been experimenting with so that I can get
things into roughly an Org Mode format while still letting me easily
look things up in the transcript:
Outlining prompt
Reorganize this rough transcript into an outline of ideas.
Format it like this:
- item 1
- details
- more details
- verbatim quote from transcript
- more details
- verbatim quote from transcript
- more details
- verbatim quote from transcript
- details
- more details
- verbatim quote from transcript
- more details
- verbatim quote from transcript
- more details
- verbatim quote from transcript
- details
- more details
- verbatim quote from transcript
- more details
- verbatim quote from transcript
- more details
- verbatim quote from transcript
- more details
- item 2 ...
Drawing: The kiddo uses the iPad a lot for reading, but maybe I can
squeeze in some time to tinker around with different apps for drawing
and animation.
Video editing: Maybe I can learn more about video editing or figure
out what gear makes sense to add to my setup.
If you have other suggestions for low-cost experiments that might pay
off in terms of making more useful blog posts or videos, I'd love to
hear them!
The X230T is a lovely computer. This particular one is a
donation from Matthew Darling, and it has an i5-3320M. I occasionally
get tempted to upgrade to maybe a desktop with a GPU so that I can do
more experiments with Whisper, ffmpeg, or local AI models, but since I
still only have a tiny sliver of computing time each day before the
kiddo wakes up, it doesn't make sense to buy a powerful computer that
will sit idle most of the time. He also gave me a Surface Book with an
i7-6600U, and I can probably run stuff on it. It has a 1GB NVIDIA GPU,
even, so maybe I should figure out how I can ssh into it since it runs
Windows at the moment. There's a W530 with an i7-3820QM around here
with a 2GB NVIDIA GPU that also tends to be idle. That one dualboots
between Windows and Linux, but it tends to be in Windows because the
kiddo uses it to play Minecraft Bedrock. I've just set up SSH access
to WSL on both of them, so that should be promising. I'm surrounded by
excess compute resources that I could use for making videos either
through interactive applications like Kdenlive or through text-based
workflows using my Emacs Lisp functions. Besides, it makes sense to
focus on very short videos for now (or even blog posts with more
screenshots and animated GIFs). Maybe I just need to spend some time
this winter break to figure out some workflows. Hmm…
At some point during the conversion of my blog from Wordpress to 11ty,
I wanted to change my sketch links to use a custom shortcode instead
of referring to the sketch in my old wp-uploads directory. Because
Wordpress changed the filenames a little, I used the ID at the start
of the filename. I forgot that many of my filenames from 2013 to 2015
just had the date without a uniquely identifying letter or number
suffix, so many old references were ambiguous and my static site
generator just linked to the first matching file. When I was listening
to my old monthly reviews as part of my upcoming 10-year review, I
noticed the repeated links. So I wrote these functions to help me find
and replace markup of the form sketchLink "2013-10-06" with
sketchLink "2013-10-06 Daily drawing - thinking on paper #drawing",
replacing references to the same date with the next sketch in the
list. I figured that would be enough to get the basic use case sorted
out (usually a list of sketches in my monthly/weekly reviews), taking advantage of the my-list-sketches function I defined in my Emacs config.
Sometimes I needed to delete the whole list and start again:
(defunmy-insert-sketch-list-between (start-date end-date)
(insert
(mapconcat
(lambda (f)
(format"<li>%s sketchLink \"%s\" %s</li>\n"
(concat"{""%") ; avoid confusing 11ty when I export this
(file-name-sans-extension f)
(concat"%""}")))
(sort (seq-filter
(lambda (f) (and (string< f end-date) (not (string< f start-date))))
(my-list-sketches nil nil '("~/sync/sketches")))
'string<)
"")))
I used find-grep-dired to search for sketchLink
\"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\" and then I just used a
keyboard macro to process each file.
Anyway, really old monthly reviews like this one for October 2013
should mostly make sense again. I could probably pull out the correct
references from the Wordpress database backup, but what I've got is
probably okay. I would probably have gotten much grumpier trying to do
this without Emacs Lisp. Yay Emacs!
I sometimes want to move the Org source for my blog posts to the same
directory as the 11ty-exported HTML. This should make it easier to
update and reexport blog posts in the future. The following code
copies or moves the subtree to the 11ty export directory.
(defunmy-org-11ty-copy-subtree (&optional do-cut)
"Copy the subtree for the current post to the 11ty export directory.With prefix arg, move the subtree."
(interactive (list current-prefix-arg))
(let* ((file-properties
(org-element-map
(org-element-parse-buffer)
'keyword
(lambda (el)
(when (string-match "ELEVENTY" (org-element-property :key el))
(list
(org-element-property :key el)
(org-element-property :value el)
(buffer-substring-no-properties
(org-element-property :begin el)
(org-element-property :end el)))))))
(entry-properties (org-entry-properties))
(filename (expand-file-name
"index.org"
(expand-file-name
(assoc-default "EXPORT_ELEVENTY_FILE_NAME" entry-properties)
(car (assoc-default "ELEVENTY_BASE_DIR" file-properties))))))
(unless (file-directory-p (file-name-directory filename))
(make-directory (file-name-directory filename) t))
;; find the heading that sets the current EXPORT_ELEVENTY_FILE_NAME
(goto-char
(org-find-property "EXPORT_ELEVENTY_FILE_NAME" (org-entry-get-with-inheritance "EXPORT_ELEVENTY_FILE_NAME")))
(org-copy-subtree 1 (if do-cut 'cut))
(with-temp-file filename
(org-mode)
(insert (or
(mapconcat (lambda (file-prop) (elt file-prop 2))
file-properties
"")
"")
"\n")
(org-yank))
(find-file filename)
(goto-char (point-min))))
Then this adds a link to it:
(defunmy-org-export-filter-body-add-index-link (string backend info)
(if (and
(member backend '(11ty html))
(plist-get info :file-name)
(plist-get info :base-dir)
(file-exists-p (expand-file-name
"index.org"
(expand-file-name
(plist-get info :file-name)
(plist-get info :base-dir)))))
(concat string
(format "<div><a href=\"%sindex.org\">View org source for this post</a></div>"
(plist-get info :permalink)))
string))
(with-eval-after-load'ox
(add-to-list 'org-export-filter-body-functions#'my-org-export-filter-body-add-index-link))
Then I want to wrap the whole thing up in an export function: