<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: photos</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/photos.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-05-04T17:09:08+00:00</updated><author><name>Simon Willison</name></author><entry><title>Breakwater Barbecue in the El Granada station for the Ocean Shore Railroad</title><link href="https://simonwillison.net/2025/May/4/breakwater/#atom-tag" rel="alternate"/><published>2025-05-04T17:09:08+00:00</published><updated>2025-05-04T17:09:08+00:00</updated><id>https://simonwillison.net/2025/May/4/breakwater/#atom-tag</id><summary type="html">
    &lt;p&gt;Our local BBQ spot here in El Granada - &lt;a href="https://www.breakwaterbbq.com/"&gt;Breakwater Barbecue&lt;/a&gt; - had a soft opening this weekend in their &lt;a href="https://maps.app.goo.gl/f9JSpUWaFH8Hevj3A"&gt;new location&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's the new building. They're still working on replacing the sign from the previous restaurant occupant:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Exterior photo of a restaurant with a faded sign reading &amp;quot;MONSTER CHEF Fine Japanese Restaurant&amp;quot; the building is cream-colored with red tile roofs and large windows. It has a little bit of a railway station vibe to it if you squint at it just the right way." src="https://static.simonwillison.net/static/2025/breakwater-today.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;It's actually our old railway station! From 1905 to 1920 the &lt;a href="https://en.wikipedia.org/wiki/Ocean_Shore_Railroad"&gt;Ocean Shore Railroad&lt;/a&gt; ran steam trains from San Francisco down through Half Moon Bay most of the way to Santa Cruz, though they never quite connected the two cities.&lt;/p&gt;
&lt;p&gt;The restaurant has some photos on the wall of the old railroad. Here's what that same building looked like &amp;gt;100 years ago.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Historical black and white photograph showing a train station with a steam train on the left and a Spanish-style station building with arched entrances on the right. It's clearly the same building, though the modern one has had a bunch of extra extensions added to it and doesn't look nearly as much like a train station." src="https://static.simonwillison.net/static/2025/breakwater-train.jpg" /&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/history"&gt;history&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/half-moon-bay"&gt;half-moon-bay&lt;/a&gt;&lt;/p&gt;



</summary><category term="history"/><category term="photos"/><category term="half-moon-bay"/></entry><entry><title>Using SQL to find my best photo of a pelican according to Apple Photos</title><link href="https://simonwillison.net/2020/May/21/dogsheep-photos/#atom-tag" rel="alternate"/><published>2020-05-21T19:16:38+00:00</published><updated>2020-05-21T19:16:38+00:00</updated><id>https://simonwillison.net/2020/May/21/dogsheep-photos/#atom-tag</id><summary type="html">
    &lt;p&gt;According to the Apple Photos internal SQLite database, this is the most aesthetically pleasing photograph I have ever taken of a pelican:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://photos.simonwillison.net/i/cbfe463f1a67e37a1d36c5db44f0159ef6f86a0d64a987b129b63b52e555f1af.jpeg?w=800" alt="A pelican" style="max-width: 100%" /&gt;&lt;/p&gt;

&lt;p&gt;Here's the SQL query that found me my best ten pelican photos:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;select
  sha256,
  ext,
  uuid,
  date,
  ZOVERALLAESTHETICSCORE
from
  photos_with_apple_metadata
where
  uuid in (
    select
      uuid
    from
      labels
    where
      normalized_string = 'pelican'
  )
order by
  ZOVERALLAESTHETICSCORE desc
limit
  10&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can &lt;a href="https://dogsheep-photos.dogsheep.net/public?sql=select%0D%0A++json_object%28%0D%0A++++%27img_src%27%2C%0D%0A++++%27https%3A%2F%2Fphotos.simonwillison.net%2Fi%2F%27+%7C%7C+sha256+%7C%7C+%27.%27+%7C%7C+ext+%7C%7C+%27%3Fw%3D600%27%0D%0A++%29+as+photo%2C%0D%0A++sha256%2C%0D%0A++ext%2C%0D%0A++uuid%2C%0D%0A++date%2C%0D%0A++ZOVERALLAESTHETICSCORE%0D%0Afrom%0D%0A++photos_with_apple_metadata%0D%0Awhere%0D%0A++uuid+in+%28%0D%0A++++select%0D%0A++++++uuid%0D%0A++++from%0D%0A++++++labels%0D%0A++++where%0D%0A++++++normalized_string+%3D+%3Alabel%0D%0A++%29%0D%0Aorder+by%0D%0A++ZOVERALLAESTHETICSCORE+desc%0D%0Alimit%0D%0A++10&amp;amp;label=pelican"&gt;try it out here&lt;/a&gt; (with some extra &lt;a href="https://github.com/simonw/datasette-json-html/blob/master/README.md#images"&gt;datasette-json-html&lt;/a&gt; magic to display the actual photos). Or try &lt;a href="https://dogsheep-photos.dogsheep.net/public?sql=select%0D%0A++json_object%28%0D%0A++++%27img_src%27%2C%0D%0A++++%27https%3A%2F%2Fphotos.simonwillison.net%2Fi%2F%27+%7C%7C+sha256+%7C%7C+%27.%27+%7C%7C+ext+%7C%7C+%27%3Fw%3D600%27%0D%0A++%29+as+photo%2C%0D%0A++sha256%2C%0D%0A++ext%2C%0D%0A++uuid%2C%0D%0A++date%2C%0D%0A++ZOVERALLAESTHETICSCORE%0D%0Afrom%0D%0A++photos_with_apple_metadata%0D%0Awhere%0D%0A++uuid+in+%28%0D%0A++++select%0D%0A++++++uuid%0D%0A++++from%0D%0A++++++labels%0D%0A++++where%0D%0A++++++normalized_string+%3D+%3Alabel%0D%0A++%29%0D%0Aorder+by%0D%0A++ZOVERALLAESTHETICSCORE+desc%0D%0Alimit%0D%0A++10&amp;amp;label=lemur"&gt;lemur&lt;/a&gt; or &lt;a href="https://dogsheep-photos.dogsheep.net/public?sql=select%0D%0A++json_object%28%0D%0A++++%27img_src%27%2C%0D%0A++++%27https%3A%2F%2Fphotos.simonwillison.net%2Fi%2F%27+%7C%7C+sha256+%7C%7C+%27.%27+%7C%7C+ext+%7C%7C+%27%3Fw%3D600%27%0D%0A++%29+as+photo%2C%0D%0A++sha256%2C%0D%0A++ext%2C%0D%0A++uuid%2C%0D%0A++date%2C%0D%0A++ZOVERALLAESTHETICSCORE%0D%0Afrom%0D%0A++photos_with_apple_metadata%0D%0Awhere%0D%0A++uuid+in+%28%0D%0A++++select%0D%0A++++++uuid%0D%0A++++from%0D%0A++++++labels%0D%0A++++where%0D%0A++++++normalized_string+%3D+%3Alabel%0D%0A++%29%0D%0Aorder+by%0D%0A++ZOVERALLAESTHETICSCORE+desc%0D%0Alimit%0D%0A++10&amp;amp;label=seal"&gt;seal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I actually think this is my best pelican photo, but Apple Photos rated it fifth:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://photos.simonwillison.net/i/a444857c4ac71ceae6af5192c8acc5ac35934ed589259136df0ed11295dbb085.jpeg?w=800" alt="A pelican" style="max-width: 100%" /&gt;&lt;/p&gt;

