Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://www.peterbe.com/rss.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Peterbe.com</title><link>http://www.peterbe.com/rss.xml</link><description>Stuff in Peter's head</description><atom:link href="http://www.peterbe.com/rss.xml" rel="self"/><language>en-us</language><lastBuildDate>Fri, 29 Aug 2025 18:37:45 +0000</lastBuildDate><item><title>gg commit with suggested --no-verify</title><link>http://www.peterbe.com/plog/gg-commit-with-suggested-no-verify</link><description>&lt;p&gt;In version &lt;a href="https://github.com/peterbe/gg2/releases/tag/v0.0.11"&gt;0.0.11&lt;/a&gt; of &lt;a href="https://github.com/peterbe/gg2"&gt;gg&lt;/a&gt; you can now type &lt;code&gt;gg commit Bla bla&lt;/code&gt; and it will try to commit but if that fails, it will ask you one more time, if you want to re-attempt to commit but with &lt;code&gt;--no-verify&lt;/code&gt;.&lt;/p&gt;
  3. &lt;p&gt;For example:&lt;/p&gt;
  4. &lt;pre&gt;&lt;code class="hljs"&gt;
  5. $ gg commit This is my commit message
  6. ❌ file has formatting problems
  7. Commit failed and you did not use --no-verify.
  8. ? Try again but with --no-verify? (y/N)
  9. &lt;/code&gt;&lt;/pre&gt;
  10.  
  11. &lt;p&gt;(&lt;em&gt;see screenshots below where color makes these things more intuitive&lt;/em&gt;)&lt;/p&gt;
  12. &lt;p&gt;This is handy when you know that the &lt;code&gt;.git/hooks/pre-commit&lt;/code&gt; might be failing for a reason that is actually not a problem &lt;em&gt;after&lt;/em&gt; you've committed.&lt;/p&gt;
  13. &lt;p&gt;In a sample repo I have:&lt;/p&gt;
  14. &lt;pre&gt;&lt;code class="hljs"&gt;
  15. $ &lt;span class="hljs-built_in"&gt;cat&lt;/span&gt; .git/hooks/pre-commit
  16. &lt;span class="hljs-comment"&gt;#!/bin/sh&lt;/span&gt;
  17.  
  18. &lt;span class="hljs-built_in"&gt;echo&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;❌ Rejecting all commits like an angry troll&amp;quot;&lt;/span&gt;
  19. &lt;span class="hljs-built_in"&gt;exit&lt;/span&gt; 1
  20. &lt;/code&gt;&lt;/pre&gt;
  21.  
  22. &lt;p&gt;And when I use &lt;code&gt;gg commit ...&lt;/code&gt; this happens:&lt;/p&gt;
  23. &lt;p&gt;&lt;a href="/cache/5a/45/5a45eeb1b7d7baebdc9d07e6eb652fc8.png"&gt;&lt;img src="/cache/ff/ff/ffff529524b78fb3a84267679f8519ee.png" alt="Prompt"  width="370" height="90"&gt;&lt;/a&gt;&lt;/p&gt;
  24. &lt;p&gt;&lt;a href="/cache/67/10/671041199ffb99018237b945e483c761.png"&gt;&lt;img src="/cache/87/0e/870e49b633d6cddde804121623bd08e1.png" alt="Said y for Yes"  width="370" height="147"&gt;&lt;/a&gt;&lt;/p&gt;</description><pubDate>Fri, 29 Aug 2025 18:37:45 +0000</pubDate><guid>http://www.peterbe.com/plog/gg-commit-with-suggested-no-verify</guid></item><item><title>Faster way to sum an integer series in Python</title><link>http://www.peterbe.com/plog/faster-way-to-sum-an-integer-series-in-python</link><description>You can sum a simple series with `n(n+1)/2`</description><pubDate>Thu, 28 Aug 2025 12:11:22 +0000</pubDate><guid>http://www.peterbe.com/plog/faster-way-to-sum-an-integer-series-in-python</guid></item><item><title>Find GitHub Pull Request by commit SHA</title><link>http://www.peterbe.com/plog/find-github-pull-request-by-commit-sha</link><description>You can find the PR by searching for any of its SHA commits</description><pubDate>Thu, 21 Aug 2025 11:38:23 +0000</pubDate><guid>http://www.peterbe.com/plog/find-github-pull-request-by-commit-sha</guid></item><item><title>Always run biome migrate after upgrading biome</title><link>http://www.peterbe.com/plog/always-run-biome-migrate-after-upgrading-biome</link><description>Use postinstall in your package.json to automatically keep your biome.json up to date when @biomejs/biome is upgraded</description><pubDate>Sat, 16 Aug 2025 16:08:34 +0000</pubDate><guid>http://www.peterbe.com/plog/always-run-biome-migrate-after-upgrading-biome</guid></item><item><title>gg shell completion</title><link>http://www.peterbe.com/plog/gg-shell-completion</link><description>gg now support shell completions in Bash and Zsh</description><pubDate>Wed, 13 Aug 2025 12:44:06 +0000</pubDate><guid>http://www.peterbe.com/plog/gg-shell-completion</guid></item><item><title>Combining Django signals with in-memory LRU cache</title><link>http://www.peterbe.com/plog/combining-django-signals-with-in-memory-lru-cache</link><description>It's easy to combine functools.lru_cache with Django signals to get a good memoization pattern on Django ORM queries.</description><pubDate>Sat, 09 Aug 2025 18:33:43 +0000</pubDate><guid>http://www.peterbe.com/plog/combining-django-signals-with-in-memory-lru-cache</guid></item><item><title>gg2 - a new CLI for helping me manage git branches</title><link>http://www.peterbe.com/plog/gg2-initial</link><description>gg2 is an executable CLI to help with tedious git commands for power users.</description><pubDate>Wed, 06 Aug 2025 08:55:30 +0000</pubDate><guid>http://www.peterbe.com/plog/gg2-initial</guid></item><item><title>Bot traffic hitting my blog</title><link>http://www.peterbe.com/plog/bot-traffic-hitting-my-blog</link><description>&lt;p&gt;I have a simple blog. It dates back years. Most things are about technology but I also have a popular &lt;a href="/plog/blogitem-040601-1"&gt;blog post about finding song by lyrics&lt;/a&gt; which gets the lion share of the traffic.&lt;/p&gt;
  25. &lt;p&gt;I have implemented my own analytics of incoming traffic:&lt;/p&gt;
  26. &lt;ol&gt;
  27. &lt;li&gt;Every request that comes to the backend server gets logged in PostgreSQL  &lt;/li&gt;
  28. &lt;li&gt;When you view any page, an async XHR request is made and that's also logged in PostgreSQL&lt;/li&gt;
  29. &lt;/ol&gt;
  30. &lt;p&gt;Most traffic terminates at the CDN. Most likely, when you're reading this page right now it never renders on my server but is served straight from the CDN, but it will send an XHR request to my analytics backend, which in a sense becomes a measure that you're in a real regular browser that supports JavaScript.&lt;/p&gt;
  31. &lt;p&gt;One thing I noticed is that the request &lt;code&gt;User-Agent&lt;/code&gt; of the incoming requests that come in, appear to be some sort of bot that is &lt;em&gt;not&lt;/em&gt; Googlebot, which &lt;em&gt;used&lt;/em&gt; to dominate the traffic on my blog.&lt;/p&gt;
  32. &lt;p&gt;&lt;a href="/cache/ed/5a/ed5ad8259eedf0b173d5e63f82152b77.png"&gt;&lt;img src="/cache/ed/5a/ed5ad8259eedf0b173d5e63f82152b77.png" alt="Bot Agent Requests" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
  33. &lt;p&gt;Notables:&lt;/p&gt;
  34. &lt;ul&gt;
  35. &lt;li&gt;Claude's bot makes a ton of traffic!&lt;/li&gt;
  36. &lt;li&gt;OpenAI appears to have two bots ("gptbot" and "searchbot") and it's large&lt;/li&gt;
  37. &lt;li&gt;What on earth is that Facebook crawler doing? Is it crawling for training Meta's LLMs?&lt;/li&gt;
  38. &lt;li&gt;What is this Amazonbot and why is it making as much traffic as Googlebot?&lt;/li&gt;
  39. &lt;/ul&gt;
  40. &lt;h3 id="javascript-or-not"&gt;&lt;a class="toclink" href="#javascript-or-not"&gt;JavaScript or not&lt;/a&gt;&lt;/h3&gt;
  41. &lt;p&gt;At the time of writing this, I had only recently started tracking the &lt;code&gt;User-Agent&lt;/code&gt; of pageviews so I can't compare historical numbers. But generally it seems only ~1% of pageviews is by a bot user agent, whereas direct server-side traffic to the server, ~66% is from a bot agent.&lt;/p&gt;
  42. &lt;p&gt;&lt;a href="/cache/84/88/8488c0c71cb1ee540c6497984c243012.png"&gt;&lt;img src="/cache/84/88/8488c0c71cb1ee540c6497984c243012.png" alt="Is bot in pageviews vs requests" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
  43. &lt;p&gt;That means that a lot of the bots don't render the page with JavaScript. Or rather, perhaps they do but they have some provision in there so as to not trigger XHR requests to my analytics (which is implemented with &lt;code&gt;sendBeacon&lt;/code&gt;).&lt;/p&gt;
  44. &lt;p&gt;The reason for the "-16.5%" drop was because I recently implemented a fix to redirect traffic that bypassed the CDN and went straight to the backend.&lt;/p&gt;</description><pubDate>Wed, 09 Jul 2025 13:07:48 +0000</pubDate><guid>http://www.peterbe.com/plog/bot-traffic-hitting-my-blog</guid></item><item><title>Native connection pooling in Django 5 with PostgreSQL</title><link>http://www.peterbe.com/plog/native-connection-pooling-django-5-pg</link><description>Enabling native connection pooling in Django 5 gives me a 5.4x speedup.</description><pubDate>Wed, 25 Jun 2025 21:36:32 +0000</pubDate><guid>http://www.peterbe.com/plog/native-connection-pooling-django-5-pg</guid></item><item><title>Video to screenshots app</title><link>http://www.peterbe.com/plog/video-to-screenshots-app</link><description>&lt;p&gt;I made a web app that helps you extract screenshots from a video file. You technically don't "upload" it but you select a video file from your computer into the web app, and the screenshots are generated more or less instantly.&lt;/p&gt;
  45. &lt;p&gt;&lt;a href="https://video-to-screenshots.peterbe.com"&gt;&lt;img src="/cache/e7/26/e726d4f194cdb37448442ce9188af62d.png" alt="With drop shadow"  width="370" height="257"&gt;&lt;/a&gt;&lt;/p&gt;
  46. &lt;h3 id="canvas-web-api"&gt;&lt;a class="toclink" href="#canvas-web-api"&gt;Canvas Web API&lt;/a&gt;&lt;/h3&gt;
  47. &lt;p&gt;Why did I make this app? Because I wanted to experiment with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API"&gt;Canvas (Web) API&lt;/a&gt; and how it can be combined with a &lt;code&gt;video&lt;/code&gt; element. I originally typed into some AI prompt and got most of the code from that, but I felt like I didn't understand it. And to be able to quickly iterate and play with it, I ended up making a simple web app so that I can tune it.&lt;/p&gt;
  48. &lt;h3 id="how-it-works"&gt;&lt;a class="toclink" href="#how-it-works"&gt;How it works&lt;/a&gt;&lt;/h3&gt;
  49. &lt;p&gt;The gist of the code is this:&lt;/p&gt;
  50. &lt;pre&gt;&lt;code class="hljs"&gt;
  51. &lt;span class="hljs-keyword"&gt;export&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;createVideoThumbnail&lt;/span&gt;(&lt;span class="hljs-params"&gt;
  52.  &lt;span class="hljs-attr"&gt;videoFile&lt;/span&gt;: &lt;span class="hljs-title class_"&gt;File&lt;/span&gt;,
  53.  &lt;span class="hljs-attr"&gt;options&lt;/span&gt;: &lt;span class="hljs-title class_"&gt;Options&lt;/span&gt;,
  54. &lt;/span&gt;): &lt;span class="hljs-title class_"&gt;Promise&lt;/span&gt;&amp;lt;&lt;span class="hljs-built_in"&gt;string&lt;/span&gt;&amp;gt; {
  55.  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; { quality = &lt;span class="hljs-number"&gt;1.0&lt;/span&gt;, captureTime = &lt;span class="hljs-number"&gt;0.1&lt;/span&gt;, format = &lt;span class="hljs-string"&gt;&amp;quot;image/jpeg&amp;quot;&lt;/span&gt; } = options
  56.  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Promise&lt;/span&gt;(&lt;span class="hljs-function"&gt;(&lt;span class="hljs-params"&gt;resolve, reject&lt;/span&gt;) =&amp;gt;&lt;/span&gt; {
  57.    &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; video = &lt;span class="hljs-variable language_"&gt;document&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;createElement&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;video&amp;quot;&lt;/span&gt;)
  58.    &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; canvas = &lt;span class="hljs-variable language_"&gt;document&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;createElement&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;canvas&amp;quot;&lt;/span&gt;)
  59.    &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; ctx = canvas.&lt;span class="hljs-title function_"&gt;getContext&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;2d&amp;quot;&lt;/span&gt;)
  60.  
  61.    &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!ctx) {
  62.      &lt;span class="hljs-title function_"&gt;reject&lt;/span&gt;(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;Failed to get canvas context&amp;quot;&lt;/span&gt;))
  63.      &lt;span class="hljs-keyword"&gt;return&lt;/span&gt;
  64.    }
  65.  
  66.    video.&lt;span class="hljs-property"&gt;preload&lt;/span&gt; = &lt;span class="hljs-string"&gt;&amp;quot;metadata&amp;quot;&lt;/span&gt;
  67.    video.&lt;span class="hljs-property"&gt;muted&lt;/span&gt; = &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  68.    video.&lt;span class="hljs-property"&gt;playsInline&lt;/span&gt; = &lt;span class="hljs-literal"&gt;true&lt;/span&gt;
  69.  
  70.    &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; videoUrl = &lt;span class="hljs-variable constant_"&gt;URL&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;createObjectURL&lt;/span&gt;(videoFile)
  71.    video.&lt;span class="hljs-property"&gt;src&lt;/span&gt; = videoUrl
  72.  
  73.    video.&lt;span class="hljs-property"&gt;onloadedmetadata&lt;/span&gt; = &lt;span class="hljs-function"&gt;() =&amp;gt;&lt;/span&gt; {
  74.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; width = video.&lt;span class="hljs-property"&gt;videoWidth&lt;/span&gt;
  75.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; height = video.&lt;span class="hljs-property"&gt;videoHeight&lt;/span&gt;
  76.      canvas.&lt;span class="hljs-property"&gt;width&lt;/span&gt; = width
  77.      canvas.&lt;span class="hljs-property"&gt;height&lt;/span&gt; = height
  78.      video.&lt;span class="hljs-property"&gt;currentTime&lt;/span&gt; = captureTime
  79.    }
  80.  
  81.    video.&lt;span class="hljs-property"&gt;onseeked&lt;/span&gt; = &lt;span class="hljs-function"&gt;() =&amp;gt;&lt;/span&gt; {
  82.      ctx.&lt;span class="hljs-title function_"&gt;drawImage&lt;/span&gt;(video, &lt;span class="hljs-number"&gt;0&lt;/span&gt;, &lt;span class="hljs-number"&gt;0&lt;/span&gt;, canvas.&lt;span class="hljs-property"&gt;width&lt;/span&gt;, canvas.&lt;span class="hljs-property"&gt;height&lt;/span&gt;)
  83.      &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; dataURI = canvas.&lt;span class="hljs-title function_"&gt;toDataURL&lt;/span&gt;(format, quality)
  84.      &lt;span class="hljs-variable constant_"&gt;URL&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;revokeObjectURL&lt;/span&gt;(videoUrl)
  85.      &lt;span class="hljs-title function_"&gt;resolve&lt;/span&gt;(dataURI)
  86.    }
  87.  
  88.    video.&lt;span class="hljs-property"&gt;onerror&lt;/span&gt; = &lt;span class="hljs-function"&gt;() =&amp;gt;&lt;/span&gt; {
  89.      &lt;span class="hljs-variable constant_"&gt;URL&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;revokeObjectURL&lt;/span&gt;(videoUrl)
  90.      &lt;span class="hljs-title function_"&gt;reject&lt;/span&gt;(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(&lt;span class="hljs-string"&gt;&amp;quot;Error loading video file&amp;quot;&lt;/span&gt;))
  91.    }
  92.  
  93.    video.&lt;span class="hljs-title function_"&gt;load&lt;/span&gt;()
  94.  })
  95. }
  96. &lt;/code&gt;&lt;/pre&gt;
  97.  
  98. &lt;p&gt;See &lt;a href="https://github.com/peterbe/video-to-screenshots/blob/main/src/create-video-thumbnail.ts"&gt;&lt;code&gt;src/create-video-thumbnail.ts&lt;/code&gt;&lt;/a&gt; on GitHub.&lt;/p&gt;
  99. &lt;h3 id="the-project"&gt;&lt;a class="toclink" href="#the-project"&gt;The project&lt;/a&gt;&lt;/h3&gt;
  100. &lt;p&gt;The code for it is here: &lt;a href="https://github.com/peterbe/video-to-screenshots"&gt;https://github.com/peterbe/video-to-screenshots&lt;/a&gt;&lt;/p&gt;
  101. &lt;p&gt;The web app is hosted on Firebase Hosting. The foundation of the code is React with &lt;code&gt;react-router&lt;/code&gt;, and uses &lt;a href="https://picocss.com/"&gt;pico css&lt;/a&gt;. I use Bun and Vite to run and build the app.&lt;/p&gt;
  102. &lt;h3 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion?&lt;/a&gt;&lt;/h3&gt;
  103. &lt;p&gt;It works and it's basic. You can download the JPEGs. It's not very pretty. It's a weekend project and it accomplishes something.&lt;/p&gt;
  104. &lt;p&gt;But it doesn't work in Safari. Anybody got any ideas?&lt;/p&gt;
  105. &lt;p&gt;Perhaps it would be cool to allow the user to see many many more thumbnails and allow them to specify more exactly which capture times to make screenshots out of. What do you think?&lt;/p&gt;</description><pubDate>Sat, 21 Jun 2025 22:43:46 +0000</pubDate><guid>http://www.peterbe.com/plog/video-to-screenshots-app</guid></item></channel></rss>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//www.peterbe.com/rss.xml

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda