<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/rss.xsl" type="text/xsl"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
	<title>Sacha Chua - category - org</title>
	<atom:link href="https://sachachua.com/blog/category/org/feed/index.xml" rel="self" type="application/rss+xml" />
	<atom:link href="https://sachachua.com/blog/category/org" rel="alternate" type="text/html" />
	<link>https://sachachua.com/blog/category/org/feed/index.xml</link>
	<description>Emacs, sketches, and life</description>
	<lastBuildDate>Mon, 06 Apr 2026 14:36:57 GMT</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>daily</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>11ty</generator>
  <item>
		<title>YE11: Fix find-function for Emacs Lisp from org-babel or scratch</title>
		<link>https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sun, 05 Apr 2026 21:03:48 GMT</pubDate>
    <category>org</category>
<category>emacs</category>
<category>elisp</category>
<category>stream</category>
<category>yay-emacs</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/</guid>
		<description><![CDATA[<p>
<video controls="1" src="https://archive.org/download/yay-emacs-11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/ye11-find-function.mp4" poster="https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/2026-04-05-19-25-03.png" type="video/mp4"><track kind="subtitles" label="Captions" src="https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/Yay%20Emacs%2011:%20Fix%20find-function%20for%20Emacs%20Lisp%20from%20org-babel%20or%20scratch.vtt" srclang="en" default=""><span>Video not supported. Thumbnail:<br><img src="https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/2026-04-05-19-25-03.png" alt="Thumbnail"></span></video>
</p>

<p>
<a href="https://archive.org/details/yay-emacs-11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch">Watch on Internet Archive</a>, <a href="https://youtube.com/live/PKkV1Tbev_Y">watch/comment on YouTube</a>, <a href="https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/Yay%20Emacs%2011:%20Fix%20find-function%20for%20Emacs%20Lisp%20from%20org-babel%20or%20scratch.vtt">download captions</a>, or <a href="mailto:sacha@sachachua.com">email me</a>
</p>


<p>
Where can you define an Emacs Lisp function so
that you can use <code>find-function</code> to jump to it
again later?
</p>

<ul class="org-ul">
<li><b>A: In an indirect buffer</b> from Org Mode source
block with your favorite eval function like
<code>eval-defun</code> <label class="hint"><input type="checkbox"> <span class="hint-desc">(hint)</span><span class="hint-text">nope</span></label>

<ul class="org-ul">
<li><p>
<code>C-c '</code> (<code>org-edit-special</code>) inside the block; execute the defun with <code>C-M-x</code> (<code>eval-defun</code>), <code>C-x C-e</code> (<code>eval-last-sexp</code>), or <code>eval-buffer</code>.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>    (<span class="org-keyword">defun</span> <span class="org-function-name">my-test-1</span> () (message <span class="org-string">"Hello"</span>))
</code></pre>
</div>
</li>
</ul></li>

<li><p>
<b>B: In an Org Mode file</b> by executing the block
with C-c C-c <label class="hint"><input type="checkbox"> <span class="hint-desc">(hint)</span><span class="hint-text">nope</span></label>
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>  (<span class="org-keyword">defun</span> <span class="org-function-name">my-test-2</span> () (message <span class="org-string">"Hello"</span>))
</code></pre>
</div>
</li>

<li><p>
<b>C: In a .el file</b> <label class="hint"><input type="checkbox"> <span class="hint-desc">(hint)</span><span class="hint-text">yup</span></label>
</p>

<p>
<a href="https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/test-search-function.el">file:///tmp/test-search-function.el</a> : execute the defun with <code>C-M-x</code> (<code>eval-defun</code>), <code>C-x C-e</code> (<code>eval-last-sexp</code>), or <code>eval-buffer</code>
</p></li>

<li><p>
<b>D: In a scratch buffer,</b> other temporary buffer,
or really any buffer thanks to eval-last-sexp
<label class="hint"><input type="checkbox"> <span class="hint-desc">(hint)</span><span class="hint-text">nope</span></label>
</p>

<p>
<code>(defun my-test-4 () (message "Hello"))</code>
</p></li>
</ul>

<p>
Only option C works - it's gotta be in an .el file for
<code>find-function</code> to find it. But I love jumping to
function definitions using <code>find-function</code> or
<code>lispy-goto-symbol</code> (which is bound to <code>M-.</code> if
you use <a target="_blank" href="https://melpa.org/#/lispy">lispy</a> and set up <code>lispy-mode</code>) so
that I can look at or change how something works.
It can be a little frustrating when I try to jump
to a definition and it says, "Don't know where
blahblahblah is defined." I just defined it five
minutes ago! It's there in one of my other
buffers, don't make me look for it myself.
Probably this will get fixed in Emacs core
someday, but no worries, we can work around it
today with a little bit of advice.
</p>

<p>
I did some digging around in the source code.
Turns out that <code>symbol-file</code> can't find the
function definition in the <code>load-history</code> variable
if you're not in a .el file, so
<code>find-function-search-for-symbol</code> gets called with
<code>nil</code> for the library, which causes the error.
(<a href="https://github.com/emacs-mirror/emacs/blob/master/lisp/subr.el">emacs:subr.el</a>)
</p>

<p>
I wrote some advice that searches in any open
<code>emacs-lisp-mode</code> buffers or in a list of other
files, like my Emacs configuration.
This is how I activate it:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">setq</span> sacha-elisp-find-function-search-extra <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"~/sync/emacs/Sacha.org"</span>))
(advice-add <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">find-function-search-for-symbol</span> <span class="org-builtin">:around</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">sacha-elisp-find-function-search-for-symbol</span>)
</code></pre>
</div>


<p>
Now I should be able to jump to all those
functions wherever they're defined.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(my-test-1)
(my-test-2)
(my-test-3)
(my-test-4)
</code></pre>
</div>


<p>
Note that by default, <code>M-.</code> in <code>emacs-lisp-mode</code> uses <code>xref-find-definitions</code>, which seems to really want files. I haven't figured out a good workaround for that yet, but <a target="_blank" href="https://melpa.org/#/lispy">lispy-mode</a> makes <code>M-.</code> work and gives me a bunch of other great shortcuts, so I'd recommend checking that out.
</p>

<p>
Here's the source code for the find function thing:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defvar</span> <span class="org-variable-name">sacha-elisp-find-function-search-extra</span>
  nil
  <span class="org-doc">"List of filenames to search for functions."</span>)

<span class="org-comment-delimiter">;;;</span><span class="org-comment">###</span><span class="org-comment"><span class="org-warning">autoload</span></span>
(<span class="org-keyword">defun</span> <span class="org-function-name">sacha-elisp-find-function-search-for-symbol</span> (fn symbol type library <span class="org-type">&amp;rest</span> _)
  <span class="org-doc">"Find SYMBOL with TYPE in Emacs Lisp buffers or `</span><span class="org-doc"><span class="org-constant">sacha-find-function-search-extra</span></span><span class="org-doc">'.</span>
<span class="org-doc">Prioritize buffers that do not have associated files, such as Org Src</span>
<span class="org-doc">buffers or *scratch*. Note that the fallback search uses \"^([</span><span class="org-doc"><span class="org-negation-char">^</span></span><span class="org-doc"> )]+\" so that</span>
<span class="org-doc">it isn't confused by preceding forms.</span>

<span class="org-doc">If LIBRARY is specified, fall back to FN.</span>

<span class="org-doc">Activate this with:</span>

<span class="org-doc">(advice-add 'find-function-search-for-symbol</span>
<span class="org-doc"> :around #'sacha-org-babel-find-function-search-for-symbol-in-dotemacs)"</span>
  (<span class="org-keyword">if</span> (null library)
      <span class="org-comment-delimiter">;; </span><span class="org-comment">Could not find library; search my-dotemacs-file just in case</span>
      (<span class="org-keyword">progn</span>
        (<span class="org-keyword">while</span> (<span class="org-keyword">and</span> (symbolp symbol) (get symbol <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">definition-name</span>))
          (<span class="org-keyword">setq</span> symbol (get symbol <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">definition-name</span>)))
        (<span class="org-keyword">catch</span> <span class="org-highlight-quoted-quote">'</span><span class="org-constant">found</span>
          (mapc
           (<span class="org-keyword">lambda</span> (buffer-or-file)
             (<span class="org-keyword">with-current-buffer</span> (<span class="org-keyword">if</span> (bufferp buffer-or-file)
                                      buffer-or-file
                                    (find-file-noselect buffer-or-file))
               (<span class="org-keyword">let*</span> ((regexp-symbol
                       (<span class="org-keyword">or</span> (<span class="org-keyword">and</span> (symbolp symbol)
                                (alist-get type (get symbol <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">find-function-type-alist</span>)))
                           (alist-get type find-function-regexp-alist)))
                      (form-matcher-factory
                       (<span class="org-keyword">and</span> (functionp (cdr-safe regexp-symbol))
                            (cdr regexp-symbol)))
                      (regexp-symbol (<span class="org-keyword">if</span> form-matcher-factory
                                         (car regexp-symbol)
                                       regexp-symbol))

                      (case-fold-search)
                      (regexp (<span class="org-keyword">if</span> (functionp regexp-symbol) regexp-symbol
                                (format (symbol-value regexp-symbol)
                                        <span class="org-comment-delimiter">;; </span><span class="org-comment">Entry for ` (backquote) macro in loaddefs.el,</span>
                                        <span class="org-comment-delimiter">;; </span><span class="org-comment">(defalias (quote \`)..., has a \ but</span>
                                        <span class="org-comment-delimiter">;; </span><span class="org-comment">(symbol-name symbol) doesn't.  Add an</span>
                                        <span class="org-comment-delimiter">;; </span><span class="org-comment">optional \ to catch this.</span>
                                        (concat <span class="org-string">"\\\\?"</span>
                                                (regexp-quote (symbol-name symbol)))))))
                 (<span class="org-keyword">save-restriction</span>
                   (widen)
                   (<span class="org-keyword">with-syntax-table</span> emacs-lisp-mode-syntax-table
                     (goto-char (point-min))
                     (<span class="org-keyword">if</span> (<span class="org-keyword">if</span> (functionp regexp)
                             (funcall regexp symbol)
                           (<span class="org-keyword">or</span> (re-search-forward regexp nil t)
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">`</span><span class="org-comment"><span class="org-constant">regexp</span></span><span class="org-comment">' matches definitions using known forms like</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">`</span><span class="org-comment"><span class="org-constant">defun</span></span><span class="org-comment">', or `</span><span class="org-comment"><span class="org-constant">defvar</span></span><span class="org-comment">'.  But some functions/variables</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">are defined using special macros (or functions), so</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">if `</span><span class="org-comment"><span class="org-constant">regexp</span></span><span class="org-comment">' can't find the definition, we look for</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">something of the form "(SOMETHING &lt;symbol&gt; ...)".</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">This fails to distinguish function definitions from</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">variable declarations (or even uses thereof), but is</span>
                               <span class="org-comment-delimiter">;; </span><span class="org-comment">a good pragmatic fallback.</span>
                               (re-search-forward
                                (concat <span class="org-string">"^([</span><span class="org-string"><span class="org-negation-char">^</span></span><span class="org-string"> )]+"</span> find-function-space-re <span class="org-string">"['(]?"</span>
                                        (regexp-quote (symbol-name symbol))
                                        <span class="org-string">"\\_&gt;"</span>)
                                nil t)))
                         (<span class="org-keyword">progn</span>
                           (beginning-of-line)
                           (<span class="org-keyword">throw</span> <span class="org-highlight-quoted-quote">'</span><span class="org-constant">found</span>
                                   (cons (current-buffer) (point))))
                       (<span class="org-keyword">when-let*</span> ((find-expanded
                                    (<span class="org-keyword">when</span> (trusted-content-p)
                                      (find-function&#45;&#45;search-by-expanding-macros
                                       (current-buffer) symbol type
                                       form-matcher-factory))))
                         (<span class="org-keyword">throw</span> <span class="org-highlight-quoted-quote">'</span><span class="org-constant">found</span>
                                 (cons (current-buffer)
                                       find-expanded)))))))))
           (delq nil
                 (append
                  (sort
                   (match-buffers <span class="org-highlight-quoted-quote">'</span>(derived-mode . emacs-lisp-mode))
                   <span class="org-builtin">:key</span> (<span class="org-keyword">lambda</span> (o) (<span class="org-keyword">or</span> (buffer-file-name o) <span class="org-string">""</span>)))
                  sacha-elisp-find-function-search-extra)))))
    (funcall fn symbol type library)))
</code></pre>
</div>


<p>
I even figured out how to <span title="(ignore (ert &quot;sacha-elisp&#45;&#45;find-function-search-for-symbol&#45;&#45;.*&quot;))">write tests for it</span>:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">ert-deftest</span> <span class="org-function-name">sacha-elisp&#45;&#45;find-function-search-for-symbol&#45;&#45;in-buffer</span> ()
  (<span class="org-keyword">let</span> ((sym (make-temp-name <span class="org-string">"&#45;&#45;test-fn"</span>))
        buffer)
    (<span class="org-keyword">unwind-protect</span>
        (<span class="org-keyword">with-temp-buffer</span>
          (emacs-lisp-mode)
          (insert (format <span class="org-string">";; Comment\n(defun %s () (message \"Hello\"))"</span> sym))
          (eval-last-sexp nil)
          (<span class="org-keyword">setq</span> buffer (current-buffer))
          (<span class="org-keyword">with-temp-buffer</span>
            (<span class="org-keyword">let</span> ((pos (sacha-elisp-find-function-search-for-symbol nil (intern sym) nil nil)))
              (<span class="org-keyword">should</span> (equal (car pos) buffer))
              (<span class="org-keyword">should</span> (equal (cdr pos) 12)))))
      (fmakunbound (intern sym)))))

(<span class="org-keyword">ert-deftest</span> <span class="org-function-name">sacha-elisp&#45;&#45;find-function-search-for-symbol&#45;&#45;in-file</span> ()
  (<span class="org-keyword">let*</span> ((sym (make-temp-name <span class="org-string">"&#45;&#45;test-fn"</span>))
         (temp-file (make-temp-file
                     <span class="org-string">"test-"</span> nil <span class="org-string">".org"</span>
                     (format
                      <span class="org-string">"#+begin_src emacs-lisp\n;; Comment\n(defun %s () (message \"Hello\"))\n#+end_src"</span>
                      sym)))
         (sacha-elisp-find-function-search-extra (list temp-file))
         buffer)
    (<span class="org-keyword">unwind-protect</span>
        (<span class="org-keyword">with-temp-buffer</span>
          (<span class="org-keyword">let</span> ((pos (sacha-elisp-find-function-search-for-symbol nil (intern sym) nil nil)))
            (<span class="org-keyword">should</span> (equal (buffer-file-name (car pos)) temp-file))
            (<span class="org-keyword">should</span> (equal (cdr pos) 35))))
      (delete-file temp-file))))
</code></pre>
</div>


<div class="note">This is part of my <a href="https://sachachua.com/dotemacs#org-mode-org-babel-fix-find-function-when-i-ve-evaluated-something-from-org-babel">Emacs configuration.</a></div><div><a href="https://sachachua.com/blog/2026/04/ye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch/index.org">View Org source for this post</a></div>
<p>You can <a href="https://social.sachachua.com/@sacha/statuses/01KNFTFSC3D0K7XH1JW1XTF6QG" target="_blank" rel="noopener noreferrer">comment on Mastodon</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2026%2F04%2Fye11-fix-find-function-for-emacs-lisp-from-org-babel-or-scratch%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Extract PDF highlights into an Org file with Python</title>
		<link>https://sachachua.com/blog/2026/04/demo-extract-pdf-highlights-into-an-org-file-with-python/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Thu, 02 Apr 2026 12:05:16 GMT</pubDate>
    <category>org</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2026/04/demo-extract-pdf-highlights-into-an-org-file-with-python/</guid>
		<description><![CDATA[<div class="update" id="orge9cc480">
<p>
<span class="timestamp-wrapper"><time class="timestamp" datetime="2026-04-06">[2026-04-06 Mon]</time></span>: Updated screenshot. I finished
reading all the pages and ended up with 202
highlights, so I'm going to have fun updating my
config with those notes!
</p>

</div>

<p>
I've been trying to find a good workflow for highlighting interesting parts of PDFs, and then getting that into my notes as images and text in Emacs. I think I've finally figured out something that works well for me that feels natural (marking things.
</p>

<p>
I wanted to read through <a href="https://protesilaos.com/emacs/dotemacs">Prot's Emacs configuration</a> while the kiddo played with her friends at the playground. I saved the web page as a PDF and exported it to <a href="http://getnoteful.com/">Noteful</a>. The PDF has 481 pages. Lots to explore! It was a bit chilly, so I had my gloves on. I used a capacitative stylus in my left hand to scroll the document and an Apple Pencil in my right hand to highlight the parts I wanted to add to my config or explore further.
</p>

<p>
Back at my computer, I used <code>pip install pymupdf</code> to install the <a href="https://github.com/pymupdf/PyMuPDF">PyMuPDF</a> library. I poked around the PDF in the Python shell to see what it had, and I noticed that the highlights were drawings with fill 0.5. So I wrote this Python script to extract the images and text near that rectangle:
</p>


<div class="org-src-container">
<pre class="src src-python"><code><span class="org-keyword">import</span> fitz
<span class="org-keyword">import</span> pathlib
<span class="org-keyword">import</span> sys
<span class="org-keyword">import</span> os

<span class="org-variable-name">BUFFER</span> <span class="org-operator">=</span> 5

<span class="org-keyword">def</span> <span class="org-function-name">extract_highlights</span>(filename, output_dir):
    <span class="org-variable-name">doc</span> <span class="org-operator">=</span> fitz.<span class="org-builtin">open</span>(filename)
    <span class="org-variable-name">s</span> <span class="org-operator">=</span> <span class="org-string">"* Excerpts</span><span class="org-constant">\n</span><span class="org-string">"</span>
    <span class="org-keyword">for</span> page_num, page <span class="org-keyword">in</span> <span class="org-builtin">enumerate</span>(doc):
        <span class="org-variable-name">page_width</span> <span class="org-operator">=</span> page.rect.width
        <span class="org-variable-name">page_text</span> <span class="org-operator">=</span> <span class="org-string">""</span>
        <span class="org-keyword">for</span> draw_num, d <span class="org-keyword">in</span> <span class="org-builtin">enumerate</span>(page.get_drawings()):
            <span class="org-keyword">if</span> d[<span class="org-string">'fill_opacity'</span>] <span class="org-operator">==</span> 0.5:
               <span class="org-variable-name">rect</span> <span class="org-operator">=</span> d[<span class="org-string">'rect'</span>]
               <span class="org-variable-name">clip_rect</span> <span class="org-operator">=</span> fitz.Rect(0, rect.y0 <span class="org-operator">-</span> BUFFER, page_width, rect.y1 <span class="org-operator">+</span> BUFFER)
               <span class="org-variable-name">img</span> <span class="org-operator">=</span> page.get_pixmap(clip<span class="org-operator">=</span>clip_rect)
               <span class="org-variable-name">img_filename</span> <span class="org-operator">=</span> <span class="org-string">"page-%03d-%d.png"</span> <span class="org-operator">%</span> (page_num <span class="org-operator">+</span> 1, draw_num <span class="org-operator">+</span> 1)
               img.save(os.path.join(output_dir, img_filename))
               <span class="org-variable-name">text</span> <span class="org-operator">=</span> page.get_text(clip<span class="org-operator">=</span>clip_rect)
               <span class="org-variable-name">page_text</span> <span class="org-operator">=</span> (page_text
                            <span class="org-operator">+</span> <span class="org-string">"[[file:%s]]</span><span class="org-constant">\n</span><span class="org-string">#+begin_quote</span><span class="org-constant">\n</span><span class="org-string">[[pdf:%s::%d][p%d]]: %s</span><span class="org-constant">\n</span><span class="org-string">#+end_quote</span><span class="org-constant">\n\n</span><span class="org-string">"</span>
                            <span class="org-operator">%</span> (img_filename,
                               os.path.join(<span class="org-string">".."</span>, filename),
                               page_num <span class="org-operator">+</span> 1,
                               page_num <span class="org-operator">+</span> 1, text))
        <span class="org-keyword">if</span> page_text <span class="org-operator">!=</span> <span class="org-string">""</span>:
            <span class="org-variable-name">s</span> <span class="org-operator">+=</span> <span class="org-string">"** Page %d</span><span class="org-constant">\n</span><span class="org-string">%s"</span> <span class="org-operator">%</span> (page_num <span class="org-operator">+</span> 1, page_text)
    pathlib.Path(os.path.join(output_dir, <span class="org-string">"index.org"</span>)).write_bytes(s.encode())

<span class="org-keyword">if</span> <span class="org-builtin">__name__</span> <span class="org-operator">==</span> <span class="org-string">'__main__'</span>:
    <span class="org-keyword">if</span> <span class="org-builtin">len</span>(sys.argv) <span class="org-operator">&lt;</span> 3:
        <span class="org-builtin">print</span>(<span class="org-string">"Usage: list-highlights.py pdf-filename output-dir"</span>)
    <span class="org-keyword">else</span>:
        extract_highlights(sys.argv[1], sys.argv[2])
</code></pre>
</div>


<p>
After I opened the resulting <code>index.org</code> file, I used <code>C-u C-u</code> <code>C-c C-x C-v</code> (<code>org-link-preview</code>) to make the images appear inline throughout the whole buffer. There's a little extra text from the PDF extraction, but it's a great starting point for cleaning up or copying. The org-pdftools package lets me link to specific pages in PDFs, neat!
</p>


<figure id="orgef7258d">
<a href="https://sachachua.com/blog/2026/04/demo-extract-pdf-highlights-into-an-org-file-with-python/2026-04-06-22-55-35.png"><img src="https://sachachua.com/blog/2026/04/demo-extract-pdf-highlights-into-an-org-file-with-python/2026-04-06-22-55-35.png" alt="2026-04-06-22-55-35.png"></a>

<figcaption><span class="figure-number">Figure 1: </span>Screenshot of Org Mode file with link previews</figcaption>
</figure>

<p>
To set up <code>org-pdftools</code>, I used:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">use-package</span> org-pdftools
  <span class="org-builtin">:hook</span> (org-mode . org-pdftools-setup-link))
</code></pre>
</div>


<p>
Here's my quick livestream about the script with a slightly older version that had an off-by-one bug in the page numbers and didn't have the fancy PDF links. =)
</p>

<p>
</p><div class="yt-video"><iframe width="456" height="315" title="YouTube video player" src="https://www.youtube-nocookie.com/embed/OTnYV2IZL_U?enablejsapi=1" frameborder="0" allowfullscreen="">nil</iframe><a href="https://youtube.com/live/OTnYV2IZL_U">Watch on YouTube</a></div>
<p></p>
<div><a href="https://sachachua.com/blog/2026/04/demo-extract-pdf-highlights-into-an-org-file-with-python/index.org">View Org source for this post</a></div>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2026%2F04%2Fdemo-extract-pdf-highlights-into-an-org-file-with-python%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Categorizing Emacs News items by voice in Org Mode</title>
		<link>https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Tue, 24 Mar 2026 01:19:29 GMT</pubDate>
    <category>speech</category>
<category>speech-recognition</category>
<category>emacs</category>
<category>org</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/</guid>
		<description><![CDATA[<p>
I'm having fun exploring which things might actually be easier to do by voice than by typing. For example, after I wrote some code to <a href="https://sachachua.com/dotemacs#writing-and-editing-speech-recognition-expanding-yasnippet-by-voice">expand yasnippets by voice</a>, I realized that it was easier to:
</p>

<ol class="org-ol">
<li>press my shortcut,</li>
<li>say "okay, define interactive function",</li>
<li>and then press my shortcut again,</li>
</ol>

<p>
than to:
</p>

<ol class="org-ol">
<li>mentally say it,</li>
<li>get the first initials,</li>
<li>type in "dfi",</li>
<li>and press Tab to expand.</li>
</ol>

<p>
Another area where I do this kind of mental translation for keyboard shortcuts is when I categorize dozens of Emacs-related links each week for <a href="https://sachachua.com/blog/category/emacs-news">Emacs News</a>. I used to do this by hand. Then I wrote a function to try to guess the category based on regular expressions (<code>my-emacs-news-guess-category</code> in <a href="https://raw.githubusercontent.com/sachac/emacs-news/refs/heads/master/index.org">emacs-news/index.org</a>, which is large). Then I set up a menu that lets me <a href="https://sachachua.com/blog/2019/06/making-a-numpad-based-hydra-for-categorizing-org-list-items/">press numbers corresponding to the most frequent categories</a> and use tab completion for the rest. 1 is Emacs Lisp, 2 is Emacs development, 3 is Emacs configuration, 4 is appearance, 5 is navigation, and so on. It's not very efficient, but some of it has at least gotten into muscle memory, which is also part of why it's hard to change the mapping. I don't come across that many links for Emacs development or Spacemacs, and I could probably change them to something else, but&hellip; Anyway.
</p>


<figure id="org36cd391">
<a href="https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/2026-03-23_20-38-33.png"><img src="https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/2026-03-23_20-38-33.png" alt="2026-03-23_20-38-33.png"></a>

<figcaption><span class="figure-number">Figure 1: </span>Screenshot of my menu for categorizing links</figcaption>
</figure>

<p>
I wanted to see if I could categorize links by voice instead. I might not always be able to count on being able to type a lot, and it's always fun to experiment with other modes of input. Here's a demonstration showing how Emacs can automatically open the URLs, wait for voice input, and categorize the links using a reasonably close match. The <code>*Messages*</code> buffer displays the recognized output to help with debugging.
</p>

<div class="media-post" id="org09d244a">
<p>
</p><figure><video controls="1" src="https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/2026-03-23-compressed.webm" type="video/webm"><a href="https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/2026-03-23-compressed.webm">Download the video</a></video><figcaption><div>Screencast with audio: categorizing links by voice</div></figcaption></figure>
<p></p>

</div>

<p>
This is how it works:
</p>

<ol class="org-ol">
<li>It starts an <code>ffmpeg</code> recording process.</li>
<li>It starts <a href="https://github.com/snakers4/silero-vad">Silero voice activity detection</a>.</li>
<li>When it detects that speech has ended, it use <code>curl</code> to send the WAV to an OpenAI-compatible server (in my case, <a href="https://speaches.ai/">Speaches</a> with the <code>Systran/faster-whisper-base.en</code> model) for transcription, along with a prompt to try to influence the recognition.</li>
<li>It compares the result with the candidates using <code>string-distance</code> for an approximate match. It calls the code to move the current item to the right category, creating the category if needed.</li>
</ol>

<p>
Since this doesn't always result in the right match, I added an Undo command. I also have a Delete command for removing the current item, Scroll Up and Scroll Down, and a way to quit.
</p>
<div id="outline-container-categorizing-emacs-news-items-by-voice-in-org-mode-initial-thoughts" class="outline-3">
<h3 id="categorizing-emacs-news-items-by-voice-in-org-mode-initial-thoughts">Initial thoughts</h3>
<div class="outline-text-3" id="text-categorizing-emacs-news-items-by-voice-in-org-mode-initial-thoughts">
<p>
I used it to categorize lots of links in this week's Emacs News, and I think it's promising. I loved the way my hands didn't have to hover over the number keys or move between those and the characters. Using voice activity detection meant that I could just keep dictating categories instead of pressing keyboard shortcuts or using the foot pedal I recently dusted off. There's a slight delay, of course, but I think it's worth it. If this settles down and becomes a solid part of my workflow, I might even be able to knit or hand-sew while doing this step, or simply do some stretching exercises.
</p>

<p>
<b>What about using streaming speech recognition?</b> I've written some code to <a href="https://sachachua.com/dotemacs#writing-and-editing-speech-recognition-streaming-speech-recognition-into-emacs-using-google-chrome-web-speech-api">use streaming speech recognition</a>, but the performance wasn't good enough when I tried it on my laptop (Lenovo P52 released in 2018, no configured GPU under Linux). The streaming server dropped audio segments in order to try to catch up. I'd rather have everything transcribed at the level of the model I want, even if I have to wait a little while. I also tried <a href="https://sachachua.com/dotemacs/index.html#writing-and-editing-speech-recognition-streaming-speech-recognition-into-emacs-using-google-chrome-web-speech-api">using the Web Speech API in Google Chrome for real-time speech transcription</a>, but it's a little finicky. I'm happy with the performance I get from either <a href="https://sachachua.com/blog/2026/01/queue-multiple-transcriptions-with-whisper-el-speech-recognition/">manually queueing speech segments</a> or <a href="https://sachachua.com/blog/2026/01/using-silero-voice-activity-detection-to-automatically-queue-multiple-transcriptions-with-natrys-whisper-el/">using VAD</a> and then using batch speech recognition with a model that's kept in memory (which is why I use a local server instead of a command-line tool). Come to think of it, I should try this with a higher-quality model like medium or large, just in case the latency turns out to be not that much more for this use case.
</p>

<p>
<b>What about external voice control systems</b> like <a href="https://talonvoice.com/">Talon Voice</a> or <a href="https://www.cursorless.org/">Cursorless</a>? They seem like neat ideas and lots of people use them. I think hacking something into Emacs with full access to its internals could be lots of fun too.
</p>

<p>
A <i>lot</i> of people have experimented with voice input for Emacs over the years. It could be fun to pick up ideas for commands and grammars. Some examples:
</p>

<ul class="org-ul">
<li><a href="https://www.youtube.com/watch?v=8SkdfdXWYaI">Using Python to Code by Voice - YouTube</a> (2013)</li>
<li><a href="https://github.com/jgarvin/mandimus">jgarvin/mandimus: Use speech recognition to command your computer and Emacs. · GitHub</a></li>
<li><a href="https://github.com/ErikPrantare/cursorfree.el">ErikPrantare/cursorfree.el: Edit and navigate from anywhere in the buffer · GitHub</a></li>
<li><a href="https://git.sr.ht/~lepisma/emacs-speech-input/tree/master/item/README.org">~lepisma/emacs-speech-input</a> - uses the idea of a voice cursor, uses an LLM to execute editing instructions</li>
</ul>

<p>
<b>What about automating myself out of this loop?</b> I've considered training a classifier or sending the list to a large language model to categorize links in order to set more reasonable defaults, but I think I'd still want manual control, since the fun is in getting a sense of all the cool things that people are tinkering around with in the Emacs community. I found that with voice control, it was easier for me to say the category than to look for the category it suggested and then say "Okay" to accept the default. If I display the suggested category in a buffer with very large text (and possibly category-specific background colours), then I can quickly glance at it or use my peripheral vision. But yeah, it's probably easier to look at a page and say "Org Mode" than to look at the page, look at the default text, see if it matches Org Mode, and then say okay if it is.
</p>
</div>
</div>
<div id="outline-container-categorizing-emacs-news-items-by-voice-in-org-mode-ideas-for-next-steps" class="outline-3">
<h3 id="categorizing-emacs-news-items-by-voice-in-org-mode-ideas-for-next-steps">Ideas for next steps</h3>
<div class="outline-text-3" id="text-categorizing-emacs-news-items-by-voice-in-org-mode-ideas-for-next-steps">
<p>
I wonder how to line up several categories. I could probably rattle off a few without waiting for the next one to load, and just pause when I'm not sure. Maybe while there's a reasonably good match within the first 1-3 words, I'll take candidates from the front of the queue. Or I could delimit it with another easily-recognized word, like "next".
</p>

<p>
I want to make a more synchronous version of this idea so that I can have a speech-enabled drop-in replacement that I can use as my <code>y-or-n-p</code> while still being able to type <code>y</code> or <code>n</code>. This probably involves using <code>sit-for</code> and polling to see if it's done. And then I can use that to play Twenty Questions, but also to do more serious stuff. It would also be nice to have replacements for <code>read-string</code> and <code>completing-read</code>, since those block Emacs until the user enters something.
</p>

<p>
I might take a side-trip into a conversational interface for M-x doctor and M-x dunnet, because why not. Naturally, it also makes sense to voice-enable <a href="https://github.com/xenodium/agent-shell">agent-shell</a> and <a href="https://github.com/karthink/gptel">gptel</a> interactions.
</p>

<p>
I'd like to figure out a number- or word-based completion mechanism so that I can control Reddit link replacement as well, since I want to select from a list of links from the page. Maybe something similar to the way <a href="https://github.com/jcaw/voicemacs">voicemacs adds numbers to helm and company</a> or how <a href="http://www.cb1.com/~john/computing/emacs/handsfree/flexi-choose.html">flexi-choose.el</a> works.
</p>

<p>
I'm also thinking about how I can shift seamlessly between typing and speaking, like when I want to edit a link title. Maybe I can check if I'm in the minibuffer and what kind of minibuffer I'm in, perhaps like the way <a href="https://github.com/oantolin/embark">Embark</a> does.
</p>

<p>
It would be really cool to define speech commands by reusing the keymap structure that menus also use. This is how to <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Easy-Menu.html">define a menu</a> in Emacs Lisp:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">easy-menu-define</span> words-menu global-map
  <span class="org-doc">"Menu for word navigation commands."</span>
  <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"Words"</span>
     [<span class="org-string">"Forward word"</span> forward-word]
     [<span class="org-string">"Backward word"</span> backward-word]))
</code></pre>
</div>


<p>
and this is how to <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Modifying-Menus.html">set just one binding</a>:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(keymap-set-after my-menu <span class="org-string">"&lt;drink&gt;"</span>
  <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"Drink"</span> . drink-command) <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">eat</span>)
</code></pre>
</div>


<p>
That makes sense to reuse for speech commands. I'd also like to be able to specify aliases while hiding them or collapsing them for a "What can I say" help view&hellip; Also, if keymaps work, then maybe minor modes or transient maps could work? This sort of feels like it should be the voice equivalent of a transient map.
</p>
</div>
</div>
<div id="outline-container-categorizing-emacs-news-items-by-voice-in-org-mode-the-code-so-far" class="outline-3">
<h3 id="categorizing-emacs-news-items-by-voice-in-org-mode-the-code-so-far">The code so far</h3>
<div class="outline-text-3" id="text-categorizing-emacs-news-items-by-voice-in-org-mode-the-code-so-far">

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-emacs-news-categorize-with-voice</span> (<span class="org-type">&amp;optional</span> skip-browse)
  (<span class="org-keyword">interactive</span> (list current-prefix-arg))
  (<span class="org-keyword">unless</span> skip-browse
    (my-spookfox-browse))
  (speech-input-cancel-recording)
  (<span class="org-keyword">let</span> ((default (<span class="org-keyword">if</span> (fboundp <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">my-emacs-news-guess-category</span>) (my-emacs-news-guess-category))))
    (speech-input-from-list
     (<span class="org-keyword">if</span> default
         (format <span class="org-string">"Category (%s): "</span> default)
       <span class="org-string">"Category: "</span>)
     <span class="org-highlight-quoted-quote">'</span>((<span class="org-string">"Org Mode"</span> <span class="org-string">"Org"</span> <span class="org-string">"Org Mode"</span>)
       <span class="org-string">"Other"</span>
       <span class="org-string">"Emacs Lisp"</span>
       <span class="org-string">"Coding"</span>
       (<span class="org-string">"Emacs configuration"</span> <span class="org-string">"Config"</span> <span class="org-string">"Configuration"</span>)
       (<span class="org-string">"Appearance"</span> <span class="org-string">"Appearance"</span>)
       (<span class="org-string">"Default"</span> <span class="org-string">"Okay"</span> <span class="org-string">"Default"</span>)
       <span class="org-string">"Community"</span>
       <span class="org-string">"AI"</span>
       <span class="org-string">"Writing"</span>
       (<span class="org-string">"Reddit"</span> <span class="org-string">"Read it"</span> <span class="org-string">"Reddit"</span>)
       <span class="org-string">"Shells"</span>
       <span class="org-string">"Navigation"</span>
       <span class="org-string">"Fun"</span>
       (<span class="org-string">"Dired"</span> <span class="org-string">"Directory"</span> <span class="org-string">"Dir ed"</span>)
       (<span class="org-string">"Mail, news, and chat"</span> <span class="org-string">"News"</span> <span class="org-string">"Mail"</span> <span class="org-string">"Chat"</span>)
       <span class="org-string">"Multimedia"</span>
       <span class="org-string">"Scroll down"</span>
       <span class="org-string">"Scroll up"</span>
       <span class="org-string">"Web"</span>
       <span class="org-string">"Delete"</span>
       <span class="org-string">"Skip"</span>
       <span class="org-string">"Undo"</span>
       (<span class="org-string">"Quit"</span> <span class="org-string">"Quit"</span> <span class="org-string">"Cancel"</span> <span class="org-string">"All done"</span>))
     (<span class="org-keyword">lambda</span> (result text)
       (message <span class="org-string">"Recognized %s original %s"</span> result text)
       (<span class="org-keyword">pcase</span> result
         (<span class="org-string">"Undo"</span>
          (undo)
          (my-emacs-news-categorize-with-voice t))
         (<span class="org-string">"Skip"</span>
          (forward-line)
          (my-emacs-news-categorize-with-voice))
         (<span class="org-string">"Quit"</span>
          (message <span class="org-string">"All done."</span>)
          (speech-input-cancel-recording))
         (<span class="org-string">"Reddit"</span>
          (my-emacs-news-replace-reddit-link)
          (my-emacs-news-categorize-with-voice t))
         (<span class="org-string">"Scroll down"</span>
          (my-spookfox-scroll-down)
          (my-emacs-news-categorize-with-voice t))
         (<span class="org-string">"Scroll up"</span>
          (my-spookfox-scroll-up)
          (my-emacs-news-categorize-with-voice t))
         (<span class="org-string">"Delete"</span>
          (delete-line)
          (undo-boundary)
          (my-emacs-news-categorize-with-voice))
         (<span class="org-string">"Default"</span>
          (my-org-move-current-item-to-category
           (concat default <span class="org-string">":"</span>))
          (undo-boundary)
          (my-emacs-news-categorize-with-voice))
         (_
          (my-org-move-current-item-to-category
           (concat result <span class="org-string">":"</span>))
          (undo-boundary)
          (my-emacs-news-categorize-with-voice))))
     t)))
</code></pre>
</div>


<p>
It uses <a href="https://sachachua.com/dotemacs#spookfox">Spookfox to control Firefox from Emacs</a>:
</p>

<p>

</p><div class="org-src-container">
<pre class="src src-emacs-lisp" id="org22d90d7"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-scroll-down</span> ()
  (<span class="org-keyword">interactive</span>)
  (spookfox-js-injection-eval-in-active-tab <span class="org-string">"window.scrollBy(0, document.documentElement.clientHeight);"</span> t))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-scroll-up</span> ()
  (<span class="org-keyword">interactive</span>)
  (spookfox-js-injection-eval-in-active-tab <span class="org-string">"window.scrollBy(0, -document.documentElement.clientHeight);"</span>))
</code></pre>
</div>


<p></p>

<p>

</p><div class="org-src-container">
<pre class="src src-emacs-lisp" id="orga82aa0d"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-spookfox-background-tab</span> (url <span class="org-type">&amp;rest</span> args)
  <span class="org-doc">"Open URL as a background tab."</span>
  (<span class="org-keyword">if</span> spookfox&#45;&#45;connected-clients
      (spookfox-tabs&#45;&#45;request (cl-first spookfox&#45;&#45;connected-clients) <span class="org-string">"OPEN_TAB"</span> <span class="org-highlight-quoted-quote">`</span>(<span class="org-builtin">:url</span> ,url))
    (browse-url url)))
</code></pre>
</div>


<p></p>

<p>
It also uses these functions for <a href="https://sachachua.com/dotemacs#digital-index-piles-with-emacs">categorizing Org Mode items</a>:
</p>

<p>

</p><div class="org-src-container">
<pre class="src src-emacs-lisp" id="org955687d"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-move-current-item-to-category</span> (category)
    <span class="org-doc">"Move current list item under CATEGORY earlier in the list.</span>
<span class="org-doc">  CATEGORY can be a string or a list of the form (text indent regexp).</span>
<span class="org-doc">  Point should be on the next line to process, even if a new category</span>
<span class="org-doc">  has been inserted."</span>
    (<span class="org-keyword">interactive</span> (list (completing-read <span class="org-string">"Category: "</span> (my-org-get-list-categories))))
    (<span class="org-keyword">when</span> category
      (<span class="org-keyword">let*</span> ((col (current-column))
             (item (point-at-bol))
             (struct (org-list-struct))
             (category-text (<span class="org-keyword">if</span> (stringp category) category (elt category 0)))
             (category-indent (<span class="org-keyword">if</span> (stringp category) 2 (+ 2 (elt category 1))))
             (category-regexp (<span class="org-keyword">if</span> (stringp category) category (elt category 2)))
             (end (elt (car (last struct)) 6))
             (pos (point))
             s)
        (<span class="org-keyword">setq</span> s (org-remove-indentation (buffer-substring-no-properties item (org-list-get-item-end item struct))))
        (<span class="org-keyword">save-excursion</span>
          (<span class="org-keyword">if</span> (string= category-text <span class="org-string">"x"</span>)
              (org-list-send-item item <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">delete</span> struct)
            (goto-char (caar struct))
            (<span class="org-keyword">if</span> (re-search-forward (concat <span class="org-string">"^ *- +"</span> category-regexp) end t)
                (<span class="org-keyword">progn</span>
                  <span class="org-comment-delimiter">;; </span><span class="org-comment">needs a patch to ol.el to check if stringp</span>
                  (org-list-send-item item (point-at-bol) struct)
                  (org-move-item-down)
                  (org-indent-item))
              (goto-char end)
              (org-list-insert-item
               (point-at-bol)
               struct (org-list-prevs-alist struct))
              (<span class="org-keyword">let</span> ((old-struct (copy-tree struct)))
                (org-list-set-ind (point-at-bol) struct 0)
                (org-list-struct-fix-bul struct (org-list-prevs-alist struct))
                (org-list-struct-apply-struct struct old-struct))
              (goto-char (point-at-eol))
              (insert category-text)
              (org-list-send-item item <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">end</span> struct)
              (org-indent-item)
              (org-indent-item))
            (recenter))))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-guess-list-category</span> (<span class="org-type">&amp;optional</span> categories)
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">require</span> <span class="org-highlight-quoted-quote">'</span><span class="org-constant">cl-lib</span>)
  (<span class="org-keyword">unless</span> categories
    (<span class="org-keyword">setq</span> categories
          (my-helm-org-list-categories-init-candidates)))
  (<span class="org-keyword">let*</span> ((beg (line-beginning-position))
         (end (line-end-position))
         (string (buffer-substring-no-properties beg end))
         (found
          (cl-member string
                     categories
                     <span class="org-builtin">:test</span>
                     (<span class="org-keyword">lambda</span> (string cat-entry)
                       (<span class="org-keyword">unless</span> (string= (car cat-entry) <span class="org-string">"x"</span>)
                         (string-match (regexp-quote (downcase (car cat-entry)))
                                       string))))))
    (<span class="org-keyword">when</span> (car found)
      (my-org-move-current-item-to-category
       (cdr (car found)))
      t)))
</code></pre>
</div>


<p></p>

<p>
For the <code>speech-input</code> functions, experimental code is at <a href="https://codeberg.org/sachac/speech-input">https://codeberg.org/sachac/speech-input</a> .
</p>
</div>
</div>
<div><a href="https://sachachua.com/blog/2026/03/categorizing-emacs-news-items-by-voice-in-org-mode/index.org">View Org source for this post</a></div>
<p>You can <a href="https://social.sachachua.com/@sacha/statuses/01KMER8FH7NZT56ENT0NHE36Q0" target="_blank" rel="noopener noreferrer">comment on Mastodon</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2026%2F03%2Fcategorizing-emacs-news-items-by-voice-in-org-mode%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Org Mode: Export HTML, copy files, and serve the results via simple-httpd so that media files work</title>
		<link>https://sachachua.com/blog/2026/03/org-mode-export-html-copy-files-and-serve-the-results-via-simple-httpd-so-that-media-files-work/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sat, 14 Mar 2026 20:43:37 GMT</pubDate>
    <category>emacs</category>
<category>org</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2026/03/org-mode-export-html-copy-files-and-serve-the-results-via-simple-httpd-so-that-media-files-work/</guid>
		<description><![CDATA[<div class="update" id="orga22e1a7">
<p>
<span class="timestamp-wrapper"><time class="timestamp" datetime="2026-03-23">[2026-03-23 Mon]</time></span>: <mark>Update</mark> Oh, ignore all of this! For some reason, when I export the regular Org Mode way, my media files work. Maybe it was just a weird hiccup!
</p>

</div>

<p>
In Org Mode, when you use "Export to HTML - As HTML file and open", the resulting HTML file is loaded using a <code>file://</code> URL. This means you can't load any media files. In my post about <a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/">pronunciation practice</a>, I wanted to test the playback without waiting for my 11ty-based static site generator to churn through the files.
</p>

<p>
simple-httpd lets you run a web server from Emacs. By default, the <code>httpd-root</code> is <code>~/public_html</code> and <code>httpd-port</code> is <code>8085</code>, but you can configure it to be somewhere else. Here I set it up to create a new temporary directory, and to delete that directory afterwards.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">use-package</span> simple-httpd
  <span class="org-builtin">:config</span>
  (<span class="org-keyword">setq</span> httpd-root (make-temp-file <span class="org-string">"httpd"</span> t))
  <span class="org-builtin">:hook</span>
  (httpd-stop . my-simple-httpd-remove-temporary-root)
  (kill-emacs . httpd-stop))
</code></pre>
</div>



<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="org-comment-delimiter">;;;</span><span class="org-comment">###</span><span class="org-comment"><span class="org-warning">autoload</span></span>
(<span class="org-keyword">defun</span> <span class="org-function-name">my-simple-httpd-remove-temporary-root</span> ()
  <span class="org-doc">"Remove `</span><span class="org-doc"><span class="org-constant">httpd-root</span></span><span class="org-doc">' only if it's a temporary directory."</span>
  (<span class="org-keyword">when</span> (file-in-directory-p httpd-root temporary-file-directory)
    (delete-directory httpd-root t)))
</code></pre>
</div>


<p>
The following code exports your Org buffer or subtree to a file in that directory, copies all the referenced local files (if they're newer) and updates the links in the HTML, and then serves it via simple-httpd. Note that it just overwrites everything without confirmation, so if you refer to files with the same name, only the last one will be kept.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">with-eval-after-load</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">ox</span>
  (org-export-define-derived-backend <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">my-html-served</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">html</span>
    <span class="org-builtin">:menu-entry</span>
    <span class="org-highlight-quoted-quote">'</span>(?s <span class="org-string">"Export to HTML and Serve"</span>
         ((?b <span class="org-string">"Buffer"</span>  my-org-serve-buffer)
          (?s <span class="org-string">"Subtree"</span> my-org-serve-subtree)))))
</code></pre>
</div>



<div class="org-src-container">
<pre class="src src-emacs-lisp"><code><span class="org-comment-delimiter">;;;</span><span class="org-comment">###</span><span class="org-comment"><span class="org-warning">autoload</span></span>
(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-serve-buffer</span> (<span class="org-type">&amp;optional</span> async _subtreep visible-only body-only ext-plist)
  (my-org-export-and-serve nil))

<span class="org-comment-delimiter">;;;</span><span class="org-comment">###</span><span class="org-comment"><span class="org-warning">autoload</span></span>
(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-serve-subtree</span> (<span class="org-type">&amp;optional</span> async _subtreep visible-only body-only ext-plist)
  (my-org-export-and-serve t))

<span class="org-comment-delimiter">;; </span><span class="org-comment">Based on org-11ty&#45;&#45;copy-files-and-replace-links</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">Might be a good idea to use something DOM-based instead</span>
(<span class="org-keyword">defun</span> <span class="org-function-name">my-html-copy-files-and-replace-links</span> (info <span class="org-type">&amp;optional</span> destination-dir)
  (<span class="org-keyword">let</span> ((file-regexp <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(?:</span></span><span class="org-string">src</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">href</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">poster</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">=\"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">file:</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">?.*?</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">\""</span>)
        (destination-dir (<span class="org-keyword">or</span> destination-dir (file-name-directory (plist-get info <span class="org-builtin">:file-path</span>))))
        file-all-urls file-name beg
        new-file file-re
        unescaped)
    (<span class="org-keyword">unless</span> (file-directory-p destination-dir)
      (make-directory destination-dir t))
    (<span class="org-keyword">unless</span> (file-directory-p destination-dir)
      (<span class="org-warning">error</span> <span class="org-string">"%s is not a directory."</span> destination-dir))
    (<span class="org-keyword">save-excursion</span>
      (goto-char (point-min))
      (<span class="org-keyword">while</span> (re-search-forward file-regexp nil t)
        (<span class="org-keyword">setq</span> file-name (<span class="org-keyword">or</span> (match-string 1) (match-string 2)))
        (<span class="org-keyword">unless</span> (<span class="org-keyword">or</span> (string-match <span class="org-string">"^#"</span> file-name)
                    (get-text-property 0 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">changed</span> file-name))
          (<span class="org-keyword">setq</span> file-name
                (replace-regexp-in-string
                 <span class="org-string">"\\?.+"</span> <span class="org-string">""</span>
                 (<span class="org-keyword">save-match-data</span> (<span class="org-keyword">if</span> (string-match <span class="org-string">"^file:"</span> file-name)
                                      (substring file-name 7)
                                    file-name))))
          (<span class="org-keyword">setq</span> unescaped
                (replace-regexp-in-string
                 <span class="org-string">"%23"</span> <span class="org-string">"#"</span>
                 file-name))
          (<span class="org-keyword">setq</span> new-file (concat
                          (<span class="org-keyword">if</span> info (plist-get info <span class="org-builtin">:permalink</span>) <span class="org-string">""</span>)
                          (file-name-nondirectory unescaped)))
          (<span class="org-keyword">unless</span> (org-url-p file-name)
            (<span class="org-keyword">let</span> ((new-file-name (expand-file-name (file-name-nondirectory unescaped)
                                                   destination-dir)))
              (<span class="org-keyword">condition-case</span> err
                  (<span class="org-keyword">when</span> (<span class="org-keyword">or</span> (not (file-exists-p new-file-name))
                            (file-newer-than-file-p unescaped new-file-name))
                    (copy-file unescaped new-file-name t))
                (<span class="org-warning">error</span> nil))
              (<span class="org-keyword">when</span> (file-exists-p new-file-name)
                (<span class="org-keyword">save-excursion</span>
                  (goto-char (point-min))
                  (<span class="org-keyword">setq</span> file-re (concat <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(?:</span></span><span class="org-string"> src=\"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string"> href=\"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string"> poster=\"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(?:</span></span><span class="org-string">file://</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">?"</span> (regexp-quote file-name) <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">"</span>))
                  (<span class="org-keyword">while</span> (re-search-forward file-re nil t)
                    (replace-match
                     (propertize
                      (<span class="org-keyword">save-match-data</span> (replace-regexp-in-string <span class="org-string">"#"</span> <span class="org-string">"%23"</span> new-file))
                      <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">changed</span> t)
                     t t nil 1)))))))))))

<span class="org-comment-delimiter">;;;</span><span class="org-comment">###</span><span class="org-comment"><span class="org-warning">autoload</span></span>
(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-export-and-serve</span> (<span class="org-type">&amp;optional</span> subtreep)
  <span class="org-doc">"Export current org buffer (or subtree if SUBTREEP) to HTML and serve via simple-httpd."</span>
  (<span class="org-keyword">interactive</span> <span class="org-string">"P"</span>)
  (<span class="org-keyword">require</span> <span class="org-highlight-quoted-quote">'</span><span class="org-constant">simple-httpd</span>)
  (httpd-stop)
  (<span class="org-keyword">unless</span> httpd-root (<span class="org-warning">error</span> <span class="org-string">"Set `</span><span class="org-string"><span class="org-constant">httpd-root</span></span><span class="org-string">'."</span>))
  (<span class="org-keyword">unless</span> (file-directory-p httpd-root)
    (make-directory httpd-root t))
  (<span class="org-keyword">unless</span> (file-directory-p httpd-root)
    (<span class="org-warning">error</span> <span class="org-string">"%s is not a directory."</span> httpd-root))
  (<span class="org-keyword">let*</span> ((out-file (expand-file-name (concat (file-name-base (buffer-file-name)) <span class="org-string">".html"</span>)
                                     httpd-root))
         (html-file (org-export-to-file <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">my-html-served</span> out-file nil subtreep)))
    <span class="org-comment-delimiter">;; </span><span class="org-comment">Copy all the files and rewrite all the links</span>
    (<span class="org-keyword">with-temp-file</span> out-file
      (insert-file-contents out-file)
      (my-html-copy-files-and-replace-links
       <span class="org-highlight-quoted-quote">`</span>(<span class="org-builtin">:permalink</span> <span class="org-string">"/"</span>) httpd-root))
    (httpd-start)
    (browse-url (format <span class="org-string">"http://localhost:%d/%s"</span>
                        httpd-port
                        (file-name-nondirectory html-file)))))
</code></pre>
</div>


<p>
Now I can use <code>C-c C-e</code> (<code>org-export-dispatch</code>), select the subtree with <code>C-s</code>, and use <code>s s</code>
to export a subtree to a webserver and have all the media files work. This took 0.46 seconds for my post on pronunciation practice and automatically opens the page in a browser window. In comparison, my 11ty static site generator took 5.18 seconds for a subset of my site (1630 files copied, 214 files generated), and I haven't yet hooked up monitoring it to Emacs, so I have to take an extra step to open the page in the browser when I think it's finished. I think exporting to HTML and serving it with simple-httpd will be much easier for simple cases like this, and then I can export to 11ty once I'm done with the basic checks.
</p>

<div class="note">This is part of my <a href="https://sachachua.com/dotemacs#org-mode-publishing-html-export-html-copy-files-and-serve-via-simple-httpd">Emacs configuration.</a></div><div><a href="https://sachachua.com/blog/2026/03/org-mode-export-html-copy-files-and-serve-the-results-via-simple-httpd-so-that-media-files-work/index.org">View Org source for this post</a></div>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2026%2F03%2Forg-mode-export-html-copy-files-and-serve-the-results-via-simple-httpd-so-that-media-files-work%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Comparing pronunciation recordings across time</title>
		<link>https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Fri, 13 Mar 2026 01:11:45 GMT</pubDate>
    <category>french</category>
<category>emacs</category>
<category>org</category>
<category>subed</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/</guid>
		<description><![CDATA[<div class="update" id="orgd4d9c01">
<ul class="org-ul">
<li><span class="timestamp-wrapper"><time class="timestamp" datetime="2026-03-27">[2026-03-27 Fri]</time></span>: Added reconstructions from today and moved more of the code to <a href="https://codeberg.org/sachac/learn-lang/">sachac/learn-lang</a>.</li>
<li><span class="timestamp-wrapper"><time class="timestamp" datetime="2026-03-24">[2026-03-24 Tue]</time></span>: Added updates from today.</li>
<li><span class="timestamp-wrapper"><time class="timestamp" datetime="2026-03-15">[2026-03-15 Sun]</time></span>: Added reference audio for the second set.</li>
<li><span class="timestamp-wrapper"><time class="timestamp" datetime="2026-03-14">[2026-03-14 Sat]</time></span>: I added pronunciation segments for the new set of tongue-twisters I got on Mar 13.</li>
<li><span class="timestamp-wrapper"><time class="timestamp" datetime="2026-03-12">[2026-03-12 Thu]</time></span>: I added a column for Feb 20, the first session with the sentences. I also added keyboard shortcuts (1..n) for playing the audio of the row that the mouse is on.</li>
</ul>

</div>

<div class="sticky-toc" id="orgf2b6963">
<div id="text-table-of-contents" role="doc-toc">
<ul>
<li><a href="https://sachachua.com/blog/feed/index.xml#comparing-pronunciation-recordings-across-time-2026-02-20-first-set-maman-peint-un-grand-lapin-blanc-etc">2026-02-20: First set: Maman peint un grand lapin blanc, etc.</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#comparing-pronunciation-recordings-across-time-2026-03-14-second-set-mon-oncle-peint-un-grand-pont-blanc-etc">2026-03-14: Second set: Mon oncle peint un grand pont blanc, etc.</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#comparing-pronunciation-recordings-across-time-the-code">The code</a></li>
</ul>
</div>

</div>
<div id="outline-container-comparing-pronunciation-recordings-across-time-2026-02-20-first-set-maman-peint-un-grand-lapin-blanc-etc" class="outline-3">
<h3 id="comparing-pronunciation-recordings-across-time-2026-02-20-first-set-maman-peint-un-grand-lapin-blanc-etc">2026-02-20: First set: Maman peint un grand lapin blanc, etc.</h3>
<div class="outline-text-3" id="text-comparing-pronunciation-recordings-across-time-2026-02-20-first-set-maman-peint-un-grand-lapin-blanc-etc">
<p>
My French tutor gave me a list of sentences to help me practise pronunciation.
</p>

<p>
I can fuzzy-match these with the word timing JSON from WhisperX, like this.
</p>

<details><summary>Extract all approximately matching phrases</summary>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(subed-record-extract-all-approximately-matching-phrases
   sentences
   <span class="org-string">"/home/sacha/sync/recordings/2026-02-20-raphael.json"</span>
   <span class="org-string">"/home/sacha/proj/french/analysis/virelangues/2026-02-20-raphael-script.vtt"</span>)
</code></pre>
</div>

</details>

<details class="code-details" style="padding: 1em;
                 border-radius: 15px;
                 font-size: 0.9em;
                 box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;">
                  <summary><strong>Sentences</strong></summary>
<ul class="org-ul">
<li>Maman peint un grand lapin blanc.</li>
<li>Un enfant intelligent mange lentement.</li>
<li>Le roi croit voir trois noix.</li>
<li>Le témoin voit le chemin loin.</li>
<li>Moins de foin au loin ce matin.</li>
<li>La laine beige sèche près du collège.</li>
<li>La croquette sèche dans l'assiette.</li>
<li>Elle mène son frère à l'hôtel.</li>
<li>Le verre vert est très clair.</li>
<li>Elle aimait manger et rêver.</li>
<li>Le jeu bleu me plaît peu.</li>
<li>Ce neveu veut un jeu.</li>
<li>Le feu bleu est dangereux.</li>
<li>Le beurre fond dans le cœur chaud.</li>
<li>Les fleurs de ma sœur sentent bon.</li>
<li>Le hibou sait où il va.</li>
<li>L'homme fort mord la pomme.</li>
<li>Le sombre col tombe.</li>
<li>L'auto saute au trottoir chaud.</li>
<li>Le château d'en haut est beau.</li>
<li>Le cœur seul pleure doucement.</li>
<li>Tu es sûr du futur ?</li>
<li>Trois très grands trains traversent trois trop grandes rues.</li>
<li>Je veux deux feux bleus, mais la reine préfère la laine beige.</li>
<li>Vincent prend un bain en chantant lentement.</li>
<li>La mule sûre court plus vite que le loup fou.</li>
<li>Luc a bu du jus sous le pont où coule la boue.</li>
<li>Le frère de Robert prépare un rare rôti rouge.</li>
<li>La mule court autour du mur où hurle le loup.</li>
</ul>


</details>

<p>
Then I can use <a href="https://github.com/sachac/subed-record">subed-record</a> to manually tweak them, add notes, and so on. I end up with VTT files like <a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-06-raphael-script.vtt">2026-03-06-raphael-script.vtt</a>. I can assemble the snippets for a session into a single audio file, like this:
</p>

<div class="media-post" id="org0128ea1">
<p>
</p><div class="audio"><audio controls="1" preload="metadata" src="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-06-raphael-virelangues.opus?2026-03-27" type="audio/ogg"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-06-raphael-virelangues.opus">Download the audio</a><track kind="captions" label="Captions" src="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-06-raphael-virelangues.vtt" srclang="en" default=""></audio><div class="captions" style="display: none"></div></div>
<p></p>

</div>

<p>
I wanted to compare my attempts over time, so I wrote some code to use Org Mode and subed-record to build a table with little audio players that I can use both within Emacs and in the exported HTML.
This collects just the last attempts for each sentence during a number of my sessions (both with the tutor and on my own). The score is from the Microsoft Azure pronunciation assessment service. I'm not entirely sure about its validity yet, but I thought I'd add it for fun. <code>*</code> indicates where I've added some notes from my tutor, which should be available as a <code>title</code> attribute on hover. (Someday I'll figure out a mobile-friendly way to do that.)
</p>

<details><summary>Calling it with my sentences and files</summary>
<div class="org-src-container">
<pre class="src src-emacs-lisp" id="orgc20e9ea"><code>(learn-lang-subed-record-summarize-segments
 sentences
 <span class="org-highlight-quoted-quote">'</span>((<span class="org-string">"/home/sacha/proj/french/analysis/virelangues/2026-02-20-raphael-script.vtt"</span> . <span class="org-string">"Feb 20"</span>)
 <span class="org-comment-delimiter">;</span><span class="org-comment">("~/sync/recordings/processed/2026-02-20-raphael-tongue-twisters.vtt" . "Feb 20")</span>
        (<span class="org-string">"~/sync/recordings/processed/2026-02-22-virelangues-single.vtt"</span> . <span class="org-string">"Feb 22"</span>)
        (<span class="org-string">"~/proj/french/recordings/2026-02-26-virelangues-script.vtt"</span> . <span class="org-string">"Feb 26"</span>)
        (<span class="org-string">"~/proj/french/recordings/2026-02-27-virelangues-script.vtt"</span> . <span class="org-string">"Feb 27"</span>)
        (<span class="org-string">"~/proj/french/recordings/2026-03-03-virelangues.vtt"</span> . <span class="org-string">"Mar 3"</span>)
        (<span class="org-string">"/home/sacha/sync/recordings/processed/2026-03-03-raphael-reference-script.vtt"</span> . <span class="org-string">"Mar 3"</span>)
        (<span class="org-string">"~/proj/french/analysis/virelangues/2026-03-06-raphael-script.vtt"</span> . <span class="org-string">"Mar 6"</span>)
        (<span class="org-string">"~/proj/french/analysis/virelangues/2026-03-12-virelangues-script.vtt"</span> . <span class="org-string">"Mar 12"</span>))
 <span class="org-string">"~/proj/french/analysis/virelangues/clip"</span>
 <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">learn-lang-subed-record-get-last-attempt</span>
 )
</code></pre>
</div>

</details>

<div class="subed-summary" id="orga7fbc17">
<table>


<colgroup>
<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Feb 20</th>
<th scope="col" class="org-left">Feb 22</th>
<th scope="col" class="org-left">Feb 26</th>
<th scope="col" class="org-left">Feb 27</th>
<th scope="col" class="org-left">Mar 3</th>
<th scope="col" class="org-left">Mar 3</th>
<th scope="col" class="org-left">Mar 6</th>
<th scope="col" class="org-left">Mar 12</th>
<th scope="col" class="org-left">Text</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-maman-peint-un-grand-lapin-blanc-01.opus?2026-03-27" class="audio-icon" title="Feb 20: X: grande">▶️ 63*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 96</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 83</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Mm hmm">▶️ 83*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Ouais">▶️ 81*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-maman-peint-un-grand-lapin-blanc-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 88</a></td>
<td class="org-left">Maman peint un grand lapin blanc.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-un-enfant-intelligent-mange-lentement-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Voila, parfait">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-un-enfant-intelligent-mange-lentement-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-un-enfant-intelligent-mange-lentement-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-un-enfant-intelligent-mange-lentement-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-un-enfant-intelligent-mange-lentement-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 96</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-un-enfant-intelligent-mange-lentement-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Parfait">▶️ 89*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-un-enfant-intelligent-mange-lentement-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Ouais, c'est bien">▶️ 92*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-un-enfant-intelligent-mange-lentement-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 83</a></td>
<td class="org-left">Un enfant intelligent mange lentement.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-roi-croit-voir-trois-noix-01.opus?2026-03-27" class="audio-icon" title="Feb 20: X: trois noix">▶️ 84*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-roi-croit-voir-trois-noix-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-roi-croit-voir-trois-noix-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-roi-croit-voir-trois-noix-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 96</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-roi-croit-voir-trois-noix-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-roi-croit-voir-trois-noix-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Mm hmm">▶️ 95*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-roi-croit-voir-trois-noix-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Uh huh">▶️ 98*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-roi-croit-voir-trois-noix-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 99</a></td>
<td class="org-left">Le roi croit voir trois noix.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-t-moin-voit-le-chemin-loin-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais, c'est bien">▶️ 80*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-t-moin-voit-le-chemin-loin-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-t-moin-voit-le-chemin-loin-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 77</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-t-moin-voit-le-chemin-loin-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-t-moin-voit-le-chemin-loin-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 97</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-t-moin-voit-le-chemin-loin-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Ouais">▶️ 92*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-t-moin-voit-le-chemin-loin-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 88</a></td>
<td class="org-left">Le témoin voit le chemin loin.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-moins-de-foin-au-loin-ce-matin-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais, parfait">▶️ 72*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-moins-de-foin-au-loin-ce-matin-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-moins-de-foin-au-loin-ce-matin-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-moins-de-foin-au-loin-ce-matin-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 77</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-moins-de-foin-au-loin-ce-matin-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 92</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-moins-de-foin-au-loin-ce-matin-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Mm hmm, parfait">▶️ 89*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-moins-de-foin-au-loin-ce-matin-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 86</a></td>
<td class="org-left">Moins de foin au loin ce matin.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-la-laine-beige-s-che-pr-s-du-coll-ge-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais, okay, parfait">▶️ 79*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-laine-beige-s-che-pr-s-du-coll-ge-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-laine-beige-s-che-pr-s-du-coll-ge-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 76</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-laine-beige-s-che-pr-s-du-coll-ge-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-laine-beige-s-che-pr-s-du-coll-ge-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 76</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-la-laine-beige-s-che-pr-s-du-coll-ge-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Mm hmm">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-laine-beige-s-che-pr-s-du-coll-ge-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Mm hmm">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-laine-beige-s-che-pr-s-du-coll-ge-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 79</a></td>
<td class="org-left">La laine beige sèche près du collège.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-la-croquette-s-che-dans-l-assiette-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Okay, parfait">▶️ 67*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-croquette-s-che-dans-l-assiette-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-croquette-s-che-dans-l-assiette-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-croquette-s-che-dans-l-assiette-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 81</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-croquette-s-che-dans-l-assiette-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-la-croquette-s-che-dans-l-assiette-06.opus?2026-03-27" class="audio-icon" title="Mar 3: C'est mieux">▶️ 99*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-croquette-s-che-dans-l-assiette-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Mm hmm">▶️ 97*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-croquette-s-che-dans-l-assiette-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 97</a></td>
<td class="org-left">La croquette sèche dans l'assiette.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-elle-m-ne-son-fr-re-l-h-tel-01.opus?2026-03-27" class="audio-icon" title="Feb 20: C'est bien">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-m-ne-son-fr-re-l-h-tel-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-m-ne-son-fr-re-l-h-tel-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 100</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-m-ne-son-fr-re-l-h-tel-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 100</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-m-ne-son-fr-re-l-h-tel-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 98</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-elle-m-ne-son-fr-re-l-h-tel-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Mm hmm">▶️ 100*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-m-ne-son-fr-re-l-h-tel-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Ouais, c'est bien">▶️ 99*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-m-ne-son-fr-re-l-h-tel-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 100</a></td>
<td class="org-left">Elle mène son frère à l'hôtel.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-verre-vert-est-tr-s-clair-01.opus?2026-03-27" class="audio-icon" title="Feb 20: C'est bien">▶️ 77*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-verre-vert-est-tr-s-clair-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 87</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-verre-vert-est-tr-s-clair-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-verre-vert-est-tr-s-clair-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 93</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-verre-vert-est-tr-s-clair-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 87</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-verre-vert-est-tr-s-clair-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Mm hmm">▶️ 87*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-verre-vert-est-tr-s-clair-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 99</a></td>
<td class="org-left">Le verre vert est très clair.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-elle-aimait-manger-et-r-ver-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Okay">▶️ 100*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-aimait-manger-et-r-ver-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-aimait-manger-et-r-ver-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 100</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-aimait-manger-et-r-ver-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-aimait-manger-et-r-ver-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-elle-aimait-manger-et-r-ver-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais">▶️ 99*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-aimait-manger-et-r-ver-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Mm hmm">▶️ 100*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-elle-aimait-manger-et-r-ver-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 100</a></td>
<td class="org-left">Elle aimait manger et rêver.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-jeu-bleu-me-pla-t-peu-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais, c'est bien">▶️ 78*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-jeu-bleu-me-pla-t-peu-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 98</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-jeu-bleu-me-pla-t-peu-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-jeu-bleu-me-pla-t-peu-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 98</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-jeu-bleu-me-pla-t-peu-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 98</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-jeu-bleu-me-pla-t-peu-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais, c'est bien">▶️ 92*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-jeu-bleu-me-pla-t-peu-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 88</a></td>
<td class="org-left">Le jeu bleu me plaît peu.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-ce-neveu-veut-un-jeu-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais">▶️ 78*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-ce-neveu-veut-un-jeu-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-ce-neveu-veut-un-jeu-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-ce-neveu-veut-un-jeu-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-ce-neveu-veut-un-jeu-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 85</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-ce-neveu-veut-un-jeu-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 85</a></td>
<td class="org-left">Ce neveu veut un jeu.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-feu-bleu-est-dangereux-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais">▶️ 73*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-feu-bleu-est-dangereux-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-feu-bleu-est-dangereux-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-feu-bleu-est-dangereux-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 96</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-feu-bleu-est-dangereux-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 97</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-feu-bleu-est-dangereux-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 100</a></td>
<td class="org-left">Le feu bleu est dangereux.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-beurre-fond-dans-le-c-ur-chaud-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Ouais, c'est bien">▶️ 87*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-beurre-fond-dans-le-c-ur-chaud-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 76</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-beurre-fond-dans-le-c-ur-chaud-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 65</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-beurre-fond-dans-le-c-ur-chaud-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-beurre-fond-dans-le-c-ur-chaud-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-beurre-fond-dans-le-c-ur-chaud-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais">▶️ 74*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-beurre-fond-dans-le-c-ur-chaud-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Ouais">▶️ 85*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-beurre-fond-dans-le-c-ur-chaud-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 96</a></td>
<td class="org-left">Le beurre fond dans le cœur chaud.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-les-fleurs-de-ma-s-ur-sentent-bon-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Mm hmm">▶️ 84*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-les-fleurs-de-ma-s-ur-sentent-bon-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 43</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-les-fleurs-de-ma-s-ur-sentent-bon-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-les-fleurs-de-ma-s-ur-sentent-bon-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 79</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-les-fleurs-de-ma-s-ur-sentent-bon-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 75</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-les-fleurs-de-ma-s-ur-sentent-bon-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 98</a></td>
<td class="org-left">Les fleurs de ma sœur sentent bon.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-hibou-sait-o-il-va-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Mm hmm">▶️ 70*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-hibou-sait-o-il-va-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 86</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-hibou-sait-o-il-va-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 79</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-hibou-sait-o-il-va-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 76</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-hibou-sait-o-il-va-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 87</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-hibou-sait-o-il-va-06.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 84</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-hibou-sait-o-il-va-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 98</a></td>
<td class="org-left">Le hibou sait où il va.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-l-homme-fort-mord-la-pomme-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Mm hmm">▶️ 92*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-homme-fort-mord-la-pomme-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-homme-fort-mord-la-pomme-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 86</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-homme-fort-mord-la-pomme-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 92</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-homme-fort-mord-la-pomme-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 98</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-l-homme-fort-mord-la-pomme-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais">▶️ 99*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-homme-fort-mord-la-pomme-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 94</a></td>
<td class="org-left">L'homme fort mord la pomme.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-sombre-col-tombe-01.opus?2026-03-27" class="audio-icon" title="Feb 20: X: sombre">▶️ 83*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-sombre-col-tombe-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 73</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-sombre-col-tombe-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 69</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-sombre-col-tombe-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 81</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-sombre-col-tombe-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 60</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-sombre-col-tombe-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Oui">▶️ 96*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-sombre-col-tombe-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 81</a></td>
<td class="org-left">Le sombre col tombe.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-l-auto-saute-au-trottoir-chaud-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Oui, c'&#233;tait mieux">▶️ 39*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-auto-saute-au-trottoir-chaud-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 49</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-auto-saute-au-trottoir-chaud-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 69</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-auto-saute-au-trottoir-chaud-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 56</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-auto-saute-au-trottoir-chaud-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 69</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-l-auto-saute-au-trottoir-chaud-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Oui">▶️ 96*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-l-auto-saute-au-trottoir-chaud-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 94</a></td>
<td class="org-left">L'auto saute au trottoir chaud.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-ch-teau-d-en-haut-est-beau-01.opus?2026-03-27" class="audio-icon" title="Feb 20">▶️ 82</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-ch-teau-d-en-haut-est-beau-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 84</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-ch-teau-d-en-haut-est-beau-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-ch-teau-d-en-haut-est-beau-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 98</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-ch-teau-d-en-haut-est-beau-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-ch-teau-d-en-haut-est-beau-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Mm hmm">▶️ 96*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-ch-teau-d-en-haut-est-beau-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 99</a></td>
<td class="org-left">Le château d'en haut est beau.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-c-ur-seul-pleure-doucement-01.opus?2026-03-27" class="audio-icon" title="Feb 20">▶️ 89</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-c-ur-seul-pleure-doucement-02.opus?2026-03-27" class="audio-icon" title="Feb 22">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-c-ur-seul-pleure-doucement-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 75</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-c-ur-seul-pleure-doucement-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 91</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-c-ur-seul-pleure-doucement-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 52</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-c-ur-seul-pleure-doucement-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Mm hmm">▶️ 75*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-c-ur-seul-pleure-doucement-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Ouais, c'est mieux">▶️ 70*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-c-ur-seul-pleure-doucement-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 98</a></td>
<td class="org-left">Le cœur seul pleure doucement.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-tu-es-s-r-du-futur-01.opus?2026-03-27" class="audio-icon" title="Feb 20: Haha, ouais">▶️ 98*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-tu-es-s-r-du-futur-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-tu-es-s-r-du-futur-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 99</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-tu-es-s-r-du-futur-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-tu-es-s-r-du-futur-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Oui">▶️ 93*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-tu-es-s-r-du-futur-07.opus?2026-03-27" class="audio-icon" title="Mar 6: Mm hmm">▶️ 97*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-tu-es-s-r-du-futur-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 99</a></td>
<td class="org-left">Tu es sûr du futur ?</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-trois-tr-s-grands-trains-traversent-trois-trop-grandes-rues-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-trois-tr-s-grands-trains-traversent-trois-trop-grandes-rues-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 93</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-trois-tr-s-grands-trains-traversent-trois-trop-grandes-rues-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 92</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-trois-tr-s-grands-trains-traversent-trois-trop-grandes-rues-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Oui, c'est mieux">▶️ 85*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-trois-tr-s-grands-trains-traversent-trois-trop-grandes-rues-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 90</a></td>
<td class="org-left">Trois très grands trains traversent trois trop grandes rues.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-je-veux-deux-feux-bleus-mais-la-reine-pr-f-re-la-laine-beige-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-je-veux-deux-feux-bleus-mais-la-reine-pr-f-re-la-laine-beige-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-je-veux-deux-feux-bleus-mais-la-reine-pr-f-re-la-laine-beige-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 97</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-je-veux-deux-feux-bleus-mais-la-reine-pr-f-re-la-laine-beige-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Oui, c'est bien">▶️ 82*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-je-veux-deux-feux-bleus-mais-la-reine-pr-f-re-la-laine-beige-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 92</a></td>
<td class="org-left">Je veux deux feux bleus, mais la reine préfère la laine beige.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-vincent-prend-un-bain-en-chantant-lentement-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 91</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-vincent-prend-un-bain-en-chantant-lentement-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 79</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-vincent-prend-un-bain-en-chantant-lentement-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 87</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-vincent-prend-un-bain-en-chantant-lentement-06.opus?2026-03-27" class="audio-icon" title="Mar 3: lentement; oui">▶️ 82*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-vincent-prend-un-bain-en-chantant-lentement-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 94</a></td>
<td class="org-left">Vincent prend un bain en chantant lentement.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-s-re-court-plus-vite-que-le-loup-fou-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 89</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-s-re-court-plus-vite-que-le-loup-fou-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 91</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-s-re-court-plus-vite-que-le-loup-fou-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 91</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-la-mule-s-re-court-plus-vite-que-le-loup-fou-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais">▶️ 84*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-s-re-court-plus-vite-que-le-loup-fou-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 92</a></td>
<td class="org-left">La mule sûre court plus vite que le loup fou.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-luc-a-bu-du-jus-sous-le-pont-o-coule-la-boue-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 91</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-luc-a-bu-du-jus-sous-le-pont-o-coule-la-boue-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 93</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-luc-a-bu-du-jus-sous-le-pont-o-coule-la-boue-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 93</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-luc-a-bu-du-jus-sous-le-pont-o-coule-la-boue-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais, c'est bien">▶️ 92*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-luc-a-bu-du-jus-sous-le-pont-o-coule-la-boue-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 96</a></td>
<td class="org-left">Luc a bu du jus sous le pont où coule la boue.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-fr-re-de-robert-pr-pare-un-rare-r-ti-rouge-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 88</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-fr-re-de-robert-pr-pare-un-rare-r-ti-rouge-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 71</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-fr-re-de-robert-pr-pare-un-rare-r-ti-rouge-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 94</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-le-fr-re-de-robert-pr-pare-un-rare-r-ti-rouge-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Oui">▶️ 86*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-le-fr-re-de-robert-pr-pare-un-rare-r-ti-rouge-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 92</a></td>
<td class="org-left">Le frère de Robert prépare un rare rôti rouge.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-court-autour-du-mur-o-hurle-le-loup-03.opus?2026-03-27" class="audio-icon" title="Feb 26">▶️ 81</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-court-autour-du-mur-o-hurle-le-loup-04.opus?2026-03-27" class="audio-icon" title="Feb 27">▶️ 84</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-court-autour-du-mur-o-hurle-le-loup-05.opus?2026-03-27" class="audio-icon" title="Mar 3">▶️ 88</a></td>
<td class="org-left"><a href="https://sachachua.com/home/sacha/proj/french/analysis/virelangues/clip-la-mule-court-autour-du-mur-o-hurle-le-loup-06.opus?2026-03-27" class="audio-icon" title="Mar 3: Ouais">▶️ 67*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/clip-la-mule-court-autour-du-mur-o-hurle-le-loup-08.opus?2026-03-27" class="audio-icon" title="Mar 12">▶️ 94</a></td>
<td class="org-left">La mule court autour du mur où hurle le loup.</td>
</tr>
</tbody>
</table>

</div>

<p>
Pronunciation still feels a bit hit or miss. Sometimes I say a sentence and my tutor says "Oui," and then I say it again and he says "Non, non&hellip;" The <code>/ʁ/</code> and <code>/y/</code> sounds are hard.
</p>

<p>
I like seeing these compact links in an Org Mode table and being able to play them, thanks to my <a href="https://sachachua.com/dotemacs#audio">custom audio link type</a>. It should be pretty easy to write a function that lets me use a keyboard shortcut to play the audio (maybe using the keys 1-9?) so that I can bounce between them for comparison.
</p>

<p>
If I screen-share from Google Chrome, I can share the tab with audio, so my tutor can listen to things at the same time. Could be fun to compare attempts so that I can try to hear the differences better. Hmm, actually, let's try adding keyboard shortcuts that let me use 1-8, n/p, and f/b to navigate and play audio. Mwahahaha! It works!
</p>
</div>
</div>
<div id="outline-container-comparing-pronunciation-recordings-across-time-2026-03-14-second-set-mon-oncle-peint-un-grand-pont-blanc-etc" class="outline-3">
<h3 id="comparing-pronunciation-recordings-across-time-2026-03-14-second-set-mon-oncle-peint-un-grand-pont-blanc-etc">2026-03-14: Second set: Mon oncle peint un grand pont blanc, etc.</h3>
<div class="outline-text-3" id="text-comparing-pronunciation-recordings-across-time-2026-03-14-second-set-mon-oncle-peint-un-grand-pont-blanc-etc">
<p>
Update 2026-03-14: My tutor gave me a new set of tongue-twisters. When I'm working on my own, I find it helpful to loop over an audio reference with a bit of silence after it so that I can repeat what I've heard. I have several choices for reference audio:
</p>

<ul class="org-ul">
<li>I can generate an audio file using text-to-speech, like a local instance of <a href="https://github.com/nazdridoy/kokoro-tts">Kokoro TTS</a>, or a hosted service like Google Translate (via <a href="https://gtts.readthedocs.io/en/latest/cli.html">gtts-cli</a>), <a href="https://elevenlabs.io/api">ElevenLabs</a>, or <a href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/text-to-speech">Microsoft Azure</a>.</li>
<li>I can extract a recording of my tutor from one of my sessions.</li>
<li>I can extract a recording of myself from one of my tutoring sessions where my tutor said that the pronunciation is alright.</li>
</ul>

<p>
Here I stumble through the tongue-twisters. I've included reference audio from Kokoro, gtts, and ElevenLabs for comparison.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org3a2dd75"><code>(my-subed-record-analyze-file-with-azure-and-references
 (subed-record-keep-last
  (subed-record-filter-skips
   (subed-parse-file
    <span class="org-string">"/home/sacha/proj/french/analysis/virelangues/2026-03-13-raphael-script.vtt"</span>)))
 <span class="org-string">"~/proj/french/analysis/virelangues-2026-03-13/2026-03-13-clip"</span>)
</code></pre>
</div>


<div class="subed-summary" id="org55d5daa">
<table>


<colgroup>
<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-right">

<col class="org-left">

<col class="org-right">

<col class="org-right">

<col class="org-right">

<col class="org-right">

<col class="org-right">

<col class="org-left">
</colgroup>
<tbody>
<tr>
<td class="org-left">Gt</td>
<td class="org-left">Kk</td>
<td class="org-left">Az</td>
<td class="org-left">Me</td>
<td class="org-right">ID</td>
<td class="org-left">Comments</td>
<td class="org-right">All</td>
<td class="org-right">Acc</td>
<td class="org-right">Flu</td>
<td class="org-right">Comp</td>
<td class="org-right">Conf</td>
<td class="org-left">&nbsp;</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-01.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-01.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-01.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-01.opus?2026-03-27" class="audio-icon" title="X: pont">▶️</a></td>
<td class="org-right">1</td>
<td class="org-left">X: pont</td>
<td class="org-right">93</td>
<td class="org-right">99</td>
<td class="org-right">90</td>
<td class="org-right">100</td>
<td class="org-right">86</td>
<td class="org-left">Mon oncle peint un grand pont blanc. {pont}</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-02.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-02.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-02.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-02.opus?2026-03-27" class="audio-icon" title="C'est mieux">▶️</a></td>
<td class="org-right">2</td>
<td class="org-left">C'est mieux</td>
<td class="org-right">68</td>
<td class="org-right">75</td>
<td class="org-right">80</td>
<td class="org-right">62</td>
<td class="org-right">87</td>
<td class="org-left">Un singe malin prend un bon raisin rond.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-03.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-03.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-03.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-03.opus?2026-03-27" class="audio-icon" title="Ouais, c'est &#231;a">▶️</a></td>
<td class="org-right">3</td>
<td class="org-left">Ouais, c'est ça</td>
<td class="org-right">83</td>
<td class="org-right">94</td>
<td class="org-right">78</td>
<td class="org-right">91</td>
<td class="org-right">89</td>
<td class="org-left">Dans le vent du matin, mon chien sent un bon parfum.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-04.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-04.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-04.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-04.opus?2026-03-27" class="audio-icon" title="ok">▶️</a></td>
<td class="org-right">4</td>
<td class="org-left">ok</td>
<td class="org-right">75</td>
<td class="org-right">86</td>
<td class="org-right">63</td>
<td class="org-right">100</td>
<td class="org-right">89</td>
<td class="org-left">Le soin du roi consiste à joindre chaque coin du royaume.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-05.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-05.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-05.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-05.opus?2026-03-27" class="audio-icon" title="Ouais, c'est &#231;a, parfait">▶️</a></td>
<td class="org-right">5</td>
<td class="org-left">Ouais, c'est ça, parfait</td>
<td class="org-right">83</td>
<td class="org-right">94</td>
<td class="org-right">74</td>
<td class="org-right">100</td>
<td class="org-right">88</td>
<td class="org-left">Dans un coin du bois, le roi voit trois points noirs.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-06.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-06.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-06.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-06.opus?2026-03-27" class="audio-icon" title="Ouais, parfait">▶️</a></td>
<td class="org-right">6</td>
<td class="org-left">Ouais, parfait</td>
<td class="org-right">90</td>
<td class="org-right">92</td>
<td class="org-right">87</td>
<td class="org-right">100</td>
<td class="org-right">86</td>
<td class="org-left">Le feu de ce vieux four chauffe peu.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-07.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-07.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-07.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-07.opus?2026-03-27" class="audio-icon" title="Ouais">▶️</a></td>
<td class="org-right">7</td>
<td class="org-left">Ouais</td>
<td class="org-right">77</td>
<td class="org-right">85</td>
<td class="org-right">88</td>
<td class="org-right">71</td>
<td class="org-right">86</td>
<td class="org-left">Deux peureux veulent un peu de feu.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-08.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-08.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-08.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-08.opus?2026-03-27" class="audio-icon">▶️</a></td>
<td class="org-right">8</td>
<td class="org-left">&nbsp;</td>
<td class="org-right">77</td>
<td class="org-right">78</td>
<td class="org-right">75</td>
<td class="org-right">83</td>
<td class="org-right">85</td>
<td class="org-left">Deux vieux bœufs veulent du beurre.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-09.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-09.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-09.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-09.opus?2026-03-27" class="audio-icon" title="Ouais, parfait">▶️</a></td>
<td class="org-right">9</td>
<td class="org-left">Ouais, parfait</td>
<td class="org-right">92</td>
<td class="org-right">94</td>
<td class="org-right">89</td>
<td class="org-right">100</td>
<td class="org-right">89</td>
<td class="org-left">Elle aimait marcher près de la rivière.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-10.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-10.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-10.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-10.opus?2026-03-27" class="audio-icon" title="Ok, c'est bien">▶️</a></td>
<td class="org-right">10</td>
<td class="org-left">Ok, c'est bien</td>
<td class="org-right">93</td>
<td class="org-right">98</td>
<td class="org-right">89</td>
<td class="org-right">100</td>
<td class="org-right">90</td>
<td class="org-left">Je vais essayer de réparer la fenêtre.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-11.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-11.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-11.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-11.opus?2026-03-27" class="audio-icon" title="Okay">▶️</a></td>
<td class="org-right">11</td>
<td class="org-left">Okay</td>
<td class="org-right">83</td>
<td class="org-right">87</td>
<td class="org-right">76</td>
<td class="org-right">100</td>
<td class="org-right">89</td>
<td class="org-left">Le bébé préfère le lait frais.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-12.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-12.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-12.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-12.opus?2026-03-27" class="audio-icon">▶️</a></td>
<td class="org-right">12</td>
<td class="org-left">&nbsp;</td>
<td class="org-right">77</td>
<td class="org-right">92</td>
<td class="org-right">70</td>
<td class="org-right">86</td>
<td class="org-right">90</td>
<td class="org-left">Charlotte cherche ses chaussures dans la chambre.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-13.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-13.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-13.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-13.opus?2026-03-27" class="audio-icon" title="Okay">▶️</a></td>
<td class="org-right">13</td>
<td class="org-left">Okay</td>
<td class="org-right">91</td>
<td class="org-right">90</td>
<td class="org-right">94</td>
<td class="org-right">91</td>
<td class="org-right">88</td>
<td class="org-left">Un chasseur sachant chasser sans son chien est-il un bon chasseur ?</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-14.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-14.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-14.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-14.opus?2026-03-27" class="audio-icon" title="Ouais">▶️</a></td>
<td class="org-right">14</td>
<td class="org-left">Ouais</td>
<td class="org-right">91</td>
<td class="org-right">88</td>
<td class="org-right">92</td>
<td class="org-right">100</td>
<td class="org-right">91</td>
<td class="org-left">Le journaliste voyage en janvier au Japon.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-15.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-15.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-15.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-15.opus?2026-03-27" class="audio-icon" title="C'est bien (X: dans un)">▶️</a></td>
<td class="org-right">15</td>
<td class="org-left">C'est bien (X: dans un)</td>
<td class="org-right">91</td>
<td class="org-right">88</td>
<td class="org-right">94</td>
<td class="org-right">100</td>
<td class="org-right">88</td>
<td class="org-left">Georges joue du jazz dans un grand bar. {dans un}</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-16.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-16.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-16.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-16.opus?2026-03-27" class="audio-icon" title="C'est bien">▶️</a></td>
<td class="org-right">16</td>
<td class="org-left">C'est bien</td>
<td class="org-right">88</td>
<td class="org-right">87</td>
<td class="org-right">94</td>
<td class="org-right">88</td>
<td class="org-right">85</td>
<td class="org-left">Un jeune joueur joue dans le grand gymnase.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-17.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-17.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-17.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-17.opus?2026-03-27" class="audio-icon">▶️</a></td>
<td class="org-right">17</td>
<td class="org-left">&nbsp;</td>
<td class="org-right">95</td>
<td class="org-right">94</td>
<td class="org-right">96</td>
<td class="org-right">100</td>
<td class="org-right">91</td>
<td class="org-left">Le compagnon du montagnard soigne un agneau.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-18.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-18.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-18.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-18.opus?2026-03-27" class="audio-icon">▶️</a></td>
<td class="org-right">18</td>
<td class="org-left">&nbsp;</td>
<td class="org-right">85</td>
<td class="org-right">88</td>
<td class="org-right">84</td>
<td class="org-right">86</td>
<td class="org-right">89</td>
<td class="org-left">La cigogne soigne l’agneau dans la campagne.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-gtts-19.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-kokoro-19.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-azure-19.opus?2026-03-27" class="audio-icon">👂🏼</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/2026-03-13-clip-19.opus?2026-03-27" class="audio-icon" title="grenouille">▶️</a></td>
<td class="org-right">19</td>
<td class="org-left">grenouille</td>
<td class="org-right">71</td>
<td class="org-right">80</td>
<td class="org-right">68</td>
<td class="org-right">75</td>
<td class="org-right">86</td>
<td class="org-left">La grenouille fouille les feuilles dans la broussaille.</td>
</tr>
</tbody>
</table>

</div>

<p>
And comparing clips over time:
</p>

<div class="subed-summary" id="org7d5b437">

<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(learn-lang-subed-record-summarize-segments
 (split-string (org-file-contents <span class="org-string">"~/proj/french/analysis/virelangues-2026-03-13/phrases.txt"</span>) <span class="org-string">"\n"</span>)
 <span class="org-highlight-quoted-quote">'</span>((<span class="org-string">"/home/sacha/proj/french/analysis/virelangues/2026-03-13-raphael-script.vtt"</span> . <span class="org-string">"Mar 13"</span>)
   (<span class="org-string">"/home/sacha/proj/french/analysis/virelangues-2026-03-13/2026-03-20-raphael-script.vtt"</span> . <span class="org-string">"Mar 20"</span>)
   (<span class="org-string">"/home/sacha/proj/french/analysis/virelangues-2026-03-13/2026-03-24-raphael-script.vtt"</span> . <span class="org-string">"Mar 24"</span>)
   (<span class="org-string">"/home/sacha/proj/french/analysis/virelangues-2026-03-13/2026-03-27-script.vtt"</span> . <span class="org-string">"Mar 27"</span>))
 <span class="org-string">"~/proj/french/analysis/virelangues-2026-03-13/all-"</span>
 <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">learn-lang-subed-record-get-last-attempt</span>)
</code></pre>
</div>


<table>


<colgroup>
<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">

<col class="org-left">
</colgroup>
<thead>
<tr>
<th scope="col" class="org-left">Mar 13</th>
<th scope="col" class="org-left">Mar 20</th>
<th scope="col" class="org-left">Mar 24</th>
<th scope="col" class="org-left">Mar 27</th>
<th scope="col" class="org-left">Text</th>
</tr>
</thead>
<tbody>
<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;mon-oncle-peint-un-grand-pont-blanc-01.opus?2026-03-27" class="audio-icon" title="Mar 13: X: pont">▶️ 93*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;mon-oncle-peint-un-grand-pont-blanc-02.opus?2026-03-27" class="audio-icon" title="Mar 20">▶️ 89</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;mon-oncle-peint-un-grand-pont-blanc-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 71*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;mon-oncle-peint-un-grand-pont-blanc-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 88</a></td>
<td class="org-left">Mon oncle peint un grand pont blanc.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-singe-malin-prend-un-bon-raisin-rond-01.opus?2026-03-27" class="audio-icon" title="Mar 13: C'est mieux">▶️ 68*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-singe-malin-prend-un-bon-raisin-rond-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm">▶️ 82*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-singe-malin-prend-un-bon-raisin-rond-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-singe-malin-prend-un-bon-raisin-rond-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 91</a></td>
<td class="org-left">Un singe malin prend un bon raisin rond.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-le-vent-du-matin-mon-chien-sent-un-bon-parfum-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ouais, c'est &#231;a">▶️ 83*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-le-vent-du-matin-mon-chien-sent-un-bon-parfum-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Oui">▶️ 89*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-le-vent-du-matin-mon-chien-sent-un-bon-parfum-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 93*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-le-vent-du-matin-mon-chien-sent-un-bon-parfum-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 95</a></td>
<td class="org-left">Dans le vent du matin, mon chien sent un bon parfum.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-soin-du-roi-consiste-joindre-chaque-coin-du-royaume-01.opus?2026-03-27" class="audio-icon" title="Mar 13: ok">▶️ 75*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-soin-du-roi-consiste-joindre-chaque-coin-du-royaume-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm">▶️ 84*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-soin-du-roi-consiste-joindre-chaque-coin-du-royaume-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais">▶️ 86*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-soin-du-roi-consiste-joindre-chaque-coin-du-royaume-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 90</a></td>
<td class="org-left">Le soin du roi consiste à joindre chaque coin du royaume.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-un-coin-du-bois-le-roi-voit-trois-points-noirs-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ouais, c'est &#231;a, parfait">▶️ 83*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-un-coin-du-bois-le-roi-voit-trois-points-noirs-02.opus?2026-03-27" class="audio-icon" title="Mar 20: C'est &#231;a. C'est bien.">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-un-coin-du-bois-le-roi-voit-trois-points-noirs-03.opus?2026-03-27" class="audio-icon" title="Mar 24">▶️ 96</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;dans-un-coin-du-bois-le-roi-voit-trois-points-noirs-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 97</a></td>
<td class="org-left">Dans un coin du bois, le roi voit trois points noirs.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-feu-de-ce-vieux-four-chauffe-peu-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ouais, parfait">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-feu-de-ce-vieux-four-chauffe-peu-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Oui">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-feu-de-ce-vieux-four-chauffe-peu-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-feu-de-ce-vieux-four-chauffe-peu-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 99</a></td>
<td class="org-left">Le feu de ce vieux four chauffe peu.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-peureux-veulent-un-peu-de-feu-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ouais">▶️ 77*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-peureux-veulent-un-peu-de-feu-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm">▶️ 77*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-peureux-veulent-un-peu-de-feu-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mmm hmm">▶️ 84*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-peureux-veulent-un-peu-de-feu-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 89</a></td>
<td class="org-left">Deux peureux veulent un peu de feu.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-vieux-b-ufs-veulent-du-beurre-01.opus?2026-03-27" class="audio-icon" title="Mar 13">▶️ 77</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-vieux-b-ufs-veulent-du-beurre-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm">▶️ 80*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-vieux-b-ufs-veulent-du-beurre-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais">▶️ 71*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;deux-vieux-b-ufs-veulent-du-beurre-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 52</a></td>
<td class="org-left">Deux vieux bœufs veulent du beurre.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;elle-aimait-marcher-pr-s-de-la-rivi-re-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ouais, parfait">▶️ 92*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;elle-aimait-marcher-pr-s-de-la-rivi-re-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Ouais">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;elle-aimait-marcher-pr-s-de-la-rivi-re-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 100*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;elle-aimait-marcher-pr-s-de-la-rivi-re-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 96</a></td>
<td class="org-left">Elle aimait marcher près de la rivière.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;je-vais-essayer-de-r-parer-la-fen-tre-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ok, c'est bien">▶️ 93*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;je-vais-essayer-de-r-parer-la-fen-tre-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm">▶️ 87*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;je-vais-essayer-de-r-parer-la-fen-tre-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 98*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;je-vais-essayer-de-r-parer-la-fen-tre-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 94</a></td>
<td class="org-left">Je vais essayer de réparer la fenêtre.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-b-b-pr-f-re-le-lait-frais-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Okay">▶️ 83*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-b-b-pr-f-re-le-lait-frais-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Ouais">▶️ 85*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-b-b-pr-f-re-le-lait-frais-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais">▶️ 93*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-b-b-pr-f-re-le-lait-frais-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 97</a></td>
<td class="org-left">Le bébé préfère le lait frais.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;charlotte-cherche-ses-chaussures-dans-la-chambre-01.opus?2026-03-27" class="audio-icon" title="Mar 13">▶️ 77</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;charlotte-cherche-ses-chaussures-dans-la-chambre-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Oui">▶️ 78*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;charlotte-cherche-ses-chaussures-dans-la-chambre-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;charlotte-cherche-ses-chaussures-dans-la-chambre-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 87</a></td>
<td class="org-left">Charlotte cherche ses chaussures dans la chambre.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-chasseur-sachant-chasser-sans-son-chien-est-il-un-bon-chasseur-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Okay">▶️ 91*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-chasseur-sachant-chasser-sans-son-chien-est-il-un-bon-chasseur-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Ouais">▶️ 92*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-chasseur-sachant-chasser-sans-son-chien-est-il-un-bon-chasseur-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 97*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-chasseur-sachant-chasser-sans-son-chien-est-il-un-bon-chasseur-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 92</a></td>
<td class="org-left">Un chasseur sachant chasser sans son chien est-il un bon chasseur ?</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-journaliste-voyage-en-janvier-au-japon-01.opus?2026-03-27" class="audio-icon" title="Mar 13: Ouais">▶️ 91*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-journaliste-voyage-en-janvier-au-japon-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm">▶️ 84*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-journaliste-voyage-en-janvier-au-japon-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-journaliste-voyage-en-janvier-au-japon-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 91</a></td>
<td class="org-left">Le journaliste voyage en janvier au Japon.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;georges-joue-du-jazz-dans-un-grand-bar-01.opus?2026-03-27" class="audio-icon" title="Mar 13: C'est bien (X: dans un)">▶️ 91*</a></td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;georges-joue-du-jazz-dans-un-grand-bar-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais, parfait">▶️ 79*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;georges-joue-du-jazz-dans-un-grand-bar-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 95</a></td>
<td class="org-left">Georges joue du jazz dans un grand bar.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-jeune-joueur-joue-dans-le-grand-gymnase-01.opus?2026-03-27" class="audio-icon" title="Mar 13: C'est bien">▶️ 88*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-jeune-joueur-joue-dans-le-grand-gymnase-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Oui, c'est bien.">▶️ 99*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-jeune-joueur-joue-dans-le-grand-gymnase-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-jeune-joueur-joue-dans-le-grand-gymnase-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 96</a></td>
<td class="org-left">Un jeune joueur joue dans le grand gymnase.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-compagnon-du-montagnard-soigne-un-agneau-01.opus?2026-03-27" class="audio-icon" title="Mar 13">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-compagnon-du-montagnard-soigne-un-agneau-02.opus?2026-03-27" class="audio-icon" title="Mar 20">▶️ 95</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-compagnon-du-montagnard-soigne-un-agneau-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 94*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-compagnon-du-montagnard-soigne-un-agneau-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 93</a></td>
<td class="org-left">Le compagnon du montagnard soigne un agneau.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-cigogne-soigne-l-agneau-dans-la-campagne-01.opus?2026-03-27" class="audio-icon" title="Mar 13">▶️ 85</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-cigogne-soigne-l-agneau-dans-la-campagne-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Mm hmm (I think I need to fix the campagne though)">▶️ 63*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-cigogne-soigne-l-agneau-dans-la-campagne-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais">▶️ 86*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-cigogne-soigne-l-agneau-dans-la-campagne-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 100</a></td>
<td class="org-left">La cigogne soigne l'agneau dans la campagne.</td>
</tr>

<tr>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-grenouille-fouille-les-feuilles-dans-la-broussaille-01.opus?2026-03-27" class="audio-icon" title="Mar 13: grenouille">▶️ 71*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-grenouille-fouille-les-feuilles-dans-la-broussaille-02.opus?2026-03-27" class="audio-icon" title="Mar 20: X: la">▶️ 64*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-grenouille-fouille-les-feuilles-dans-la-broussaille-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais">▶️ 79*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-grenouille-fouille-les-feuilles-dans-la-broussaille-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 91</a></td>
<td class="org-left">La grenouille fouille les feuilles dans la broussaille.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-vieille-abeille-travaille-dans-la-broussaille-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Ouais, c'est bien">▶️ 66*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-vieille-abeille-travaille-dans-la-broussaille-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Mm hmm">▶️ 79*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;la-vieille-abeille-travaille-dans-la-broussaille-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 73</a></td>
<td class="org-left">La vieille abeille travaille dans la broussaille.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-juge-jean-juge-justement-les-jolis-bijoux-de-julie-02.opus?2026-03-27" class="audio-icon" title="Mar 20: Y: Mm hmm">▶️ 96*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-juge-jean-juge-justement-les-jolis-bijoux-de-julie-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais">▶️ 90*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;le-juge-jean-juge-justement-les-jolis-bijoux-de-julie-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 95</a></td>
<td class="org-left">Le juge Jean juge justement les jolis bijoux de Julie.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;ma-compagne-m-accompagne-la-campagne-avec-une-autre-compagne-03.opus?2026-03-27" class="audio-icon" title="Mar 24: Y: Ouais, c'est &#231;a">▶️ 92*</a></td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;ma-compagne-m-accompagne-la-campagne-avec-une-autre-compagne-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 93</a></td>
<td class="org-left">Ma compagne m'accompagne à la campagne avec une autre compagne.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;une-tortue-t-tue-marche-dessus-sous-une-pluie-continue-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 82</a></td>
<td class="org-left">Une tortue têtue marche dessus sous une pluie continue.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;une-mule-t-tue-pousse-une-roue-lourde-sur-une-rue-boueuse-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 76</a></td>
<td class="org-left">Une mule têtue pousse une roue lourde sur une rue boueuse.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;trois-gros-rats-gris-grimpent-dans-trois-grands-greniers-rugueux-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 73</a></td>
<td class="org-left">Trois gros rats gris grimpent dans trois grands greniers rugueux.</td>
</tr>

<tr>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left">&nbsp;</td>
<td class="org-left"><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/all&#45;&#45;un-professeur-rigoureux-corrige-un-rapport-sur-une-erreur-rare-04.opus?2026-03-27" class="audio-icon" title="Mar 27">▶️ 84</a></td>
<td class="org-left">Un professeur rigoureux corrige un rapport sur une erreur rare.</td>
</tr>
</tbody>
</table>

</div>
</div>
</div>
<div id="outline-container-comparing-pronunciation-recordings-across-time-the-code" class="outline-3">
<h3 id="comparing-pronunciation-recordings-across-time-the-code">The code</h3>
<div class="outline-text-3" id="text-comparing-pronunciation-recordings-across-time-the-code">
<details><summary>Code for summarizing the segments</summary>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-subed-record-analyze-file-with-azure-and-references</span> (subtitles prefix <span class="org-type">&amp;optional</span> always-create)
  (cons
   <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"Gt"</span> <span class="org-string">"Kk"</span> <span class="org-string">"Az"</span> <span class="org-string">"Me"</span> <span class="org-string">"ID"</span> <span class="org-string">"Comments"</span> <span class="org-string">"All"</span> <span class="org-string">"Acc"</span> <span class="org-string">"Flu"</span> <span class="org-string">"Comp"</span> <span class="org-string">"Conf"</span>)
   (seq-map-indexed
    (<span class="org-keyword">lambda</span> (sub i)
      (<span class="org-keyword">let</span> ((sound-file (expand-file-name (format <span class="org-string">"%s-%02d.opus"</span>
                                                  prefix
                                                  (1+ i))))
            (tts-services
             <span class="org-highlight-quoted-quote">'</span>((<span class="org-string">"gtts"</span> . learn-lang-tts-gtts-say)
               (<span class="org-string">"kokoro"</span> . learn-lang-tts-kokoro-fastapi-say)
               (<span class="org-string">"azure"</span> . learn-lang-tts-azure-say)))
            tts-files
            (note (subed-record-get-directive <span class="org-string">"#+NOTE"</span> (elt sub 4))))
        (<span class="org-keyword">when</span> (<span class="org-keyword">or</span> always-create (not (file-exists-p sound-file)))
          (subed-record-extract-audio-for-current-subtitle-to-file sound-file sub))
        (<span class="org-keyword">setq</span>
         tts-files
         (mapcar
          (<span class="org-keyword">lambda</span> (row)
            (<span class="org-keyword">let</span> ((reference (format <span class="org-string">"%s-%s-%02d.opus"</span> prefix (car row) (1+ i) )))
              (<span class="org-keyword">when</span> (<span class="org-keyword">or</span> always-create (not (file-exists-p reference)))
                (funcall (cdr row)
                         (subed-record-simplify (elt sub 3))
                         <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">sync</span>
                         reference))
              (org-link-make-string
               (concat <span class="org-string">"audio:"</span> reference <span class="org-string">"?icon=t&amp;note="</span> (url-hexify-string (car row)))
               <span class="org-string">"&#128066;&#127996;"</span>)))
          tts-services))
        (append
         tts-files
         (list
          (org-link-make-string
           (concat <span class="org-string">"audio:"</span> sound-file <span class="org-string">"?icon=t"</span>
                   (format <span class="org-string">"&amp;source-start=%s"</span> (elt sub 1))
                   (<span class="org-keyword">if</span> (<span class="org-keyword">and</span> note (not (string= note <span class="org-string">""</span>)))
                       (format <span class="org-string">"&amp;title=%s"</span>
                               (url-hexify-string note))
                     <span class="org-string">""</span>))
           <span class="org-string">"&#9654;&#65039;"</span>)
          (format <span class="org-string">"%d"</span> (1+ i))
          (<span class="org-keyword">or</span> note <span class="org-string">""</span>))
         (learn-lang-azure-subed-record-parse (elt sub 4))
         (list
          (elt sub 3)))))
    subtitles)))
</code></pre>
</div>

</details>

<p>
Jumping to the source again:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-subed-record-org-clip-view-source</span> ()
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">let*</span> ((params
          (url-parse-query-string
           (cdr
            (url-path-and-query
             (url-generic-parse-url
              (org-element-property <span class="org-builtin">:raw-link</span> (org-element-context))))))))
    (find-file (car (alist-get <span class="org-string">"source-file"</span> params nil nil <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">string=</span>)))
    (subed-jump-to-subtitle-id-at-msecs
     (string-to-number (car (alist-get <span class="org-string">"source-start"</span> params nil nil <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">string=</span>))))))
</code></pre>
</div>


<p>
I've moved the rest of the code to <a href="https://codeberg.org/sachac/learn-lang/">sachac/learn-lang on Codeberg</a>.
</p>
</div>
</div>
<div><a href="https://sachachua.com/blog/2026/03/comparing-pronunciation-recordings-across-time/index.org">View Org source for this post</a></div>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2026%2F03%2Fcomparing-pronunciation-recordings-across-time%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Sorting completion candidates, such as sorting Org headings by level</title>
		<link>https://sachachua.com/blog/2026/02/sorting-completion-candidates-such-as-sorting-org-headings-by-level/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 19:08:59 GMT</pubDate>
    <category>emacs</category>
<category>org</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2026/02/sorting-completion-candidates-such-as-sorting-org-headings-by-level/</guid>
		<description><![CDATA[<div class="update" id="org0847ab9">
<p>
<span class="timestamp-wrapper"><time class="timestamp" datetime="2026-02-26">[2026-02-26 Thu]</time></span>: Made the code even neater with <code>:key</code>, included the old code as well
</p>

</div>

<p>
At this week's <a href="https://emacs-berlin.org/">Emacs Berlin meetup</a>, someone wanted to know how to change the order of completion candidates. Specifically, they wanted to list the top level Org Mode headings before the second level headings and so on. They were using <a href="https://github.com/alphapapa/org-ql">org-ql</a> to navigate Org headings, but since org-ql sorts its candidates by the number of matches according to the code in the <code>org-ql-completing-read</code> function, I wasn't quite sure how to get it to do what they wanted. (And I realized my org-ql setup was broken, so I couldn't fiddle with it live. Edit: Turns out I needed to update the <a target="_blank" href="https://elpa.gnu.org/packages/peg.html">peg</a> package) Instead, I showed folks <code>consult-org-heading</code> which is part of the <a href="https://github.com/minad/consult">Consult</a> package, which I like to use to jump around the headings in a single Org file. It's a short function that's easy to use as a starting point for something custom.
</p>

<p>
Here's some code that allows you to use <code>consult-org-heading</code> to jump to an Org heading in the current file with completions sorted by level.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">with-eval-after-load</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult-org</span>
  (advice-add
   <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">consult-org&#45;&#45;headings</span>
   <span class="org-builtin">:filter-return</span>
   (<span class="org-keyword">lambda</span> (candidates)
     (sort candidates
           <span class="org-builtin">:key</span> (<span class="org-keyword">lambda</span> (o) (car (get-text-property 0 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult-org&#45;&#45;heading</span> o)))))))
</code></pre>
</div>



<figure id="org8098e5b">
<img src="https://sachachua.com/blog/2026/02/sorting-completion-candidates-such-as-sorting-org-headings-by-level/2026-02-26_13-42-58.png" alt="2026-02-26_13-42-58.png">

<figcaption><span class="figure-number">Figure 1: </span>Screenshot showing where the candidates transition from top-level headings to second-level headings</figcaption>
</figure>

<p>
My previous approach defined a different function based on <code>consult-org-heading</code>, but using the advice feels a little cleaner because it will also make it work for any other function that uses <code>consult-org&#45;&#45;headings</code>. I've included the old code in case you're curious. Here, we don't modify the function's behaviour using advice, we just make a new function (<code>my-consult-org-heading</code>) that calls another function that processes the results a little (<code>my-consult-org&#45;&#45;headings</code>).
</p>

<details><summary>Old code, if you're curious</summary>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-consult-org&#45;&#45;headings</span> (prefix match scope <span class="org-type">&amp;rest</span> skip)
  (<span class="org-keyword">let</span> ((candidates (consult-org&#45;&#45;headings prefix match scope)))
    (sort candidates
          <span class="org-builtin">:lessp</span>
          (<span class="org-keyword">lambda</span> (a b)
            (<span class="org-keyword">let</span> ((level-a (car (get-text-property 0 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult-org&#45;&#45;heading</span> a)))
                  (level-b (car (get-text-property 0 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult-org&#45;&#45;heading</span> b))))
              (<span class="org-keyword">cond</span>
               ((&lt; level-a level-b) t)
               ((&lt; level-b level-a) nil)
               ((string&lt; a b) t)
               ((string&lt; b a) nil)))))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-consult-org-heading</span> (<span class="org-type">&amp;optional</span> match scope)
  <span class="org-doc">"Jump to an Org heading.</span>

<span class="org-doc">MATCH and SCOPE are as in `</span><span class="org-doc"><span class="org-constant">org-map-entries</span></span><span class="org-doc">' and determine which</span>
<span class="org-doc">entries are offered.  By default, all entries of the current</span>
<span class="org-doc">buffer are offered."</span>
  (<span class="org-keyword">interactive</span> (<span class="org-keyword">unless</span> (derived-mode-p <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">org-mode</span>)
                 (<span class="org-warning">user-error</span> <span class="org-string">"Must be called from an Org buffer"</span>)))
  (<span class="org-keyword">let</span> ((prefix (not (memq scope <span class="org-highlight-quoted-quote">'</span>(nil tree region region-start-level file)))))
    (consult&#45;&#45;read
     (<span class="org-keyword">consult&#45;&#45;slow-operation</span> <span class="org-string">"Collecting headings..."</span>
       (<span class="org-keyword">or</span> (my-consult-org&#45;&#45;headings prefix match scope)
           (<span class="org-warning">user-error</span> <span class="org-string">"No headings"</span>)))
     <span class="org-builtin">:prompt</span> <span class="org-string">"Go to heading: "</span>
     <span class="org-builtin">:category</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-heading</span>
     <span class="org-builtin">:sort</span> nil
     <span class="org-builtin">:require-match</span> t
     <span class="org-builtin">:history</span> <span class="org-highlight-quoted-quote">'</span>(<span class="org-builtin">:input</span> consult-org&#45;&#45;history)
     <span class="org-builtin">:narrow</span> (consult-org&#45;&#45;narrow)
     <span class="org-builtin">:state</span> (consult&#45;&#45;jump-state)
     <span class="org-builtin">:annotate</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">consult-org&#45;&#45;annotate</span>
     <span class="org-builtin">:group</span> (<span class="org-keyword">and</span> prefix <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">consult-org&#45;&#45;group</span>)
     <span class="org-builtin">:lookup</span> (apply-partially <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">consult&#45;&#45;lookup-prop</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-marker</span>))))
</code></pre>
</div>

</details>

<p>
I also wanted to get this to work for <code>C-u org-refile</code>, which uses <code>org-refile-get-location</code>.
This is a little trickier because the table of completion candidates is a list of cons cells that don't store the level, and it doesn't pass the metadata to <code>completing-read</code> to tell it not to re-sort the results. We'll just fake it by counting the number of "/", which is the path separator used if <code>org-outline-path-complete-in-steps</code> is set to <code>nil</code>.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">with-eval-after-load</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org</span>
  (advice-add
   <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-refile-get-location</span>
   <span class="org-builtin">:around</span>
   (<span class="org-keyword">lambda</span> (fn <span class="org-type">&amp;rest</span> args)
     (<span class="org-keyword">let</span> ((completion-extra-properties
            <span class="org-highlight-quoted-quote">'</span>(<span class="org-builtin">:display-sort-function</span>
              (<span class="org-keyword">lambda</span> (candidates)
                (sort candidates
                      <span class="org-builtin">:key</span> (<span class="org-keyword">lambda</span> (s) (length (split-string s <span class="org-string">"/"</span>))))))))
       (apply fn args)))))
</code></pre>
</div>



<figure id="org535f616">
<img src="https://sachachua.com/blog/2026/02/sorting-completion-candidates-such-as-sorting-org-headings-by-level/2026-02-26_14-01-28.png" alt="2026-02-26_14-01-28.png">

<figcaption><span class="figure-number">Figure 2: </span>Screenshot of sorted refile entries</figcaption>
</figure>

<p>
In general, if you would like completion candidates to be in a certain order, you can specify  <code>display-sort-function</code> either by calling <code>completing-read</code> with a collection that's a lambda function instead of a table of completion candidates, or by overriding it with <code>completion-category-overrides</code> if there's a category you can use or <code>completion-extra-properties</code> if not.
</p>

<p>
Here's a short example of passing a lambda to a completion function (<a href="https://manueluberti.eu/posts/2025-09-21-date-at-point/">thanks to Manuel Uberti</a>):
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">mu-date-at-point</span> (date)
  <span class="org-doc">"Insert current DATE at point via `</span><span class="org-doc"><span class="org-constant">completing-read</span></span><span class="org-doc">'."</span>
  (<span class="org-keyword">interactive</span>
   (<span class="org-keyword">let*</span> ((formats <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"%Y%m%d"</span> <span class="org-string">"%F"</span> <span class="org-string">"%Y%m%d%H%M"</span> <span class="org-string">"%Y-%m-%dT%T"</span>))
          (vals (mapcar <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">format-time-string</span> formats))
          (opts
           (<span class="org-keyword">lambda</span> (string pred action)
             (<span class="org-keyword">if</span> (eq action <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">metadata</span>)
                 <span class="org-highlight-quoted-quote">'</span>(metadata (display-sort-function . identity))
               (complete-with-action action vals string pred)))))
     (list (completing-read <span class="org-string">"Insert date: "</span> opts nil t))))
  (insert date))
</code></pre>
</div>


<p>
If you use <code>consult&#45;&#45;read</code> from the <a href="https://github.com/minad/consult">Consult</a> completion framework, there is a <code>:sort</code> property that you can set to either nil or your own function.
</p>

<p>
This entry is part of the <a href="https://sachachua.com/blog/2026/01/emacs-carnival-february-2026-completion/">Emacs Carnival for Feb 2026: Completion</a>.
</p>

<div class="note">This is part of my <a href="https://sachachua.com/dotemacs#org">Emacs configuration.</a></div><div><a href="https://sachachua.com/blog/2026/02/sorting-completion-candidates-such-as-sorting-org-headings-by-level/index.org">View Org source for this post</a></div>
<p>You can <a href="https://social.sachachua.com/@sacha/statuses/01KJDNREMY8GE4DA5A48P5RD2R" target="_blank" rel="noopener noreferrer">comment on Mastodon</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2026%2F02%2Fsorting-completion-candidates-such-as-sorting-org-headings-by-level%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Playing around with org-db-v3 and consult: vector search of my blog post Org files, with previews</title>
		<link>https://sachachua.com/blog/2025/10/playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Wed, 29 Oct 2025 00:32:24 GMT</pubDate>
    <category>emacs</category>
<category>org</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2025/10/playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews/</guid>
		<description><![CDATA[<div class="update" id="orgbe0a687">
<p>
<span class="timestamp-wrapper"><time class="timestamp" datetime="2025-10-29">[2025-10-29 Wed]</time></span>: Sort <code>my-org-db-v3-to-emacs-rag-search</code> by similarity score.
</p>

</div>
<div class="sticky-toc" id="org3c0bfac">
<div class="text-table-of-contents toc-id" role="doc-toc">
<ul>
<li><a href="https://sachachua.com/blog/feed/index.xml#playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-demo">Demo</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-background">Background</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-my-org-files">Indexing my Org files</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-blog-posts-via-exported-org-files">Indexing blog posts via exported Org files</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-searching-my-blog-posts">Searching my blog posts</a></li>
<li><a href="https://sachachua.com/blog/feed/index.xml#playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-quick-thoughts-and-next-steps">Quick thoughts and next steps</a></li>
</ul>
</div>

</div>

<p>
I tend to use different words even when I'm writing about the same ideas.
When I use traditional search tools like <code>grep</code>, it can be hard to look up old blog posts or sketches if I can't think of the exact words I used.
When I write a blog post, I want to automatically remind myself of possibly relevant notes without requiring words to exactly match what I'm looking for.
</p>
<div id="outline-container-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-demo" class="outline-3">
<h3 id="playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-demo">Demo</h3>
<div class="outline-text-3" id="text-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-demo">
<p>
Here's a super quick demo of what I've been hacking together so far, doing vector search on some of my blog posts using the .org files I indexed with <a href="https://github.com/jkitchin/org-db-v3">org-db-v3</a>:
</p>

<div class="media-post" id="org4513128">
<p>
</p><figure><video controls="1" src="https://sachachua.com/blog/2025/10/playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews/2025-10-28_20.39.30.webm" type="video/webm"><a href="https://sachachua.com/blog/2025/10/playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews/2025-10-28_20.39.30.webm">Download the video</a></video><figcaption><div>Screencast of my-blog-similar-link</div></figcaption></figure>
<p></p>

<p>
Play by play:
</p>

<ul class="org-ul">
<li><span class="media-time" data-start="0.000">0:00:00</span> Use <code>M-x my-blog-similar-link</code> to look for "forgetting things", flip through results, and use RET to select one.</li>
<li><span class="media-time" data-start="25.000">0:00:25</span> Select "convert the text into a link" and use <code>M-x my-blog-similar-link</code> to change it into a link.</li>
<li><span class="media-time" data-start="44.000">0:00:44</span> I can call it with <code>C-u M-x my-blog-similar-link</code> and it will do the vector search using all of the post's text. This is pretty long, so I don't show it in the prompt.</li>
<li><span class="media-time" data-start="56.000">0:00:56</span> I can use Embark to select and insert multiple links. <code>C-SPC</code> selects them from the completion buffer, and <code>C-. A</code> acts on all of them.</li>
<li><span class="media-time" data-start="77.000">0:01:17</span> I can also use Embark's <code>C-. S</code> (<code>embark-collect</code>) to keep a snapshot that I can act on, and I can use <code>RET</code> in that buffer to insert the links.</li>
</ul>

</div>
</div>
</div>
<div id="outline-container-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-background" class="outline-3">
<h3 id="playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-background">Background</h3>
<div class="outline-text-3" id="text-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-background">
<p>
A few weeks ago, John Kitchin demonstrated a vector search server
in his video <a href="https://www.youtube.com/watch?v=FwP3HQkrP28">Emacs RAG with LibSQL - Enabling
semantic search of org-mode headings with Claude
Code - YouTube</a>. I checked out
<a href="https://github.com/jkitchin/emacs-rag-libsql">jkitchin/emacs-rag-libsql</a> and got the server
running. My system's a little slow (no GPU), so
<code>(setq emacs-rag-http-timeout nil)</code> was helpful.
It feels like a lighter-weight version of <a href="https://github.com/khoj-ai/khoj">Khoj</a>
(which also supports Org Mode
files) and maybe more focused on Org than <a href="https://github.com/jwiegley/rag-client">jwiegley/rag-client</a>. At the moment,
I'm more interested in embeddings and
vector/hybrid search than generating summaries or
using a conversational interface, so something
simple is fine. I just want a list of
possibly-related items that I can re-read myself.
</p>

<p>
Of course, while these notes were languishing in
my draft file, John Kitchin had already moved on
to something else.
He posted <a href="https://www.youtube.com/watch?v=t879-fipgtg">Fulltext, semantic text and image search
in Emacs - YouTube</a>, linking to a new vibe-coded
project called <a href="https://github.com/jkitchin/org-db-v3">org-db-v3</a> that promises to offer
semantic, full-text, image, and headline search.
The interface is ever so slightly different: POST
instead of GET, a different data structure for
results.
Fortunately, it was easy enough to adapt my code.
I just needed a small adapter function to make the
output of <code>org-db-v3</code> look like the output from
<code>emacs-rag-search</code>.
</p>

<p>

</p><div class="org-src-container">
<pre class="src src-emacs-lisp" id="org744748e"><code>(<span class="org-keyword">use-package</span> org-db-v3
  <span class="org-builtin">:load-path</span> <span class="org-string">"~/vendor/org-db-v3/elisp"</span>
  <span class="org-builtin">:init</span>
  (<span class="org-keyword">setq</span> org-db-v3-auto-enable nil))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-db-v3-to-emacs-rag-search</span> (query <span class="org-type">&amp;optional</span> limit filename-pattern)
  <span class="org-doc">"Search org-db-v3 and transform the data to look like emacs-rag-search's output."</span>
  (org-db-v3-ensure-server)
  (<span class="org-keyword">setq</span> limit (<span class="org-keyword">or</span> limit 100))
  (mapcar (<span class="org-keyword">lambda</span> (o)
            <span class="org-highlight-quoted-quote">`</span>((source_path . ,(assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">filename</span> o))
              (line_number . ,(assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">begin_line</span> o))
              ,@o))
          (sort
           (assoc-default <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">results</span>
                          (plz <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">post</span> (concat (org-db-v3-server-url) <span class="org-string">"/api/search/semantic"</span>)
                            <span class="org-builtin">:headers</span> <span class="org-highlight-quoted-quote">'</span>((<span class="org-string">"Content-Type"</span> . <span class="org-string">"application/json"</span>))
                            <span class="org-builtin">:body</span> (json-encode <span class="org-highlight-quoted-quote">`</span>((query . ,query)
                                                 (limit . ,limit)
                                                 (filename_pattern . ,filename-pattern)))
                            <span class="org-builtin">:as</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">json-read</span>))
           <span class="org-builtin">:key</span> (<span class="org-keyword">lambda</span> (o) (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">similarity_score</span> o))
           <span class="org-builtin">:reverse</span> t)))
</code></pre>
</div>


<p></p>

<p>
I'm assuming that <code>org-db-v3</code> is what John's going
to focus on instead of <code>emacs-rag-search</code> (for
now, at least). I'll focus on that for the rest of
this post, although I'll include some of the
<code>emacs-rag-search</code> stuff just in case.
</p>
</div>
</div>
<div id="outline-container-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-my-org-files" class="outline-3">
<h3 id="playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-my-org-files">Indexing my Org files</h3>
<div class="outline-text-3" id="text-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-my-org-files">
<p>
Both <code>emacs-rag</code> and <code>org-db-v3</code> index Org files by
submitting them to a local web server. Here are the key files I want to index:
</p>

<ul class="org-ul">
<li>organizer.org: my personal projects and reference notes</li>
<li>reading.org: snippets from books and webpages</li>
<li>resources.org: bookmarks and frequently-linked sites</li>
<li>posts.org: draft posts</li>
</ul>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(<span class="org-keyword">dolist</span> (file <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"~/sync/orgzly/organizer.org"</span>
                <span class="org-string">"~/sync/orgzly/posts.org"</span>
                <span class="org-string">"~/sync/orgzly/reading.org"</span>
                <span class="org-string">"~/sync/orgzly/resources.org"</span>))
  (org-db-v3-index-file-async file))
</code></pre>
</div>


<p>
(<code>emacs-rag</code> uses <code>emacs-rag-index-file</code> instead.)
</p>
</div>
</div>
<div id="outline-container-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-blog-posts-via-exported-org-files" class="outline-3">
<h3 id="playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-blog-posts-via-exported-org-files">Indexing blog posts via exported Org files</h3>
<div class="outline-text-3" id="text-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-indexing-blog-posts-via-exported-org-files">
<p>
Then I figured I'd index my recent blog posts,
except for the ones that are mostly lists of
links, like Emacs News or my weekly/monthly/yearly
reviews. I write my posts in Org Mode before
exporting them with ox-11ty and converting them
with the 11ty static site generator. I'd
previously written some code to <a href="https://sachachua.com/dotemacs#moving-my-org-post-subtree-to-the-11ty-directory">automatically
export a copy of my Org draft</a> in case people
wanted to look at the source of a blog post, or in
case I wanted to tweak the post in the future.
(Handy for things like Org Babel.) This was
generally exported as an <code>index.org</code> file in the
post's directory. I can think of a few uses for a
list of these files, so I'll make a function for
it.
</p>

<p>

</p><div class="org-src-container">
<pre class="src src-emacs-lisp" id="org9d3ee5b"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-blog-org-files-except-reviews</span> (after-date)
  <span class="org-doc">"Return a list of recent .org files except for Emacs News and weekly/monthly/yearly reviews.</span>
<span class="org-doc">AFTER-DATE is in the form yyyy, yyyy-mm, or yyyy-mm-dd."</span>
  (<span class="org-keyword">setq</span> after-date (<span class="org-keyword">or</span> after-date <span class="org-string">"2020"</span>))
  (<span class="org-keyword">let</span> ((after-month (substring after-date 0 7))
        (posts (my-blog-posts)))
    (seq-keep
     (<span class="org-keyword">lambda</span> (filename)
       (<span class="org-keyword">when</span> (not (string-match <span class="org-string">"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-emacs-news"</span> filename))
         (<span class="org-keyword">when</span> (string-match <span class="org-string">"/blog/</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">[0-9]+</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">/</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">[0-9]+</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">/"</span> filename)
           (<span class="org-keyword">let</span> ((month (match-string 2 filename))
                 (year (match-string 1 filename)))
             (<span class="org-keyword">unless</span> (string&gt; after-month
                              (concat year <span class="org-string">"-"</span> month))
               (<span class="org-keyword">let</span> ((info (my-blog-post-info-for-url (replace-regexp-in-string <span class="org-string">"~/proj/static-blog</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">index\\.org$</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">\\.org$"</span> <span class="org-string">""</span> filename) posts)))
                 (<span class="org-keyword">let-alist</span> info

                   (<span class="org-keyword">when</span> (<span class="org-keyword">and</span>
                          info
                          (string&gt; .date after-date)
                          (not (seq-intersection .categories
                                                 <span class="org-highlight-quoted-quote">'</span>(<span class="org-string">"emacs-news"</span> <span class="org-string">"weekly"</span> <span class="org-string">"monthly"</span> <span class="org-string">"yearly"</span>)
                                                 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">string=</span>)))
                     filename))))))))
     (sort
      (directory-files-recursively <span class="org-string">"~/proj/static-blog/blog"</span> <span class="org-string">"\\.org$"</span>)
      <span class="org-builtin">:lessp</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">string&lt;</span>
      <span class="org-builtin">:reverse</span> t))))
</code></pre>
</div>


<p></p>

<p>
This is in the <a href="https://sachachua.com/dotemacs#org-mode-publishing-11ty-static-site-generation-listing-exported-org-posts">Listing exported Org posts</a> section of my config. I have a <a href="https://sachachua.com/dotemacs#org-mode-publishing-11ty-static-site-generation-linking-to-blog-posts-making-it-easier-to-add-a-category-to-a-blog-post">my-blog-post-info-for-url</a> function that helps me look up the categories. I get the data out of the <a href="https://sachachua.com/blog/all/index.json">JSON that has all of my blog posts in it</a>.
</p>

<p>
Then it's easy to index those files:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp"><code>(mapc <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">org-db-v3-index-file-async</span> (my-blog-org-files-except-reviews))
</code></pre>
</div>

</div>
</div>
<div id="outline-container-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-searching-my-blog-posts" class="outline-3">
<h3 id="playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-searching-my-blog-posts">Searching my blog posts</h3>
<div class="outline-text-3" id="text-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-searching-my-blog-posts">
<p>
Now that my files are indexed, I want to be able to turn up things that might be related to whatever I'm currently writing about.
This might help me <a href="https://sachachua.com//blog/2025/03/playing-with-chunk-size-when-writing/">build up thoughts</a> better, especially if a long time has passed in between posts.
</p>

<p>
<code>org-db-v3-semantic-search-ivy</code> didn't quite work for me out of the box, but I'd written an <a href="https://github.com/minad/consult">Consult</a>-based interface for <code>emacs-rag-search-vector</code> that was easy to adapt. This is how I put it together.
</p>

<p>
First I started by looking at <code>emacs-rag-search-vector</code>. That shows the full chunks, which feels a little unwieldy.
</p>


<figure id="org76e97cb">
<img src="https://sachachua.com/blog/2025/10/playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews/2025-10-09_10-05-58.png" alt="2025-10-09_10-05-58.png">

<figcaption><span class="figure-number">Figure 1: </span>Screenshot showing the chunks returned by a search for "semantic search"</figcaption>
</figure>

<p>
Instead, I wanted to see the years and titles of the
blog posts as a quick summary, with the ability to
page through them for a quick preview. consult.el
lets me define a custom completion command with
that behavior. <a href="https://sachachua.com/dotemacs#my-blog-similar-link">Here's the code</a>:
</p>

<p>

</p><div class="org-src-container">
<pre class="src src-emacs-lisp" id="orgad8fafe"><code>(<span class="org-keyword">defun</span> <span class="org-function-name">my-blog-similar-link</span> (link)
  <span class="org-doc">"Vector-search blog posts using `</span><span class="org-doc"><span class="org-constant">emacs-rag-search</span></span><span class="org-doc">' and insert a link.</span>
<span class="org-doc">If called with \\[universal-argument</span><span class="org-doc"><span class="org-warning">\</span></span><span class="org-doc">], use the current post's text.</span>
<span class="org-doc">If a region is selected, use that as the default QUERY.</span>
<span class="org-doc">HIDE-INITIAL means hide the initial query, which is handy if the query is very long."</span>
  (<span class="org-keyword">interactive</span> (list
                (<span class="org-keyword">if</span> embark&#45;&#45;command
                    (read-string <span class="org-string">"Link: "</span>)
                  (my-blog-similar
                   (<span class="org-keyword">cond</span>
                    (current-prefix-arg (my-11ty-post-text))
                    ((region-active-p)
                     (buffer-substring (region-beginning)
                                       (region-end))))
                   current-prefix-arg))))
  (my-embark-blog-insert-link link))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-embark-blog&#45;&#45;inject-target-url</span> (<span class="org-type">&amp;rest</span> args)
  <span class="org-doc">"Replace the completion text with the URL."</span>
  (delete-minibuffer-contents)
  (insert (my-blog-url (get-text-property 0 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult&#45;&#45;candidate</span> (plist-get args <span class="org-builtin">:target</span>)))))

(<span class="org-keyword">with-eval-after-load</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">embark</span>
  (add-to-list <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">embark-target-injection-hooks</span> <span class="org-highlight-quoted-quote">'</span>(my-blog-similar-link my-embark-blog&#45;&#45;inject-target-url)))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-11ty-interactive-context</span> (use-post)
  <span class="org-doc">"Returns (query hide-initial) for use in interactive arguments.</span>
<span class="org-doc">If USE-POST is non-nil, query is the current post text and hide-initial is t.</span>
<span class="org-doc">If the region is active, returns that as the query."</span>
  (list (<span class="org-keyword">cond</span>
         (embark&#45;&#45;command (read-string <span class="org-string">"Input: "</span>))
         (use-post (my-11ty-post-text))
         ((region-active-p)
          (buffer-substring (region-beginning)
                            (region-end))))
        use-post))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-blog-similar</span> (<span class="org-type">&amp;optional</span> query hide-initial)
  <span class="org-doc">"Vector-search blog posts using org-db-v3 and present results via Consult.</span>
<span class="org-doc">If called with \\[universal-argument</span><span class="org-doc"><span class="org-warning">\</span></span><span class="org-doc">], use the current post's text.</span>
<span class="org-doc">If a region is selected, use that as the default QUERY.</span>
<span class="org-doc">HIDE-INITIAL means hide the initial query, which is handy if the query is very long."</span>
  (<span class="org-keyword">interactive</span> (my-11ty-interactive-context current-prefix-arg))
  (consult&#45;&#45;read
   (<span class="org-keyword">if</span> hide-initial
       (my-org-db-v3-blog-post&#45;&#45;collection query)
     (consult&#45;&#45;dynamic-collection
         <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">my-org-db-v3-blog-post&#45;&#45;collection</span>
       <span class="org-builtin">:min-input</span> 3 <span class="org-builtin">:debounce</span> 1))
   <span class="org-builtin">:lookup</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">consult&#45;&#45;lookup-cdr</span>
   <span class="org-builtin">:prompt</span> <span class="org-string">"Search blog posts (approx): "</span>
   <span class="org-builtin">:category</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">my-blog</span>
   <span class="org-builtin">:sort</span> nil
   <span class="org-builtin">:require-match</span> t
   <span class="org-builtin">:state</span> (my-blog-post&#45;&#45;state)
   <span class="org-builtin">:initial</span> (<span class="org-keyword">unless</span> hide-initial query)))

(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-blog-semantic-search-source</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-db-v3</span>)
(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-db-v3-blog-post&#45;&#45;collection</span> (input)
  <span class="org-doc">"Perform the RAG search and format the results for Consult.</span>
<span class="org-doc">Returns a list of cons cells (DISPLAY-STRING . PLIST)."</span>
  (<span class="org-keyword">let</span> ((posts (my-blog-posts)))
    (mapcar (<span class="org-keyword">lambda</span> (o)
              (my-blog-format-for-completion
               (append o
                       (my-blog-post-info-for-url (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">source_path</span> o)
                                                  posts))))
            (seq-uniq
               (my-org-db-v3-to-emacs-rag-search input 100 <span class="org-string">"%static-blog%"</span>)
               (<span class="org-keyword">lambda</span> (a b) (string= (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">source_path</span> a)
                                      (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">source_path</span> b)))))))
</code></pre>
</div>


<p></p>

<p>
It uses some functions I defined in other parts of my config:
</p>
<ul class="org-ul">
<li><a href="https://sachachua.com/dotemacs#org-mode-publishing-11ty-static-site-generation-linking-to-blog-posts-making-it-easier-to-add-a-category-to-a-blog-post">Making it easier to add a category to a blog post</a>
<ul class="org-ul">
<li><code>my-embark-blog-insert-link</code></li>
<li><code>my-blog-format-for-completion</code></li>
</ul></li>
<li><a href="https://sachachua.com/dotemacs#mastodon-tooting-a-link-to-the-current-post">Tooting a link to the current post</a>
<ul class="org-ul">
<li><code>my-11ty-post-text</code></li>
</ul></li>
</ul>

<p>
When I explored <code>emacs-rag-search</code>, I also tried
hybrid search (vector + full text). At first, I
got "database disk image is malformed". I fixed
this by <a href="https://stackoverflow.com/questions/5274202/sqlite3-database-or-disk-is-full-the-database-disk-image-is-malformed">dumping the SQLite3 database</a>. Using hybrid
search, I tended to get less-relevant results
based on the repetition of common words, though,
so that might be something for future exploration.
Anyway, <code>my-emacs-rag-search</code> and
<code>my-emacs-rag-search-hybrid</code> are in <a href="https://sachachua.com/dotemacs#org-mode-vector-search-emacs-rag-search">the
emacs-rag-search part of my config</a> just in case.
</p>

<p>
Along the way, I contributed some notes to
<a href="https://github.com/minad/consult?tab=readme-ov-file#creating-simple-commands">consult.el's README.org</a> so that it'll be easier to
figure this stuff out in the future. In
particular, it took me a while to figure out how
to use <code>:lookup #'consult&#45;&#45;lookup-cdr</code> to get
richer information after selecting a completion
candidate, and also how to use
<code>consult&#45;&#45;dynamic-collection</code> to work with slower
dynamic sources.
</p>
</div>
</div>
<div id="outline-container-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-quick-thoughts-and-next-steps" class="outline-3">
<h3 id="playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-quick-thoughts-and-next-steps">Quick thoughts and next steps</h3>
<div class="outline-text-3" id="text-playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-post-org-files-with-previews-quick-thoughts-and-next-steps">
<p>
It <i>is</i> kinda nice being able to look up posts without using the exact words.
</p>

<p>
Now I can display a list of blog posts that are somewhat similar to what I'm currently working on. It should be pretty straightforward to filter the list to show only posts I haven't linked to yet.
</p>

<p>
I can probably get this to index the text versions of my sketches, too.
</p>

<p>
It might also be interesting to have a multi-source Consult command that starts off with fast sources (exact title or headline match) and then adds the slower sources (<a href="https://sachachua.com//blog/2025/07/finding-unlinked-text/">Google web search</a>, semantic blog post search via org-db-v3) as the results become available.
</p>

<p>
I'll save that for another post, though!</p>
</div>
</div>
<div><a href="https://sachachua.com/blog/2025/10/playing-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews/index.org">View org source for this post</a></div>
<p>You can <a href="https://social.sachachua.com/@sacha/statuses/01K8PPYM3GW5R5KE2X8KCA9GNV" target="_blank" rel="noopener noreferrer">comment on Mastodon</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2025%2F10%2Fplaying-around-with-org-db-v3-and-consult-vector-search-of-my-blog-posts-with-previews%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item>
	</channel>
</rss>