<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.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>tutorial &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/tutorial/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Wed, 05 Nov 2025 06:20:32 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>tutorial &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[An Easy Guide To BlueSky Verification]]></title>
		<link>https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/</link>
					<comments>https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 19 Nov 2024 12:34:21 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[BlueSky]]></category>
		<category><![CDATA[BSky]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=54022</guid>

					<description><![CDATA[The new Twitter-Wannabe BlueSky has an interesting approach to verifying accounts. Rather than you sending in your passport, or paying a 3rd party, or bribing an employee - you can self-verify for free!  This opens up verification to small organisations, individuals, and anyone who wants to prove who they are. Brilliant!  Verification means that your @username will change to @Your.Website.com -…]]></description>
										<content:encoded><![CDATA[<p>The new Twitter-Wannabe <a href="https://bsky.app/">BlueSky</a> has an interesting approach to verifying accounts. Rather than you sending in your passport, or paying a 3rd party, or bribing an employee - you can self-verify <em>for free</em>!</p>

<p>This opens up verification to small organisations, individuals, and anyone who wants to prove who they are. Brilliant!</p>

<p>Verification means that your <code>@username</code> will change to <code>@Your.Website.com</code> - this means that everyone can see your BlueSky account is owned by that specific website. When you change your name, you keep all your followers and posts.</p>

<p>Here are some organisations and people at risk of impersonation who have already done this:</p>

<ul>
<li>UK Newspaper <a href="https://bsky.app/profile/theguardian.com">https://bsky.app/profile/theguardian.com</a></li>
<li>Trade Union <a href="https://bsky.app/profile/utaw.tech">https://bsky.app/profile/utaw.tech</a></li>
<li>Labour MPs <a href="https://bsky.app/profile/sarahowen.org.uk">https://bsky.app/profile/sarahowen.org.uk</a></li>
<li>Small Publisher <a href="https://bsky.app/profile/canongate.co.uk">https://bsky.app/profile/canongate.co.uk</a></li>
<li>Fun Website <a href="https://bsky.app/profile/openbenches.org">https://bsky.app/profile/openbenches.org</a></li>
</ul>

<p>There is an easy way to get verified and <a href="https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial">a hard way</a>.  Let's do the easy way!</p>

<h2 id="1-sign-up-for-bluesky"><a href="https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#1-sign-up-for-bluesky">1) Sign Up For BlueSky</a></h2>

<p>Sign up and register a username. This can be anything you want. For example, I registered <code>edent.bsky.social</code></p>

<h2 id="2-change-your-user-id"><a href="https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#2-change-your-user-id">2) Change Your User ID</a></h2>

<p>Follow these steps:</p>

<ol>
<li>Visit <a href="https://bsky.app/settings">https://bsky.app/settings</a></li>
<li>Scroll down and select "Change Handle"</li>
<li>Click "I have my own domain"</li>
<li>Select "No DNS Panel". The screen should look like this:

<ul>
<li><img src="https://shkspr.mobi/blog/wp-content/uploads/2024/11/Change-Handle-fs8.png" alt="Change Handle screen." width="1440" height="1408" class="aligncenter size-full wp-image-54026"></li>
</ul></li>
<li>Type in the domain name you want to verify</li>
<li>Click "Copy File Contents"</li>
</ol>

<p>Keep this web page open.</p>

<h2 id="3-copy-and-save-your-did"><a href="https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#3-copy-and-save-your-did">3) Copy and Save Your DID</a></h2>

<p>On your clipboard, you will have a bit of text which looks like this <code>did:plc:dip7ueksh627fxacagfrdyz2</code></p>

<p>Save it in a text file called <code>atproto-did</code></p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/11/atproto-fs8.png" alt="Screenshot of a text editor." width="623" height="183" class="aligncenter size-full wp-image-54024">

<p>It is very important that the file doesn't end with <code>.txt</code> - it must be called <code>atproto-did</code> and nothing else.</p>

<p>The file should only contain the text you copied. Nothing else.</p>

<h2 id="4-upload-the-file-to-your-website"><a href="https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#4-upload-the-file-to-your-website">4) Upload The File To Your Website</a></h2>

<p>This is the only technical bit of the process.  You need the ability to upload a file to your website.  I don't know whether you use FTP, a control panel, or email things to the person who manages your site.</p>

<p>You need to save the <code>atproto-did</code> file in a folder called <code>/.well-known/</code></p>

<p>If that folder doesn't exist, create it. The folder name <em>must</em> be typed exactly like that, with the dot at the start.</p>

<p>You can check it has worked by visiting <code>YourWebsite.com/.well-known/atproto-did</code></p>

<p>If you can see your DID, it worked!</p>

<h2 id="5-change-your-username"><a href="https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#5-change-your-username">5) Change Your Username</a></h2>

<p>Go back to the "Change Handle" web page you opened in Step 2.</p>

<p>Click "Verify Text File" and then "Update".</p>

<h2 id="6-thats-it"><a href="https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/#6-thats-it">6) That's It!</a></h2>

<p>Feel free to share this guide with people and organisations who want to get verified on BSky.</p>