&lt;h3&gt;How this works&lt;/h3&gt;

&lt;p&gt;Apple Photos keeps photo metadata in a SQLite database. It runs machine learning models to identify the contents of every photo, and separate machine learning models to calculate quality scores for those photographs. All of this data lives in SQLite files on my laptop. The trick is knowing where to look.&lt;/p&gt;

&lt;p&gt;I'm not running queries directly against the Apple Photos SQLite file - it's a little hard to work with, and the label metadata is stored in a separate database file. Instead, this query runs against a combined database created by my new &lt;a href="https://github.com/dogsheep/dogsheep-photos"&gt;dogsheep-photos&lt;/a&gt; tool.&lt;/p&gt;

&lt;h3&gt;An aside: Why I love Apple Photos&lt;/h3&gt;

&lt;p&gt;The Apple Photos app - on both macOS and iOS - is in my opinion Apple's most underappreciated piece of software. In my experience most people who use it are missing some of the most valuable features. A few highlights:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;It can show you ALL of your photos on a map. On iOS go to the "Albums" tab, scroll half way down and then click on "Places" (no wonder people miss this feature!) - on macOS Photos it's the "Library -&amp;gt; Places" sidebar item.  It still baffles me that Google Photos doesn't do this (I have &lt;a href="https://twitter.com/simonw/status/1227060020694503425"&gt;conspiracy theories&lt;/a&gt; about it). This is my most common way for finding a photo I've taken - I remember where it was, then zoom in on that area of the map.&lt;/li&gt;&lt;li&gt;It runs machine learning models &lt;em&gt;on your phone&lt;/em&gt; (or laptop) to identify the subject of your photos, and makes them searchable. Try searching for "dog" and you'll see all of the photos you've taken of dogs! I love that this runs on-device: it's much less creepy than uploading your photos to the cloud in order to do this.&lt;/li&gt;&lt;li&gt;It has a really great faceted search implementation - particularly in the phone app. Try searching for "dog", then add "selfie" and the name of a city to see all of the selfies you've taken with dogs in that place!&lt;/li&gt;&lt;li&gt;It has facial recognition, again running on device, which you can use to teach it who your friends are (autocompleting against your contacts). A little bit of effort spent training this and you can see photos you've taken of specific friends in specific places and with specific animals!&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;As with most Apple software, Photos uses SQLite under the hood. The underlying database is undocumented and clearly not intended as a public API, but it exists. And I've wanted to gain access to what's in it for years.&lt;/p&gt;

&lt;h3&gt;Querying the Apple Photos SQLite database&lt;/h3&gt;

&lt;p&gt;If you run Apple Photos on a Mac (which will synchronize with your phone via iCloud) then most of your photo metadata can be found in a database file that lives here:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mine is 752MB, for aroud 40,000 photos. There's a lot of detailed metadata in there!&lt;/p&gt;

&lt;p&gt;Querying the database isn't straight-forward. Firstly it's almost always locked by some other process - the workaround for that is to create a copy of the file. Secondly, it uses some custom undocumented Apple SQLite extensions. I've not figured out a way to load these, and without them a lot of my queries ended up throwing errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/RhetTbull/osxphotos"&gt;osxphotos&lt;/a&gt; to the rescue! I ran a GitHub code search for one of the tables in that database (searching for &lt;a href="https://github.com/search?l=Python&amp;amp;q=RKPerson&amp;amp;type=Code"&gt;RKPerson in Python code&lt;/a&gt;) and was delighted to stumble across the &lt;code&gt;osxphotos&lt;/code&gt; project by Rhet Turnbull. It's a well designed and extremely actively maintained Python tool for accessing the Apple Photos database, including code to handle several iterations of the underlying database structure.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;code&gt;osxphotos&lt;/code&gt; the first iteration of my own code for accessing the Apple Photos metadata was &lt;a href="https://github.com/dogsheep/dogsheep-photos/commit/b3c20e08b1a99c8898f13cc0266e1c5c012cf23c"&gt;less than 100 lines of code&lt;/a&gt;. This gave me locations, people, albums and places (human names of geographical areas) almost for free!&lt;/p&gt;

&lt;h3&gt;Quality scores&lt;/h3&gt;

&lt;p&gt;Apple Photos has a fascinating database table called &lt;code&gt;ZCOMPUTEDASSETATTRIBUTES&lt;/code&gt;, with a bewildering collection of columns. Each one is a floating point number calculated presumably by some kind of machine learning model. Here's a full list, each one linking to my public photos sorted by that score:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZBEHAVIORALSCORE"&gt;ZBEHAVIORALSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZFAILURESCORE"&gt;ZFAILURESCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZHARMONIOUSCOLORSCORE"&gt;ZHARMONIOUSCOLORSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZIMMERSIVENESSSCORE"&gt;ZIMMERSIVENESSSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZINTERACTIONSCORE"&gt;ZINTERACTIONSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZINTERESTINGSUBJECTSCORE"&gt;ZINTERESTINGSUBJECTSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZINTRUSIVEOBJECTPRESENCESCORE"&gt;ZINTRUSIVEOBJECTPRESENCESCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZLIVELYCOLORSCORE"&gt;ZLIVELYCOLORSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZLOWLIGHT"&gt;ZLOWLIGHT&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZNOISESCORE"&gt;ZNOISESCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTCAMERATILTSCORE"&gt;ZPLEASANTCAMERATILTSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTCOMPOSITIONSCORE"&gt;ZPLEASANTCOMPOSITIONSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTLIGHTINGSCORE"&gt;ZPLEASANTLIGHTINGSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTPATTERNSCORE"&gt;ZPLEASANTPATTERNSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTPERSPECTIVESCORE"&gt;ZPLEASANTPERSPECTIVESCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTPOSTPROCESSINGSCORE"&gt;ZPLEASANTPOSTPROCESSINGSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTREFLECTIONSSCORE"&gt;ZPLEASANTREFLECTIONSSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPLEASANTSYMMETRYSCORE"&gt;ZPLEASANTSYMMETRYSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZSHARPLYFOCUSEDSUBJECTSCORE"&gt;ZSHARPLYFOCUSEDSUBJECTSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZTASTEFULLYBLURREDSCORE"&gt;ZTASTEFULLYBLURREDSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZWELLCHOSENSUBJECTSCORE"&gt;ZWELLCHOSENSUBJECTSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZWELLFRAMEDSUBJECTSCORE"&gt;ZWELLFRAMEDSUBJECTSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZWELLTIMEDSHOTSCORE"&gt;ZWELLTIMEDSHOTSCORE&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;I'm not enormously impressed with the results I get from these. They're clearly not intended for end-user visibility, and sorting them might not even be something that makes sense.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ZGENERICASSET&lt;/code&gt; table provides four more scores, which seem to provide much more useful results:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZOVERALLAESTHETICSCORE"&gt;ZOVERALLAESTHETICSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZCURATIONSCORE"&gt;ZCURATIONSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZPROMOTIONSCORE"&gt;ZPROMOTIONSCORE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_sort_desc=ZHIGHLIGHTVISIBILITYSCORE"&gt;ZHIGHLIGHTVISIBILITYSCORE&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;My guess is that these overall scores are derived from the &lt;code&gt;ZCOMPUTEDASSETATTRIBUTES&lt;/code&gt; ones. I've seen the best results from &lt;code&gt;ZOVERALLAESTHETICSCORE&lt;/code&gt;, so that's the one I used in my "show me my best photo of a pelican" query.&lt;/p&gt;

&lt;h3&gt;A note about the demo&lt;/h3&gt;

&lt;p&gt;The demo I'm running at &lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata"&gt;dogsheep-photos.dogsheep.net&lt;/a&gt; currently only contains 496 photos. My private instance of this has over 40,000, but I decided to just publish a subset of that in the demo so I wouldn't have to carefully filter out private screenshots and photos with sensitive locations and suchlike. Details of how the demo work (using the &lt;code&gt;dogsheep-photos create-subset&lt;/code&gt; command to create a subset database containing just photos in my Public album) can be found &lt;a href="https://github.com/dogsheep/dogsheep-photos/issues/25"&gt;in this issue&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Automatic labeling of photo contents&lt;/h3&gt;

&lt;p&gt;Even more impressive than the quality scores are the machine learning labels.&lt;/p&gt;

&lt;p&gt;Automatically labeling the content of a photo is surprisingly easy these days, thanks to &lt;a href="https://en.wikipedia.org/wiki/Convolutional_neural_network"&gt;convolutional neural networks&lt;/a&gt;. I wrote a bit about these in &lt;a href="https://simonwillison.net/2018/Oct/29/transfer-learning/"&gt;Automatically playing science communication games with transfer learning and fastai&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Apple download a machine learning model to your device and do the label classification there. After quite a bit of hunting (I ended up using Activity Monitor's Inspect -&amp;gt; Open Files and Ports option against the &lt;code&gt;photoanalysisd&lt;/code&gt; process) I finally figured out where the results go: the &lt;code&gt;~/Pictures/Photos\ Library.photoslibrary/database/search/psi.sqlite&lt;/code&gt; database file.&lt;/p&gt;

&lt;p&gt;(Inspecting &lt;code&gt;photoanalysisd&lt;/code&gt; also lead me to the &lt;code&gt;/System/Library/Frameworks/Vision.framework/Versions/A/Resources/&lt;/code&gt; folder, which solved another mystery: where do Apple keep the models? There are &lt;a href="https://gist.github.com/simonw/6ce25981931e3c99f51f2ff0c8bcb0b1"&gt;some fascinating files&lt;/a&gt; in there.)&lt;/p&gt;

&lt;p&gt;It took &lt;a href="https://github.com/dogsheep/dogsheep-photos/issues/16"&gt;some work&lt;/a&gt; to figure out how to match those labels with their corresponding photos, mainly because the &lt;code&gt;psi.sqlite&lt;/code&gt; database stores photo UUIDs as a pair of signed integers whereas the &lt;code&gt;Photos.sqlite&lt;/code&gt; database stores a UUID string.&lt;/p&gt;

&lt;p&gt;I'm now pulling the labels out into a separate &lt;code&gt;labels&lt;/code&gt; table. You can &lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category"&gt;browse that in the demo&lt;/a&gt; to see how it is structured. Labels belong to numeric categories - here are some of my guesses as to what those mean:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=2024"&gt;Category 2024&lt;/a&gt; appears to be actual content labels - Seal, Water Body, Pelican etc.&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=2027"&gt;Category 2027&lt;/a&gt; is more contextual: Entertainment, Trip, Travel, Museum, Beach Activity etc.&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=1014"&gt;Category 1014&lt;/a&gt; is simply the month the photo was taken. &lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=1015"&gt;1015&lt;/a&gt; is the year, and &lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=2030"&gt;2030&lt;/a&gt; is the season.&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=2056"&gt;Category 2056&lt;/a&gt; is the original filename.&lt;/li&gt;&lt;li&gt;&lt;a href="https://dogsheep-photos.dogsheep.net/public/labels?_facet=category&amp;amp;category=12"&gt;Category 12&lt;/a&gt; is the country the photo was taken in.&lt;/li&gt;&lt;/ul&gt;

Here's &lt;a href="https://dogsheep-photos.dogsheep.net/public?sql=select%0D%0A++photo%2C%0D%0A++%28%0D%0A++++select%0D%0A++++++json_group_array%28%0D%0A++++++++normalized_string%0D%0A++++++%29%0D%0A++++from%0D%0A++++++labels%0D%0A++++where%0D%0A++++++labels.uuid+%3D+photos_with_apple_metadata.uuid%0D%0A++%29+as+labels%2C%0D%0A++date%2C%0D%0A++albums%2C%0D%0A++persons%2C%0D%0A++ZOVERALLAESTHETICSCORE%0D%0Afrom%0D%0A++photos_with_apple_metadata"&gt;a query&lt;/a&gt; that shows the labels (from every category) next to each photo.

&lt;h3&gt;Geography&lt;/h3&gt;

&lt;p&gt;Photos taken on an iPhone have embedded latitudes and longitudes... which means I can &lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_on_a_map"&gt;display them on a map&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2020/photos-on-a-map.png" alt="My photos on a map" style="max-width: 100%" /&gt;&lt;/p&gt;

&lt;p&gt;Apple also perform reverse-geocoding on those photos, resolving them to cities, regions and countries. This is great for faceted browse: here are my photos &lt;a href="https://dogsheep-photos.dogsheep.net/public/photos_with_apple_metadata?_facet=place_state_province&amp;amp;_facet=place_country&amp;amp;_facet=place_city"&gt;faceted by country, city and state/province&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Hosting and serving the images&lt;/h3&gt;

&lt;p&gt;My least favourite thing about Apple Photos is how hard it is to get images from it onto the internet. If you enable iCloud sharing your images are accessible through &lt;a href="https://www.icloud.com/"&gt;icloud.com&lt;/a&gt; - but they aren't given publicly accessible URLs, so you can't embed them in blog entries or do other webby things with them.&lt;/p&gt;

&lt;p&gt;I also really want to "own" my images. I want them in a place that I control.&lt;/p&gt;

&lt;p&gt;Amazon S3 is ideal for image storage. It's incredibly inexpensive and essentially infinite.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;dogsheep-photos upload&lt;/code&gt; command takes ANY directory as input, scans through that directory for image files and then uploads them to the configured S3 bucket.&lt;/p&gt;

&lt;p&gt;I designed this to work independently of Apple Photos, mainly to preserve my ability to switch to alternative image solutions in the future.&lt;/p&gt;

&lt;p&gt;I'm using the &lt;a href="https://en.wikipedia.org/wiki/Content-addressable_storage"&gt;content addressable storage&lt;/a&gt; pattern to store the images. Their filename is the sha256 hash of the file contents. The idea is that since sensible photo management software leaves the original files unmodified I should be able to de-duplicate my photo files no matter where they are from and store everything in the one bucket.&lt;/p&gt;

&lt;p&gt;Original image files come with privacy concerns: they embed accurate latitude and longitude data in the EXIF data, so they can be used to reconstruct your exact location history and even figure out your address. This is why systems like Google Photos &lt;a href="https://issuetracker.google.com/issues/80379228"&gt;make it difficult&lt;/a&gt; to export images with location data intact.&lt;/p&gt;

&lt;p&gt;I've addressed this by making the content in my S3 bucket private. Access to the images takes place through &lt;a href="https://github.com/simonw/s3-image-proxy"&gt;s3-image-proxy&lt;/a&gt; - a proxy server I wrote and deployed on &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; (previously Zeit Now). The proxy strips EXIF data and can optionally resize images based on querystring parameters. It also serves them with far-future cache expire headers, which means they sit in Vercel's CDN cache rather than being resized every time they are accessed.&lt;/p&gt;

&lt;p&gt;iPhones default to saving photos in HEIC format, which fails to display using with the &lt;code&gt;&amp;lt;img src=""&amp;gt;&lt;/code&gt; tag in the browsers I tested. The proxy uses &lt;a href="https://pypi.org/project/pyheif/"&gt;pyheif&lt;/a&gt; to convert those into JPEGs.&lt;/p&gt;

&lt;p&gt;Here's an example HEIC image, resized by the proxy and converted to JPEG:
&lt;a href="https://photos.simonwillison.net/i/59854a70f125154cdf8dad89a4c730e6afde06466d4a6de24689439539c2d863.heic?w=600"&gt;https://photos.simonwillison.net/i/59854a70f125154cdf8dad89a4c730e6afde06466d4a6de24689439539c2d863.heic?w=600&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Next steps&lt;/h3&gt;

&lt;p&gt;This project is a little daunting in that there are so many possibilities for where to take it next!&lt;/p&gt;

&lt;p&gt;In the short term:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://github.com/dogsheep/dogsheep-photos/issues/3"&gt;Import EXIF data&lt;/a&gt; from the images into a table. The Apple Photos tables give me some of this already (particularly GPS data) but I want things like ISO, aperture, what lens I used.&lt;/li&gt;&lt;li&gt;Load the labels into SQLite full-text search.&lt;/li&gt;&lt;li&gt;I'd like other people to be able to play with this easily. Getting it all up and running right now is a fair amount of work - I think I can improve this with usability improvements and better documentation.&lt;/li&gt;&lt;li&gt;The system only handles static images at the moment. I'd like to &lt;a href="https://github.com/dogsheep/dogsheep-photos/issues/13"&gt;get my movies&lt;/a&gt; and more importantly my live photos in there as well.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;And in the longer term:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;Only iPhone photos have location data at the moment - I'd like to derive approximate latitude/longitude points for my DSLR images by matching against images from my phone based on date.&lt;/li&gt;&lt;li&gt;Running my photos through other computer vision systems like Google's Cloud Vision APIs &lt;a href="https://github.com/dogsheep/dogsheep-photos/issues/14"&gt;could be really interesting&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;For better spotting of duplicate images I'm interested in exploring &lt;a href="https://github.com/dogsheep/dogsheep-photos/issues/7"&gt;image content hashing&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;The UI for all of this right now is just regular Datasette. Building a custom UI (running against the Datasette JSON API) could be a lot of fun.&lt;/li&gt;&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/photography"&gt;photography&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/dogsheep"&gt;dogsheep&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/apple-photos"&gt;apple-photos&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="photography"/><category term="photos"/><category term="projects"/><category term="sql"/><category term="sqlite"/><category term="datasette"/><category term="dogsheep"/><category term="weeknotes"/><category term="apple-photos"/></entry><entry><title>Weeknotes: Archiving coronavirus.data.gov.uk, custom pages and directory configuration in Datasette, photos-to-sqlite</title><link href="https://simonwillison.net/2020/Apr/29/weeknotes/#atom-tag" rel="alternate"/><published>2020-04-29T19:41:11+00:00</published><updated>2020-04-29T19:41:11+00:00</updated><id>https://simonwillison.net/2020/Apr/29/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;I mainly made progress on three projects this week: Datasette, photos-to-sqlite and a cleaner way of archiving data to a git repository.&lt;/p&gt;

&lt;h3&gt;Archiving coronavirus.data.gov.uk&lt;/h3&gt;

&lt;p&gt;The UK goverment have a new portal website sharing detailed Coronavirus data for regions around the country, at &lt;a href="https://coronavirus.data.gov.uk/"&gt;coronavirus.data.gov.uk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As with everything else built in 2020, it's a big single-page JavaScript app. Matthew Somerville &lt;a href="http://dracos.co.uk/wrote/coronavirus-dashboard/"&gt;investigated&lt;/a&gt; what it would take to build a much lighter (and faster loading) site displaying the same information by moving much of the rendering to the server.&lt;/p&gt;

&lt;p&gt;One of the best things about the SPA craze is that it strongly encourages structured data to be published as JSON files. Matthew's article inspired me to take a look, and sure enough the government figures are available in an extremely comprehensive (and 3.3MB in size) JSON file, available from &lt;a href="https://c19downloads.azureedge.net/downloads/data/data_latest.json"&gt;https://c19downloads.azureedge.net/downloads/data/data_latest.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Any time I see a file like this my first questions are how often does it change - and what kind of changes are being made to it?&lt;/p&gt;

&lt;p&gt;I've written about scraping to a git repository (see my new &lt;a href="https://simonwillison.net/tags/gitscraping/"&gt;gitscraping&lt;/a&gt; tag) a bunch in the past:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://simonwillison.net/2017/Sep/10/scraping-irma/"&gt;Scraping hurricane Irma&lt;/a&gt; - September 2017&lt;/li&gt;&lt;li&gt;&lt;a href="https://simonwillison.net/2017/Oct/10/fires-in-the-north-bay/"&gt;Changelogs to help understand the fires in the North Bay&lt;/a&gt; - October 2017&lt;/li&gt;&lt;li&gt;&lt;a href="https://simonwillison.net/2019/Mar/13/tree-history/"&gt;Generating a commit log for San Francisco’s official list of trees&lt;/a&gt; - March 2019&lt;/li&gt;&lt;li&gt;&lt;a href="https://simonwillison.net/2019/Oct/10/pge-outages/"&gt;Tracking PG&amp;amp;E outages by scraping to a git repo&lt;/a&gt; - October 2019&lt;/li&gt;&lt;li&gt;&lt;a href="https://simonwillison.net/2020/Jan/21/github-actions-cloud-run/"&gt;Deploying a data API using GitHub Actions and Cloud Run&lt;/a&gt; - January 2020&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Now that I've figured out a really clean way to &lt;a href="https://github.com/simonw/til/blob/master/github-actions/commit-if-file-changed.md"&gt;Commit a file if it changed&lt;/a&gt; in a GitHub Action knocking out new versions of this pattern is really quick.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/simonw/coronavirus-data-gov-archive"&gt;simonw/coronavirus-data-gov-archive&lt;/a&gt; is my new repo that does exactly that: it periodically fetches the latest versions of the JSON data files powering that site and commits them if they have changed. The aim is to build a &lt;a href="https://github.com/simonw/coronavirus-data-gov-archive/commits/master/data_latest.json"&gt;commit history&lt;/a&gt; of changes made to the underlying data.&lt;/p&gt;

&lt;p&gt;The first implementation was extremely simple - here's the &lt;a href="https://github.com/simonw/coronavirus-data-gov-archive/blob/c83d69e95ec6400bf77d7b0d474e868baa78841e/.github/workflows/scheduled.yml"&gt;entire action&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;name: Fetch latest data

on:
push:
repository_dispatch:
schedule:
    - cron:  '25 * * * *'

jobs:
scheduled:
    runs-on: ubuntu-latest
    steps:
    - name: Check out this repo
    uses: actions/checkout@v2
    - name: Fetch latest data
    run: |-
        curl https://c19downloads.azureedge.net/downloads/data/data_latest.json | jq . &amp;gt; data_latest.json
        curl https://c19pub.azureedge.net/utlas.geojson | gunzip | jq . &amp;gt; utlas.geojson
        curl https://c19pub.azureedge.net/countries.geojson | gunzip | jq . &amp;gt; countries.geojson
        curl https://c19pub.azureedge.net/regions.geojson | gunzip | jq . &amp;gt; regions.geojson
    - name: Commit and push if it changed
    run: |-
        git config user.name "Automated"
        git config user.email "actions@users.noreply.github.com"
        git add -A
        timestamp=$(date -u)
        git commit -m "Latest data: ${timestamp}" || exit 0
        git push&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It uses a combination of &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; (both available &lt;a href="https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md"&gt;in the default worker environment&lt;/a&gt;) to pull down the data and pretty-print it (better for readable diffs), then commits the result.&lt;/p&gt;

&lt;p&gt;Matthew Somerville &lt;a href="https://twitter.com/dracos/status/1255221799085846532"&gt;pointed out&lt;/a&gt; that inefficient polling sets a bad precedent. Here I'm hitting &lt;code&gt;azureedge.net&lt;/code&gt;, the Azure CDN, so that didn't particularly worry me - but since I want this pattern to be used widely it's good to provide a best-practice example.&lt;/p&gt;

&lt;p&gt;Figuring out the best way to make &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests"&gt;conditional get requests&lt;/a&gt; in a GitHub Action lead me down &lt;a href="https://github.com/simonw/coronavirus-data-gov-archive/issues/1"&gt;something of a rabbit hole&lt;/a&gt;. I wanted to use &lt;a href="https://daniel.haxx.se/blog/2019/12/06/curl-speaks-etag/"&gt;curl's new ETag support&lt;/a&gt; but I ran into &lt;a href="https://github.com/curl/curl/issues/5309"&gt;a curl bug&lt;/a&gt;, so I ended up rolling a simple Python CLI tool called &lt;a href="https://github.com/simonw/conditional-get"&gt;conditional-get&lt;/a&gt; to solve my problem. In the time it took me to release that tool (just a few hours) a &lt;a href="https://github.com/curl/curl/issues/5309#issuecomment-621265179"&gt;new curl release&lt;/a&gt; came out with a fix for that bug!&lt;/p&gt;

&lt;p&gt;Here's &lt;a href="https://github.com/simonw/coronavirus-data-gov-archive/blob/a95d7661b236a9ee9a26a441dd948eb00308f919/.github/workflows/scheduled.yml"&gt;the workflow&lt;/a&gt; using my &lt;code&gt;conditional-get&lt;/code&gt; tool. See &lt;a href="https://github.com/simonw/coronavirus-data-gov-archive/issues/1"&gt;the issue thread&lt;/a&gt; for all of the other potential solutions, including a really neat &lt;a href="https://github.com/hubgit/curl-etag"&gt;Action shell-script solution&lt;/a&gt; by Alf Eaton.&lt;/p&gt;

&lt;p&gt;To my absolute delight, the project has already been forked once by Daniel Langer to &lt;a href="https://github.com/dlanger/coronavirus-hc-infobase-archive"&gt;capture Canadian Covid-19 cases&lt;/a&gt;!&lt;/p&gt;

&lt;h3 id="new-datasette-features"&gt;New Datasette features&lt;/h3&gt;

&lt;p&gt;I pushed two new features to &lt;a href="https://github.com/simonw/datasette"&gt;Datasette&lt;/a&gt; master, ready for release in 0.41.&lt;/p&gt;

&lt;h4&gt;Configuration directory mode&lt;/h4&gt;

&lt;p&gt;This is an idea I had while building &lt;a href="https://github.com/simonw/datasette-publish-now"&gt;datasette-publish-now&lt;/a&gt;. Datasette instances can be run with custom metadata, custom plugins and custom templates. I'm increasingly finding myself working on projects that run using something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ datasette data1.db data2.db data3.db \
    --metadata=metadata.json
    --template-dir=templates \
    --plugins-dir=plugins&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Directory configuration mode introduces the idea that Datasette can configure itself based on a directory layout. The above example can instead by handled by creating the following layout:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;my-project/data1.db
my-project/data2.db
my-project/data3.db
my-project/metadatata.json
my-project/templates/index.html
my-project/plugins/custom_plugin.py&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then run Datasette directly targetting that directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ datasette my-project/&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;See &lt;a href="https://github.com/simonw/datasette/issues/731"&gt;issue #731&lt;/a&gt; for more details. Directory configuration mode &lt;a href="https://datasette.readthedocs.io/en/latest/config.html#configuration-directory-mode"&gt;is documented here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;Define custom pages using templates/pages&lt;/h4&gt;

&lt;p&gt;In &lt;a href="https://simonwillison.net/2019/Nov/25/niche-museums/"&gt;niche-museums.com, powered by Datasette&lt;/a&gt; I described how I built the &lt;a href="https://www.niche-museums.com/"&gt;www.niche-museums.com&lt;/a&gt; website as a heavily customized Datasette instance.&lt;/p&gt;

&lt;p&gt;That site has &lt;a href="https://www.niche-museums.com/about"&gt;/about&lt;/a&gt; and &lt;a href="https://www.niche-museums.com/map"&gt;/map&lt;/a&gt; pages which are served by custom templates - but I had to do some gnarly hacks with empty &lt;code&gt;about.db&lt;/code&gt; and &lt;code&gt;map.db&lt;/code&gt; files to get them to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/simonw/datasette/issues/648"&gt;Issue #648&lt;/a&gt; introduces a new mechanism for creating this kind of page: create a &lt;code&gt;templates/pages/map.html&lt;/code&gt; template file and custom 404 handling code will ensure that any hits to &lt;code&gt;/map&lt;/code&gt; serve the rendered contents of that template.&lt;/p&gt;

&lt;p&gt;This could work really well with the &lt;a href="https://github.com/simonw/datasette-template-sql"&gt;datasette-template-sql&lt;/a&gt; plugin, which allows templates to execute abritrary SQL queries (ala PHP or ColdFusion).&lt;/p&gt;

&lt;p&gt;Here's the new &lt;a href="https://datasette.readthedocs.io/en/latest/custom_templates.html#custom-pages"&gt;documentation on custom pages&lt;/a&gt;, including details of how to use the new &lt;code&gt;custom_status()&lt;/code&gt;, &lt;code&gt;custom_header()&lt;/code&gt; and &lt;code&gt;custom_redirect()&lt;/code&gt; template functions to go beyond just returning HTML.&lt;/p&gt;

&lt;h3&gt;photos-to-sqlite&lt;/h3&gt;

&lt;p&gt;My &lt;a href="https://dogsheep.github.io/"&gt;Dogsheep&lt;/a&gt; personal analytics project brings my &lt;a href="https://github.com/dogsheep/twitter-to-sqlite"&gt;tweets&lt;/a&gt;, &lt;a href="https://github.com/dogsheep/github-to-sqlite"&gt;GitHub activity&lt;/a&gt;, &lt;a href="https://github.com/dogsheep/swarm-to-sqlite"&gt;Swarm checkins&lt;/a&gt; and more together in one place. But the big missing feature is my photos.&lt;/p&gt;

&lt;p&gt;As-of yesterday, I have 39,000 photos from Apple Photos uploaded to an S3 bucket using my new &lt;a href="https://github.com/dogsheep/photos-to-sqlite/"&gt;photos-to-sqlite&lt;/a&gt; tool. I can run the following SQL query and get back ten random photos!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;select
  json_object(
    'img_src',
    'https://photos.simonwillison.net/i/' || 
    sha256 || '.' || ext || '?w=400'
  ),
  filepath,
  ext
from
  photos
where
  ext in ('jpeg', 'jpg', 'heic')
order by
  random()
limit
  10&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;photos.simonwillison.net&lt;/code&gt; is running a modified version of my &lt;a href="https://github.com/simonw/heic-to-jpeg"&gt;heic-to-jpeg&lt;/a&gt; image converting and resizing proxy, which I'll release at some point soon.&lt;/p&gt;

&lt;p&gt;There's still plenty of work to do - I still need to import EXIF data (including locations) into SQLite, and I plan to use &lt;a href="https://github.com/RhetTbull/osxphotos"&gt;osxphotos&lt;/a&gt; to export additional metadata from my Apple Photos library. But this week it went from a pure research project to something I can actually start using, which is exciting.&lt;/p&gt;

&lt;h3&gt;TIL this week&lt;/h3&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/til/blob/master/macos/fixing-compinit-insecure-directories.md"&gt;Fixing "compinit: insecure directories" error&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/til/blob/master/tailscale/lock-down-sshd.md"&gt;Restricting SSH connections to devices within a Tailscale network&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/til/blob/master/python/generate-nested-json-summary.md"&gt;Generated a summary of nested JSON data&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/til/blob/master/pytest/session-scoped-tmp.md"&gt;Session-scoped temporary directories in pytest&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/til/blob/master/pytest/mock-httpx.md"&gt;How to mock httpx using pytest-mock&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Generated using &lt;a href="https://til.simonwillison.net/til?sql=select+json_object(%27pre%27%2C+group_concat(%27*+[%27+||+title+||+%27](%27+||+url+||+%27)%27%2C+%27%0D%0A%27))+from+til+where+%22created_utc%22+%3E%3D+%3Ap0+order+by+updated_utc+desc+limit+101&amp;amp;p0=2020-04-23"&gt;this query&lt;/a&gt;.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/git"&gt;git&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/http"&gt;http&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/matthew-somerville"&gt;matthew-somerville&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/covid19"&gt;covid19&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/git-scraping"&gt;git-scraping&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="git"/><category term="http"/><category term="matthew-somerville"/><category term="photos"/><category term="projects"/><category term="datasette"/><category term="weeknotes"/><category term="covid19"/><category term="git-scraping"/></entry><entry><title>Photos from our tour of the amazing bone collection of Ray Bandar</title><link href="https://simonwillison.net/2018/Feb/21/photos-our-tour-amazing-bone-collection-ray-bandar/#atom-tag" rel="alternate"/><published>2018-02-21T04:58:35+00:00</published><updated>2018-02-21T04:58:35+00:00</updated><id>https://simonwillison.net/2018/Feb/21/photos-our-tour-amazing-bone-collection-ray-bandar/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://twitter.com/simonw/status/966174716061876224"&gt;Photos from our tour of the amazing bone collection of Ray Bandar&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Ray Bandar (1927-2017) was an artist, scientist, naturalist and an incredibly prolific collector of bones. His collection is in the process of moving to the California Academy of Sciences but Natalie managed to land us a private tour lead by his great nephew. The collection is truly awe-inspiring, and a testament to an extraordinary life lived following a very particular passion.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/natalie-downe"&gt;natalie-downe&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;&lt;/p&gt;



</summary><category term="natalie-downe"/><category term="photos"/></entry><entry><title>PostgreSQL: How can I store images in a database? What existing products makes it easy for a user to upload photos into a general database?</title><link href="https://simonwillison.net/2013/Nov/19/postgresql-how-can-i/#atom-tag" rel="alternate"/><published>2013-11-19T15:06:00+00:00</published><updated>2013-11-19T15:06:00+00:00</updated><id>https://simonwillison.net/2013/Nov/19/postgresql-how-can-i/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;em&gt;My answer to &lt;a href="https://www.quora.com/PostgreSQL-How-can-I-store-images-in-a-database-What-existing-products-makes-it-easy-for-a-user-to-upload-photos-into-a-general-database/answer/Simon-Willison"&gt;PostgreSQL: How can I store images in a database? What existing products makes it easy for a user to upload photos into a general database?&lt;/a&gt; on Quora&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a general rule, it's a bad idea to store images in a database. This is due to the large amount of space they take up, which can affect database read performance and will greatly increases the size of your backups, making them both take longer and cost more to store.&lt;/p&gt;

&lt;p&gt;Instead, it's best to store the images elsewhere and then store a reference to them in your database. These days the easiest way of doing this is generally to use Amazon S3, which can cheaply and reliably store an unlimited number of images. Save them to S3, then store the S3 URL (or the bucket + key combination) in a string in your database row.&lt;/p&gt;

&lt;p&gt;If you're determined to store them in a database you can do so using a BLOB field, or by base64 encoding them and storing them in a large text field (which will even further inflate the size of your tables).&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/databases"&gt;databases&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/postgresql"&gt;postgresql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapps"&gt;webapps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/web-development"&gt;web-development&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/quora"&gt;quora&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="databases"/><category term="photos"/><category term="postgresql"/><category term="webapps"/><category term="web-development"/><category term="quora"/></entry><entry><title>For a Django application, deployed on Heroku, what are my options for storing user-uploaded media files?</title><link href="https://simonwillison.net/2013/Oct/21/for-a-django-application/#atom-tag" rel="alternate"/><published>2013-10-21T18:28:00+00:00</published><updated>2013-10-21T18:28:00+00:00</updated><id>https://simonwillison.net/2013/Oct/21/for-a-django-application/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;em&gt;My answer to &lt;a href="https://www.quora.com/For-a-Django-application-deployed-on-Heroku-what-are-my-options-for-storing-user-uploaded-media-files/answer/Simon-Willison"&gt;For a Django application, deployed on Heroku, what are my options for storing user-uploaded media files?&lt;/a&gt; on Quora&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;S3 is really a no-brainer for this, it's extremely inexpensive, very easy to integrate with and unbelievably reliable. It's so cheap that it will be practically free for testing purposes (expect to spend pennies a month on it).&lt;/p&gt;

&lt;p&gt;You could store uploaded files in the Heroku database, but that will explode the size of your backups and will be much more expensive than paying for S3.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/aws"&gt;aws&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/programming"&gt;programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/s3"&gt;s3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/web-development"&gt;web-development&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/quora"&gt;quora&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/heroku"&gt;heroku&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="aws"/><category term="django"/><category term="photos"/><category term="programming"/><category term="python"/><category term="s3"/><category term="web-development"/><category term="quora"/><category term="heroku"/></entry><entry><title>Help pick the best photos, but watch out, it's addictive!</title><link href="https://simonwillison.net/2010/Jan/25/best/#atom-tag" rel="alternate"/><published>2010-01-25T00:36:35+00:00</published><updated>2010-01-25T00:36:35+00:00</updated><id>https://simonwillison.net/2010/Jan/25/best/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.wildlifenearyou.com/blog/2010/jan/24/best-photos/"&gt;Help pick the best photos, but watch out, it&amp;#x27;s addictive!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
My favourite WildlifeNearYou feature yet—our new tool asks you to pick the best from two photos, then uses the results to rank all of the photos for each species. It’s surprisingly addictive—we had over 5,000 votes in the first two hours, peaking at 4 or 5 votes a second. The feature seems to be staying nice and speedy thanks to Redis under the hood. Photos in the top three for any given species display a medal on their photo page.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/crowdsourcing"&gt;crowdsourcing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/redis"&gt;redis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/wildlifenearyou"&gt;wildlifenearyou&lt;/a&gt;&lt;/p&gt;



</summary><category term="crowdsourcing"/><category term="photos"/><category term="projects"/><category term="redis"/><category term="wildlifenearyou"/></entry><entry><title>PhotoSketch turns a rough sketch in to a photo montage</title><link href="https://simonwillison.net/2009/Oct/6/photosketch/#atom-tag" rel="alternate"/><published>2009-10-06T07:59:20+00:00</published><updated>2009-10-06T07:59:20+00:00</updated><id>https://simonwillison.net/2009/Oct/6/photosketch/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://gizmodo.com/5374890/this-is-a-photoshop-and-it-blew-my-mind"&gt;PhotoSketch turns a rough sketch in to a photo montage&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Computer vision is really exciting at the moment—Photosketch is an application which takes a rough labeled sketch, finds images matching the labels, filters them by the sketched shapes and composes them in to a not-too-bad photo montage. As wmf on Hacker News points out, “this technology has epic potential in the LOLcat market”.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://news.ycombinator.com/item?id=863294"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/computer-vision"&gt;computer-vision&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photosketch"&gt;photosketch&lt;/a&gt;&lt;/p&gt;



</summary><category term="computer-vision"/><category term="photos"/><category term="photosketch"/></entry><entry><title>Building Rome in a Day</title><link href="https://simonwillison.net/2009/Jul/29/building/#atom-tag" rel="alternate"/><published>2009-07-29T15:41:03+00:00</published><updated>2009-07-29T15:41:03+00:00</updated><id>https://simonwillison.net/2009/Jul/29/building/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://grail.cs.washington.edu/rome/"&gt;Building Rome in a Day&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
“The ﬁrst system capable of city-scale reconstruction from unstructured photo collections”—computer vision techniques used to construct 3D models of cities using 10s of thousands of photos from Flickr. Reminiscent of Microsoft PhotoSynth.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://digitalurban.blogspot.com/2009/07/building-rome-in-day-3d-city-via-flickr.html"&gt;Digital Urban&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/3d"&gt;3d&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/computer-vision"&gt;computer-vision&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photosynth"&gt;photosynth&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/research"&gt;research&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rome"&gt;rome&lt;/a&gt;&lt;/p&gt;



</summary><category term="3d"/><category term="computer-vision"/><category term="flickr"/><category term="photos"/><category term="photosynth"/><category term="research"/><category term="rome"/></entry><entry><title>Slouching towards Bethlehem</title><link href="https://simonwillison.net/2009/Jul/15/slouching/#atom-tag" rel="alternate"/><published>2009-07-15T10:19:25+00:00</published><updated>2009-07-15T10:19:25+00:00</updated><id>https://simonwillison.net/2009/Jul/15/slouching/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.photographyserved.com/Gallery/Slouching-towards-Bethlehem-___/56780"&gt;Slouching towards Bethlehem&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Photos of the various installations that contributed to the construction of the first atom bomb.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/atombomb"&gt;atombomb&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/history"&gt;history&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nuclear"&gt;nuclear&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;&lt;/p&gt;



</summary><category term="atombomb"/><category term="history"/><category term="nuclear"/><category term="photos"/></entry><entry><title>Large Hadron Collider nearly ready - The Big Picture</title><link href="https://simonwillison.net/2008/Aug/1/lhc/#atom-tag" rel="alternate"/><published>2008-08-01T19:46:47+00:00</published><updated>2008-08-01T19:46:47+00:00</updated><id>https://simonwillison.net/2008/Aug/1/lhc/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.boston.com/bigpicture/2008/08/the_large_hadron_collider.html"&gt;Large Hadron Collider nearly ready - The Big Picture&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Stunningly beautiful set of photographs of the LHC. I love Big Science.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/bigscience"&gt;bigscience&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cern"&gt;cern&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/lhc"&gt;lhc&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/thebigpicture"&gt;thebigpicture&lt;/a&gt;&lt;/p&gt;



</summary><category term="bigscience"/><category term="cern"/><category term="lhc"/><category term="photos"/><category term="thebigpicture"/></entry><entry><title>A Look at the Presidential Candidates</title><link href="https://simonwillison.net/2008/Jul/4/look/#atom-tag" rel="alternate"/><published>2008-07-04T21:09:10+00:00</published><updated>2008-07-04T21:09:10+00:00</updated><id>https://simonwillison.net/2008/Jul/4/look/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.boston.com/bigpicture/2008/07/a_look_at_the_presidential_can.html"&gt;A Look at the Presidential Candidates&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The Big Picture (the Boston Globe’s fantastic photojournalism blog) presents a fascinating collection of historical photos of Senators Barack Obama and John McCain.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/barack-obama"&gt;barack-obama&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/john-mccain"&gt;john-mccain&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photography"&gt;photography&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/politics"&gt;politics&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/thebigpicture"&gt;thebigpicture&lt;/a&gt;&lt;/p&gt;



</summary><category term="barack-obama"/><category term="john-mccain"/><category term="photography"/><category term="photos"/><category term="politics"/><category term="thebigpicture"/></entry><entry><title>Video on Flickr!</title><link href="https://simonwillison.net/2008/Apr/9/video/#atom-tag" rel="alternate"/><published>2008-04-09T13:16:29+00:00</published><updated>2008-04-09T13:16:29+00:00</updated><id>https://simonwillison.net/2008/Apr/9/video/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.flickr.net/en/2008/04/09/video-on-flickr-2/"&gt;Video on Flickr!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
There’s a 90 second length limit, because “... Flickr is all about sharing photos that you yourself have taken. Video will be no different and so what quickly bubbled up was the idea of long photos, of capturing slices of life to share.”


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/video"&gt;video&lt;/a&gt;&lt;/p&gt;



</summary><category term="flickr"/><category term="photos"/><category term="video"/></entry><entry><title>Flickr: The Commons</title><link href="https://simonwillison.net/2008/Jan/16/flickr/#atom-tag" rel="alternate"/><published>2008-01-16T21:38:57+00:00</published><updated>2008-01-16T21:38:57+00:00</updated><id>https://simonwillison.net/2008/Jan/16/flickr/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.flickr.com/commons"&gt;Flickr: The Commons&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Exciting pilot collaboration with the Library of Congress to release images with “no known copyright restrictions”. The header photo (of a bench) is one of my favourite spots in the world, in Mission Dolores Park, San Francisco.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/libraryofcongress"&gt;libraryofcongress&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/missiondolores"&gt;missiondolores&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/publicdomain"&gt;publicdomain&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/san-francisco"&gt;san-francisco&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/the-commons"&gt;the-commons&lt;/a&gt;&lt;/p&gt;



</summary><category term="flickr"/><category term="libraryofcongress"/><category term="missiondolores"/><category term="photos"/><category term="publicdomain"/><category term="san-francisco"/><category term="the-commons"/></entry><entry><title>Photos taken in Brighton on Flickr!</title><link href="https://simonwillison.net/2007/Nov/21/places/#atom-tag" rel="alternate"/><published>2007-11-21T08:28:38+00:00</published><updated>2007-11-21T08:28:38+00:00</updated><id>https://simonwillison.net/2007/Nov/21/places/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.flickr.com/places/United Kingdom/England/Brighton"&gt;Photos taken in Brighton on Flickr!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The new Flickr Places feature has finally launched, and it’s absolutely beautiful.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://www.flickr.com/places"&gt;Flickr: Places&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/brighton"&gt;brighton&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/places"&gt;places&lt;/a&gt;&lt;/p&gt;



</summary><category term="brighton"/><category term="flickr"/><category term="photos"/><category term="places"/></entry><entry><title>My Washington D.C. photos</title><link href="https://simonwillison.net/2005/Apr/4/washington/#atom-tag" rel="alternate"/><published>2005-04-04T01:43:30+00:00</published><updated>2005-04-04T01:43:30+00:00</updated><id>https://simonwillison.net/2005/Apr/4/washington/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.flickr.com/photos/simon/sets/205806/"&gt;My Washington D.C. photos&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Touristy photos. Includes squirrels... and ENIAC!


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/eniac"&gt;eniac&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/squirrels"&gt;squirrels&lt;/a&gt;&lt;/p&gt;



</summary><category term="eniac"/><category term="flickr"/><category term="photos"/><category term="squirrels"/></entry><entry><title>My photos of squirrels</title><link href="https://simonwillison.net/2004/Aug/30/photos/#atom-tag" rel="alternate"/><published>2004-08-30T16:29:46+00:00</published><updated>2004-08-30T16:29:46+00:00</updated><id>https://simonwillison.net/2004/Aug/30/photos/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.flickr.com/photos/simon/tags/squirrels/"&gt;My photos of squirrels&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I like squirrels, and I’ve started playing with Flickr.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photos"&gt;photos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/squirrels"&gt;squirrels&lt;/a&gt;&lt;/p&gt;



</summary><category term="flickr"/><category term="photos"/><category term="squirrels"/></entry></feed>