<p>Leave a comment if you found it useful or want me to clarify something.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=54022&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/11/an-easy-guide-to-bluesky-verification/feed/</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Importing IntenseDebate Comment XML into Commentics]]></title>
		<link>https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/</link>
					<comments>https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 04 Aug 2023 11:34:29 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46329</guid>

					<description><![CDATA[This is ridiculously niche. If this is of help to anyone other than to me... please shout!  The IntenseDebate comment system is slowly dying. It hasn&#039;t received any updates from Automattic for years. Recently it stopped being able to let users submit new comments.  So I&#039;ve switched to Commentics which is a self-hosted PHP / MySQL comment system. It&#039;s lightweight, pretty good at respecting users&#039;…]]></description>
										<content:encoded><![CDATA[<p>This is <em>ridiculously</em> niche. If this is of help to anyone other than to me... please shout!</p>

<p>The <a href="https://www.intensedebate.com/">IntenseDebate</a> comment system is slowly dying. It hasn't received any updates from Automattic for years. Recently it stopped being able to let users submit new comments.</p>

<p>So I've switched to <a href="https://commentics.com/">Commentics</a> which is a self-hosted PHP / MySQL comment system. It's lightweight, pretty good at respecting users' privacy, and very customisable.  But it doesn't let you easily import comments.  Here's how I fixed that.</p>

<h2 id="export-from-intensedebate"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#export-from-intensedebate">Export From IntenseDebate</a></h2>

<p>Go to your site's dashboard and click export. They'll email you a link when the process has finished.  It's an XML file which looks something like this:</p>

<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;output&gt;
    &lt;blogpost&gt;
        &lt;url&gt;https%3A%2F%2Fopenbenches.org%2Fbench%2F123&lt;/url&gt;
        &lt;title&gt;The Page Title&lt;/title&gt;
        &lt;guid&gt;https://openbenches.org/bench/123&lt;/guid&gt;
        &lt;comments&gt;
            &lt;comment id='123456798' parentid='0'&gt;
                &lt;isAnon&gt;0&lt;/isAnon&gt;
                &lt;name&gt;&lt;![CDATA[Terence Eden]]&gt;&lt;/name&gt;
                &lt;email&gt;terence.eden@example.com&lt;/email&gt;
                &lt;url&gt;https://example.com&lt;/url&gt;
                &lt;ip&gt;198.51.100.123&lt;/ip&gt;
                &lt;text&gt;&lt;![CDATA[You can read more about the song at Wikipedia &lt;a href="https://en.wikipedia.org/wiki/Pokarekare_Ana" target="_blank"&gt;https://en.wikipedia.org/wiki/Pokarekare_Ana&lt;/a&gt;  ]]&gt;&lt;/text&gt;
                &lt;date&gt;2017-08-01 14:51:55&lt;/date&gt;
                &lt;gmt&gt;2017-08-01 14:51:55&lt;/gmt&gt;
                &lt;score&gt;1&lt;/score&gt;
            &lt;/comment&gt;
        &lt;/comments&gt;
    &lt;/blogpost&gt;
    ...
</code></pre>

<h2 id="understand-the-commentics-table-structure"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#understand-the-commentics-table-structure">Understand the Commentics Table Structure</a></h2>

<p>Once you've installed and configured Commentics, you will be able to replace the database with your old comments. To do that, you'll need to convert your exported XML file into three CSV files.</p>

<p>there are 3 tables you need to understand.</p>

<h3 id="users-table"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#users-table">Users Table</a></h3>

<p>This is the easiest one to understand.  The columns are:</p>

<p><code>id, avatar_id, avatar_pending_id, avatar_selected, avatar_login, name, email, moderate, token, to_all, to_admin, to_reply, to_approve, format, ip_address, date_modified, date_added</code></p>

<p>Hopefully they're self-explanatory. The <code>moderate</code> is always set to <code>default</code>. The <code>token</code> can be any random string.  A typical user will look like this:</p>

<p><code>2, 0, 0, , , Terence Eden, terence.eden@example.com, default, abc123, 1, 1, 1, 1, html, 127.0.0.1, 2017-08-01 14:51:55, 2017-08-01 14:51:55</code></p>

<p><strong>Note</strong> The user's URL is <em>not</em> part of this table! That confused me at first. It is part of the <code>comment</code> table.</p>

<h3 id="pages-table"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#pages-table">Pages Table</a></h3>

<p>Every comment is associated with a page. Therefore every page needs a table.</p>

<p><code>id, site_id, identifier, reference, url, moderate, is_form_enabled, date_modified, date_added</code></p>

<p>Again, pretty straightforward. A typical page looks like:</p>

<p><code>123, 1, openbenches.org/bench/123, OpenBenches - Bench 123, https://openbenches.org/bench/123, default, 1, 2023-07-16 21:29:52, 2023-07-16 21:29:52</code></p>

<p>The <code>id</code> is a unique number. I've set it to be the same as my page's actual ID - but it doesn't need to be.</p>

<h3 id="comments-table"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#comments-table">Comments Table</a></h3>

<p>This is a slightly cumbersome table:</p>

<p><code>id, user_id, page_id, website, town, state_id, country_id, rating, reply_to, headline, original_comment, comment, reply, ip_address, is_approved, notes, is_admin, is_sent, sent_to, likes, dislikes, reports, is_sticky, is_locked, is_verified, number_edits, session_id, date_modified, date_added</code></p>

<p>You can ignore most of them - unless you <em>really</em> want to record someone's home town - and a typical user looks like this:</p>

<p><code>4, 17, 123, , , , , , , , Wow! That's a great bench. And I imagine the view must be special too.   , Wow! That's a great bench. And I imagine the view must be special too., , 127.0.0.1, 1, Moderating all comments., 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, abc123, 2017-08-10 17:56:50, 2017-08-10 17:56:50</code></p>

<p>The <code>id</code> is unique per comment. The <code>user_id</code> is matched to the <code>id</code> on the <code>users</code> table. And the <code>page_id</code> is the <code>id</code> on the <code>pages</code> table. <code>notes</code> appears to be hardcoded to <code>Moderating all comments.</code></p>

<h2 id="horrible-evil-no-good-python"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#horrible-evil-no-good-python">Horrible evil no-good Python</a></h2>

<p>Here's a Python script I made after a few beers. It ingests the XML file and spits out 3 CSV files.</p>

<pre><code class="language-python">import csv
import random
import string

import xml.etree.ElementTree as ET
tree = ET.parse('IntenseDebate.xml')
root = tree.getroot()

comment_header = ["id","user_id","page_id","website","town","state_id","country_id","rating","reply_to","headline","original_comment","comment","reply","ip_address","is_approved","notes","is_admin","is_sent","sent_to","likes","dislikes","reports","is_sticky","is_locked","is_verified","number_edits","session_id","date_modified","date_added"]

i = 1

with open('comments_to_import.csv', 'w') as file:
    writer = csv.writer(file)
    # writer.writerow(comment_header)   
    for child in root:
        for children in child:
            if (children.tag == "guid") :
                guid = children.text
                page_id = int(''.join(filter(str.isdigit, guid)))
            if (children.tag == "comments") :
                for comments in children :
                    commentID = comments.attrib["id"]
                    for commentData in comments :
                        if (commentData.tag == "name") :
                            username = commentData.text
                        if (commentData.tag == "text") :
                            commentHTML = commentData.text
                        if (commentData.tag == "url") :
                            userurl = commentData.text
                        if (commentData.tag == "date") :
                            commentDate = commentData.text
                            i = i+1
                            digits  = random.choices(string.digits, k=10)
                            letters = random.choices(string.ascii_lowercase, k=10)
                            sample  = random.sample(digits + letters, 20)
                            session_id  = "".join(sample)
                            row = [i,i,page_id,userurl,"","","","","","",commentHTML,commentHTML,"","127.0.0.1","1","Moderating all comments.","0","1","0","0","0","0","0","0","0","0",session_id,commentDate,commentDate]
                            writer.writerow(row)

pages_header = ["id","site_id","identifier","reference","url","moderate","is_form_enabled","date_modified","date_added"]
with open('pages_to_import.csv', 'w') as file:
    writer = csv.writer(file)
    # writer.writerow(pages_header)
    for i in range(30000):
        row = [i,"1","openbenches.org/bench/" + str(i),"OpenBenches - Bench " + str(i),"https://openbenches.org/bench/" + str(i),"default","1","2023-07-16 21:29:52","2023-07-16 21:29:52"]
        writer.writerow(row)

users_header = ["id","avatar_id","avatar_pending_id","avatar_selected","avatar_login","name","email","moderate","token","to_all","to_admin","to_reply","to_approve","format","ip_address","date_modified","date_added"]
i = 1
with open('users_to_import.csv', 'w') as file:
    writer = csv.writer(file)
    # writer.writerow(users_header)
    for child in root:
        for children in child:
            if (children.tag == "guid") :
                guid = children.text
                page_id = int(''.join(filter(str.isdigit, guid)))
            if (children.tag == "comments") :
                for comments in children :
                    commentID = comments.attrib["id"]
                    for commentData in comments :
                        if (commentData.tag == "name") :
                            username = commentData.text
                        if (commentData.tag == "url") :
                            userurl = commentData.text
                        if (commentData.tag == "date") :
                            commentDate = commentData.text
                            i = i+1
                            digits  = random.choices(string.digits, k=10)
                            letters = random.choices(string.ascii_lowercase, k=10)
                            sample  = random.sample(digits + letters, 20)
                            session_id  = "".join(sample)
                            row = [i,"0","0","","",username,"","default",session_id,"1","1","1","1","html","127.0.0.1",commentDate,commentDate]
                            writer.writerow(row)
</code></pre>

<p><strong>Note</strong> my pages all follow a numeric sequence <code>/1</code>, <code>/2</code> etc, hence the loop to quickly regenerate them. Your pages may be different.</p>

<h2 id="importing"><a href="https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/#importing">Importing</a></h2>

<p>You will need to use PHPmyAdmin or a similar database manager. <code>TRUNCATE</code> the tables for <code>pages</code>, <code>users</code>, and <code>comments</code>. Then import the CSV files into each one.</p>

<p>If everything works, you will have all your old comments imported into your new system.</p>

<p>Enjoy!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46329&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/08/importing-intensedebate-comment-xml-into-commentics/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[How to make the Watchy vibrate]]></title>
		<link>https://shkspr.mobi/blog/2023/07/how-to-make-the-watchy-vibrate/</link>
					<comments>https://shkspr.mobi/blog/2023/07/how-to-make-the-watchy-vibrate/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 09 Jul 2023 11:34:42 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[watchy]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46247</guid>

					<description><![CDATA[I am enjoying playing with the eInk Watchy. It is a cute package and is everything I want in a Smart-Watch; geeky, long battery life, and not obnoxious.  But - fuck me! - the documentation is atrocious! Well, that&#039;s a lie. There is no documentation. It has the &#34;Chat to us on Discord&#34; anti-pattern that infects so many otherwise great projects.  So I&#039;m left to figure out how to make the Watchy&#039;s…]]></description>
										<content:encoded><![CDATA[<p>I am enjoying playing with the eInk Watchy. It is a cute package and is everything I want in a Smart-Watch; geeky, long battery life, and not obnoxious.</p>

<p>But - fuck me! - the documentation is atrocious! Well, that's a lie. There is no documentation. It has the "Chat to us on Discord" anti-pattern that infects so many otherwise great projects.</p>

<p>So I'm left to figure out how to make the Watchy's haptics work.</p>

<p>The example watchfaces have a file called <code>settings.h</code> which contains <a href="https://github.com/sqfmi/Watchy/blob/342eb48a497eb3dcb1ec03c5c156937fdec07ba9/examples/WatchFaces/7_SEG/settings.h#L24"><code>.vibrateOClock = true</code></a>. Set that and you'll get a little buzz on the hour, <em>every</em> hour.</p>

<p>But how do you make it buzz when you've hit your step-count goal? Or when the time is 13:37? Or whenever <em>you</em> want?</p>

<p>There's no documentation. And searching the code for "vibrate" or "vibe" or "vibration" yielded nothing.  But I accidentally typo'd my search and entered "vib" - and that did the trick.</p>

<p>In <code>config.h</code> there's this line: <a href="https://github.com/sqfmi/Watchy/blob/342eb48a497eb3dcb1ec03c5c156937fdec07ba9/src/config.h#L25"><code>#define VIB_MOTOR_PIN 13</code></a>.</p>

<p>So, pin 13 is wired in to the vibration motor. We can add that line to our own config file. But how do we turn the buzzer on and off?</p>

<p>With a bit of help from Discord, it became clear:</p>

<pre><code class="language-c">pinMode(VIB_MOTOR_PIN, OUTPUT);     // Set vibration motor pin as an output.
digitalWrite(VIB_MOTOR_PIN, true);  // Enable vibration motor
delay(150);                         // Vibe for 150 ms.
digitalWrite(VIB_MOTOR_PIN, false); // Disable vibration motor.
delay(500);                         // Wait half a second
digitalWrite(VIB_MOTOR_PIN, true);  // Enable vibration motor
delay(250);                         // Vibe for quarter of a second
digitalWrite(VIB_MOTOR_PIN, false); // Stop vibrating
</code></pre>

<p>That's the same as the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate">HTML5 Vibrate API</a> - you can't control the intensity, but you can <a href="https://shkspr.mobi/blog/2014/01/malicious-use-of-the-html5-vibrate-api/">program your own vibration patterns</a>.</p>

<p>I just don't understand why hardware vendors are <em>so</em> crap at writing documentation. Don't they like people using their products?</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46247&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/07/how-to-make-the-watchy-vibrate/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Getting Started with Mastodon's Conversations API]]></title>
		<link>https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/</link>
					<comments>https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 17 Nov 2022 12:34:10 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[MastodonAPI]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=44124</guid>

					<description><![CDATA[The social network service &#34;Mastodon&#34; allows people to publish posts. People can reply to those posts. Other people can reply to those replies - and so on.  What does that look like in the API?  Here&#039;s a quick guide to the concepts you need to know - and some code to help you visualise conversations.  When you scroll through the website, you normally see a list of replies.  It looks like this:    …]]></description>
										<content:encoded><![CDATA[<p>The social network service "Mastodon" allows people to publish posts. People can reply to those posts. Other people can reply to those replies - and so on.  What does that look like in the API?  Here's a quick guide to the concepts you need to know - and some code to help you visualise conversations.</p>

<p>When you scroll through the website, you normally see a list of replies.  It looks like this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/11/conversation-fs8.png" alt="A list of posts. People are writing comments, but there's no link to whom they are replying." width="418" height="721" class="aligncenter size-full wp-image-44135">

<p>Because it acts as a one-dimensional list, there's no easy way to figure out which post someone is replying to.</p>

<p>The data structure underlying the conversation is quite different.  It actually looks like this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/11/Conversation-ASCII-tree-fs8.png" alt="A threaded conversation. You can see the order in which people have replied to each other - and what posts they are referencing." width="315" height="379" class="aligncenter size-full wp-image-44136">

<h2 id="concepts"><a href="https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#concepts">Concepts</a></h2>

<p>In Mastodon's API, a post is called a <code>status</code>.</p>

<p>Every status on Mastodon has an ID.  This is <em>usually</em> a <a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/">Snowflake ID</a> which is represented as a number.</p>

<p>When someone replies to a status on Mastodon, they create a new status which has a field called <code>in_reply_to_id</code>. As its name suggests, has the ID of the status they are replying to.</p>

<p>Let's imagine this simple conversation:</p>

<ol>
<li>Ada: "How are you?"</li>
<li>Bob: "I'm fine. And you?"</li>
<li>Ada: "Quite well, thank you!"</li>
</ol>

<p>Message 2 is in reply to message 1.  Message 3 is in reply to message 2.</p>

<p>In Mastodon's jargon, message 1 is the <em>ancestor</em> of message 2. Similarly, message 3 is the <em>descendant</em> of message 2.</p>

<pre><code class="language-text">  → Descendants →
1--------2-------3
   ← Ancestors ←
</code></pre>

<h3 id="branches"><a href="https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#branches">Branches</a></h3>

<p>Now, let's imagine a more complicated conversation - one with branches!</p>

<pre><code class="language-text">1. Alice: What's your favourite pizza topping?
├── 2. Bette: Pineapple
│   ├── 4. Chuck: You make me sick!
│   └── 7. Dave: Yeah, I love pineapple too
└── 3. Chuck: Mushroom are the best
    ├── 5. Alice: Really?
    │   └── 6. Dave: Button mushrooms are best!
    └── 8. Elle: I like them too!
</code></pre>

<p>As you can see, people reply in threads.  In this example, <code>2</code> is a different "branch" of the conversations than <code>3</code>.</p>

<p>It looks a bit more complicated with hundreds of replies, but that's it! That's all you need to know!</p>

<h2 id="api"><a href="https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#api">API</a></h2>

<p>If you want to download a <em>single</em> status with an ID of <code>1234</code> the API call is <a href="https://docs.joinmastodon.org/methods/statuses/"><code>/api/v1/statuses/1234</code></a></p>

<p>If you want to download a conversation, it is a little bit more complicated. Mastdon's API calls a conversation a <a href="https://docs.joinmastodon.org/entities/context/"><code>context</code></a></p>

<p>Let's take the above simple example - Ada and Bob speaking. Ada's first status has an ID of <code>1</code>.  To get the conversation, the API call is <code>/api/v1/statuses/1/context</code></p>

<p>That returns two things:</p>

<ul>
<li>A list of <code>ancestors</code>. This is empty because <code>1</code> is the first status in this conversation.</li>
<li>A list of <code>descendants</code>. This contains statuses <code>2</code> and <code>3</code>.</li>
</ul>

<p>You will note, the <code>context</code> does <strong>not</strong> return the status <code>1</code> itself.</p>

<p>Let's suppose that, instead of asking for the context of status <code>1</code>, we instead asked for <code>2</code>. This would return:</p>

<ul>
<li>A list of <code>ancestors</code>. This contains status <code>1</code>.</li>
<li>A list of <code>descendants</code>. This contains status <code>3</code>.</li>
</ul>

<p>What about if we asked for <code>3</code>? This would return:</p>

<ul>
<li>A list of <code>ancestors</code>. This contains status <code>1</code> and <code>2</code></li>
<li>A list of <code>descendants</code>. This is empty because <code>3</code> is the last message in this conversation.</li>
</ul>

<h3 id="branches"><a href="https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#branches">Branches</a></h3>

<p>When it comes to complex threads - like the pizza example - things become a bit more difficult.  Let's see the example again:</p>

<pre><code class="language-text">1. Alice: What's your favourite pizza topping?
├── 2. Bette: Pineapple
│   ├── 4. Chuck: You make me sick!
│   └── 7. Dave: Yeah, I love pineapple too
└── 3. Chuck: Mushroom are the best
    ├── 5. Alice: Really?
    │   └── 6. Dave: Button mushrooms are best!
    └── 8. Elle: I like them too!
</code></pre>

<p>Suppose we ask for the <code>context</code> of the message with ID <code>5</code>.  This would return:</p>

<ul>
<li>A list of <code>ancestors</code>. This contains statuses <code>1</code> and <code>3</code></li>
<li>A list of <code>descendants</code>. This contains status <code>6</code>.</li>
</ul>

<p>That's it!?!? Where are the rest? They are part of a <em>different</em> conversation branch. Even status <code>8</code> isn't returned because it's a reply to <code>3</code>, not <code>5</code>.</p>

<p>In order to get the full conversation, we need to be sneaky!</p>

<p>The list of <code>ancestors</code> contains the first message in the conversation.  So we can grab that, and then call <code>context</code> again for its ID.</p>

<p>Let's dive into some Python code to see how it works.</p>

<h2 id="code"><a href="https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#code">Code</a></h2>

<p>This uses the <a href="https://mastodonpy.readthedocs.io/">Mastodon.py</a> library for calling the Mastodon API and the <a href="https://treelib.readthedocs.io/en/latest/">Python treelib</a> to create a conversation tree data structure.</p>

<p>This code connects to Mastodon and receives the status for a single ID.</p>

<pre><code class="language-python">from mastodon import Mastodon
from treelib import Node, Tree

mastodon = Mastodon( api_base_url="https://mastodon.example", access_token="Your personal access token from your instance" )

status_id =  109348943537057532 
status = mastodon.status(status_id)
</code></pre>

<p>Getting the conversation means calling the <code>context</code> API:</p>

<pre><code class="language-python">conversation = mastodon.status_context(status_id)
</code></pre>

<p><mark>⚠ Note:</mark> Calling the <code>context</code> on a large thread may take a long time. The longer the conversation, the longer you'll have to wait.</p>

<p>If there are ancestors, that means we are only on a single branch.  The 0th ancestor is the top of the conversation tree.  So let's get the <code>context</code> for that top status:</p>

<pre><code class="language-python">if len(conversation["ancestors"]) &gt; 0 :
   status = conversation["ancestors"][0]
   status_id = status["id"]
   conversation = mastodon.status_context(status_id)
</code></pre>

<p>Next, we need to create a data structure to hold the conversation.  We'll start by adding to it the first status in the conversation:</p>

<pre><code class="language-python">tree = Tree()

tree.create_node(status["uri"], status["id"])
</code></pre>

<p>Finally, we add any replies which are in the <code>descendants</code>. It is possible that some earlier statuses have been deleted. So we won't add any status which are replies to deleted statuses:</p>

<pre><code class="language-python">for status in conversation["descendants"] :
   try :
      tree.create_node(status["uri"], status["id"], parent=status["in_reply_to_id"])
   except :
      #  If a parent node is missing
      print("Problem adding node to the tree")
</code></pre>

<p>That's it! Let's show the tree:</p>

<pre><code class="language-python">tree.show()
</code></pre>

<p>Here's what it should look like:</p>

<pre><code class="language-text">2022-11-14 20:02 Edent: Today I was meant to be flying in to San Francisco to attend Twitter's Developer Conference - Chirp.Twitter had paid for my flights and hotel, because I was one of their developer insiders. I planned to spend the week meeting friends old and new.Instead, Alan the Hyperprat canceled the conference. So I'm staying in the UK.So I'm going to spend the week hacking on Mastdon's #API and building cool shit.  That'll show him!You can see what I'm working on at https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/ https://mastodon.social/users/Edent/statuses/109343943300929632
├── 2022-11-14 20:10 Edent: Oh! And I was meant to be attending a Belle &amp; Sebastian gig tonight. I canceled those tickets for I could fly to SF.So far, I reckon Alan's acquisition of Twitter has cost me close to £190.Wonder if he's good for the money? https://mastodon.social/users/Edent/statuses/109343972435801664
│   ├── 2022-11-14 20:14 thehodge: @Edent reminds me of the time I was booked to speak at a conference in Munich and I excitedly booked a behind the scenes tour of the worlds largest miniature city!Then the company went under!Gutted. https://mastodon.social/users/thehodge/statuses/109343989481494630
│   ├── 2022-11-14 21:16 Janiqueka: @Edent the way my bill for him keeps increasing https://mastodon.online/users/Janiqueka/statuses/109344233355230523
│   ├── 2022-11-14 21:19 henry: @Edent I was due to be at B&amp;S tomorrow but it’s been postponed again.. not sure if that makes it better or worse for you! https://social.lc/users/henry/statuses/109344244402822729
│   │   └── 2022-11-15 04:53 Edent: @henry again!? Ah well!Hope you get to see them soon. https://mastodon.social/users/Edent/statuses/109346031194446940
│   ├── 2022-11-15 09:18 Amandafclark: @Edent send him an invoice :) https://mastodon.social/users/Amandafclark/statuses/109347071811426672
│   └── 2022-11-15 11:29 Edent: One of the #MastodonAPI projects I'm working on is a better way to view long &amp; complex threads.You may have seen me build something similar for the other site a while ago - demo at https://shkspr.mobi/blog/2021/09/augmented-reality-twitter-conversations/ - so I'm hoping I can do something similarly interesting.Main limitation is getting *all* of the conversation threads. It looks like the context API isn't paginated. But I might be being thick. https://mastodon.social/users/Edent/statuses/109347587353822637
│       ├── 2022-11-15 11:36 bensb: @Edent Excellent project. You might have seen, but there's also this feature request for better 🧵 handling: https://github.com/mastodon/mastodon/issues/8615 https://genomic.social/users/bensb/statuses/109347612990393791
│       ├── 2022-11-15 11:39 Edent: Cor! That @katebevan is good for engagement! Look at all those conversations she's kicked off! https://mastodon.social/users/Edent/statuses/109347627634008550
│       │   ├── 2022-11-15 11:58 Edent: Indeed, how could they be?That means that ID of a reply is different depending on where you see it.So the ID of this post is:mastodon. social /@ edent/ 123456But when you see it on your server, it might appear as:your. server /@ edent/ 987654The #MastodonAPI copes with this really well. But it is a mite confusing to get one's head around. https://mastodon.social/users/Edent/statuses/109347703064222520
│       │   │   ├── 2022-11-15 12:02 erincandescent: @Edent the numeric IDs are not part of the protocol - it's all URL based. Pleroma uses UUIDs for example https://queer.af/users/erincandescent/statuses/109347716173491502
│       │   │   │   └── 2022-11-15 12:06 Edent: @erincandescent oh! That's interesting. Thanks. https://mastodon.social/users/Edent/statuses/109347734283971306
</code></pre>

<p>Once you have a tree, you can format the contents however you like.</p>

<h2 id="grab-the-code"><a href="https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/#grab-the-code">Grab the code</a></h2>

<p>You can <a href="https://codeberg.org/edent/Mastodon_Tools">download the code for my Mastodon API tools from CodeBerg</a>. Enjoy!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=44124&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2022/11/getting-started-with-mastodons-conversations-api/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[How to search Mastodon by date & time]]></title>
		<link>https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/</link>
					<comments>https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 14 Nov 2022 12:34:40 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=44028</guid>

					<description><![CDATA[Two years ago to the day, I built Twistory - a service for seeing what you posted on Twitter on this day in previous years.   If you&#039;ve ever used Facebook, you&#039;ll know how it is supposed to work.  You see posts which show that exactly 5 years ago you were starting a new job, 6 years ago you were at a wedding, etc.  The Twitter version never really worked properly because the Twitter API doesn&#039;t…]]></description>
										<content:encoded><![CDATA[<p>Two years ago to the day, I built <a href="https://shkspr.mobi/blog/2020/11/introducing-on-this-day-in-twistory/">Twistory</a> - a service for seeing what you posted on Twitter on this day in previous years.   If you've ever used Facebook, you'll know how it is supposed to work.  You see posts which show that exactly 5 years ago you were starting a new job, 6 years ago you were at a wedding, etc.</p>

<p>The Twitter version never really worked properly because the Twitter API doesn't support searching for historic Tweets.  What I had to do was manually build search queries like: <a href="https://twitter.com/search?q=from%3Aedent%20(until%3A2008-11-15%20since%3A2008-11-14)">?q=from:edent (until:2011-11-15 since:2011-11-14)</a> and redirect people to the website.</p>

<p>Eugh!</p>

<p>I'm trying to build something similar for the Mastodon social network. Yes, I know it is new to you - but some of us have been there for several years.</p>

<p>So here's how to search Mastodon for posts made on specific dates!</p>

<p>(<a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#final-code">Skip to the code and ignore all the exciting preamble.</a>)</p>

<p>Sadly, the <a href="https://docs.joinmastodon.org/methods/search/">Mastodon Search API is still quite basic</a>.  It isn't possible to directly search by user or by data parameters.</p>

<p>If you download the archive of all your posts, you'll find an ActivityPub feed called <code>outbox.json</code> - which is a collection of everything you've ever posted.</p>

<p>It can be parsed using <a href="https://stedolan.github.io/jq/">jq</a> to get all of the statuses posted on a specific day:</p>

<pre><code class="language-_">cat outbox.json | jq ".orderedItems[] | select (.published | fromdateiso8601 &gt; 1636533813) | select (.published | fromdateiso8601 &lt; 1636620302)"
</code></pre>

<p>The <code>fromdateiso8601</code> are the Unix epoch times from 365 days ago and 364 days ago.</p>

<p>So, conceptually, it's possible to build - as long as you're willing to download your data and manually parse it.  Let's see if we can do a little better than that.</p>

<h2 id="building-in-python"><a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#building-in-python">Building in Python</a></h2>

<p>We're going to build this using Python3 and the <a href="https://github.com/halcy/Mastodon.py">Mastodon.py</a> library.</p>

<p>Install the library on the command line with:</p>

<pre><code class="language-_">pip3 install -U Mastodon.py
</code></pre>

<p>Now, launch Python and load the library:</p>

<pre><code class="language-python">from mastodon import Mastodon
</code></pre>

<p>We'll need an API key. Go to the website of your Mastodon instance. In settings, there should be an option called "Development". Use that to create a new app which has the "Read" scope.</p>

<p>Once created, we will use the "Your access token" which will be a long string of random letters and numbers.  In this example, we'll be using <code>abc123</code>. Your real access token will be longer!</p>

<p>Let's set up a connection to your Mastodon instance:</p>

<pre><code class="language-python">instance = "https://mastodon.example.com"
mastodon = Mastodon( api_base_url=instance, access_token="abc123" )
</code></pre>

<p>Next, we need your user ID. This isn't your @ name, instead it is the numerical ID assigned by the server.  For this, we need the <a href="https://docs.joinmastodon.org/methods/accounts/#verify_credentials">Verify account credentials</a> API call:</p>

<pre><code class="language-python">mastodon.me()
</code></pre>

<p>That produces:</p>

<pre><code class="language-JSON">{
   'id': 7112,
   'username': 'Edent',
   'acct': 'Edent',
   'display_name': 'Terence Eden',
   ...
</code></pre>

<p>Looks like I was a pretty early adopter!</p>

<p>Getting the last 20 statuses is:</p>

<pre><code class="language-python">me = mastodon.me()
my_id = me["id"]
mastodon.account_statuses(id = my_id)
</code></pre>

<p>The problem is, that can only receive a maximum of 40 statuses at a time.</p>

<pre><code class="language-python">statuses = mastodon.account_statuses(id = my_id, limit="40")
</code></pre>

<p>We need to use <a href="https://mastodonpy.readthedocs.io/en/stable/12_utilities.html">Pagination</a>. The API makes it pretty easy to grab the next page. There's also a call to get <em>every</em> post.</p>

<p>Be warned - this can take a <em>long</em> time. If you have thousands of posts it may take a few minutes. It can also quickly deplete your API rate limits. Use with caution!</p>

<p>We can reduce some of the load by excluding anything you've "boosted".</p>

<pre><code class="language-python">statuses = mastodon.account_statuses(id = my_id, limit="40", exclude_reblogs=True)
all_statuses = mastodon.fetch_remaining(statuses)
</code></pre>

<p>You can run <code>len(all_statuses)</code> to see how many you have retrieved.</p>

<p>The next step is finding all the posts which happened on a certain day each year.  Let's say we want every post which happened on <em>a</em> 14th of February.</p>

<p>The <a href="https://mastodonpy.readthedocs.io/en/stable/02_return_values.html">library returns timestamps as Python DateTime objects</a>.</p>

<pre><code class="language-python">status_date = all_statuses[1]["created_at"]
</code></pre>

<p>Shows</p>

<pre><code class="language-python">datetime.datetime(2016, 11, 1, 17, 4, 23, 842000, tzinfo=tzutc())
</code></pre>

<p>The datetime library is pretty handy. You can find the day using <code>status_date.day</code> and month with <code>status_date.month</code>.</p>

<p>This means we can loop through every status and show only the ones we care about.</p>

<pre><code class="language-python">for status in all_statuses:
     if (status["created_at"].day == 14 and status["created_at"].month == 2):
             print(status["uri"])
</code></pre>

<p>That will get you a list of URls which contain posts made on a specific day in previous years.</p>

<h3 id="putting-it-all-together"><a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#putting-it-all-together">Putting it all together</a></h3>

<pre><code class="language-python">from mastodon import Mastodon
instance = "https://mastodon.example.com"
mastodon = Mastodon( api_base_url=instance, access_token="abc123" )
me = mastodon.me()
my_id = me["id"]
mastodon.account_statuses(id = my_id)
statuses = mastodon.account_statuses(id = my_id, limit="40", exclude_reblogs=True)
all_statuses = mastodon.fetch_remaining(statuses)
for status in all_statuses:
   if (status["created_at"].day == 14 and status["created_at"].month == 2 and status["created_at"].year &lt; 2022) :
      print(status["uri"])
</code></pre>

<h2 id="building-a-time-machine"><a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#building-a-time-machine">Building a time machine</a></h2>

<p>All of the above works, but is pretty inefficient because there's no way to search for specific timeframes on Mastodon. <em>Or so I thought!</em></p>

<blockquote class="social-embed" id="social-embed-109321406286622035" lang=""><header class="social-embed-header"><a href="https://mastodon.social/@Gargron" class="social-embed-user"><img class="social-embed-avatar" src="data:image/webp;base64,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" alt=""><div class="social-embed-user-names"><p class="social-embed-user-names-name">Gargron</p>@Eugen Rochko</div></a><img class="social-embed-logo" alt="Mastodon" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-label='Mastodon' role='img' viewBox='0 0 512 512' fill='%23fff'%3E%3Cpath d='m0 0H512V512H0'/%3E%3ClinearGradient id='a' y2='1'%3E%3Cstop offset='0' stop-color='%236364ff'/%3E%3Cstop offset='1' stop-color='%23563acc'/%3E%3C/linearGradient%3E%3Cpath fill='url(%23a)' d='M317 381q-124 28-123-39 69 15 149 2 67-13 72-80 3-101-3-116-19-49-72-58-98-10-162 0-56 10-75 58-12 31-3 147 3 32 9 53 13 46 70 69 83 23 138-9'/%3E%3Cpath d='M360 293h-36v-93q-1-26-29-23-20 3-20 34v47h-36v-47q0-31-20-34-30-3-30 28v88h-36v-91q1-51 44-60 33-5 51 21l9 15 9-15q16-26 51-21 43 9 43 60'/%3E%3C/svg%3E"></header><section class="social-embed-text"><p><span class="h-card" translate="no"><a href="https://mastodon.social/@Edent" class="u-url mention">@<span>Edent</span></a></span> Mastodon uses snowflake IDs, which means the IDs correspond to datetimes. You would need to look up how to construct the ID from a date, but you can use this to retrieve posts by date using min_id/max_id.</p><div class="social-embed-media-grid"></div></section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://mastodon.social/@Gargron/109321406286622035"><span aria-label="10 likes" class="social-embed-meta">❤️ 10</span><span aria-label="1 replies" class="social-embed-meta">💬 1</span><span aria-label="3 reposts" class="social-embed-meta">🔁 3</span><time datetime="">20:31 - Thu 10 November 2022</time></a></footer></blockquote>

<p>If we can calculate the maximum and minimum IDs for a given day, we will have a <em>much</em> more efficient search!</p>

<p>Let's dive in to the <a href="https://github.com/mastodon/mastodon/blob/main/lib/mastodon/snowflake.rb">Mastodon Snowflake code</a>. It is really well documented:</p>

<blockquote><p>Our ID will be composed of the following:
6 bytes (48 bits) of millisecond-level timestamp
2 bytes (16 bits) of sequence data</p></blockquote>

<p>OK! Let's look at a typical Mastodon Status ID <code>mastodon.social/@Edent/109326536843609210</code>, it was posted on 2022-11-11 at 18:16.</p>

<p>Let's take the ID <code>109326536843609210</code> and perform a <a href="https://wiki.python.org/moin/BitwiseOperators">bitwise shift</a> on it.</p>

<pre><code class="language-python">print(109326536843609210 &gt;&gt; 16)
1668190564630
</code></pre>

<p>Hey! That looks a bit like a UNIX timestamp!  The last three numbers will be the sequence, so we can eliminate them and see what happens if we convert it to a timestamp.</p>

<pre><code class="language-python">from datetime import datetime
datetime.fromtimestamp(1668190564630/1000)
datetime.datetime(2022, 11, 11, 18, 16, 4, 630000)
</code></pre>

<p>Nice!  So we can go backward and take a date - say this time last year - and convert it to a maximum and minimum ID.</p>

<pre><code class="language-python">min_id = ( int( datetime(2022,11,11,00,00).timestamp() ) &lt;&lt; 16 ) * 1000
max_id = ( int( datetime(2022,11,11,23,59).timestamp() ) &lt;&lt; 16 ) * 1000
</code></pre>

<p>Which gives us <code>109322226892800000</code> and <code>109327885271040000</code> respectively.</p>

<p>Let's try that with the API!</p>

<h3 id="final-code"><a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#final-code">Final Code</a></h3>

<pre><code class="language-python">from datetime import datetime, timedelta
from mastodon import Mastodon

#  Set up access
instance = "https://mastodon.example"
mastodon = Mastodon( api_base_url=instance, access_token="abc123" )

#  Get user's info
me = mastodon.me()
my_id = me["id"]
year_joined = me["created_at"].year

#  Today's date
year_now  = datetime.now().year
month_now = datetime.now().month
day_now   = datetime.now().day

#  Counter
year_counter = year_now

#  Loop through previous years
#  Start with last year and go down until the user joined
while (year_counter &gt;= year_joined ) :
   year_counter -= 1
   #  The end of today is the start of tomorrow
   #  This means yesterday can take into account leap-years
   today_end = datetime(year_counter, month_now, day_now, 00, 00) + timedelta(days=1)
   yesterday_end = today_end - timedelta(days=1)
   #  Bitwise shift the integer representation and convert to milliseconds
   max_id = ( int( today_end.timestamp() )     &lt;&lt; 16 ) * 1000
   min_id = ( int( yesterday_end.timestamp() ) &lt;&lt; 16 ) * 1000
   #  Call the API
   statuses = mastodon.account_statuses(id = my_id, max_id=max_id, min_id=min_id, limit="40", exclude_reblogs=True)
   #  Fetch further statuses if there are any
   all_statuses = mastodon.fetch_remaining(statuses)
   #  Print the date and URl
   for status in all_statuses :
      print( str(status["created_at"]) + " " + status["uri"] )
</code></pre>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/#next-steps">Next Steps</a></h2>

<p>It works on my machine!  But that's not really good enough.  Ideally I'd like to turn this in to a web app which people could use with their own account.</p>

<p>If you're interested in helping out with that <a href="https://codeberg.org/edent/Mastodon_Tools">grab the code</a> or drop me a line!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=44028&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2022/11/building-an-on-this-day-service-for-mastodon/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[A quick (and silly) way to create generative avatars]]></title>
		<link>https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/</link>
					<comments>https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 08 Jul 2022 11:34:28 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[faustshop]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=42763</guid>

					<description><![CDATA[I was asked to help create some pseudo-NFT style avatars for Cambridge Digital Humanities&#039; Faust Shop project.  Something with vaguely the same æsthetic as those daft &#34;Crypto Punks&#34;.  You can see it in action partway through this TikTok video.    @cambridgeuniversity Visit the #Faust Shop and see what happens when you make a deal with your digital double. #Devil #EduTok #Cambridge #Performance …]]></description>
										<content:encoded><![CDATA[<p>I was asked to help create some pseudo-NFT style avatars for Cambridge Digital Humanities' <a href="https://www.faust-shop.org/">Faust Shop project</a>.  Something with vaguely the same æsthetic as those daft "Crypto Punks".</p>

<p>You can see it in action partway through <a href="https://www.tiktok.com/@cambridgeuniversity/video/7109505500160494853">this TikTok video</a>.</p>

<blockquote class="tiktok-embed" cite="https://www.tiktok.com/@cambridgeuniversity/video/7109505500160494853" data-video-id="7109505500160494853" data-embed-from="oembed" style="max-width:605px; min-width:325px;"> <section> <a target="_blank" title="@cambridgeuniversity" href="https://www.tiktok.com/@cambridgeuniversity?refer=embed">@cambridgeuniversity</a> <p>Visit the <a title="faust" target="_blank" href="https://www.tiktok.com/tag/faust?refer=embed">#Faust</a> Shop and see what happens when you make a deal with your digital double. <a title="devil" target="_blank" href="https://www.tiktok.com/tag/devil?refer=embed">#Devil</a> <a title="edutok" target="_blank" href="https://www.tiktok.com/tag/edutok?refer=embed">#EduTok</a> <a title="cambridge" target="_blank" href="https://www.tiktok.com/tag/cambridge?refer=embed">#Cambridge</a> <a title="performance" target="_blank" href="https://www.tiktok.com/tag/performance?refer=embed">#Performance</a> <a title="philosophy" target="_blank" href="https://www.tiktok.com/tag/philosophy?refer=embed">#Philosophy</a></p> <a target="_blank" title="♬ original sound - Cambridge University" href="https://www.tiktok.com/music/original-sound-7109505523976702726?refer=embed">♬ original sound - Cambridge University</a> </section> </blockquote>

<script async="" src="https://www.tiktok.com/embed.js"></script>

<p>Here are some examples of the types of avatars I generated:</p>

<p><img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/smileglasses.png" alt="" width="230" height="230" class="alignnone size-medium wp-image-42766"><img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/humanglasses.png" alt="" width="230" height="230" class="alignnone size-medium wp-image-42767"><img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/sadglasses.png" alt="" width="230" height="230" class="alignnone size-medium wp-image-42768"><img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/sadhalo.png" alt="" width="230" height="230" class="alignnone size-medium wp-image-42769"><img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/halo.png" alt="" width="230" height="230" class="alignnone size-medium wp-image-42770"><img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/devil.png" alt="" width="230" height="230" class="alignnone size-medium wp-image-42771"></p>

<p>First off, how do generative avatars work? Obviously an artist hasn't drawn hundreds of thousands of unique pieces of art. Instead, they have created a few base elements and then use those to generate millions of combinations.</p>

<p>Here's a very quick guide to making very bad generative art.</p>

<h2 id="start-with-a-random-string"><a href="https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/#start-with-a-random-string">Start with a random string</a></h2>

<p>Those planet-burning NFTs are derived from a cryptographic hash of something. For example, the address of a cryptocurrency wallet or transaction.  We're going to use something much simpler; the humble <a href="https://en.wikipedia.org/wiki/MD5">MD5 algorithm</a>.</p>

<p>MD5 shouldn't be used for anything which needs to be kept safe and secure. But for our purposes, it generates 32 sufficiently-random hexadecimal values. For example <code>d41d8cd98f00b204e9800998ecf8427e</code>.</p>

<p>In our case, we take some data from the user - for example their email address - and then hash it with MD5 to produce 32 hexadecimal characters.  Here's some code in PHP:</p>

<pre><code class="language-_">$hash  =  md5($email);
$characters = str_split($hash);
</code></pre>

<h2 id="extract-meaning-from-noise"><a href="https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/#extract-meaning-from-noise">Extract meaning from noise</a></h2>

<p>Let's take the above MD5 hash and get something useful out of it.  For example, the first three characters could be the colour of the background <code>#d41</code>. The next three could be the colour of the avatar's skin <code>#d8c</code>. And so on.</p>

<pre><code class="language-_">//  A canvas to draw the avatar on
$canvas = imagecreatetruecolor(23, 23);
// Generated colours
$colour_background = imagecolorallocate($canvas,
    hexdec($characters[0].$characters[0]),
    hexdec($characters[1].$characters[1]),
    hexdec($characters[2].$characters[2]),
);
</code></pre>

<p>We can also use the characters as the basis for decision-making. For example, if the 7th character is greater than 5, the avatar will have a big nose. If it's less than 5, it will have a small nose.</p>

<pre><code class="language-_">if ( hexdec($characters[7]) &gt; 5 ) {
    //  Big nose
    imagefilledrectangle($canvas, 10, 12, 10, 13, $colour_nose);
} else {
    imagefilledrectangle($canvas, 10, 12, 10, 12, $colour_nose);
}
</code></pre>

<p>We can also use that to decide whether the avatar should have a specific feature. For example, if the 8th character is odd then draw a halo, but don't draw anything if it is even.</p>

<h2 id="generate-base-elements"><a href="https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/#generate-base-elements">Generate base elements</a></h2>

<p>For this project, we didn't need lots of different elements. A few simple ones were enough to generate hundreds of thousands of combinations:</p>

<ul>
<li>Background colour</li>
<li>Skin colour</li>
<li>Nose colour</li>
<li>Nose shape</li>
<li>Lip colour</li>
<li>Lip shape</li>
<li>Eye colour</li>
<li>Eye direction</li>
<li>Decorations like halos, horns, glasses, and earrings</li>
<li>Whether it is facing left or right</li>
</ul>

<h2 id="limitations"><a href="https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/#limitations">Limitations</a></h2>

<p>There are a few problems with this approach. There is no guarantee that the background colour will be sufficiently different from the foreground colour. If the mouth is the same colour as the skin, it will disappear.</p>

<p>If the colours are broadly similar, they might be hard to visually distinguish. As in this example:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/05/low-contrast.png" alt="" width="230" height="230" class="aligncenter size-full wp-image-42765">

<p>Because this was a quick project, there wasn't much time to create lots of varieties of decorations. For example, hair is always a rectangle - albeit one which varies in height.</p>

<p>Pixel art is an unsubtle medium. There's no way to add anything small - like zits or eyebrows - to a face which is only a few pixels wide.</p>

<h2 id="final-thoughts"><a href="https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/#final-thoughts">Final thoughts</a></h2>

<p>Cryptographic hash visualisations were first conceived, as far as I am aware, <a href="https://users.ece.cmu.edu/~adrian/projects/validation/index.html">at the tail end of the last century</a>. They have been a prominent feature of some PKI packages for <a href="http://undeadly.org/cgi?action=article&amp;sid=20080615022750">over a decade</a>.</p>

<p>Humans have difficulty recognising long strings of digits. But we're reasonably good at recognising images - especially faces. Using generative art to create visually distinct faces <em>could</em> be a reasonable way to help people validate whether they've previously encountered a cryptographic signature.</p>

<p>But, for now, this is just a simple implementation for generating something that looks and behaves like a basic NFT.</p>

<hr>

<p>This post <a href="https://www.faust-shop.org/generate-your-souls-avatar/">first appeared on the Faust-Shop website</a></p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=42763&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2022/07/a-quick-and-silly-way-to-create-generative-avatars/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[How to migrate Google For Your Domain to normal Gmail]]></title>
		<link>https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/</link>
					<comments>https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 23 Jan 2022 12:34:54 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[gmail]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=41748</guid>

					<description><![CDATA[Google has decided to fuck over its early adopters. Way back in 2006, Google announced Google Apps for Your Domain. Basically it was Gmail - but you could use your own domain. No more example@gmail.com now you could be me@example.com. Hurrah!  At the time, they said:  organizations that sign up during the beta period will not ever have to pay for users accepted during that period (provided Google …]]></description>
										<content:encoded><![CDATA[<p>Google has decided to fuck over its early adopters. Way back in 2006, Google announced <a href="http://googlepress.blogspot.com/2006/08/google-launches-hosted-communications_28.html">Google Apps for Your Domain</a>. Basically it was Gmail - but you could use your own domain. No more <code>example@gmail.com</code> now you could be <code>me@example.com</code>. Hurrah!</p>

<p>At the time, they said:</p>

<blockquote><p>organizations that sign up during the beta period will not ever have to pay for users accepted during that period (provided Google continues to offer the service).</p></blockquote>

<p>Google still offers the service - since renamed G-Suite, then Workplace, and next week to be renamed Google Plus for Work Home Edition Beta. <a href="https://arstechnica.com/gadgets/2022/01/google-tells-free-g-suite-users-pay-up-or-lose-your-account/">But they're now going to charge people for it</a>. Fair enough I guess⸮ Of course, Google don't offer a personal plan or a family plan - you have to sign up to an ENTERPRISE PLAN FOR SRS BZNIZ. Which is about £50 per user per year.</p>

<p>So, here's a quick(ish) guide to transferring your domain to a free* <code>@Gmail.com</code> account.
<small>* For now. Perhaps they'll start charging tomorrow.</small></p>

<p><strong>Note:</strong> Obviously the better solution is to leave Google and go to Zoho, ProtonMail, or some other company. But some of us are used to the Gmail app or don't have the patience to run our own email server.</p>

<h2 id="table-of-contents"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#table-of-contents">Table of Contents</a></h2>

<ul>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#sign-up-for-a-new-gmail-account">Sign up for a new Gmail account</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#import-your-mail">Import your mail</a>

<ul>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#or-export-/-import">Or export / import</a></li>
</ul></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#stop-sending-mail-to-google">Stop sending mail to Google</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#catch-all">Catch All</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#1-forward-your-email">1. Forward your email</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#2-check-your-email">2. Check your email</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#send-email-from-gmail-using-your-domain">Send email from Gmail using your domain</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#summing-up">Summing Up</a></li>
<li><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#fuck-you-google">Fuck You Google</a></li>
</ul>

<h2 id="sign-up-for-a-new-gmail-account"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#sign-up-for-a-new-gmail-account">Sign up for a new Gmail account</a></h2>

<p>Go to <a href="https://gmail.com/">Gmail.com</a> and sign up for a new account. If your domain was <code>example.biz</code> then I recommend signing up for <code>example.biz@gmail.com</code>. That'll be easy to remember.</p>

<p>Just go through the usual sign up process. Remember to pick a strong password and to turn on 2FA.</p>

<h2 id="import-your-mail"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#import-your-mail">Import your mail</a></h2>

<p>Google offers a mail import feature. You can find it in "Settings".
<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/Gmail-import-Settings.png" alt="Screenshot of the Gmail import Settings." width="939" height="79" class="aligncenter size-full wp-image-41749"></p>

<p>There is <a href="https://support.google.com/mail/answer/21289?">some documentation for importing mail</a></p>

<p><a href="https://support.google.com/mail/answer/7104828?hl=en">Turn on POP in your old email</a>. Then give your <em>new</em> Gmail account your <em>old</em> account's username and password. 
For example <code>your.address@example.biz</code> as the username, the server will be <code>pop.gmail.com</code> with port <code>995</code>.  And then your <em>old</em> password.</p>

<p><strong>Note:</strong> If you have 2-Factor-Authentication turned on, you will either need to turn it off, or <a href="https://support.google.com/accounts/answer/185833">set up an App Specific Password</a>. That will let Gmail log into Gmail to get your Gmail.</p>

<p>This process will take a long time - mine took about 24 hours. Oh, and you will find that Gmail will mark a bunch of legitimate email as spam. Better double check everything!</p>

<p>You'll also probably want to mark everything as "read".
<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/24000-emails.png" alt="Pop up from Gmail asking if I want to mark over 24,000 as read." width="630" height="232" class="aligncenter size-full wp-image-41766"></p>

<p>Google doesn't make this stuff easy!</p>

<h3 id="or-export-import"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#or-export-import">Or export / import</a></h3>

<p>Apparently I have 3.5GB of data in my email.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/export.png" alt="Screenshot showing 3.5GB used." width="275" height="50" class="aligncenter size-full wp-image-41750">
You can download it all via <a href="https://support.google.com/mail/answer/10016932?hl=en-GB">Gmail Export</a> - or you can <a href="https://support.google.com/a/answer/100458">export via the admin tools</a>.</p>

<p>I went via <a href="https://takeout.google.com">Google Takeout</a> - which also allowed me to grab anything in Google Docs.</p>

<p><strong>Note:</strong> This can take a while - but it is useful to have a backup.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/Google-Takeout.png" alt="This process can take a long time (possibly hours or days) to complete. You'll receive an email when your export is done." width="822" height="237" class="aligncenter size-full wp-image-41760">
It took a couple of hours to generate mine.</p>

<p><a href="https://support.google.com/mail/thread/29798218/how-can-i-import-a-gmail-export-file?hl=en">Google doesn't support importing a Gmail export</a>. Because they hate you and want you to suffer.</p>

<p>There is a <a href="https://github.com/jay0lee/got-your-back/wiki#--action-restore-mbox">command-line tool for restoring an MBOX backup to Gmail</a>. I've not tried it though.</p>

<h2 id="stop-sending-mail-to-google"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#stop-sending-mail-to-google">Stop sending mail to Google</a></h2>

<p>If you set up Google Apps For Your Domain - your <a href="https://mxtoolbox.com">MX Records</a> will be directing all email to Gmail's servers. You'll need to change that. Here's where things get tricky.</p>

<p>Log in to your domain management console. I can't tell you how to do this. You will need to go to something like "Zone Editor" where you'll see something like this:
<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/Zone-Editor.png" alt="Screenshot of lots of domain records" width="846" height="555" class="aligncenter size-full wp-image-41751">
You will need to change all of your mail related records so that they point to your web-host's mail provider. Again, I can't help you with this. Speak to your hosting provider to find the details you need.</p>

<h2 id="catch-all"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#catch-all">Catch All</a></h2>

<p>You will need to set up a "catch all" email address - this is sometimes called "default routing". You want <em>any</em> email to <code>@example.biz</code> to be received rather than rejected.</p>

<p>You may need to set up a new username and password. Keep these safe - you may need them later.</p>

<p>Now your email is being received by your host. You have two options</p>

<h2 id="1-forward-your-email"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#1-forward-your-email">1. Forward your email</a></h2>

<p>This is harder to set up, but I find it to be quicker at delivering email.</p>

<p>Find the email forwarding set up with your domain provider. Set it so that every email to your catch-all is redirected to <code>example.biz@gmail.com</code></p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/Default-Address.png" alt="Screenshot showing default email routing." width="752" height="487" class="aligncenter size-full wp-image-41753">

<p>That means each email to your domain will go to your new gmail address.</p>

<h2 id="2-check-your-email"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#2-check-your-email">2. Check your email</a></h2>

<p>This is simpler, but it does mean that your email isn't always delivered straight away.</p>

<p>Back in Gmail, go to settings, and select "Import mail and contacts"
<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/01/Gmail-Import-Options.png" alt="Gmail Import Options screen." width="525" height="391" class="aligncenter size-full wp-image-41752">
Give Gmail your catch-all email's address and password. Google will periodically check your mail and move it to your new Gmail account.</p>

<p>OK! You can now receive <code>whatever@example.biz</code> via Gmail! But what about sending?</p>

<h2 id="send-email-from-gmail-using-your-domain"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#send-email-from-gmail-using-your-domain">Send email from Gmail using your domain</a></h2>

<p>Follow the <a href="https://support.google.com/mail/answer/22370?hl=en-GB#zippy=">guide to sending Gmail using an alias</a>. Basically, give them the username and password for the catch-all email address you created.</p>

<p>This will send email via your domain provider's mailservers.</p>

<p>You <em>may</em> need to set up DMARC, SPF, and DKIM on your domain. This is outside the scope of this article and you should speak to your domain provider.</p>

<h2 id="summing-up"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#summing-up">Summing Up</a></h2>

<ol>
<li>Register a new Gmail address</li>
<li>Import your old email into your new address</li>
<li>Remove the Gmail MX records on your domain</li>
<li>Add MX records to point to your host's mailserver</li>
<li>Set up a catch-all email address</li>
<li>Forward all email to Gmail</li>
<li>Set up an email alias which sends via your mail servers</li>
</ol>

<h2 id="fuck-you-google"><a href="https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/#fuck-you-google">Fuck You Google</a></h2>

<p>Google could have implemented this as a one-click solution. "Hey, if you don't want to pay, click here to transfer to a personal Gmail account. You'll lose all these great workspace features, but keep your email address."</p>

<p>They didn't. Which is a pity.</p>

<p>If you can, switch your email completely away from Google. If you can't, I hope the above is useful.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=41748&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2022/01/how-to-migrate-google-for-your-domain-to-normal-gmail/feed/</wfw:commentRss>
			<slash:comments>24</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Turning an eInk screen into a monochrome art gallery]]></title>
		<link>https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/</link>
					<comments>https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 26 Sep 2021 11:34:43 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[art]]></category>
		<category><![CDATA[eink]]></category>
		<category><![CDATA[images]]></category>
		<category><![CDATA[nook]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=40394</guid>

					<description><![CDATA[Previously on Terence Eden&#039;s Blog: I turned an old eReader into an Information Screen.  This time, I&#039;m taking a different Nook, and turning it into a magic gallery.  Here&#039;s what it looks like in action:  Terence Eden is on Mastodon@edentUpcycled an old eReader into an art frame.Displays a new black &#38; white piece of art from Flickr every few minutes.Full write-up this weekend, but pretty…]]></description>
										<content:encoded><![CDATA[<p><em>Previously on Terence Eden's Blog:</em> <a href="https://shkspr.mobi/blog/2020/02/turn-an-old-ereader-into-an-information-screen-nook-str/">I turned an old eReader into an Information Screen</a>.</p>

<p>This time, I'm taking a different Nook, and turning it into a magic gallery.  Here's what it looks like in action:</p>

<blockquote class="social-embed" id="social-embed-1440788013236195335" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/edent" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRkgBAABXRUJQVlA4IDwBAACQCACdASowADAAPrVQn0ynJCKiJyto4BaJaQAIIsx4Au9dhDqVA1i1RoRTO7nbdyy03nM5FhvV62goUj37tuxqpfpPeTBZvrJ78w0qAAD+/hVyFHvYXIrMCjny0z7wqsB9/QE08xls/AQdXJFX0adG9lISsm6kV96J5FINBFXzHwfzMCr4N6r3z5/Aa/wfEoVGX3H976she3jyS8RqJv7Jw7bOxoTSPlu4gNbfXYZ9TnbdQ0MNnMObyaRQLIu556jIj03zfJrVgqRM8GPwRoWb1M9AfzFe6Mtg13uEIqrTHmiuBpH+bTVB5EEQ3uby0C//XOAPJOFv4QV8RZDPQd517Khyba8Jlr97j2kIBJD9K3mbOHSHiQDasj6Y3forATbIg4QZHxWnCeqqMkVYfUAivuL0L/68mMnagAAA" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Terence Eden is on Mastodon</p>@edent</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody">Upcycled an old eReader into an art frame.<br>Displays a new black &amp; white piece of art from Flickr every few minutes.<br>Full write-up this weekend, but pretty straightforward to do. <a href="https://twitter.com/edent/status/1440788013236195335/photo/1">pic.x.com/ttvrbhz3ee</a><div class="social-embed-media-grid"><a href="https://pbs.twimg.com/media/E_603EtVcAYB5NZ.jpg" class="social-embed-media-link"><img class="social-embed-media" alt="A wooden frame surrounds an eInk screen. A monochrome photo of graffiti is displayed." src="data:image/webp;base64,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"></a><a href="https://pbs.twimg.com/media/E_606roUUAM7P4W.jpg" class="social-embed-media-link"><img class="social-embed-media" alt="The same frame showing a moody photograph." src="data:image/webp;base64,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"></a><a href="https://pbs.twimg.com/media/E_60-SmVUAgeFJB.jpg" class="social-embed-media-link"><img class="social-embed-media" alt="The same frame displays a black and white drawing." src="data:image/webp;base64,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"></a></div></section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/edent/status/1440788013236195335"><span aria-label="137 likes" class="social-embed-meta">❤️ 137</span><span aria-label="14 replies" class="social-embed-meta">💬 14</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2021-09-22T21:20:05.000Z" itemprop="datePublished">21:20 - Wed 22 September 2021</time></a></footer></blockquote>

<h2 id="video"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#video">Video</a></h2>

<p>With the front light off:</p>

<p></p><div style="width: 620px;" class="wp-video"><video class="wp-video-shortcode" id="video-40394-3" width="620" height="349" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2021/09/offweb.mp4?_=3"><a href="https://shkspr.mobi/blog/wp-content/uploads/2021/09/offweb.mp4">https://shkspr.mobi/blog/wp-content/uploads/2021/09/offweb.mp4</a></video></div><p></p>

<p>With the front light on:</p>

<p></p><div style="width: 620px;" class="wp-video"><video class="wp-video-shortcode" id="video-40394-4" width="620" height="349" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2021/09/onweb.mp4?_=4"><a href="https://shkspr.mobi/blog/wp-content/uploads/2021/09/onweb.mp4">https://shkspr.mobi/blog/wp-content/uploads/2021/09/onweb.mp4</a></video></div><p></p>

<h2 id="how-it-works"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#how-it-works">How it works</a></h2>

<p>Every few minutes, it finds a black and white artistic image from Flickr and displays it. Simple!</p>

<h2 id="inspiration"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#inspiration">Inspiration</a></h2>

<p><a href="https://debugger.medium.com/how-to-build-a-very-slow-movie-player-in-2020-c5745052e4e4">Ambient home cinema by Tom Whitwell</a></p>

<h2 id="build"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#build">Build</a></h2>

<p>I've previously <a href="https://shkspr.mobi/blog/2015/09/replacing-the-battery-on-a-barnes-noble-eink-nook/">replaced the battery on an eInk Nook</a>, so I know how <a href="https://www.ifixit.com/Teardown/Nook+Simple+Touch+with+GlowLight+Teardown/9301">easy they are to disassemble</a>. But for this I wanted to keep the plastic frame on, so that I'd be able to attach a more æsthetically pleasing wooden picture frame.</p>

<p>Finding a picture frame of the right size was a little tricky. The physical dimension of the unit are ~170mm x ~140mm. But the screen is only ~125mm x ~93mm .</p>

<p>I used <a href="https://www.eframe.co.uk/">eFrame</a> - they have a really handy website for custom sized frames and inserts. I ordered a mount for an 135mm x 103mm picture (they cut them slightly smaller but I didn't want any overhang) with 20mm top and bottom borders, and 35mm left and right borders. That gave me enough space to fit the Nook. But, as long as the aperture lets you see the eInk, and masks off the plastic frame, you should be OK.</p>

<p>I was also able to buy a frame without a glass front. As this is my first attempt, I went for simple and cheap wood - rather than something more ornate and artistic.  Total cost including shipping was £25.</p>

<h3 id="attaching-the-frame"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#attaching-the-frame">Attaching the frame</a></h3>

<p>It is useful to be able to get to the main "N" button - and there needs to be a gap for the charging cable. I thought about cutting out a little bit of the frame but, in the end, it was easier to attach the Nook to the frame using velcro and blu-tak. Because of the weight of the Nook, I used some sturdy hooks for the wall mount.</p>

<h2 id="power"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#power">Power</a></h2>

<p>There wasn't enough room in the frame for a normal USB cable, so I used a <a href="https://amzn.to/3CD6c7K">right-angled micro-USB cable</a>.  With the page refreshing every 5 minutes between 0600-2300, it got about 3 days of battery life - less if it used the Glow-Light. So a permanent wired connection seemed sensible.</p>

<h2 id="background"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#background">Background</a></h2>

<p>On the surface this seems quite simple - get a bunch of images and display them. But there are some limitations.</p>

<p>The original <a href="https://en.wikipedia.org/wiki/Nook_Simple_Touch">Nook Simple Touch</a> is, bless it, an old and slow device. I want to do the minimum amount of processing on it as possible. The screen resolution is only 600x800.  I don't want to convert colour artwork to black and white - that will just look messy. I also want the image size to roughly match the screen - so things aren't shrunk down.</p>

<p>So, I want a list of art which meets the following criteria:</p>

<ul>
<li>Intended for monochrome display<sup id="fnref:mono"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fn:mono" class="footnote-ref" title="OK, bit more complicated than that. eInk Pearl can display 16 shades of grey." role="doc-noteref">0</a></sup></li>
<li>Roughly 4:3 aspect ratio</li>
<li>Proper art<sup id="fnref:1"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fn:1" class="footnote-ref" title="Whatever that is..." role="doc-noteref">1</a></sup> by real artists<sup id="fnref:2"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fn:2" class="footnote-ref" title="Whoever they are..." role="doc-noteref">2</a></sup></li>
</ul>

<p>I started by looking at the Open Data produced by various museums and art galleries. Unfortunately, none of them indicated whether an artwork was greyscale. So, I turned to Flickr!</p>

<h2 id="using-the-flickr-api"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#using-the-flickr-api">Using the Flickr API</a></h2>

<p>Sadly, the Flickr API has been neglected for the best part of a decade. So the documentation is grossly out of date.</p>

<p>Two of the undocumented Search API calls are <code>styles</code> and <code>orientation</code>. I use them to search for black and white images in landscape orientation.
You can discover more by using the <a href="https://www.flickr.com/search/?text=">Flickr search website</a> and opening the "Advanced" panel.</p>

<p>The rest of the API call is quite standard. A keyword to search for ("art") - or whatever you want. Sorted by "interestingness" - although popularity is also a good metric. With <code>safe_search</code> turned off - no prudes in our house. And an image size <a href="https://www.flickr.com/services/api/misc.urls.html">no wider that 800px</a> - because serving a correctly scaled image means less work for the eReader.</p>

<h2 id="the-code"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#the-code">The Code</a></h2>

<p>It is almost embarrassing in its simplicity. It uses <a href="https://github.com/samwilson/phpflickr/">Sam Wilson's PHP library for Flickr</a>. It grabs 100 images, and then picks a random one to serve. It is resized to fit in a bounding box of 800x600 and repaginated to centre the image. The image is rotated it 90 degrees and served over HTTP with an appropriate caching policy. Nothing else.  Here it is<sup id="fnref:mit"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fn:mit" class="footnote-ref" title="Licenced as MIT - but it is so basic that I won't be annoyed if you do something else with it." role="doc-noteref">3</a></sup>.</p>

<pre><code class="language-php">require_once 'vendor/autoload.php';
$flickr = new \Samwilson\PhpFlickr\PhpFlickr("123465789");

$search_terms = ["art", "artistic", "painting", "drawing"];
$search_rand = array_rand($search_terms, 1);

$sort_terms = ["date-posted-asc", "date-posted-desc", "date-taken-asc", "date-taken-desc", "interestingness-desc", "interestingness-asc", "relevance"];
$sort_rand = array_rand($sort_terms, 1);

$s = $flickr-&gt;photos()-&gt;search(["text"        =&gt; $search_terms[$search_rand],
                                "per_page"    =&gt; "100",
                                "styles"      =&gt; "blackandwhite",
                                "orientation" =&gt; "landscape",
                                "sort"        =&gt; $sort_terms[$sort_rand],
                                "safe_search" =&gt;"3"
                               ]);

$number_of_photos = count($s["photo"]);

$random_photo = random_int(0, $number_of_photos - 1);

$p = $s["photo"][$random_photo];

$farm   = $p["farm"];
$server = $p["server"];
$id     = $p["id"];
$secret = $p["secret"];
$title  = $p["title"];

$image_url = "https://live.staticflickr.com/{$server}/{$id}_{$secret}_c.jpg";

$image = imagecreatefromjpeg( $image_url );

$cropped = imagecropauto( $image, IMG_CROP_DEFAULT );
if ( $cropped !== false ) { 
   imagedestroy($image);  
   $image = $cropped;
}

$x = imagesx( $image );
$y = imagesy( $image );

$width_ratio  = $x / 800;
$height_ratio = $y / 600;

// Scale
if ( $height_ratio &gt;= $width_ratio ) {
   $new_height = ( 600 / $y ) * $x;
   $scaled = imagescale( $image, $new_height, 600 );
} else {
   $scaled = imagescale( $image, 800, -1 );
}

$x = imagesx( $scaled );
$y = imagesy( $scaled );


// Make a background canvas
$canvas = imagecreatetruecolor( 800, 600 );
$black  = imagecolorallocate( $canvas, 0, 0, 0 );
imagefilledrectangle( $canvas, 0, 0, 800, 600, $black );

$dst_x = ( ( 800 - $x ) / 2 );
$dst_y = ( ( 600 - $y ) / 2 );

imagecopy( $canvas, $scaled, $dst_x, $dst_y, 0, 0, $x, $y );

$rotated = imagerotate( $canvas, 90, 0 );

header('Content-type: image/jpeg');
header("Expires: on, 01 Jan 1970 00:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

imagejpeg( $rotated );

// Tidy Up
imagedestroy( $image );
imagedestroy( $scaled );
imagedestroy( $rotated );
</code></pre>

<h2 id="displaying"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#displaying">Displaying</a></h2>

<p>As I mentioned in <a href="https://shkspr.mobi/blog/2020/02/turn-an-old-ereader-into-an-information-screen-nook-str/">my previous post about displaying web pages on the Nook</a> - the browser is slow, old, and can't handle http<strong>s</strong> connections.</p>

<p>So I used <a href="https://github.com/jfriesne/Electric-Sign">ElectricSign</a> which is an old Android app. Give it a URL and it will display the contents on screen. It can be set to periodically refresh.</p>

<p>I've had it running for months on a different screen and it hasn't crashed.</p>

<p>That's it!</p>

<h2 id="todo"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#todo">TODO</a></h2>

<ul>
<li>Buy more cheap, 2nd hand eInk screens on eBay. Preferably larger and with higher DPI.</li>
<li>Get some fancy frames.</li>
<li>Start a pirate art museum.</li>
<li>Monetise it by tracking how long people stare at a particular artwork.</li>
<li>BitCoin?<sup id="fnref:lol"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fn:lol" class="footnote-ref" title="LOL! Nope!" role="doc-noteref">4</a></sup></li>
</ul>

<h2 id="thanks-for-reading"><a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#thanks-for-reading">Thanks for reading</a></h2>

<p>If you've enjoyed this blog post, you can <a href="https://www.amazon.co.uk/hz/wishlist/ls/13GFCFR2B2IX4?type=wishlist&amp;linkCode=sl2&amp;tag=shksprblogwish-21">buy me something from my wishlist</a> or <a href="https://github.com/sponsors/edent">sponsor my GitHub</a>.  Find out more way to <a href="https://shkspr.mobi/blog/support/">support this blog</a>.</p>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:mono">
<p>OK, bit more complicated than that. <a href="https://wiki.mobileread.com/wiki/E_Ink_Pearl">eInk Pearl can display 16 shades of grey</a>.&nbsp;<a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fnref:mono" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:1">
<p>Whatever that is...&nbsp;<a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:2">
<p>Whoever they are...&nbsp;<a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:mit">
<p>Licenced as MIT - but it is so basic that I won't be annoyed if you do something else with it.&nbsp;<a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fnref:mit" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:lol">
<p>LOL! Nope!&nbsp;<a href="https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/#fnref:lol" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=40394&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/09/turning-an-eink-screen-into-a-monochrome-art-gallery/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		<enclosure url="https://shkspr.mobi/blog/wp-content/uploads/2021/09/offweb.mp4" length="1424365" type="video/mp4" />
<enclosure url="https://shkspr.mobi/blog/wp-content/uploads/2021/09/onweb.mp4" length="1274366" type="video/mp4" />

			</item>
		<item>
		<title><![CDATA[Animated TreeMaps in R - the hard way]]></title>
		<link>https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/</link>
					<comments>https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 14 Jun 2021 11:23:24 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[data]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[MSc]]></category>
		<category><![CDATA[r]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=39262</guid>

					<description><![CDATA[As I am a bear of very little brain, these are notes to myself on my slightly shonky process for creating animated TreeMaps in R. The aim is to end up with something like this:  https://shkspr.mobi/blog/wp-content/uploads/2021/06/animated-tree-map.mp4  Generate the images  Getting the data is left as an exercise for the reader (sorry!). This loops through the data and generates a separate image…]]></description>
										<content:encoded><![CDATA[<p>As I am a bear of very little brain, these are notes to myself on my slightly shonky process for creating animated TreeMaps in R. The aim is to end up with something like this:</p>

<p></p><div style="width: 540px;" class="wp-video"><video class="wp-video-shortcode" id="video-39262-6" width="540" height="540" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2021/06/animated-tree-map.mp4?_=6"><a href="https://shkspr.mobi/blog/wp-content/uploads/2021/06/animated-tree-map.mp4">https://shkspr.mobi/blog/wp-content/uploads/2021/06/animated-tree-map.mp4</a></video></div><p></p>

<h2 id="generate-the-images"><a href="https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/#generate-the-images">Generate the images</a></h2>

<p>Getting the data is left as an exercise for the reader (sorry!). This loops through the data and generates a separate image for each TreeMap:</p>

<pre><code class="language-R">for(week in weeks) {
  weekly_data &lt;- subset(file_data, Week == week)

  size &lt;- sqrt(sum(weekly_data$Count)) / 2
  if (size &lt; 40) {
    size &lt;- 40
  }

  map &lt;- ggplot(weekly_data, aes(area = Count, label = paste(Filetype,formatC(Count, big.mark=",") ,sep="\n"), subgroup = Category, fill=Category)) +
    geom_treemap(layout="fixed") +
    geom_treemap_text(colour = "white", place = "centre", grow = TRUE, layout="fixed")

  file_name &lt;- paste("media/", weekly_data$Week[1], ".png", sep="")
  ggsave(file_name, map, width = size, height = size, units = "mm")
}
</code></pre>

<p>The width and height are proportionate the the square-root of the size of the data. Annoyingly, ggplot works in millimetres rather than pixels!</p>

<p>If images are too small, R throws an error of "Viewport has zero dimension(s)". So this sets a minimum size. This value was found using trial and error.</p>

<p>The layout is fixed, <a href="https://cran.r-project.org/web/packages/treemapify/vignettes/introduction-to-treemapify.html">as per the documentation</a> which keeps the order of the elements and their labels.</p>

<h2 id="resize-and-reorientate-the-images"><a href="https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/#resize-and-reorientate-the-images">Resize and reorientate the images</a></h2>

<p>Now I have a directory of images, each a different size. I want all of them to have the same size canvas and to be placed against the right-hand edge.</p>

<pre><code class="language-_">mogrify -gravity east -background white -extent 1750x1750 *.png 
</code></pre>

<p>That sets the "gravity" to the right - so the original image is centred vertically but is up against the right edge. The <code>extent</code> is the dimension of the new image.</p>

<p>Mogrify <em>overwrites</em> the original images.</p>

<h2 id="make-a-video"><a href="https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/#make-a-video">Make a video</a></h2>

<p>This is a lazy way to shove all the images into a video.</p>

<pre><code class="language-_">cat *.png | ffmpeg -f image2pipe -r 10 -vcodec png -i - -vcodec libx264 out.mp4
</code></pre>

<p>or</p>

<pre><code class="language-_">ffmpeg -framerate 8 -pattern_type glob -i '*.png' -c:v libx264 -r 30 -pix_fmt yuv420p out.mp4
</code></pre>

<p>Some websites will need further conversion as they have specific codec requirements.</p>

<h2 id="thats-it"><a href="https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/#thats-it">That's it</a></h2>

<p>It isn't the prettiest way to do things, but it seemed pretty effective. If you know of a more efficient - or more R-ish way to accomplish the same animation - please let me know.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=39262&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/06/animated-treemaps-in-r-the-hard-way/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://shkspr.mobi/blog/wp-content/uploads/2021/06/animated-tree-map.mp4" length="201481" type="video/mp4" />

			</item>
		<item>
		<title><![CDATA[Quick and Dirty Self-Hosted Alexa Skills (2019)]]></title>
		<link>https://shkspr.mobi/blog/2019/09/quick-and-dirty-self-hosted-alexa-skills-2019/</link>
					<comments>https://shkspr.mobi/blog/2019/09/quick-and-dirty-self-hosted-alexa-skills-2019/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 05 Sep 2019 06:50:44 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[alexa]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[skill]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=32631</guid>

					<description><![CDATA[I hate creating Alexa skills. What should be a 3-click process inevitably ends up requiring trips to multiple websites, to set up weird parameters, and reading outdated tutorials for obsolete libraries.  So this is how to create a self-hosted Skill, using PHP. It runs on your own server and doesn&#039;t require any interaction.  The Skill  At a basic level, all your website has to do is spit out a…]]></description>
										<content:encoded><![CDATA[<p>I hate creating Alexa skills. What should be a 3-click process inevitably ends up requiring trips to multiple websites, to set up weird parameters, and reading outdated tutorials for obsolete libraries.</p>

<p>So this is how to create a self-hosted Skill, using PHP. It runs on your own server and doesn't require any interaction.</p>

<h2 id="the-skill"><a href="https://shkspr.mobi/blog/2019/09/quick-and-dirty-self-hosted-alexa-skills-2019/#the-skill">The Skill</a></h2>

<p>At a basic level, all your website has to do is spit out <a href="https://developer.amazon.com/docs/custom-skills/request-and-response-json-reference.html#standard-response-to-canfulfillintentrequest-launchrequest-or-intentrequest-example">a piece of JSON for Alexa</a> to read out.</p>

<pre><code class="language-php">//   Set the correct header for JSON data
header('Content-Type: application/json');
//   Set the response
$response = [
  "response" =&gt; [
    "outputSpeech" =&gt; [
      "type" =&gt; "PlainText",
      "text" =&gt; "I'm a little teapot"
    ]
  ]
];
echo json_encode($response);
</code></pre>

<p>That's it.</p>

<p>This is perfect for when you have a simple query - "Yo! Alexa! Bus time?" - one question, no parameters.</p>

<p>OK, you can make <a href="https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-a-web-service.html#verify-request-sent-by-alexa">sure that the request genuinely came from Alexa</a>, and do all sorts of certificate checks. But why bother? Totally unnecessary for a personal skill.</p>

<p>Obviously, you can do some programming make the text say <code>$whatever</code>.</p>

<p>That was easy, right?</p>

<h2 id="the-amazon-side"><a href="https://shkspr.mobi/blog/2019/09/quick-and-dirty-self-hosted-alexa-skills-2019/#the-amazon-side">The Amazon Side</a></h2>

<p>OK kid, this is where it gets complicated.</p>

<p>For this you will need an Amazon developer account. Getting one is beyond the scope of this tutorial.</p>

<p>Go to <a href="https://developer.amazon.com/alexa/console/ask"></a><a href="https://developer.amazon.com/alexa/console/ask">https://developer.amazon.com/alexa/console/ask</a></p>

<p>Click the "Create Skill" button.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console.png" alt="The button on a website." width="775" height="248" class="aligncenter size-full wp-image-32632"></p>

<p>Give your skill a name. It defaults to USA! USA! USA! English - even though Amazon knows your location and the location of your Echo. If you think AI is hard, that ain't nothing compared to localisation.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console1.png" alt="Create a new skill screen." width="477" height="300" class="aligncenter size-full wp-image-32633">

<p>You need to select a Custom skill and to "Provision your own endpoint".</p>

<p><img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console2.png" alt="" width="280" height="197" class="aligncenter size-medium wp-image-32635"><img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console3.png" alt="" width="280" height="218" class="aligncenter size-full wp-image-32634"></p>

<p>If you try to use AWS Lambdas you'll be in for a world of pain. Don't even bother - they are a nightmare for a beginner to use.</p>

<p>Now scroll <em>all the way</em> back to the top of the website to click the "Create Skill" button. You'd think that after filling in a form, the "next" button would be at the bottom. But that's too easy. If you think AI is hard, that ain't nothing compared to designing an easy to use form.</p>

<p>We're going to choose the "Start From Scratch" template.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console4.png" alt="" width="745" height="325" class="aligncenter size-full wp-image-32636">
This has nothing to do with the Scratch programming language. Which is a shame, because Scratch is a great language and well designed for complex voice interaction models.</p>

<p>Before doing anything else, set up your "endpoint".  This is the website where you uploaded your PHP file.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console5.png" alt="" width="937" height="356" class="aligncenter size-full wp-image-32637">
Your website needs to support https - you may need to change the certificate type on this page if Amazon has difficulties with it.
Hit "Save Endpoints" - again, it is at the top of the screen. Because people naturally work from the bottom of the form to the top of the form.</p>

<p>Next up, we need a default intent. It doesn't really matter what you put in here. This is not a complex skill which can take multiple routes. We just want something to happen whenever it is launched.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console6.png" alt="" width="693" height="224" class="aligncenter size-full wp-image-32638"></p>

<p>Because Alexa isn't really an AI - we have to give lots of "sample" utterances. So, if you want bus times, you might add "next bus", "buses", "when is my bus" - and every variation you can think of.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console7.png" alt="" width="1090" height="281" class="aligncenter size-full wp-image-32639">
Now hit "Build Model" at the top. Once that's done, go to test.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2019/08/Screenshot_2019-08-22-Alexa-Developer-Console8.png" alt="" width="1110" height="137" class="aligncenter size-full wp-image-32640">
Set the dropdown to "Development".</p>

<p>That's it! You're finally done!  You can test the skill by either typing "whatever" into the text box, or by saying "Alexa! Whatever."</p>

<p>Alexa will read out whatever is in your PHP script.</p>

<p>I wish it were easier. Why can't I just say "When I ask <em>this</em> question, reply with the data from <em>that</em> website."  Sites like IFTTT and Zapier make it so easy to create services - I wish there was a Voice Assistant which was easy to make skills for.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=32631&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2019/09/quick-and-dirty-self-hosted-alexa-skills-2019/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Adjusting timestamps on images with Python]]></title>
		<link>https://shkspr.mobi/blog/2017/11/adjusting-timestamps-on-images-with-python/</link>
					<comments>https://shkspr.mobi/blog/2017/11/adjusting-timestamps-on-images-with-python/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 05 Nov 2017 12:31:45 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=24427</guid>

					<description><![CDATA[As ever, mostly notes to myself.  I have a bunch of old images which don&#039;t have any timestamp associated with them.  This quick Python script will add a DateTime EXIF metadata tag to an image.  This uses piexif which can be installed using  pip install piexif   This simple script reads a photo, adds a timestamp, then saves a copy of the new photo.  from PIL import Image from PIL.ExifTags import…]]></description>
										<content:encoded><![CDATA[<p>As ever, mostly notes to myself.</p>

<p>I have a bunch of old images which don't have any timestamp associated with them.  This quick Python script will add a <code>DateTime</code> EXIF metadata tag to an image.</p>

<p>This uses <a href="https://github.com/hMatoba/Piexif">piexif</a> which can be installed using</p>

<pre><code>pip install piexif
</code></pre>

<p>This simple script reads a photo, adds a timestamp, then saves a copy of the new photo.</p>

<pre><code class="language-python">from PIL import Image
from PIL.ExifTags import TAGS
import piexif
from datetime import datetime

im = Image.open("test.jpg")
exif_dict = piexif.load(im.info["exif"])

exif_dict["0th"][piexif.ImageIFD.DateTime]=datetime.strptime("2000/12/25 12:32","%Y/%m/%d %H:%M").strftime("%Y:%m:%d %H:%M:%S")
exif_bytes = piexif.dump(exif_dict)
im.save("new.jpg", "jpeg", exif=exif_bytes, quality="keep", optimize=True)
</code></pre>

<p>A point to note about the last line.  By default, PIL saves JPGs with a quality of 75. By using <code>quality="keep"</code> the original quality is preserved.  The <code>optimize=True</code> attempts to losslessly improve the image.</p>

<p>A word of warning - <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html">timestamps are stored in multiple EXIF locations</a>.  If your image already has a timestamp, it is possible that this script won't change it.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=24427&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2017/11/adjusting-timestamps-on-images-with-python/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Easy Tutorial For Getting Twitter Friends Using Python & Tweepy]]></title>
		<link>https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/</link>
					<comments>https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 09 Jun 2017 11:01:11 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[tweepy]]></category>
		<category><![CDATA[twitter]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=25344</guid>

					<description><![CDATA[Here&#039;s a very simple introduction to getting started with Tweepy - a Python program which lets you access Twitter. This will work on small computers like the Raspberry Pi.  Everything here takes place in the Terminal on the Command Line.  This should work on Windows and Mac - but I&#039;m using Linux.  Get Python  Open your terminal or command prompt. Type python  You should see something like: …]]></description>
										<content:encoded><![CDATA[<p>Here's a very simple introduction to getting started with Tweepy - a Python program which lets you access Twitter. This will work on small computers like the Raspberry Pi.</p>

<p>Everything here takes place in the Terminal on the Command Line.  This <em>should</em> work on Windows and Mac - but I'm using Linux.</p>

<h2 id="get-python"><a href="https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#get-python">Get Python</a></h2>

<p>Open your terminal or command prompt. Type <code>python</code></p>

<p>You should see something like:</p>

<pre><code>Python 2.7.9 (default, Dec 28 2016, 18:26:44)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt;
</code></pre>

<p>If you do, you have Python installed! Press <code>[ctrl]</code> and <code>d</code> to exit.</p>

<p>If you get an error, you will need to <a href="https://wiki.python.org/moin/BeginnersGuide/Download">install Python</a></p>

<h2 id="install-tweepy"><a href="https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#install-tweepy">Install Tweepy</a></h2>

<p><a href="https://github.com/tweepy/tweepy">Tweepy</a> is a library which makes it easy to use the Twitter API.</p>

<p>To install it, type:</p>

<p><code>pip install tweepy</code></p>

<h2 id="get-api-keys"><a href="https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#get-api-keys">Get API Keys</a></h2>

<p>You will need to visit <a href="https://apps.twitter.com/">https://apps.twitter.com/</a> and follow Twitter's instructions for creating an app.</p>

<p>You will end up with a "Consumer Key" and a "Consumer Secret"</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2017/06/Twitter-Keys.png" alt="A screenshot of teh Twitter website showing application keys" width="665" height="276" class="aligncenter size-full wp-image-25346">

<p>These are the secret tokens which belong to your app.  What we need to do next is to sign in to the app and generate your <em>personal</em> tokens.</p>

<h2 id="get-tokens"><a href="https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#get-tokens">Get Tokens</a></h2>

<ul>
<li>You can save this piece of code as <code>oauth.py</code></li>
<li>You run it as <code>python oauth.py</code></li>
</ul>

<pre><code>import tweepy

consumer_key = "YOUR CONSUMER KEY HERE"
consumer_secret = "YOUR CONSUMER SECRET HERE"

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.secure = True
auth_url = auth.get_authorization_url()
print 'Visit this URL and authorise the app to use your Twitter account: ' + auth_url
verifier = raw_input('Type in the generated PIN: ').strip()
auth.get_access_token(verifier)

# Print out the information for the user
print "consumer_key        = '%s'" % consumer_key
print "consumer_secret     = '%s'" % consumer_secret
print "access_token        = '%s'" % auth.access_token
print "access_token_secret = '%s'" % auth.access_token_secret
</code></pre>

<ul>
<li>It will print a URL, open that URL in your browser and it will take you to Twitter.</li>
<li>Log in to Twitter with your account.</li>
<li>Twitter will show you a PIN - 6 numbers long.</li>
<li>Type the PIN into the Command Line.</li>
<li>The program will display <em>all</em> your API Tokens.</li>
</ul>

<p>So, how do we use them?</p>

<h2 id="using-your-twitter-tokens"><a href="https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#using-your-twitter-tokens">Using your Twitter tokens</a></h2>

<ul>
<li>Add your tokens to this code.</li>
<li>You can save this piece of code as <code>tweet.py</code></li>
<li>You run it as <code>python tweet.py</code></li>
<li>It will Tweet a message on your behalf.</li>
</ul>

<pre><code>import tweepy

# Consumer keys and access tokens, used for OAuth
consumer_key        = 'YOUR CONSUMER KEY HERE'
consumer_secret     = 'YOUR CONSUMER SECRET HERE'
access_token        = 'YOUR ACCESS TOKEN HERE'
access_token_secret = 'YOUR ACCESS TOKEN SECRET HERE'

# OAuth process, using the keys and tokens
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Creation of the actual interface, using authentication
api = tweepy.API(auth)

# Send the tweet
message = 'This is a test'
api.update_status(message)
</code></pre>

<h2 id="get-a-list-of-all-the-people-you-follow"><a href="https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/#get-a-list-of-all-the-people-you-follow">Get a list of all* the people you follow</a></h2>

<ul>
<li>Add your tokens to this code.</li>
<li>You can save this piece of code as <code>following.py</code></li>
<li>You run it as <code>python following.py</code></li>
<li>It will get the <em>ID numbers</em> of everyone* you follow and print them out.</li>
</ul>

<pre><code>import tweepy

# Consumer keys and access tokens, used for OAuth
consumer_key        = 'YOUR CONSUMER KEY HERE'
consumer_secret     = 'YOUR CONSUMER SECRET HERE'
access_token        = 'YOUR ACCESS TOKEN HERE'
access_token_secret = 'YOUR ACCESS TOKEN SECRET HERE'

# OAuth process, using the keys and tokens
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Creation of the actual interface, using authentication
api = tweepy.API(auth)

# Get all the people the user follows
friends = api.friends_ids()

# Print out each one
for id in friends:
    print(id)
</code></pre>

<p>* For technical reasons, this will only get the first 5,000 people you follow.</p>

<p>Let's save these to a file.  Note the differences in this code and the previous piece of code.</p>

<pre><code>import tweepy
import os

# Consumer keys and access tokens, used for OAuth
consumer_key        = 'YOUR CONSUMER KEY HERE'
consumer_secret     = 'YOUR CONSUMER SECRET HERE'
access_token        = 'YOUR ACCESS TOKEN HERE'
access_token_secret = 'YOUR ACCESS TOKEN SECRET HERE'

# OAuth process, using the keys and tokens
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Creation of the actual interface, using authentication
api = tweepy.API(auth)
friends = sorted(api.friends_ids())

friendsFile = open("friends.txt","w")

for id in friends:
    friendsFile.write(str(id) + "\n")

friendsFile.close()
</code></pre>

<p>You will now have a file called "friends.txt" which contains the ID Numbers of everyone you follow.</p>

<p>Handy for backing up your account :-)</p>

<blockquote class="social-embed" id="social-embed-872863823996350464" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><blockquote class="social-embed" id="social-embed-872862984950996992" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/edent" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRkgBAABXRUJQVlA4IDwBAACQCACdASowADAAPrVQn0ynJCKiJyto4BaJaQAIIsx4Au9dhDqVA1i1RoRTO7nbdyy03nM5FhvV62goUj37tuxqpfpPeTBZvrJ78w0qAAD+/hVyFHvYXIrMCjny0z7wqsB9/QE08xls/AQdXJFX0adG9lISsm6kV96J5FINBFXzHwfzMCr4N6r3z5/Aa/wfEoVGX3H976she3jyS8RqJv7Jw7bOxoTSPlu4gNbfXYZ9TnbdQ0MNnMObyaRQLIu556jIj03zfJrVgqRM8GPwRoWb1M9AfzFe6Mtg13uEIqrTHmiuBpH+bTVB5EEQ3uby0C//XOAPJOFv4QV8RZDPQd517Khyba8Jlr97j2kIBJD9K3mbOHSHiQDasj6Y3forATbIg4QZHxWnCeqqMkVYfUAivuL0L/68mMnagAAA" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Terence Eden is on Mastodon</p>@edent</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody"><small class="social-embed-reply"><a href="https://twitter.com/Suw/status/872861640076734465">Replying to @Suw</a></small><a href="https://twitter.com/Suw">@Suw</a> <a href="https://twitter.com/paul_clarke">@paul_clarke</a> Hmmm. I could make a "following backer up thingamajig". Might be of some use. But would anyone use it *before* it happened?</section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/edent/status/872862984950996992"><span aria-label="2 likes" class="social-embed-meta">❤️ 2</span><span aria-label="0 replies" class="social-embed-meta">💬 0</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2017-06-08T17:08:47.000Z" itemprop="datePublished">17:08 - Thu 08 June 2017</time></a></footer></blockquote><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/paul_clarke" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRvQCAABXRUJQVlA4IOgCAACQDACdASowADAAPqlCnEmmI6KhMdmd+MAVCWIAnTNQyxDBO2DJPpEKMZ1Mmpxs5NV891blUBmDkkEdQ5pL/oOOFpfjZrbYRylF6SO1Nv/SW5jtRQjeE6efGfTBPgeHL8Z+BeALfcFRvd3hg/TgAPzzLCzHO8qqwgvjX+n9EGApSYlGxdmBraM0FmvyLTsqyNeNjsXiGGfpM03SW69GqibiW0wLmec1On56m0mXN7u2BEkXHnmq2Q3O8kkR/k8W0eRZoOjorBJRUkTO50yV/lptq/QXz4Dn2tWIrpvPbGTuC6Zo90ZwSSVftIom3He0mBqV11dMsmWFSMxZJId10QYU1A7ZJSu4uFFGLidn0MN6o3GokIcM8W73ZabURwBy/mnvPYKdO5V8z31CHJhv11/phmcmAuclFN5Mz0SgggAqDptSdmQpBhSW/k7ZS8s81MHoc01BfR8nYm6q+V+tOUisVXyN5A9oZvZnj7ZVx5mrZkS6kHvP53tumb6DUOXv1xbzCO9cK5GSgozM92nfm78EKNCVgTHuJdohhR4fQSs2sVoU5hFiQtgxUA7zG3Q+xV8CM87hHdgO07wKJhCQ7KFdP6C/M3j6UZtSfL0YxRETjW7bffYjOs4IgKGS/rdVpG2djNpfwK5n2lZLq/QmL5UG78hV7bpojg95lk6oxUBqMblnNrZTR9+z0pZr7Tg5O8vAo5maiAX8jwM2Jhxt7P0xUHjM4L5bTddIpol01DvwU5omc6MoYjkhnGsXcXJoYd1tftNnzhdZI4lMd8B15X87W6DwBp/i/84bcd86ZFfxX4RL0VGF+aF3+tTvvZy0MvRuzcfoBm6Z0DBN8Iq31xZa2NjX/C8Q3sQnHXd4d2kQc/yfyfBWx7oMfeg7n9/jmRv5fXA+BQtMGu6HG6tPYzvV73/3x04D2s01yDIt0wqmOvF4P/lUvtq7/bb72kVajNRXspjGxsjx5JXkMNvdG2NAeir+zGoGAAA=" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Paul Clarke</p>@paul_clarke</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody"><small class="social-embed-reply"><a href="https://twitter.com/edent/status/872862984950996992">Replying to @edent</a></small><a href="https://twitter.com/edent">@edent</a> <a href="https://twitter.com/Suw">@Suw</a> I would. But I am on the edge.</section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/paul_clarke/status/872863823996350464"><span aria-label="0 likes" class="social-embed-meta">❤️ 0</span><span aria-label="1 replies" class="social-embed-meta">💬 1</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2017-06-08T17:12:07.000Z" itemprop="datePublished">17:12 - Thu 08 June 2017</time></a></footer></blockquote>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=25344&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2017/06/easy-tutorial-for-getting-twitter-friends-using-python-tweepy/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Sort Folders Into Alphabetic Sub-Folders]]></title>
		<link>https://shkspr.mobi/blog/2017/01/sort-folders-into-alphabetic-sub-folders/</link>
					<comments>https://shkspr.mobi/blog/2017/01/sort-folders-into-alphabetic-sub-folders/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 02 Jan 2017 10:01:35 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=24553</guid>

					<description><![CDATA[Scratching my own itch.  I have a bunch of directories which I want moved into alphabetic sub-directories.  This is handy is you have a bunch of MP3s, books, or other catalogued files.  This bash script moves a top level directory (and all the files and subdirectories under it), to a folder based on the (upper-case) version of the first character of the directory name.  #!/bin/bash for dir in */…]]></description>
										<content:encoded><![CDATA[<p>Scratching my own itch.  I have a bunch of directories which I want moved into alphabetic sub-directories.  This is handy is you have a bunch of MP3s, books, or other catalogued files.</p>

<p>This bash script moves a top level directory (and all the files and subdirectories under it), to a folder based on the (upper-case) version of the first character of the directory name.</p>

<pre><code>#!/bin/bash
for dir in */ ; do
    start=${dir:0:1}
    mkdir -p ${start^^}
    mv "$dir" ${start^^}
done
</code></pre>

<p>Save that as <code>sort.sh</code>, make it executable with <code>chmod +x sort.sh</code> and run with <code>./sort.sh</code></p>

<p>It turns this:</p>

<pre><code>.
├── Adella
├── adrianna
├── Barb
├── bebe
├── Carleen
├── catherina
</code></pre>

<p>Into:</p>

<pre><code>.
├── A
│&nbsp;&nbsp; ├── Adella
│&nbsp;&nbsp; ├── adrianna
├── B
│&nbsp;&nbsp; ├── Barb
│&nbsp;&nbsp; └── bebe
├── C
 &nbsp;&nbsp; ├── Carleen
 &nbsp;&nbsp; ├── catherina
</code></pre>

<h2 id="how-it-works"><a href="https://shkspr.mobi/blog/2017/01/sort-folders-into-alphabetic-sub-folders/#how-it-works">How it works</a></h2>

<p>Let's break this down to see what everything does</p>

<ul>
<li><code>for dir in */ ; do</code></li>
<li>This loops through the current directory and finds every item which <em>ends</em> with a <code>/</code> - that is, all directories</li>
</ul>

<p>&nbsp;</p>

<ul>
<li><code>start=${dir:0:1}</code></li>
<li>Grab a <a href="http://www.tldp.org/LDP/abs/html/string-manipulation.html">sub-string</a> from the directory name. In this case, the first character.</li>
</ul>

<p>&nbsp;</p>

<ul>
<li><code>mkdir -p ${start^^}</code></li>
<li>Make a directory, <a href="http://superuser.com/questions/165157/what-does-mkdir-p-flag-do">use the <code>-p</code> flag to make sure we don't get an error when trying to create a duplicate directory</a>. The <a href="http://aty.sdsu.edu/bibliog/latex/debian/bash.html"><code>^^</code> command makes things upper case</a>.</li>
</ul>

<p>&nbsp;</p>

<ul>
<li><code>mv "$dir" ${start^^}</code></li>
<li>Move the directory we found into the newly created directory.</li>
</ul>

<p>&nbsp;</p>

<ul>
<li><code>done</code></li>
<li>Finish the loop</li>
</ul>

<h2 id="warnings"><a href="https://shkspr.mobi/blog/2017/01/sort-folders-into-alphabetic-sub-folders/#warnings">Warnings</a></h2>

<ul>
<li>Only works with folders - it won't move any files in your top level directory.</li>
<li>Non-ASCII characters are supported. For example, <code>你好</code> is sorted into <code>你</code>.</li>
<li>Handles directories with spaces, punctuation, and numbers at the start.</li>
<li>No doubt there is some horrific bug lurking in those half-dozen lines. Be a friend and drop me a note in the comments, would you?</li>
</ul>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=24553&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2017/01/sort-folders-into-alphabetic-sub-folders/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Replacing IFTTT - Part 1: RSS & Tumblr]]></title>
		<link>https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/</link>
					<comments>https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 30 Dec 2016 15:55:58 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[rant]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=24455</guid>

					<description><![CDATA[I&#039;ve grown to loath IFTTT. What started out as a cool way to plug internet things together has being an opaque an uncommunicative company with no real interest in customer service. That&#039;s not surprising, I suppose, its paying customers are the companies who can&#039;t be bothered to develop a proper API and so just shove some integrations up there.  But it is annoying for those of us who want…]]></description>
										<content:encoded><![CDATA[<p>I've grown to loath IFTTT. What started out as a cool way to plug internet things together has being an opaque an uncommunicative company with no real interest in customer service. That's not surprising, I suppose, its paying customers are the companies who can't be bothered to develop a proper API and so just shove some integrations up there.</p>

<p>But it is annoying for those of us who want something simple - like debug logs or notifications when scripts fail. Or something complex like conditional processing.  Or even just a human to talk to when things break.</p>

<p>So, sod the lot of them. I'm slowing replacing my IFTTT "applets" with my own home-brew.  Starting with...</p>

<h2 id="rss-tumblr"><a href="https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/#rss-tumblr">RSS &amp; Tumblr</a></h2>

<p>I have a IFTTT recipe which</p>

<ol>
<li>Reads an RSS feed</li>
<li>If there is a new item, queue it to Tumblr</li>
</ol>

<p>Simple! And yet IFTTT randomly fails. It either can't read the RSS, or it doesn't spot new items, or it duplicates all my posts.  Oh, and there's no way to set queued posts to automatically Tweet. It basically seems that IFTTT's MVP is certainly minimal, but sadly unviable.</p>

<p>This is a Python script which can be run as often as you like.  It reads RSS, checks for new items, queues them, and sets Tweet text.  NB - no Python 3 support yet because <a href="https://github.com/tumblr/pytumblr/issues/51">Tumblr can't be bothered to update their libraries</a>.</p>

<h3 id="prerequisites"><a href="https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/#prerequisites">Prerequisites</a></h3>

<ul>
<li><input type="checkbox"> Install <a href="https://github.com/tumblr/pytumblr/">pytumblr</a></li>
<li><input type="checkbox"> Install <a href="http://www.pythonforbeginners.com/feedparser/using-feedparser-in-python">feedparser</a></li>
<li><input type="checkbox"> <a href="https://www.tumblr.com/oauth/apps">Create an app on Tumblr</a>.</li>
<li><input type="checkbox"> <a href="https://api.tumblr.com/console">Generate your Tumblr API tokens</a></li>
</ul>

<h3 id="the-code"><a href="https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/#the-code">The code</a></h3>

<pre><code>import pytumblr
import feedparser
import sys

id_file = "id_file"

# Authenticate via OAuth
tumblr = pytumblr.TumblrRestClient(
        'AAAAA',
        'BBBBB',
        'CCCCC',
        'DDDDD'
)

feed = feedparser.parse('https://EXAMPLE.COM/FEED.RSS')

#       Read the last id
with open(id_file,'r') as file_:
        last_id = file_.read().strip()

most_recent_id = feed['entries'][0]['id']

if (last_id == most_recent_id):
        # Do nothing
        sys.exit()
else:
        #       Save the new ID for later
        with open(id_file, 'w') as file_:
                file_.write(most_recent_id)

        #       Loop through the posts
        for post in feed['entries']:
                #       If the post has been encountered before, time to end things.
                if (last_id == post["id"]):
                        sys.exit()
                else:
                        #       Queue it on Tumblr
                        tumblr.create_text(
                                "YOUR-TUMBLR-NAME",
                                state="queue",
                                title=post["title"].encode("utf-8"),
                                format="html",
                                body=post["summary"].encode("utf-8")
                        )
</code></pre>

<h3 id="running"><a href="https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/#running">Running</a></h3>

<p>I have a <a href="https://crontab.guru/">crontab</a> set up to run this a few times per day. By default, IFTTT seems to check every hour.</p>

<h3 id="caveats"><a href="https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/#caveats">Caveats</a></h3>

<ul>
<li>You will need a file called <code>id_file</code> to store the latest ID of the RSS feed.</li>
<li>If your RSS feed doesn't have unique IDs, this may not work.</li>
<li>This isn't exactly a robust, error-proof solution - but then, neither is IFTTT.</li>
</ul>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=24455&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2016/12/replacing-ifttt-part-1-rss-tumblr/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Converting RAR to ZIP in Linux]]></title>
		<link>https://shkspr.mobi/blog/2016/12/converting-rar-to-zip-in-linux/</link>
					<comments>https://shkspr.mobi/blog/2016/12/converting-rar-to-zip-in-linux/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 11 Dec 2016 21:11:38 +0000</pubDate>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[ubuntu]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=24314</guid>

					<description><![CDATA[As ever, mostly notes to myself.  RAR is a silly and proprietary format.  I prefer free software and I find that ZIP files are smaller and decompress faster. Not everyone agrees, and that&#039;s fine. Assuming you&#039;ve downloaded a RAR file and want to convert it to ZIP, what&#039;s the easiest way?  Install P7Zip  sudo apt-get install p7zip-full p7zip-rar   Script  This basic bash script will   Extract a…]]></description>
										<content:encoded><![CDATA[<p>As ever, mostly notes to myself.</p>

<p>RAR is a silly and proprietary format.  I prefer free software and I find that ZIP files are smaller and decompress faster. Not everyone agrees, and that's fine.
Assuming you've downloaded a RAR file and want to convert it to ZIP, what's the easiest way?</p>

<h3 id="install-p7zip"><a href="https://shkspr.mobi/blog/2016/12/converting-rar-to-zip-in-linux/#install-p7zip">Install P7Zip</a></h3>

<pre><code>sudo apt-get install p7zip-full p7zip-rar
</code></pre>

<h3 id="script"><a href="https://shkspr.mobi/blog/2016/12/converting-rar-to-zip-in-linux/#script">Script</a></h3>

<p>This basic bash script will</p>

<ol>
<li>Extract a RAR <code>example.rar</code> file to a temporary directory on a <a href="http://sharadchhetri.com/2012/08/15/devshm-mount-devshm/">RAM disk</a>.</li>
<li>Recompress to <code>example.zip</code>.</li>
<li>Delete the temporary directory.</li>
</ol>

<p>Easy!</p>

<p>It is based on <a href="http://comicrack.cyolito.com/forum/13-scripts/30013-cbr2cbz-rar-to-zip-conversion-for-linux?start=10#30419">this conversion from CBR to CBZ</a>.</p>

<pre><code class="language-bash">#!/bin/bash
echo "Converting RARs to ZIPs"

# Separate files using ␜ http://graphemica.com/%E2%90%9C.
IFS="␜"

# Use RAM disk for temporary files.
WORKDIR="/dev/shm/"

# Set name for the temp dir. This directory will be created under WORKDIR
TEMPDIR="rar2zip"

# Run using "./rar2zip.sh /full/path/to/files/"
# If no directory is specified, then use the current working directory (".").

if test -z $1; then
   SOURCEDIR=`pwd`
else
   SOURCEDIR="$1"
fi

echo "Using $SOURCEDIR"

# Create an temporary directory to work in.
cd $WORKDIR
mkdir $TEMPDIR
cd $TEMPDIR

# Find all the .rar files in the specified directory.
# Using -iname means it will find .rar .RAR .RaR etc.
# "-printf "%p␜" will cause the file names to be separated by the ␜ symbol,
# rather than the default newline.

for OLDFILE in `find $SOURCEDIR -iname "*.rar" -printf "%p␜"`; do

   # Get the file name without the extension
   BASENAME=`basename "${OLDFILE%.*}"`

   # Path for the file. The ".zip" file will be moved there.
   DIRNAME=`dirname $OLDFILE`

   # Name of the .zip file
   NEWNAME="$BASENAME.zip"

   # Create a temporary folder for unRARed files
   echo "Extracting $OLDFILE"
   mkdir "$BASENAME"
   7z x "$OLDFILE" -O"$BASENAME"
   cd "$BASENAME"

   # Zip the files with maximum compression
   7z a -tzip -mx=9 "$NEWNAME" *
   # Alternative. MUCH SLOWER, but better compression
   # 7z a -mm=Deflate -mfb=258 -mpass=15 -r "$NEWNAME" *

   # Move the new .zip to the directory containing the original ".rar" file
   mv "$NEWNAME" $DIRNAME/"$NEWNAME"

   # Delete the temporary directory
   cd $WORKDIR
   rm -r "$BASENAME"

   # OPTIONAL. Delete the RAR file
   # cd $DIRNAME
   # rm "$OLDFILE"

done

# Delete the temporary directory
cd $WORKDIR
rm -r $TEMPDIR

echo "Conversion Done"
</code></pre>

<p>That's it! I suppose it might be nice if there were a simpler way to do it - but this is fairly quick and error-proof.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=24314&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2016/12/converting-rar-to-zip-in-linux/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Easy ways to add watermarks to images and videos in Linux]]></title>
		<link>https://shkspr.mobi/blog/2016/08/easy-ways-to-add-watermarks-to-images-and-videos-in-linux/</link>
					<comments>https://shkspr.mobi/blog/2016/08/easy-ways-to-add-watermarks-to-images-and-videos-in-linux/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 02 Aug 2016 11:14:32 +0000</pubDate>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=23151</guid>

					<description><![CDATA[Mostly notes to myself :-)  Here is a quick way to add watermarks to photos and videos. All Linux command line based - so perfect if you&#039;ve got a lot of images you want to manipulate.  Here is a delightful photo I&#039;ve taken of a bee covered in pollen. I want to add a little copyright notice to it in order to discourage people using it without permission.    This command uses imagemagick&#039;s…]]></description>
										<content:encoded><![CDATA[<p>Mostly notes to myself :-)</p>

<p>Here is a quick way to add watermarks to photos and videos. All Linux command line based - so perfect if you've got a lot of images you want to manipulate.</p>

<p>Here is a delightful photo I've taken of a bee covered in pollen. I want to add a little copyright notice to it in order to discourage people using it without permission.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/Bee-covered-in-pollen.jpg" alt="Bee covered in pollen" width="512" height="320" class="aligncenter size-full wp-image-23154">

<p>This command uses <a href="http://www.ImageMagick.org/">imagemagick</a>'s "<a href="http://www.imagemagick.org/Usage/annotating/">annotate</a>" option.</p>

<p><code>convert bee.jpg -gravity SouthEast -pointsize 16 -font TinyUnicode-Medium -fill "#fffdc3" -annotate +10+10 "(C) @edent" bee1.jpg</code></p>

<p>It produces this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/Bee-covered-in-pollen-with-pixel-watermark.jpg" alt="Bee covered in pollen with pixel watermark" width="512" height="320" class="aligncenter size-full wp-image-23152">

<p>As you can see, a small watermark in the bottom right - specified using <code>-gravity SouthEast</code>.  I've chosen a yellow colour for the font - in homage to the <a href="https://people.duke.edu/~ng46/collections/steg-eurion-constellation.htm">EURion anti-counterfeiting symbols</a>.</p>

<p>The <code>-annotate</code> command contains the distance in pixels from the edges</p>

<p>For small text, I favour a Pixel Font like <a href="http://www.dafont.com/tinyunicode.font">TinyUnicode</a> - but feel free to choose one which suits your needs.</p>

<p>In this example, I position the message in the top left, with a larger font size, in pale blue, closer to the horizontal edge and further from the vertical edge.</p>

<p><code>convert bee.jpg -gravity NorthWest -pointsize 32 -font Helvetica -fill "#cee3f8" -annotate +1+50 "(C) @edent" bee2.jpg</code>
<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/Bee-covered-in-pollen-with-Helvetica-watermark.jpg" alt="Bee covered in pollen with Helvetica watermark" width="512" height="320" class="aligncenter size-full wp-image-23155"></p>

<h2 id="video"><a href="https://shkspr.mobi/blog/2016/08/easy-ways-to-add-watermarks-to-images-and-videos-in-linux/#video">Video</a></h2>

<p>Adding a watermark to a video is more complex.  This will require either ffmpeg - which isn't always installed by default on Linux - or avconv (I don't pretend to understand why <a href="http://blog.pkh.me/p/13-the-ffmpeg-libav-situation.html">ffmpeg and avconv split</a> - but there we are).</p>

<p>I am going to <a href="https://ffmpeg.org/ffmpeg-filters.html#overlay-1">overlay</a> a transparent image on top of the video.</p>

<p>I've created this watermark image with a transparent background.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/small.png" alt="A transparent watermark" width="355" height="55" class="aligncenter size-full wp-image-23158">

<p>To overlay it, I use:</p>

<p><code>ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=1500:1000" output.mp4</code></p>

<p>or for <a href="https://web.archive.org/web/20160802062315/https://libav.org/documentation/libavfilter.html#overlay-1">avconv's overlay</a>:</p>

<p><code>avconv -i input.mp4 -i watermark.png -filter_complex 'overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10' output.mp4</code></p>

<p></p><div style="width: 620px;" class="wp-video"><video class="wp-video-shortcode" id="video-23151-8" width="620" height="349" loop="" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/small.mp4?_=8"><a href="https://shkspr.mobi/blog/wp-content/uploads/2016/07/small.mp4">https://shkspr.mobi/blog/wp-content/uploads/2016/07/small.mp4</a></video></div><p></p>

<p>ffmpeg's <code>overlay=</code> option allows me to specify where the <em>top left</em> of the image will appear on the video.  So adjust those number based on the resolution of your watermark and of your video.</p>

<p>avconv has a more complex syntax.  It's possible to specify the <em>absolute</em> position using <code>overlay=x=1500:y=1000</code> or to use <em>relative</em> positions with <code>overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10</code>.</p>

<p>Or, for a one-liner:</p>

<pre><code class="language-_">ffmpeg -i input.mp4 -vf "drawtext=text='@edent':x=10:y=H-th-10:fontfile=/home/edent/.local/share/fonts/OCRAEXT.TTF:fontsize=15:fontcolor=#cee3f8" -vcodec h264 -strict -2 output.mp4
</code></pre>

<p>So, there you have it - hopefully simple ways to watermark your media files.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=23151&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2016/08/easy-ways-to-add-watermarks-to-images-and-videos-in-linux/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://shkspr.mobi/blog/wp-content/uploads/2016/07/small.mp4" length="2089661" type="video/mp4" />

			</item>
		<item>
		<title><![CDATA[Reducing the filesize of complex 3D .OBJ models]]></title>
		<link>https://shkspr.mobi/blog/2016/08/reducing-the-filesize-of-complex-3d-obj-models/</link>
					<comments>https://shkspr.mobi/blog/2016/08/reducing-the-filesize-of-complex-3d-obj-models/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 01 Aug 2016 11:46:49 +0000</pubDate>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[3d]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=23143</guid>

					<description><![CDATA[Loading large 3D Models in the browser is extremely resource intensive.  2D images are trivial to resize and resample with negligible loss of perceived quality.  3D resizing is complex.  As part of my &#34;Pirate Museum&#34; I wanted to display 3D scans of statues using WebVR.  The only problem is, these files are huge.  Take The Dancing Faun - at full resolution, that&#039;s around 230MB.  Even on fast…]]></description>
										<content:encoded><![CDATA[<p>Loading large 3D Models in the browser is <em>extremely</em> resource intensive.  2D images are trivial to resize and resample with negligible loss of perceived quality.  3D resizing is complex.</p>

<p>As part of my "<a href="https://github.com/edent/PirateMuseum/">Pirate Museum</a>" I wanted to display 3D scans of statues using <a href="https://aframe.io/">WebVR</a>.  The only problem is, these files are <em>huge</em>.</p>

<p>Take <a href="http://www.thingiverse.com/thing:196048">The Dancing Faun</a> - at full resolution, that's around 230MB.  Even on fast broadband that's a pain to download - and even on a fast computer it is slow to render. Once you add several models like that to a scene, everything grinds to a halt.</p>

<p>I wanted a simple way to turn a complex model into something simple - but still recognisable.</p>

<p>On the left you can see the face of the full sized model - on the right is it after being shrunk.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/Reducing-model-complexity-.jpg" alt="Reducing model complexity." width="1024" height="502" class="aligncenter size-full wp-image-23145">

<p>A surprising level of detail remains - and the filesize is a miniscule 1MB.  Small enough for a floppy disk!</p>

<p>To do this, I used <a href="http://meshlab.sourceforge.net/">MeshLab</a> - the Open Source 3D modelling software.  Meshlab has a great GUI - but it can be slow to work with, especially if you're trying to change lots of models at once.</p>

<p>The first step is to <a href="https://support.shapeways.com/hc/en-nl/articles/17001740700060-Polygon-Reduction-with-MeshLab">create a filter script in the Meshlab GUI</a>.  This is what I settled on, a script to reduce any model to 32,000 faces.</p>

<pre><code class="language-xml">&lt;!DOCTYPE FilterScript&gt;
&lt;FilterScript&gt;
 &lt;filter name="Quadric Edge Collapse Decimation"&gt;
  &lt;Param type="RichInt"   value="32000" name="TargetFaceNum"/&gt;
  &lt;Param type="RichFloat" value="0"     name="TargetPerc"/&gt;
  &lt;Param type="RichFloat" value="1"     name="QualityThr"/&gt;
  &lt;Param type="RichBool"  value="true"  name="PreserveBoundary"/&gt;
  &lt;Param type="RichFloat" value="1"     name="BoundaryWeight"/&gt;
  &lt;Param type="RichBool"  value="true"  name="PreserveNormal"/&gt;
  &lt;Param type="RichBool"  value="false" name="PreserveTopology"/&gt;
  &lt;Param type="RichBool"  value="true"  name="OptimalPlacement"/&gt;
  &lt;Param type="RichBool"  value="true"  name="PlanarQuadric"/&gt;
  &lt;Param type="RichBool"  value="false" name="QualityWeight"/&gt;
  &lt;Param type="RichBool"  value="true"  name="AutoClean"/&gt;
  &lt;Param type="RichBool"  value="false" name="Selected"/&gt;
 &lt;/filter&gt;
&lt;/FilterScript&gt;
</code></pre>

<p>The files is then saved as <code>32k.mlx</code>.</p>

<p>I followed <a href="https://web.archive.org/web/20160826033420/http://www.andrewhazelden.com/blog/2012/04/automate-your-meshlab-workflow-with-mlx-filter-scripts/">Andrew Hazelden's blog post</a> to run the script using Meshlab server.</p>

<p><code>meshlabserver -i original.obj -o small.obj -s 32k.mlx</code></p>

<p>After a few moments - depending on the speed of your kit and size of model - a reduced size version will be spat out.</p>

<p>It is important to note that the MeshLab work only changes the <em>quality</em> of the model - not its apparent size in 3D space.</p>

<p>Models can be permanently scaled using <a href="https://github.com/tapio/obj-magic/">Obj-Magic</a>.  For example, to scale a model to be half its physical size, run:
<code>./obj-magic -s 0.5 -o half.obj original.obj</code></p>

<p>Finally, we have the issue of rotating models in 3D space.  Some models are oriented in surprising ways.  This can be done with <code>obj-magic</code> if you already know how you want the model to be rotated.  I found it easier to use <a href="https://www.blender.org/">Blender</a> on some of the models to rotate them and re-centre them.</p>

<p>Blender is notoriously complicated - all you need to do is import the .obj. Select it in the right hand panel. Then select the Cube / Object menu.  In there you'll find the controls to shrink and rotate the object.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/07/Blender-.png" alt="A screen from Blender showing a rotated 3D model" width="858" height="530" class="aligncenter size-full wp-image-23148">

<p>Once done, select "Export" then "Wavefront (.obj)".</p>

<p>So, with that you can easily shrink 3D models to a more compact file size.</p>

<p>You can <a href="https://edent.github.io/PirateMuseum">visit my Pirate Museum</a> to experience the models.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=23143&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2016/08/reducing-the-filesize-of-complex-3d-obj-models/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[How to type Emoji in Ubuntu]]></title>
		<link>https://shkspr.mobi/blog/2016/02/how-to-type-emoji-in-ubuntu/</link>
					<comments>https://shkspr.mobi/blog/2016/02/how-to-type-emoji-in-ubuntu/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 01 Feb 2016 12:22:02 +0000</pubDate>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[emoji]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[unicode]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=22373</guid>

					<description><![CDATA[New tech site Gadgette has a great article on how to type Emoji on Mac and Windows - but they (understandably) didn&#039;t cover Ubuntu.  So here I am to show you how.  Get The Fonts  If your computer doesn&#039;t have the requite font, install the latest version of Symbola.  Simply open up the .zip file, double click on the .ttf font, then choose &#34;Install&#34;.   Find The Character  You almost certainly have…]]></description>
										<content:encoded><![CDATA[<p>New tech site Gadgette has a <a href="https://web.archive.org/web/20160324122443/http://www.gadgette.com/2016/02/01/how-to-type-emojis-on-your-computer/">great article on how to type Emoji on Mac and Windows</a> - but they (understandably) didn't cover Ubuntu.  So here I am to show you how.</p>

<h2 id="get-the-fonts"><a href="https://shkspr.mobi/blog/2016/02/how-to-type-emoji-in-ubuntu/#get-the-fonts">Get The Fonts</a></h2>

<p>If your computer doesn't have the requite font, <a href="http://users.teilar.gr/~g1951d/">install the latest version of Symbola</a>.</p>

<p>Simply open up the .zip file, double click on the .ttf font, then choose "Install".
<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/02/Symbola-Install-fs8.png" alt="Symbola Install-fs8" width="745" height="446" class="aligncenter size-full wp-image-22374"></p>

<h2 id="find-the-character"><a href="https://shkspr.mobi/blog/2016/02/how-to-type-emoji-in-ubuntu/#find-the-character">Find The Character</a></h2>

<p>You almost certainly have the <a href="https://wiki.gnome.org/Apps(2f)Gucharmap.html">GNU CharMap app</a> installed.  If not, run
<code>apt-get install gucharmap</code>.</p>

<p>You'll find it in "Accessories" or by running <code>charmap</code> from the terminal.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/02/CharMap-Run-fs8.png" alt="CharMap Run-fs8" width="714" height="195" class="aligncenter size-full wp-image-22375"></p>

<p>On the left hand side, you'll see a list of all the Unicode Block.  Scroll down to "Emoticons":</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/02/CharMap-Emoji-fs8.png" alt="CharMap Emoji-fs8" width="1025" height="576" class="aligncenter size-full wp-image-22377">

<p>Or "Miscellaneous Symbols"
<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/02/CharMap-Misc-fs8.png" alt="CharMap Misc-fs8" width="1025" height="576" class="aligncenter size-full wp-image-22376"></p>

<p>Click on the character you want, then press <code>CTRL+C</code> to copy them to your clipboard.</p>

<p>You can also search through all the character names if you're not sure where it lives.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2016/02/CharMap-Search-fs8.png" alt="CharMap Search-fs8" width="1025" height="576" class="aligncenter size-full wp-image-22378"></p>

<p>Enjoy!</p>

<p>😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=22373&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2016/02/how-to-type-emoji-in-ubuntu/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Android Tutorial - Clickable Widgets]]></title>
		<link>https://shkspr.mobi/blog/2010/07/android-tutorial-clickable-widgets/</link>
					<comments>https://shkspr.mobi/blog/2010/07/android-tutorial-clickable-widgets/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 12 Jul 2010 10:09:42 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[sdk]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=2149</guid>

					<description><![CDATA[Another quick Android tutorial.  I couldn&#039;t find an easy or correct method of launching a browser when you click on a homescreen widget.  Well, here it is...  public class clickWidget extends AppWidgetProvider { @Override public void onUpdate( Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds ) { RemoteViews remoteViews =    new RemoteViews( context.getPackageName(),…]]></description>
										<content:encoded><![CDATA[<p>Another quick Android tutorial.  I couldn't find an easy or correct method of launching a browser when you click on a homescreen widget.  Well, here it is...</p>

<pre lang="java">public class clickWidget extends AppWidgetProvider
{
@Override
public void onUpdate( Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds )
{
RemoteViews remoteViews =
   new RemoteViews( context.getPackageName(), R.layout.widget );
remoteViews.setImageViewResource(R.id.ImageView01, drawableResourse);

ComponentName myWidget =
   new ComponentName( context, clickWidget.class );

// Create an Intent to launch Browser
Intent intent =
   new Intent(
      Intent.ACTION_VIEW, Uri.parse("http://example.com")
   );
PendingIntent pendingIntent =
   PendingIntent.getActivity(context, 0, intent, 0);

remoteViews.setOnClickPendingIntent(R.id.ImageView01, pendingIntent);
appWidgetManager.updateAppWidget( myWidget, remoteViews);
}
</pre>

<p>I've used this as the basis of a demo widget - "MI5 Terror Threat Level".  The widget displays the UK's Threat Level on your homescreen.  Clicking on it takes you to the <a href="https://web.archive.org/web/20101014002748/ttps://www.mi5.gov.uk/output/threat-levels.html">MI5 page discussing the threat level</a>.</p>

<p>The threat level is determined by parsing the <a href="https://web.archive.org/web/20101014003201/https://www.mi5.gov.uk/output/threat-level-rss.html">RSS that the security services so helpfully provide</a>.  At the moment, the widget keeps a local copy of the graphics because the <a href="http://www.mi5.gov.uk/UKThreatLevel/UKThreatLevel.xml">RSS feed</a> contains references to "localhost" images.</p>

<p>You can download the widget by scanning in this QR code.
</p><div id="attachment_2155" style="width: 174px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-2155" src="https://shkspr.mobi/blog/wp-content/uploads/2010/07/mi5.png" alt="MI5 Widget - QR Code" title="MI5 Widget - QR Code" width="164" height="164" class="size-full wp-image-2155"><p id="caption-attachment-2155" class="wp-caption-text">MI5 Widget - QR Code</p></div><p></p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=2149&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2010/07/android-tutorial-clickable-widgets/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
