<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Eric Schaefer - software // music // nonsense</title><description>Eric Schaefer is a software engineer in Berlin.</description><link>https://eric-schaefer.com/</link><item><title>Work Studio in Effect</title><link>https://eric-schaefer.com/blog/2012-09-04-work-studio-effect/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2012-09-04-work-studio-effect/</guid><description>Getting settled in Neukölln.</description><pubDate>Mon, 03 Sep 2012 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/home-office.JPG&quot; alt=&quot;Maximinimal work studio&quot;&gt;&lt;/p&gt;
&lt;p&gt;I&apos;m finally settled in my new apartment in Neukölln, and rent is cheap enough here to afford extra space for a studio. So voila, here&apos;s the minimalist studio where I&apos;ll be working, with the only (welcome) distractions coming from the backgammon players at the shisha bar downstairs. The windows will remain permanently open until the rainy days of fall force me to close them.&lt;/p&gt;
&lt;p&gt;In the meantime I will be neck-deep in some new Ruby on Rails adventures, and beginning a &lt;a href=&quot;https://www.coursera.org/course/progfun&quot;&gt;Functional Programming Principles in Scala&lt;/a&gt; course to keep me on my toes.&lt;/p&gt;
</content:encoded></item><item><title>Plug and Play Performance Boost with mod_pagespeed</title><link>https://eric-schaefer.com/blog/2012-10-10-mod-pagespeed-boost/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2012-10-10-mod-pagespeed-boost/</guid><description>An easy way to boost your website&apos;s performance if the server is running Apache.</description><pubDate>Tue, 09 Oct 2012 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For real? This nifty Apache module just jumped out of beta, and an nginx version is available as well. Over 40 output filters that range from CSS rewriting to image manipulation and preprocessing. &lt;a href=&quot;http://www.igvita.com/2012/10/10/automating-web-performance-with-mod_pagespeed/&quot;&gt;Google&apos;s Ilya Grigorik has a great writeup on it here&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Also launched: Axel Pfaender&apos;s web store</title><link>https://eric-schaefer.com/blog/2012-11-04-axel-pfaenders-web-store/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2012-11-04-axel-pfaenders-web-store/</guid><description>You wouldn&apos;t Wordpress.</description><pubDate>Sat, 03 Nov 2012 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/axel-web-store.png&quot; alt=&quot;Axel Pfaender&apos;s web store&quot;&gt;&lt;/p&gt;
&lt;p&gt;In an unlikely turn of events, I took on a Wordpress project this month as well. Take one look at this guy&apos;s stuff and you&apos;ll know why. Behold &lt;a href=&quot;http://shop.axelpfaender.com/&quot;&gt;Axel Pfaender&apos;s web store&lt;/a&gt;. I had to make some significant changes to Shopp&apos;s core and UI files to make it play nicely with Germany&apos;s value-added-tax laws. The same would apply to a number of other EU countries as well. Anyway, the site is also multilingual, and I learned a lot about how internationalization works on the back end. Cool stuff. I&apos;m just fantasizing about porting this whole thing to Rails though...&lt;/p&gt;
</content:encoded></item><item><title>Building an audiovisual installation using PD</title><link>https://eric-schaefer.com/blog/2013-02-04-puredata-installation/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-02-04-puredata-installation/</guid><description>A PureData patch for audiovisual installations.</description><pubDate>Sun, 03 Feb 2013 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/pd-patch.png&quot; alt=&quot;PD screenshot&quot;&gt;&lt;/p&gt;
&lt;p&gt;Creative code time. I started the putting together a PureData patch this week, based on a installation proposal &lt;a href=&quot;https://soundcloud.com/sftstps/&quot;&gt;my friend Nick&lt;/a&gt; and I collaborated on. The concept is to take video motion data from a room, and make the aggregate change in motion speed (people moving) directly influence a full-surround sound environment. No motion would result in a low drone, and quick motion would turn the environment into a responsive dance freakout technorave. It&apos;ll be rad.&lt;/p&gt;
&lt;p&gt;Currently the patch just takes motion detection data and translates the values to influence a few oscillators. Time to switch those out for a looping audio file...&lt;/p&gt;
&lt;p&gt;I&apos;m making the code &lt;a href=&quot;https://github.com/eschaefer/field&quot;&gt;freely available on Github&lt;/a&gt;, too.&lt;/p&gt;
</content:encoded></item><item><title>Launched: SAND Journal</title><link>https://eric-schaefer.com/blog/2012-12-11-sand-journal/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2012-12-11-sand-journal/</guid><description>The official website for SAND Journal.</description><pubDate>Mon, 10 Dec 2012 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/sand-website.jpg&quot; alt=&quot;SAND Journal&quot;&gt;&lt;/p&gt;
&lt;p&gt;Labors of love tend to get drawn-out longer than any other projects, at least for me. I think I began working on &lt;a href=&quot;http://www.sandjournal.com/&quot;&gt;SAND&lt;/a&gt;&apos;s new website back in April after one of their editors approached me about the project during an event at the American Academy in Berlin. How could I turn down helping out a literary journal? I want my trajectory to include projects that are more culturally relevant or important, and the SAND staff are certainly working their asses off to make this journal amazing.&lt;/p&gt;
&lt;p&gt;The website is built on Drupal, since content management was pretty central to what SAND needs. They needed custom content types to feature exclusive online content, which could include anything from photo-essays to video to issue previews to poetry and yeah anything in between. No fancy development footwork here. Just giving a boost to an already kickass lit journal.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.sandjournal.com/purchase&quot;&gt;Get a copy of SAND&lt;/a&gt; at a local bookstore if you live in Berlin.&lt;/p&gt;
</content:encoded></item><item><title>Berlin Boombox launch and web store</title><link>https://eric-schaefer.com/blog/2012-11-04-berlin-boombox-launch-web-store/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2012-11-04-berlin-boombox-launch-web-store/</guid><description>The official website for the Berlin Boombox.</description><pubDate>Sat, 03 Nov 2012 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/berlin-boombox.jpg&quot; alt=&quot;Berlin Boombox website&quot;&gt;&lt;/p&gt;
&lt;p&gt;Today I wrapped up the official website for the &lt;a href=&quot;http://www.berlinboombox.com/&quot;&gt;Berlin Boombox&lt;/a&gt;, which is a badass DIY soundsystem developed by Berlin designer &lt;a href=&quot;http://www.axelpfaender.com/&quot;&gt;Axel Pfaender&lt;/a&gt;. The site includes a lot of cool stuff, and some of the best-designed instructions ever. Since the website didn&apos;t really need any special app-like functionality, I built it on Drupal with a custom &lt;a href=&quot;http://drupal.org/project/zen&quot;&gt;Zen&lt;/a&gt;-based sub theme. Coming soon will be an interactive Google Maps mashup on the &amp;quot;Buy&amp;quot; page, which will give a nice overview of the retail locations. Can&apos;t wait to see Axel&apos;s design ideas on that.&lt;/p&gt;
</content:encoded></item><item><title>Launched: Kofelgschroa&apos;s Band Website</title><link>https://eric-schaefer.com/blog/2013-04-07-kofelgschroa-website-launched/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-04-07-kofelgschroa-website-launched/</guid><description>The official website for Kofelgschroa.</description><pubDate>Sat, 06 Apr 2013 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/kofelgschroa-website.jpg&quot; alt=&quot;Kofelgschroa&apos;s front page&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://kofelgschroa.by&quot;&gt;Kofelgschroa&apos;s website&lt;/a&gt; is a collaboration project with the mega-talented designers at Berlin&apos;s &lt;a href=&quot;http://sunst-studio.com/&quot;&gt;Sunst-Studio&lt;/a&gt;. I&apos;ll be working with them again on some other projects, so definitely looking forward to that. Swear to god, working with designers makes the development process so much smoother.&lt;/p&gt;
&lt;p&gt;The website is built on Drupal with a totally custom Zen-based theme. I also made good use of &lt;a href=&quot;http://isotope.metafizzy.co&quot;&gt;Isotope&lt;/a&gt; for sorting and filtering the blog posts page in an elegant way that works on all browsers. Additionally, I&apos;m finding that parts of Twitter&apos;s Bootstrap framework are insanely useful. I&apos;m starting to think of Bootstrap as a collection of small front-end tools that can be plucked out rather than a static framework.&lt;/p&gt;
</content:encoded></item><item><title>Automate responsive screen captures with Review</title><link>https://eric-schaefer.com/blog/2013-02-20-automated-screen-resolution-review/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-02-20-automated-screen-resolution-review/</guid><description>Awesome node.js package that uses PhantomJS for easy screengrabs.</description><pubDate>Tue, 19 Feb 2013 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Review is an awesome node.js package that uses PhantomJS to grab screenshots of all your websites in different screen resolutions.&lt;/p&gt;
&lt;p&gt;It&apos;s best said on the &lt;a href=&quot;https://github.com/juliangruber/review&quot;&gt;review&lt;/a&gt; Github page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Updating large and possibly responsively designed sites can be a hassle. You never know whether your change breakes anything on the other end of your sitemap, or in a certain resolution, except if have a look at every individual page...in every resolution you care about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Simply install review from the node.js package manager and feed it a JSON string of different websites and screen resolutions that you&apos;d like to see them in. Highly recommended for anyone who needs to keep tabs on a responsive design, etc.&lt;/p&gt;
&lt;p&gt;Install with the node package manager:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install -g review
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lots of detailed usage examples &lt;a href=&quot;https://github.com/juliangruber/review&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/review-screenshot.png&quot; alt=&quot;Taken from the review Github page&quot;&gt;&lt;/p&gt;
</content:encoded></item><item><title>How (Code) Language Creates Its Own Culture</title><link>https://eric-schaefer.com/blog/2013-04-16-ezeep-code-culture/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-04-16-ezeep-code-culture/</guid><description>How the culture around programming languages shapes the real-life projects we work on.</description><pubDate>Mon, 15 Apr 2013 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;a href=&amp;quot;http://youtu.be/uMc4RnEmHLc?t=4m18s&amp;quot;&amp;gt;&lt;img src=&quot;/images/posts/ezeep-code-culture-hangout.png&quot; alt=&quot;Google Hangout with ezeep&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Yesterday I participated in a discussion that Berlin-based startup &lt;a href=&quot;https://www.ezeep.com/&quot;&gt;ezeep&lt;/a&gt; hosted, in which we discussed the various cultures around programming languages, and how those cultures shape the real-life projects we work on. The topic stemmed from ezeep&apos;s recent decision to &lt;a href=&quot;http://blog.ezeep.com/why-java-sucks-for-ezeep&quot;&gt;rewrite their backend in Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something that Marian Zange mentioned struck me as the real meat of this discussion:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...even if you join our team through a marketing or sales route, you’ll get an engineering intro (the happy, smiling &apos;light edition&apos;) to explore parts of our code, learn how to read it and understand the inner-workings of our product.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This kind of intro for non-technical participants to a project&apos;s codebase was once unheard of. It&apos;s symptomatic of a larger trend in programming--I believe--that communication with those outside the walls of the programming community is essential to evolving a project, and more broadly, evolving how languages are structured. The culture around languages like Python and Ruby are certainly making coding more accessible to anyone interested. Free online tools like &lt;a href=&quot;http://www.codecademy.com&quot;&gt;Code Academy&lt;/a&gt; seem to have caught on like wildfire, and in the long term, will simply make people smarter and more well-rounded problem solvers. And as long as companies like ezeep take the time to explain the inner-workings of their product to their employees, we might see less of a dividing line between the technical and &amp;quot;non-technical&amp;quot; camps among people that have a common interest in technology.&lt;/p&gt;
</content:encoded></item><item><title>Launched: Filmcake&apos;s Website</title><link>https://eric-schaefer.com/blog/2013-12-09-filmcake-website-launched/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-12-09-filmcake-website-launched/</guid><description>Another Wordpress project.</description><pubDate>Sun, 08 Dec 2013 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;a href=&amp;quot;http://about.filmcake.com&amp;quot;&amp;gt;&lt;img src=&quot;/images/posts/filmcake-done.jpg&quot; alt=&quot;Filmcake&apos;s about page&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Another successful launch in collaboration with the rad &lt;a href=&quot;http://sunst-studio.com/&quot;&gt;Sunst-Studio&lt;/a&gt; design team in Berlin. The &lt;a href=&quot;http://about.filmcake.com&quot;&gt;Filmcake&lt;/a&gt; website is fully responsive and was built on Wordpress, using the &lt;a href=&quot;http://themble.com/bones/&quot;&gt;Bones&lt;/a&gt; base theme, which I found incredibly flexible, and comes with minimal Javascript weight. Additionally it comes with both LESS and SASS starting points (choose your poison) baked-in for rapid styling and development.&lt;/p&gt;
&lt;p&gt;This was a very straightforward project with no technical hurdles, and raw development time was probably only two weeks.&lt;/p&gt;
</content:encoded></item><item><title>Launched: Bechstein Network</title><link>https://eric-schaefer.com/blog/2014-04-22-bechstein-netowork-launched/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2014-04-22-bechstein-netowork-launched/</guid><description>The official website for Bechstein Network.</description><pubDate>Mon, 21 Apr 2014 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;a href=&amp;quot;http://www.bechstein-network.com&amp;quot;&amp;gt;&lt;img src=&quot;/images/posts/bechstein-network-screenshot.jpg&quot; alt=&quot;Bechstein Network&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Yet another launch in collaboration with the ever-badass &lt;a href=&quot;http://sunst-studio.com/&quot;&gt;Sunst-Studio&lt;/a&gt; design team in Berlin. The brand new &lt;a href=&quot;http://www.bechstein-network.com&quot;&gt;Bechstein Network&lt;/a&gt; website is built on Wordpress, which provides a familiar backend for the network members. Easy posting was paramount for this project, so members (galleries, clubs, restaurants, architects, etc) can quickly showcase events and work.&lt;/p&gt;
&lt;p&gt;A customized filtering system was built to find network members based on categories, and network member profile pages have a flexible back-end that allows members to highlight their strengths and showcase detailed photos of their work.&lt;/p&gt;
&lt;p&gt;This is also the first project I used &lt;a href=&quot;http://gulpjs.com&quot;&gt;gulp&lt;/a&gt; for a build process in both development and production environments.&lt;/p&gt;
</content:encoded></item><item><title>The quickest way to set-up live reload in Chrome</title><link>https://eric-schaefer.com/blog/2013-12-14-live-reload-chrome/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-12-14-live-reload-chrome/</guid><description>A quick walkthrough on how to set up live reload in Chrome.</description><pubDate>Fri, 13 Dec 2013 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Front-end development just got a little easier. Ever wanted to have like four browser windows next to each other, and have them automatically refresh when you&apos;ve made an update to the project&apos;s javascript, CSS, or whatever?&lt;/p&gt;
&lt;p&gt;I read a bunch of walkthroughs on setting up &lt;a href=&quot;https://github.com/guard/guard&quot;&gt;guard&lt;/a&gt; and &lt;a href=&quot;https://github.com/guard/guard-livereload&quot;&gt;guard-livereload&lt;/a&gt; through the terminal, but hopefully I can save you from having to do that as well.&lt;/p&gt;
&lt;p&gt;Setting up LiveReload through Sublime Text 2&apos;s package manager is a much easier option.&lt;/p&gt;
&lt;p&gt;So, to get directly to the point:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open Submlime Text 2 and install the LiveReload package through Package Control.&lt;/li&gt;
&lt;li&gt;Re-open Sublime Text 2 and open up whatever project directory you&apos;re working from.&lt;/li&gt;
&lt;li&gt;Install the Chrome browser extension called &lt;a href=&quot;https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei&quot;&gt;LiveReload&lt;/a&gt; (or the &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/livereload/&quot;&gt;same extension for Firefox&lt;/a&gt;). Once it&apos;s installed, toggle the extension.&lt;/li&gt;
&lt;li&gt;Navigate to your dev project in Chrome, and behold it automatically updating when you save changes in ST2!&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Launched: Benjamin Obdyke</title><link>https://eric-schaefer.com/blog/2014-01-07-benjamin-obdyke-website-launched/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2014-01-07-benjamin-obdyke-website-launched/</guid><description>The official website for Benjamin Obdyke.</description><pubDate>Mon, 06 Jan 2014 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;a href=&amp;quot;http://www.benjaminobdyke.com&amp;quot;&amp;gt;&lt;img src=&quot;/images/posts/benjamin-obdyke-screenshot.jpg&quot; alt=&quot;Benjamin Obdyke&apos;s new home page&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Since May 2013 I&apos;ve been working with the insanely talented designers and developers at &lt;a href=&quot;http://kingdesignllc.com&quot;&gt;King Design&lt;/a&gt;. My time there has been pretty nuts; lots of client work, jumping into projects that have been brewing for many months.&lt;/p&gt;
&lt;p&gt;I&apos;m proud to say my first complete front-end build with King Design is also my first &lt;a href=&quot;http://ellislab.com/expressionengine&quot;&gt;ExpressionEngine&lt;/a&gt; build. And I have to admit, ExpressionEngine rules. After a brief learning curve, I found the templating engine a breeze to work with. I still have a lot to learn, and I&apos;ve only scratched the surface of what&apos;s under the hood in EE.&lt;/p&gt;
&lt;p&gt;This particular project took a little under two months to code, which included a few iterations of functionality and design. More updates are on the way for this one too. I&apos;m really looking forward to another ExpressionEngine project now!&lt;/p&gt;
</content:encoded></item><item><title>Now Exhibiting in Berlin: PLAYING THE FIELD</title><link>https://eric-schaefer.com/blog/2013-06-15-now-showing-playing-the-field/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-06-15-now-showing-playing-the-field/</guid><description>A PureData patch for audiovisual installations.</description><pubDate>Fri, 14 Jun 2013 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The audiovisual installation that I worked on with my friend Nick is now exhibiting in Berlin at the &lt;a href=&quot;http://staycationmuseum.com/exhibitions/playing-the-field/&quot;&gt;Staycation Museum&lt;/a&gt;. Here&apos;s an excerpt from their website:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Playing the Field&lt;/p&gt;
&lt;p&gt;OPENING 14-16 JUNE 2013 (48 Stunden Neukölln)&lt;br&gt;
Friday, 14 June, 19:00-22:00&lt;br&gt;
Saturday, 15 June, 12:00-22:00&lt;br&gt;
Sunday, 16 June, 12:00-19:00&lt;/p&gt;
&lt;p&gt;CLOSING 28 JUNE 2013&lt;br&gt;
19:00–22:00&lt;/p&gt;
&lt;p&gt;Imagine an environment not as an ambience but rather as a complex ecology where each part is equally responsive and responsible in its own becoming. Key to this responsiveness is the awareness of inside and outside collapsing in the realization that each element is embedded in a constellation whose patterns exceed that of any single component.&lt;/p&gt;
&lt;p&gt;We are embedded in our own affect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All source code for the video and audio processing is now freely available on Github at &lt;a href=&quot;https://github.com/eschaefer/field&quot;&gt;https://github.com/eschaefer/field&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&apos;s someone interacting with it!&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe src=&amp;quot;https://www.facebook.com/video/embed?video_id=10101398862370559&amp;quot; width=&amp;quot;568&amp;quot; height=&amp;quot;320&amp;quot; frameborder=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>How to Render jQuery Flot Charts through AngularJS</title><link>https://eric-schaefer.com/blog/2013-07-26-rendering-flot-charts-through-angular-js/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-07-26-rendering-flot-charts-through-angular-js/</guid><description>An archival take on how to render Flot charts through AngularJS. Long since deprecated.</description><pubDate>Thu, 25 Jul 2013 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There are lots of javascript data visualization and charting libries out there, but only a couple bother to minimize the IE8 compatability headache. &lt;a href=&quot;http://www.flotcharts.org&quot;&gt;Flot Charts&lt;/a&gt; seems to work nicely for my purposes on this particular project. I need bar charts and donut charts, both of which Flot handles well.&lt;/p&gt;
&lt;p&gt;Thing is, I really want to use &lt;a href=&quot;http://angularjs.org&quot;&gt;AngularJS&lt;/a&gt; for the project I&apos;m working on right now. I don&apos;t know of any Angular-native charting libraries (though that would be a killer addition to &lt;a href=&quot;https://github.com/angular-ui&quot;&gt;Angular UI&lt;/a&gt;). Now I&apos;m faced with the added layer of wrapping a jQuery plugin inside of Angular functions. What?&lt;/p&gt;
&lt;p&gt;Luckily AngularJS can render Flot through a unique feature called a &lt;a href=&quot;http://docs.angularjs.org/guide/directive&quot;&gt;directive&lt;/a&gt;. Stackoverflow user jm- &lt;a href=&quot;http://stackoverflow.com/questions/13103671/how-to-integrate-flot-with-angularjs&quot;&gt;got me started&lt;/a&gt; in the right direction on this. I&apos;ve expanded his example in my own JSFiddle, which &lt;a href=&quot;http://jsfiddle.net/LC5JE/&quot;&gt;renders a bar chart through AngularJS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;ve worked with Flot before in regular ol&apos; jQuery, then you&apos;re probably familiar with the &lt;a href=&quot;https://github.com/flot/flot/blob/master/API.md&quot;&gt;Flot API&lt;/a&gt;. If you&apos;re new to Flot, then give the API a quick read. You can customize pretty much anything in your graph/chart with it.&lt;/p&gt;
&lt;p&gt;Here&apos;s some example DOM markup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div ng-app=&amp;quot;App&amp;quot;&amp;gt;
  &amp;lt;div ng-controller=&amp;quot;barChartController&amp;quot;&amp;gt;
    &amp;lt;chart ng-model=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/chart&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the AngularJS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var App = angular.module(&amp;quot;App&amp;quot;, []);

App.controller(&amp;quot;barChartController&amp;quot;, function ($scope) {
  var daftPoints = [[0, 4]],
    punkPoints = [[1, 20]];

  var data1 = [
    {
      data: daftPoints,
      color: &amp;quot;#00b9d7&amp;quot;,
      bars: {
        show: true,
        barWidth: 1,
        fillColor: &amp;quot;#00b9d7&amp;quot;,
        order: 1,
        align: &amp;quot;center&amp;quot;,
      },
    },
    {
      data: punkPoints,
      color: &amp;quot;#3a4452&amp;quot;,
      bars: {
        show: true,
        barWidth: 1,
        fillColor: &amp;quot;#3a4452&amp;quot;,
        order: 2,
        align: &amp;quot;center&amp;quot;,
      },
    },
  ];

  $scope.data = data1;
});

App.directive(&amp;quot;chart&amp;quot;, function () {
  return {
    restrict: &amp;quot;E&amp;quot;,
    link: function (scope, elem, attrs) {
      var chart = null,
        options = {
          xaxis: {
            ticks: [
              [0, &amp;quot;Daft&amp;quot;],
              [1, &amp;quot;Punk&amp;quot;],
            ],
          },
          grid: {
            labelMargin: 10,
            backgroundColor: &amp;quot;#e2e6e9&amp;quot;,
            color: &amp;quot;#ffffff&amp;quot;,
            borderColor: null,
          },
        };

      var data = scope[attrs.ngModel];

      // If the data changes somehow, update it in the chart
      scope.$watch(&amp;quot;data&amp;quot;, function (v) {
        if (!chart) {
          chart = $.plot(elem, v, options);
          elem.show();
        } else {
          chart.setData(v);
          chart.setupGrid();
          chart.draw();
        }
      });
    },
  };
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How to get this to work on IE8&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add &amp;lt;code&amp;gt;id=&amp;quot;ng-app&amp;quot;&amp;lt;/code&amp;gt; to the document&apos;s root element in conjunction with &amp;lt;code&amp;gt;ng-app&amp;lt;/code&amp;gt; attribute&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot; xmlns:ng=&amp;quot;http://angularjs.org&amp;quot; id=&amp;quot;ng-app&amp;quot; ng-app=&amp;quot;App&amp;quot;&amp;gt;
  ...
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Load the &lt;a href=&quot;https://github.com/angular-ui/ui-utils/tree/master/modules/ie-shiv&quot;&gt;AngularUI IE shiv&lt;/a&gt;, at the bottom of your javascript file list, in an IE8-targeted comment, and declare the &amp;lt;code&amp;gt;chart&amp;lt;/code&amp;gt; directive. IE8 won&apos;t load that &lt;code&gt;&amp;lt;chart&amp;gt;&lt;/code&gt; element without this.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&amp;quot;http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;js/application.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
...
&amp;lt;!--[if lte IE 8]&amp;gt;
  &amp;lt;script&amp;gt;
    // Declare custom directive names and load shiv for AngularJS in here. Or else IE8 bummertown.
    window.myCustomTags = [&amp;quot;chart&amp;quot;];
  &amp;lt;/script&amp;gt;
  &amp;lt;script src=&amp;quot;js/ie-shiv.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;![endif]--&amp;gt;
...
&amp;lt;body&amp;gt;
  ...
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Launched: Openers</title><link>https://eric-schaefer.com/blog/2014-06-10-openers-website-launched/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2014-06-10-openers-website-launched/</guid><description>The official website for Openers.</description><pubDate>Mon, 09 Jun 2014 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;a href=&amp;quot;http://www.opnrs.com&amp;quot;&amp;gt;&lt;img src=&quot;/images/posts/openers.jpg&quot; alt=&quot;Openers&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;At long last, the minds behind &lt;a href=&quot;http://toaberlin.com&quot;&gt;Tech Open Air Berlin&lt;/a&gt; have launched a sister company called &lt;a href=&quot;http://www.opnrs.com&quot;&gt;Openers&lt;/a&gt;. The primary goal of the website is to showcase their portfolio and strengths in consulting, media, events, branding and strategy.&lt;/p&gt;
&lt;p&gt;The design was done by long-time collaborators &lt;a href=&quot;http://sunst-studio.com/&quot;&gt;Sunst-Studio&lt;/a&gt;, and I built-out the backend in Wordpress. Easy management of portfolio galleries, and flexibility to move content around made Wordpress a solid choice.&lt;/p&gt;
&lt;p&gt;Go hire these folks if you want the best-connected strategists in Berlin!&lt;/p&gt;
</content:encoded></item><item><title>Launched: Tech Open Air Berlin</title><link>https://eric-schaefer.com/blog/2014-07-17-tech-open-air-berlin-launched/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2014-07-17-tech-open-air-berlin-launched/</guid><description>The official website for Tech Open Air Berlin.</description><pubDate>Wed, 16 Jul 2014 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;a href=&amp;quot;http://toaberlin.com&amp;quot;&amp;gt;&lt;img src=&quot;/images/posts/tech-open-air-berlin.jpg&quot; alt=&quot;Tech Open Air Berlin&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;In collaboration with &lt;a href=&quot;http://avalonemerson.com&quot;&gt;Avalon Emerson&lt;/a&gt;, and with record time constraints, we built the 2014 edition of the &lt;a href=&quot;http://toaberlin.com&quot;&gt;Tech Open Air Berlin&lt;/a&gt; website. Once again I chose Wordpress to bolster easy content administration for the small team of event organizers. A number of custom content types, and lots of metadeta about events required building some custom plugins. More integration with Eventbrite would have been nice to dig into, but we simply didn&apos;t have the time to go that far.&lt;/p&gt;
</content:encoded></item><item><title>How to Spoof a (Valid) Random MAC Address on Bootup</title><link>https://eric-schaefer.com/blog/2014-05-22-randomize-mac-address-on-boot/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2014-05-22-randomize-mac-address-on-boot/</guid><description>A quick and dirty way to spoof a random MAC address on bootup.</description><pubDate>Wed, 21 May 2014 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Updated for Yosemite 10.10.4&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I spend a lot of time at coffee shops, using their wi-fi networks. I&apos;m not sure who is operating these things, or if they&apos;re hacked, so I wanted to add a layer anonymity to my connection. Tons of routers record and store the MAC address of anyone connected. That makes me uneasy. Here&apos;s how I randomized a new (and valid) MAC address on every reboot of my MacBook pro, running OS X 10.10.4 (Yosemite).&lt;/p&gt;
&lt;h3&gt;Create a Bash script&lt;/h3&gt;
&lt;p&gt;Open your terminal. Let&apos;s create a folder and an empty script file in one swoop.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir /Scripts &amp;amp;&amp;amp; sudo nano /Scripts/mac-random.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nano will open up a new &lt;code&gt;mac-random.sh&lt;/code&gt; file with blank contents. Paste the following in there:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash

# disconnect the airport from any network

sudo /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -z

# use OpenSSL to generate a random MAC address

mac=$(openssl rand -hex 1 | tr &apos;[:lower:]&apos; &apos;[:upper:]&apos; | xargs echo &amp;quot;obase=2;ibase=16;&amp;quot; | bc | cut -c1-6 | sed &apos;s/$/00/&apos; | xargs echo &amp;quot;obase=16;ibase=2;&amp;quot; | bc | sed &amp;quot;s/$/:$(openssl rand -hex 5 | sed &apos;s/\(..\)/\1:/g; s/.$//&apos; | tr &apos;[:lower:]&apos; &apos;[:upper:]&apos;)/&amp;quot; )

# reassign the wifi adapter with the saved $mac variable above

# the wifi adapter in my case is &amp;quot;en0&amp;quot;. yours may be different. run &amp;quot;ifconfig&amp;quot; to find yours.

sudo ifconfig en0 ether $mac
networksetup -detectnewhardware
networksetup -setairportpower airport off
networksetup -setairportpower airport on

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the elaborate chain of commands that we used with OpenSSL? Props to commenter &lt;a href=&quot;http://osxdaily.com/2012/03/01/change-mac-address-os-x/#comment-384258&quot;&gt;osmium&lt;/a&gt; for that. This ensures that the &lt;a href=&quot;https://en.wikipedia.org/wiki/MAC_address#Address_details&quot;&gt;least significant bit of the first byte is 0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Save &lt;code&gt;mac-random.sh&lt;/code&gt; with the new contents. Now we get need to reference this file on startup!&lt;/p&gt;
&lt;h3&gt;Load the Bash script on every bootup&lt;/h3&gt;
&lt;p&gt;But first let&apos;s make sure it has all the right permissions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chown -R root:admin /Scripts/mac-random.sh
sudo chmod u=rwx /Scripts/mac-random.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The kosher way OS X loads boot items is with a launchd &lt;code&gt;.plist&lt;/code&gt; file. Let&apos;s create one which references the script.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /Library/LaunchDaemons &amp;amp;&amp;amp; sudo nano com.superuser.macrandom.plist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste the following XML-style contents into the blank file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;!DOCTYPE plist PUBLIC &amp;quot;-//Apple//DTD PLIST 1.0//EN&amp;quot; &amp;quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;quot;&amp;gt;
&amp;lt;plist version=&amp;quot;1.0&amp;quot;&amp;gt;
&amp;lt;dict&amp;gt;
  &amp;lt;key&amp;gt;Label&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;com.superuser.macrandom&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;ProgramArguments&amp;lt;/key&amp;gt;
  &amp;lt;array&amp;gt;
    &amp;lt;string&amp;gt;/bin/sh&amp;lt;/string&amp;gt;
    &amp;lt;string&amp;gt;/Users/ericschaefer/Scripts/mac-random.sh&amp;lt;/string&amp;gt;
  &amp;lt;/array&amp;gt;
  &amp;lt;key&amp;gt;UserName&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;root&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;GroupName&amp;lt;/key&amp;gt;
  &amp;lt;string&amp;gt;wheel&amp;lt;/string&amp;gt;
  &amp;lt;key&amp;gt;RunAtLoad&amp;lt;/key&amp;gt;
  &amp;lt;true/&amp;gt;
  &amp;lt;key&amp;gt;Debug&amp;lt;/key&amp;gt;
  &amp;lt;true/&amp;gt;
  &amp;lt;key&amp;gt;KeepAlive&amp;lt;/key&amp;gt;
  &amp;lt;false/&amp;gt;
&amp;lt;/dict&amp;gt;
&amp;lt;/plist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and exit nano. Then change the permissions on this file that you just saved:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chown -R root:wheel com.superuser.macrandom.plist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then load the file into launchd:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo launchctl load com.superuser.macrandom.plist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check if you&apos;re good to go...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo launchctl list | grep macrandom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see the launch item, then this will load on boot!&lt;/p&gt;
&lt;p&gt;Once you&apos;ve rebooted, run &lt;code&gt;ifconfig&lt;/code&gt; to check if your MAC address has changed for your wi-fi adapter (named &lt;code&gt;en1&lt;/code&gt; in my case).&lt;/p&gt;
</content:encoded></item><item><title>Avoid a Headache: Precompiling Zurb (Foundation) on Heroku</title><link>https://eric-schaefer.com/blog/2013-04-24-rails-zurb-foundation-import-heroku/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-04-24-rails-zurb-foundation-import-heroku/</guid><description>A quick fix for a common Heroku deployment problem.</description><pubDate>Tue, 23 Apr 2013 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It always happens. My pristine development environment is running my app just fine. Then I deploy it to Heroku and whomp, trouble abounds. Let&apos;s leave alone the fact that I&apos;m using the zurb-foundation gem, and instead focus on the problem which has to be afflicting you as well if you&apos;re reading this.&lt;/p&gt;
&lt;p&gt;I got this message in my Heroku log while deploying my app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    ....
    Preparing app for Rails asset pipeline
    Running: rake assets:precompile
    rake aborted!
    File to import not found or unreadable: foundation/common/ratios.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, look in your assets/stylesheets folder, the zurb-foundation gem generated a file called &amp;lt;code&amp;gt;foundation_and_overrides.scss&amp;lt;/code&amp;gt;. The first thing it does is &amp;lt;code&amp;gt;@import &amp;quot;foundation/common/ratios&amp;quot;;&amp;lt;/code&amp;gt;. So, what? Well, if you&apos;re lazy like me, you&apos;re loading the zurb-foundation gem without a specified version number:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;group :assets do
gem &apos;sass-rails&apos;, &apos;~&amp;gt; 3.2.3&apos;
gem &apos;coffee-rails&apos;, &apos;~&amp;gt; 3.2.1&apos;
gem &apos;compass-rails&apos;
gem &apos;zurb-foundation&apos;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the time of writing this, the zurb-foundation gem is at 4.1.2. Which means that &apos;foundation/common/ratios&apos; is no longer included in the gem. The quick and dirty fix would be to lock the zurb-foundation gem down to 3.2.5 (&lt;a href=&quot;http://stackoverflow.com/questions/15123667/syntax-error-file-to-import-not-found-or-unreadable-foundation-common-ratios&quot;&gt;thank you Stackoverflow&lt;/a&gt;). So now I declare the gem &apos;zurb-foundation&apos; in the assets group like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &apos;zurb-foundation&apos;, &apos;= 3.2.5&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save your gemfile, now run &amp;lt;code&amp;gt;bundle update&amp;lt;/code&amp;gt;, commit your changes to git, and re-deploy to Heroku. Should be smooth sailing and your assets should compile just fine.&lt;/p&gt;
</content:encoded></item><item><title>Deploying a Rails Application to Dreamhost with Capistrano</title><link>https://eric-schaefer.com/blog/2012-09-26-deploying-rails-app-dreamhost-capistrano/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2012-09-26-deploying-rails-app-dreamhost-capistrano/</guid><description>Deploying a Rails app to Dreamhost with Capistrano.</description><pubDate>Tue, 25 Sep 2012 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I don&apos;t think there&apos;s a quick way to figure out your first &amp;quot;real&amp;quot; Rails deployment process. Sure there&apos;s Heroku (and now cloudControl, based here in Berlin) with magic one- or two-liner deployments. But they get expensive very quickly if you&apos;re running more than a dev server. Your file system also remains fixed after deployment, so you will end up storing your user assets in yet another cloud service. So for now I am relying on a cheap VPS from Dreamhost. A little more complex but also more flexible, and I&apos;m learning a lot.&lt;/p&gt;
&lt;h4&gt;Some background before getting started:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://railscasts.com/episodes/335-deploying-to-a-vps&quot;&gt;Railscast: Deploying to a VPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.dreamhost.com/Capistrano&quot;&gt;Capistrano on Dreamhost (kinda dated, but useful)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.dreamhost.com/Git#Setup_One:_For_the_Impatient&quot;&gt;Syncing a git repository&lt;/a&gt; directly between your machine and Dreamhost. No GitHub!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I spent a good while figuring out a recipe to deploy a Rails app using Git with Capistrano on Dreamhost&apos;s particular hosting setup. I felt compelled now to share some of the snags I hit, so hopefully I can save someone else out there from wasting a few hours.&lt;/p&gt;
&lt;h4&gt;A Capistrano recipe for Dreamhost&lt;/h4&gt;
&lt;p&gt;I&apos;m assuming you set up Capistrano in your app. My config/deploy.rb ended up looking like this. Read through just to get a sense of how this works with Dreamhost. All-caps should be replaced with your own values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;set :user, &amp;quot;YOURUSERNAME&amp;quot; # Your dreamhost account&apos;s username
set :domain, &apos;YOURSERVER.dreamhost.com&apos; # Dreamhost servername where your account is located
set :project, &apos;YOURPROJECT&apos; # Your application as its called in the git repository
set :application, &apos;YOURAPPFOLDER&apos; # Your app&apos;s location (domain or sub-domain name as setup in panel)
set :applicationdir, &amp;quot;/home/#{user}/#{application}&amp;quot; # The standard Dreamhost setup

set :repository, &amp;quot;ssh://YOURUSERNAME@SERVER.dreamhost.com/home/YOURUSERNAME/repos/YOURPROJECT.git&amp;quot;

ssh_options[:forward_agent] = true
default_run_options[:pty] = true

set :scm, :git
set :branch, &apos;master&apos; # Or whatever branch you&apos;re on.
set :git_shallow_clone, 1
set :scm_verbose, true

# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`

role :web, domain # Your HTTP server, Apache/etc
role :app, domain # This may be the same as your `Web` server
role :db, domain, :primary =&amp;gt; true # This is where Rails migrations will run

# deploy config

set :deploy_to, applicationdir
set :deploy_via, :remote_cache
set :chmod755, &amp;quot;app config db lib public vendor script script/_ public/disp_&amp;quot;

set :use_sudo, false

set :normalize_asset_timestamps, false

namespace :deploy do
namespace :assets do
task :precompile, :roles =&amp;gt; :web, :except =&amp;gt; { :no_release =&amp;gt; true } do
from = source.next_revision(current_revision)
if capture(&amp;quot;cd #{latest_release} &amp;amp;&amp;amp; #{source.local.log(from)} vendor/assets/ app/assets/ | wc -l&amp;quot;).to_i &amp;gt; 0
run %Q{cd #{latest_release} &amp;amp;&amp;amp; #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile}
else
logger.info &amp;quot;Skipping asset pre-compilation because there were no asset changes&amp;quot;
end
end
end
end

# if you want to clean up old releases on each deploy uncomment this:

after &amp;quot;deploy:restart&amp;quot;, &amp;quot;deploy:cleanup&amp;quot;

# if you&apos;re still using the script/reaper helper you will need

# these http://github.com/rails/irs_process_scripts

# If you are using Passenger mod_rails uncomment this:

namespace :deploy do
task :start do ; end
task :stop do ; end
task :restart, :roles =&amp;gt; :app, :except =&amp;gt; { :no_release =&amp;gt; true } do
run &amp;quot;#{try_sudo} touch #{File.join(current_path,&apos;tmp&apos;,&apos;restart.txt&apos;)}&amp;quot;
end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your git repo is in place, and you just want to deploy your damn shit from your local terminal, run cap deploy:setup just once before running cap deploy. Capistrano will be verbose about anything going wrong, so it won&apos;t be a mystery to track down what the problem is, if you have one. Something probably will go wrong the first time.&lt;/p&gt;
&lt;p&gt;More like than not, your asset pipeline won&apos;t precompile properly through the Capistrano deployment. If you figured out the secret to that, let me know. So for now I SSH into my &amp;lt;code&amp;gt;/home/username/app/current&amp;lt;/code&amp;gt; folder and run &amp;lt;code&amp;gt;bundle exec rake:assets --precompile&amp;lt;/code&amp;gt;.&lt;/p&gt;
&lt;h4&gt;Set up your shared gems folder&lt;/h4&gt;
&lt;p&gt;You might also find your VPS environment needs customization to use gems shared across different Rails apps, and StackOverflow will be your friend there.&lt;/p&gt;
&lt;p&gt;If you&apos;re sharing gems, the most important thing you&apos;ll need that&apos;s specific to Dreamhost is this bit of code in the top of your &amp;lt;code&amp;gt;config/config.ru&amp;lt;/code&amp;gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;ENV[&apos;GEM_HOME&apos;] = &apos;/home/YOURUSERNAME/YOURSHAREDGEMFOLDER&apos;
require &apos;rubygems&apos;
Gem.clear_paths
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Passenger will also be vocal about what you&apos;re missing if you try to access your newly deployed app from a web browser.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Prettify JSON data in the Rails console with pretty_generate</title><link>https://eric-schaefer.com/blog/2013-02-13-pretty-json-rails-console/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2013-02-13-pretty-json-rails-console/</guid><description>A nice way to format JSON in the Rails console.</description><pubDate>Tue, 12 Feb 2013 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Using the Rails console has been a big time-saver for me. I&apos;m trying to minimize switching between my browser window and my development windows (terminals, Sublime Text), and a lot of the debugging that I once did through the browser can easily be moved into IRB.&lt;/p&gt;
&lt;p&gt;But the first problem I encountered was JSON formatting in IRB. I&apos;ve been working with JSON and re-mapping some hashes quite a bit. Not pretty in the console.&lt;/p&gt;
&lt;h4&gt;Example 1: Jumbled JSON&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;1.9.3-p362 :035 &amp;gt; puts some_json
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;{&amp;quot;count&amp;quot;=&amp;gt;1, &amp;quot;results&amp;quot;=&amp;gt;[{&amp;quot;listing_id&amp;quot;=&amp;gt;96441649, &amp;quot;state&amp;quot;=&amp;gt;&amp;quot;active&amp;quot;, &amp;quot;user_id&amp;quot;=&amp;gt;11923733, &amp;quot;category_id&amp;quot;=&amp;gt;69150359, &amp;quot;title&amp;quot;=&amp;gt;&amp;quot;Hipster Owl with Glasses - Yellow - Space Invaders - Retro&amp;quot;, &amp;quot;description&amp;quot;=&amp;gt;&amp;quot;This Hipster Owl is just too cute!!!\r\n\r\nHe measures approx. 10&amp;amp;quot; tall and 9.5&amp;amp;quot; wide.\r\n\r\nHis eyes, nose and glasses are made from felt. \r\n\r\nHis body is white with tiny black dots. On his tummy, I used a Charlie Brown yellow and black fabric and his wings are a space invader print.\r\n\r\n\r\n\* Not machine washable\r\n\r\n**\*** This will be a duplicate of the item shown above.&amp;quot;, &amp;quot;creation_tsz&amp;quot;=&amp;gt;1359839867, &amp;quot;ending_tsz&amp;quot;=&amp;gt;1370145600, &amp;quot;original_creation_tsz&amp;quot;=&amp;gt;1333072966, &amp;quot;last_modified_tsz&amp;quot;=&amp;gt;1359840873, &amp;quot;price&amp;quot;=&amp;gt;&amp;quot;30.00&amp;quot;, &amp;quot;currency_code&amp;quot;=&amp;gt;&amp;quot;USD&amp;quot;, &amp;quot;quantity&amp;quot;=&amp;gt;1, &amp;quot;tags&amp;quot;=&amp;gt;[&amp;quot;hipster&amp;quot;, &amp;quot;plushie&amp;quot;, &amp;quot;nursery&amp;quot;, &amp;quot;baby&amp;quot;, &amp;quot;birthday&amp;quot;, &amp;quot;gift&amp;quot;, &amp;quot;pillow&amp;quot;, &amp;quot;retro&amp;quot;, &amp;quot;old school&amp;quot;, &amp;quot;dot&amp;quot;, &amp;quot;charlie brown&amp;quot;, &amp;quot;space invaders&amp;quot;, &amp;quot;geek&amp;quot;], &amp;quot;category_path&amp;quot;=&amp;gt;[&amp;quot;Geekery&amp;quot;], &amp;quot;category_path_ids&amp;quot;=&amp;gt;[69150359], &amp;quot;materials&amp;quot;=&amp;gt;[&amp;quot;cotton&amp;quot;, &amp;quot;fiberfil&amp;quot;, &amp;quot;felt&amp;quot;, &amp;quot;buttons&amp;quot;], &amp;quot;shop_section_id&amp;quot;=&amp;gt;11341955, &amp;quot;featured_rank&amp;quot;=&amp;gt;5, &amp;quot;state_tsz&amp;quot;=&amp;gt;1359839867, &amp;quot;url&amp;quot;=&amp;gt;&amp;quot;http://www.etsy.com/listing/96441649/hipster-owl-with-glasses-yellow-space?utm_source=tipsy&amp;amp;utm_medium=api&amp;amp;utm_campaign=api&amp;quot;, &amp;quot;views&amp;quot;=&amp;gt;768, &amp;quot;num_favorers&amp;quot;=&amp;gt;121, &amp;quot;shipping_profile_id&amp;quot;=&amp;gt;169274918, &amp;quot;processing_min&amp;quot;=&amp;gt;1, &amp;quot;processing_max&amp;quot;=&amp;gt;3, &amp;quot;who_made&amp;quot;=&amp;gt;&amp;quot;i_did&amp;quot;, &amp;quot;is_supply&amp;quot;=&amp;gt;&amp;quot;false&amp;quot;, &amp;quot;when_made&amp;quot;=&amp;gt;&amp;quot;made_to_order&amp;quot;, &amp;quot;recipient&amp;quot;=&amp;gt;nil, &amp;quot;occasion&amp;quot;=&amp;gt;nil, &amp;quot;style&amp;quot;=&amp;gt;nil, &amp;quot;non_taxable&amp;quot;=&amp;gt;false}], &amp;quot;params&amp;quot;=&amp;gt;{&amp;quot;listing_id&amp;quot;=&amp;gt;&amp;quot;96441649&amp;quot;}, &amp;quot;type&amp;quot;=&amp;gt;&amp;quot;Listing&amp;quot;, &amp;quot;pagination&amp;quot;=&amp;gt;{}}
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ugly, right? The nesting structure is impenetrable at a quick glance. But the standard JSON module includes a little-known method called pretty_generate for exactly this situation. Check it out:&lt;/p&gt;
&lt;h4&gt;Example 2: Pretty JSON&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;1.9.3-p362 :034 &amp;gt; puts JSON.pretty_generate some_json
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;{
&amp;quot;count&amp;quot;: 1,
&amp;quot;results&amp;quot;: [
{
&amp;quot;listing_id&amp;quot;: 96441649,
&amp;quot;state&amp;quot;: &amp;quot;active&amp;quot;,
&amp;quot;user_id&amp;quot;: 11923733,
&amp;quot;category_id&amp;quot;: 69150359,
&amp;quot;title&amp;quot;: &amp;quot;Hipster Owl with Glasses - Yellow - Space Invaders - Retro&amp;quot;,
&amp;quot;description&amp;quot;: &amp;quot;This Hipster Owl is just too cute!!!\r\n\r\nHe measures approx. 10&amp;amp;quot; tall and 9.5&amp;amp;quot; wide.\r\n\r\nHis eyes, nose and glasses are made from felt. \r\n\r\nHis body is white with tiny black dots. On his tummy, I used a Charlie Brown yellow and black fabric and his wings are a space invader print.\r\n\r\n\r\n\* Not machine washable\r\n\r\n**\*** This will be a duplicate of the item shown above.&amp;quot;,
&amp;quot;creation_tsz&amp;quot;: 1359839867,
&amp;quot;ending_tsz&amp;quot;: 1370145600,
&amp;quot;original_creation_tsz&amp;quot;: 1333072966,
&amp;quot;last_modified_tsz&amp;quot;: 1359840873,
&amp;quot;price&amp;quot;: &amp;quot;30.00&amp;quot;,
&amp;quot;currency_code&amp;quot;: &amp;quot;USD&amp;quot;,
&amp;quot;quantity&amp;quot;: 1,
&amp;quot;tags&amp;quot;: [
&amp;quot;hipster&amp;quot;,
&amp;quot;plushie&amp;quot;,
&amp;quot;nursery&amp;quot;,
&amp;quot;baby&amp;quot;,
&amp;quot;birthday&amp;quot;,
&amp;quot;gift&amp;quot;,
&amp;quot;pillow&amp;quot;,
&amp;quot;retro&amp;quot;,
&amp;quot;old school&amp;quot;,
&amp;quot;dot&amp;quot;,
&amp;quot;charlie brown&amp;quot;,
&amp;quot;space invaders&amp;quot;,
&amp;quot;geek&amp;quot;
],
&amp;quot;category_path&amp;quot;: [
&amp;quot;Geekery&amp;quot;
],
&amp;quot;category_path_ids&amp;quot;: [
69150359
],
&amp;quot;materials&amp;quot;: [
&amp;quot;cotton&amp;quot;,
&amp;quot;fiberfil&amp;quot;,
&amp;quot;felt&amp;quot;,
&amp;quot;buttons&amp;quot;
],
&amp;quot;shop_section_id&amp;quot;: 11341955,
&amp;quot;featured_rank&amp;quot;: 5,
&amp;quot;state_tsz&amp;quot;: 1359839867,
&amp;quot;url&amp;quot;: &amp;quot;http://www.etsy.com/listing/96441649/hipster-owl-with-glasses-yellow-space?utm_source=tipsy&amp;amp;amp;utm_medium=api&amp;amp;amp;utm_campaign=api&amp;quot;,
&amp;quot;views&amp;quot;: 768,
&amp;quot;num_favorers&amp;quot;: 121,
&amp;quot;shipping_profile_id&amp;quot;: 169274918,
&amp;quot;processing_min&amp;quot;: 1,
&amp;quot;processing_max&amp;quot;: 3,
&amp;quot;who_made&amp;quot;: &amp;quot;i_did&amp;quot;,
&amp;quot;is_supply&amp;quot;: &amp;quot;false&amp;quot;,
&amp;quot;when_made&amp;quot;: &amp;quot;made_to_order&amp;quot;,
&amp;quot;recipient&amp;quot;: null,
&amp;quot;occasion&amp;quot;: null,
&amp;quot;style&amp;quot;: null,
&amp;quot;non_taxable&amp;quot;: false
}
],
&amp;quot;params&amp;quot;: {
&amp;quot;listing_id&amp;quot;: &amp;quot;96441649&amp;quot;
},
&amp;quot;type&amp;quot;: &amp;quot;Listing&amp;quot;,
&amp;quot;pagination&amp;quot;: {
}
}
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nicely formatted so you can remap elements, and get a nice clear picture of whatever JSON you&apos;re consuming (or serving up).&lt;/p&gt;
&lt;h4&gt;Another option: &lt;a href=&quot;https://github.com/michaeldv/awesome_print&quot;&gt;Awesome Print&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This is an extra ruby gem that you&apos;ll need to install in your dev environment, but it adds color and all kinds of goodies. Keeps you cozy in that console.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Music and Noise Compilation: Teenage Mustache</title><link>https://eric-schaefer.com/blog/2015-07-26-new-mix-teenage-mustache/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2015-07-26-new-mix-teenage-mustache/</guid><description>A compilation of ambient, noise, and new-wave music.</description><pubDate>Sat, 25 Jul 2015 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Figured it&apos;s not a terrible idea to share some of the music rumbling in the background of life. Lately I&apos;ve been listening to a bunch of ambient, noise, German new-wave, and everything in between.&lt;/p&gt;
&lt;p&gt;Behold, a mix. I&apos;ll aim to keep these semi-frequent.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe width=&amp;quot;100%&amp;quot; height=&amp;quot;360&amp;quot; src=&amp;quot;https://www.mixcloud.com/widget/iframe/?embed_type=widget_standard&amp;amp;embed_uuid=b9147660-a0f4-460e-bda5-31a84a7ddad0&amp;amp;feed=https%3A%2F%2Fwww.mixcloud.com%2Fericfaceplace%2Fteenage-mustache%2F&amp;amp;hide_cover=1&amp;amp;replace=0&amp;quot; frameborder=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;div style=&amp;quot;clear: both; height: 3px; width: auto;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p style=&amp;quot;display: block; font-size: 11px; font-family: &apos;Open Sans&apos;, Helvetica, Arial, sans-serif; margin: 0px; padding: 3px 4px; color: rgb(153, 153, 153); width: auto;&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.mixcloud.com/ericfaceplace/teenage-mustache/?utm_source=widget&amp;amp;utm_medium=web&amp;amp;utm_campaign=base_links&amp;amp;utm_term=resource_link&amp;quot; target=&amp;quot;_blank&amp;quot; style=&amp;quot;color:#808080; font-weight:bold;&amp;quot;&amp;gt;Teenage Mustache&amp;lt;/a&amp;gt;&amp;lt;span&amp;gt; by &amp;lt;/span&amp;gt;&amp;lt;a href=&amp;quot;https://www.mixcloud.com/ericfaceplace/?utm_source=widget&amp;amp;utm_medium=web&amp;amp;utm_campaign=base_links&amp;amp;utm_term=profile_link&amp;quot; target=&amp;quot;_blank&amp;quot; style=&amp;quot;color:#808080; font-weight:bold;&amp;quot;&amp;gt;Eric&amp;lt;/a&amp;gt;&amp;lt;span&amp;gt; on &amp;lt;/span&amp;gt;&amp;lt;a href=&amp;quot;https://www.mixcloud.com/?utm_source=widget&amp;amp;utm_medium=web&amp;amp;utm_campaign=base_links&amp;amp;utm_term=homepage_link&amp;quot; target=&amp;quot;_blank&amp;quot; style=&amp;quot;color:#808080; font-weight:bold;&amp;quot;&amp;gt; Mixcloud&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;div style=&amp;quot;clear: both; height: 3px; width: auto;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Removing Trailing Slashes from a Rails App on Heroku</title><link>https://eric-schaefer.com/blog/2015-07-24-removing-trailing-slashes-rails-app-heroku/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2015-07-24-removing-trailing-slashes-rails-app-heroku/</guid><description>A quick way to remove trailing slashes from a Rails app on Heroku.</description><pubDate>Thu, 23 Jul 2015 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I need to redirect those terrible trailing URL slashes on a large project with thousands of articles. Turns out that trailing slash &lt;code&gt;==&lt;/code&gt; duplicate content.&lt;/p&gt;
&lt;p&gt;The first suggestion is usually to &lt;a href=&quot;http://stackoverflow.com/questions/627006/how-to-remove-a-urls-trailing-slash-in-a-rails-app-in-a-seo-view&quot;&gt;use Apache&apos;s mod_rewrite&lt;/a&gt;, however I wanted a server-agnostic way to handle these redirects. Not every project these days is sitting on top of Apache. And in my particular case, I need this to work on Heroku&apos;s very tightly controlled configuration stack.&lt;/p&gt;
&lt;p&gt;The solution which worked for me is just including and configuring the &lt;a href=&quot;https://github.com/jtrupiano/rack-rewrite&quot;&gt;rack-rewrite&lt;/a&gt; gem.&lt;/p&gt;
&lt;h4&gt;1. Add &lt;code&gt;rack-rewrite&lt;/code&gt; to your Gemfile&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &apos;rack-rewrite&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then rerun &lt;code&gt;bundle install&lt;/code&gt; from your shell to make it available to your project.&lt;/p&gt;
&lt;h4&gt;2. Configure&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Rails 4&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;config/application.rb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;require &amp;quot;rack/rewrite&amp;quot;

...

# Rewrite trailing slashes

# goes inside your main Application class

config.middleware.insert_before(Rack::Runtime, Rack::Rewrite) do
r301 %r{^/(.\*)/$}, &apos;/$1&apos;
end
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Setting Up Docker From Nothing</title><link>https://eric-schaefer.com/blog/2015-06-19-starting-docker-from-nothing/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2015-06-19-starting-docker-from-nothing/</guid><description>A quick walkthrough on how to set up Docker.</description><pubDate>Thu, 18 Jun 2015 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/posts/docker.png&quot; alt=&quot;Docker&quot;&gt;&lt;/p&gt;
&lt;h1&gt;Docker... Starting from Nothing&lt;/h1&gt;
&lt;p&gt;On OS X, you will need Three applications... VirtualBox, Boot2Docker and Compose.&lt;/p&gt;
&lt;h2&gt;Boot2Docker&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Because the Docker daemon uses Linux-specific kernel features, you can&apos;t run Docker natively in OS X. Instead, you must install the Boot2Docker application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;1. Download/install &lt;a href=&quot;https://www.virtualbox.org/wiki/Downloads&quot;&gt;VirtualBox&lt;/a&gt;&lt;/h4&gt;
&lt;h4&gt;2. Install Boot2docker and Compose to run Docker on OS X.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew install boot2docker docker-compose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the Homebrew instructions to manage launching on startup, etc.&lt;/p&gt;
&lt;h4&gt;3. Create a new Boot2Docker VM. You only need to do this once, yay!&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ boot2docker init
$ boot2docker start
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. Display the environment variables for the Docker client.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ boot2docker shellinit
Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem
    export DOCKER_HOST=tcp://192.168.59.103:2376
    export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. Set the environment variables in your shell:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ eval &amp;quot;$(boot2docker shellinit)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll need to run this for every terminal session that invokes the &lt;code&gt;docker&lt;/code&gt; or &lt;code&gt;docker-compose&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Auto initiate this by adding the following line to the bottom of either your &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ eval `boot2docker shellinit 2&amp;gt;/dev/null`
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6. Run the hello-world container to verify your setup.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker run hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Example Dockerfile for a Rails project&lt;/h3&gt;
&lt;p&gt;In the root directory of your Rails project, create a file called &lt;code&gt;Dockerfile&lt;/code&gt;. Example contents for our Wildeisen project would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-docker&quot;&gt;FROM ruby:2.2.2

RUN apt-get update -qq &amp;amp;&amp;amp; apt-get install -y build-essential

# for postgres
RUN apt-get install -y libpq-dev

# for nokogiri
RUN apt-get install -y libxml2-dev libxslt1-dev

# for a JS runtime
# patch the version of Node since apt-get would grab an olllld version
RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash -
RUN apt-get install -y nodejs

# for npm task runners
# grab newer version, not from apt-get
RUN curl -L https://www.npmjs.com/install.sh | sh

ENV APP_HOME /project
RUN mkdir $APP_HOME

WORKDIR /tmp
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
COPY package.json package.json
RUN bundle install
RUN npm install

ADD . $APP_HOME
WORKDIR $APP_HOME

# for running grunt task, which builds SVG sprites
RUN npm install -g grunt-cli

ADD . $APP_HOME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To build this Docker image, from Rails root run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker build .
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Compose&lt;/h2&gt;
&lt;p&gt;Compose lets us configure our application from one &lt;code&gt;yml&lt;/code&gt; file. Pretty sweet.&lt;/p&gt;
&lt;p&gt;In the root directory of our Rails project, create a file called &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s a really basic example that works for my project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;db:
  image: postgres

redis:
  image: redis

web:
  build: .
  volumes:
   - .:/project
  command: bin/rails server --port 3000 --binding 0.0.0.0
  links:
   - db
   - redis
  ports:
   - &amp;quot;3000:3000&amp;quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then build the project with the above config:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can run it with our Docker container!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find out which IP address to access the Rails server from by opening a new shell window and typing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ boot2docker ip
192.168.59.103
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, I can access 192.168.59.103, with the port we set in our &lt;code&gt;yml&lt;/code&gt; file, 3000.&lt;/p&gt;
&lt;p&gt;So voila, I can now see my Rails project in my browser at &lt;code&gt;http://192.168.59.103:3000&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;npm&lt;/code&gt; watch/build tasks is easy too.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose run web npm run watch
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose run web npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or whatever task runners you have defined in your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
</content:encoded></item><item><title>Apollo + GraphQL: A step toward more declarative UIs</title><link>https://eric-schaefer.com/blog/2017-10-22-apollo-graphql-talk-berlinjs/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2017-10-22-apollo-graphql-talk-berlinjs/</guid><description>A talk I gave at BerlinJS.</description><pubDate>Sat, 21 Oct 2017 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div class=&amp;quot;center-block&amp;quot;&amp;gt;
&amp;lt;blockquote class=&amp;quot;twitter-tweet&amp;quot; data-lang=&amp;quot;en&amp;quot;&amp;gt;&amp;lt;p lang=&amp;quot;en&amp;quot; dir=&amp;quot;ltr&amp;quot;&amp;gt;And here is &amp;lt;a href=&amp;quot;https://twitter.com/ultrasandwich?ref_src=twsrc%5Etfw&amp;quot;&amp;gt;@ultrasandwich&amp;lt;/a&amp;gt;, kicking off today’s series of talks! &amp;lt;a href=&amp;quot;https://t.co/EzNBVEjPgG&amp;quot;&amp;gt;pic.twitter.com/EzNBVEjPgG&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;— BerlinJS (@berlinjs) &amp;lt;a href=&amp;quot;https://twitter.com/berlinjs/status/921070699342630912?ref_src=twsrc%5Etfw&amp;quot;&amp;gt;October 19, 2017&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;
&amp;lt;script async src=&amp;quot;//platform.twitter.com/widgets.js&amp;quot; charset=&amp;quot;utf-8&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;Today I gave my first talk at BerlinJS! I started with some gripes that I have
with building imperative-style user interfaces, and gave some examples of how
declarative UIs are simpler to maintain. I wrapped-up with an example React app
that I built with Apollo and GraphQL, and demonstrated how we can get selective
server-side rendering and all kinds of smart performance improvements with this
toolset.&lt;/p&gt;
&lt;p&gt;I also open-sourced the boilerplate that I built for this talk, so check it out
if you want a starting-point for your next app that needs a more declarative
approach to fetching data:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/eschaefer/react-apollo-ssr-boilerplate&quot;&gt;https://github.com/eschaefer/react-apollo-ssr-boilerplate&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Compiling dcraw to WebAssembly</title><link>https://eric-schaefer.com/blog/2020-01-10-compiling-dcraw-to-webassembly/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2020-01-10-compiling-dcraw-to-webassembly/</guid><description>My first adventure into WebAssembly.</description><pubDate>Thu, 09 Jan 2020 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At EyeEm we handle about every digital photo format you can imagine. And with the variety of digital cameras out there, some of our web-based tools must accept &lt;a href=&quot;https://en.wikipedia.org/wiki/Raw_image_format&quot;&gt;RAW photo formats&lt;/a&gt;. Sometimes a JPG just won&apos;t make the cut.&lt;/p&gt;
&lt;p&gt;RAW photo file sizes can be huge. Depending on which camera you&apos;re using, they can vary from 10MB to well over 100MB.&lt;/p&gt;
&lt;p&gt;That&apos;s of course impractical for web use. I want to take these huge RAW files and compress them for web usage so we can have thumbnail previews. And I want to do this without a round-trip to another server.&lt;/p&gt;
&lt;h2&gt;Goals&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Process RAW images in the browser. We &lt;strong&gt;don&apos;t&lt;/strong&gt; want to do this server-side.&lt;/li&gt;
&lt;li&gt;Leverage WebAssembly for speed and memory safety.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Emscripten&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://emscripten.org/index.html&quot;&gt;Emscripten&lt;/a&gt; is a toolchain for compiling to asm.js and WebAssembly, built using LLVM, that lets you run C and C++ on the web at near-native speed without plugins.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How magical does that sound? It also sounds a little bit like things might get complicated. However though, I found that was &lt;strong&gt;not&lt;/strong&gt; the case. In fact I was genuinely in awe of how much Emscripten can do with minimal configuration.&lt;/p&gt;
&lt;h2&gt;dcraw&lt;/h2&gt;
&lt;p&gt;David Coffin wrote a brilliant &lt;a href=&quot;https://www.dechifro.org/dcraw/&quot;&gt;open source library&lt;/a&gt; written in C called &lt;code&gt;dcraw&lt;/code&gt; which supports decoding RAW files from 731 different digital cameras. It&apos;s been around since the &apos;90s and seems pretty battle-tested. There&apos;s even a &lt;a href=&quot;https://github.com/zfedoran/dcraw.js&quot;&gt;port&lt;/a&gt; of &lt;code&gt;dcraw&lt;/code&gt;to Node.js by Zelemir Fedoran. And while this port &lt;strong&gt;can&lt;/strong&gt; run as plain Javascript in the browser through some neat &lt;code&gt;MEMFS&lt;/code&gt; tricks, it does not leverage WASM for the heavy lifting.&lt;/p&gt;
&lt;p&gt;Looking at Fedoran&apos;s &lt;a href=&quot;https://github.com/zfedoran/dcraw.js/blob/master/makefile&quot;&gt;build process&lt;/a&gt; gave me a great starting point for building &lt;code&gt;dcraw&lt;/code&gt; with Emscripten&apos;s &lt;code&gt;emcc&lt;/code&gt; tool. I want to leverage the speed of WASM too, so my end-goal is to compile &lt;code&gt;dcraw&lt;/code&gt; so we can call it directly in the browser with all the speed that the WASM binary gives us.&lt;/p&gt;
&lt;h2&gt;Extracting embedded JPGs from a RAW file&lt;/h2&gt;
&lt;p&gt;Did you know that &lt;strong&gt;most RAW files include an embedded JPG thumbnail&lt;/strong&gt;? These may be used for low-penalty previews in a camera&apos;s display screen. Well no wonder RAW files are so big. The size and resolution of these JPGs can vary a bit depending on the camera model, so you might want to run some checks on what you extract. I&apos;ve seen some extracted preview JPGs that are only 400px wide, which doesn&apos;t meet our size requirements for thumbnail resolution.&lt;/p&gt;
&lt;p&gt;So how do we extract these?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dcraw&lt;/code&gt; includes an &lt;code&gt;-e&lt;/code&gt; (extract?) flag, which will return an extracted thumbnail from a RAW file, if it has one. If you were running it as a command-line tool, you would do so like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ dcraw -e &amp;lt;filename&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;First pass&lt;/h2&gt;
&lt;p&gt;Let&apos;s first try the simplest &lt;code&gt;emcc&lt;/code&gt; build targeting WASM.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc dcraw.c -s WASM=1

dcraw.c:77:10: fatal error: &apos;jasper/jasper.h&apos; file not found
#include &amp;lt;jasper/jasper.h&amp;gt;      /* Decode Red camera movies */
         ^~~~~~~~~~~~~~~~~
1 error generated.
shared:ERROR: &apos;/usr/local/opt/emscripten/libexec/llvm/bin/clang -target asmjs-unknown-emscripten -D__EMSCRIPTEN_major__=1 -D__EMSCRIPTEN_minor__=38 -D__EMSCRIPTEN_tiny__=44 -D_LIBCPP_ABI_VERSION=2 -Werror=implicit-function-declaration -Xclang -nostdsysteminc -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/include/libcxx -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/lib/libcxxabi/include -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/include/compat -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/include -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/include/libc -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/lib/libc/musl/arch/emscripten -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/local/include -DEMSCRIPTEN dcraw/dcraw.c -Xclang -disable-O0-optnone -Xclang -isystem/usr/local/Cellar/emscripten/1.38.44/libexec/system/include/SDL -c -o /var/folders/4k/bv2brsrx6cb5r4b20320_gp00000gp/T/emscripten_temp_HVI35t/dcraw_0.o -emit-llvm&apos; failed (1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ah, looks like there are some extra dependencies in &lt;code&gt;dcraw&lt;/code&gt;. Luckily its documentation says we have the option to compile without them!&lt;/p&gt;
&lt;p&gt;So I passed this extra flag like so.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc dcraw.c -lm -DNODEPS -s WASM=1

...
shared:ERROR: BINARYEN_ROOT must be set up in .emscripten
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another error, which may be specific to my OS X environment. It&apos;s a strange message, since after installing &lt;code&gt;emscripten&lt;/code&gt; with Homebrew, it specifically instructed me to comment-out the &lt;code&gt;BINARYEN_ROOT&lt;/code&gt; line in my &lt;code&gt;.emscripten&lt;/code&gt; file. Luckily, I found &lt;a href=&quot;https://github.com/Homebrew/homebrew-core/issues/47869#issuecomment-566700418&quot;&gt;this Github comment&lt;/a&gt; which had a working fix.&lt;/p&gt;
&lt;p&gt;So, I installed &lt;code&gt;binaryen&lt;/code&gt; manually.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew install binaryen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then opened &lt;code&gt;~/.emscripten&lt;/code&gt; and set&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;BINARYEN_ROOT = &apos;/usr/local/opt/binaryen&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now once again, I tried&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc dcraw.c -lm -DNODEPS -s WASM=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🎉 And it worked! A few non-fatal warnings were generated, but so were two files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a.out.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a.out.wasm&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have our WASM file, and a Javascript &amp;quot;glue&amp;quot; file which lets us interface with it.&lt;/p&gt;
&lt;p&gt;Those file names are pretty meaningless though, so let&apos;s pass another flag to specify the output names.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc dcraw.c -lm -DNODEPS -s WASM=1 -o dcraw.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which yields&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dcraw.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dcraw.wasm&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;So how do we call our WebAssembly module?&lt;/h3&gt;
&lt;p&gt;First things first, let&apos;s just call that Javascript file in a basic page, and see if anything happens in the dev tools console.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;script async src=&amp;quot;dcraw.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure enough! It prints the full &lt;code&gt;man&lt;/code&gt; page for &lt;code&gt;dcraw&lt;/code&gt; as if we were using it in a shell environment with no arguments!&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&amp;quot;https://user-images.githubusercontent.com/547148/72154445-eeb01800-33b0-11ea-912b-97c3727a4b83.png&amp;quot; alt=&amp;quot;dcraw browser console output&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;The Javascript file generated from Emscripten has automatically imported our WASM binary in the background, and run &lt;code&gt;dcraw&lt;/code&gt; in the WASM sandbox, printing the contents of &lt;code&gt;stdout&lt;/code&gt;. Pretty cool.&lt;/p&gt;
&lt;p&gt;But we don&apos;t want to immediately run &lt;code&gt;dcraw&lt;/code&gt;, which is what&apos;s happening by default. So let&apos;s disable that by adding &lt;code&gt;INVOKE_RUN=0&lt;/code&gt; and a named runtime method when we do our build.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc dcraw.c -lm -DNODEPS -s WASM=1 -s INVOKE_RUN=0 -s EXTRA_EXPORTED_RUNTIME_METHODS=&apos;[&amp;quot;callMain&amp;quot;]&apos; -o dcraw.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By doing this, we can now invoke &lt;code&gt;dcraw&lt;/code&gt; later, from a &lt;code&gt;callMain&lt;/code&gt; function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;script&amp;gt;
  var Module = {
    onRuntimeInitialized: () =&amp;gt; {
      // Set a 2 second delay to test!
      setTimeout(() =&amp;gt; Module.callMain(), 2000);
    }
  };
&amp;lt;/script&amp;gt;

&amp;lt;script async src=&amp;quot;dcraw.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we see the same output, but delayed by two seconds. The &lt;code&gt;Module.callMain()&lt;/code&gt; function is now what executes &lt;code&gt;dcraw&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&apos;s worth noting that &lt;strong&gt;only&lt;/strong&gt; &lt;code&gt;main()&lt;/code&gt; from &lt;code&gt;dcraw.c&lt;/code&gt; is called here by default, which is good enough for us right now.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By default, Emscripten-generated code always just calls the &lt;code&gt;main()&lt;/code&gt; function, and other functions are eliminated as dead code. &amp;lt;sup&amp;gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_wasm&quot;&gt;MDN&lt;/a&gt;&amp;lt;/sup&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we are calling &lt;code&gt;dcraw&lt;/code&gt;, but it&apos;s pretty useless without any files or flags. Let&apos;s add those.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Module.callMain()&lt;/code&gt; can receive one array of arguments. The last one must always be a &lt;strong&gt;reference&lt;/strong&gt; to a file buffer stored as a &lt;code&gt;Uint8Array&lt;/code&gt;. All precending ones can be flags like &lt;code&gt;-e&lt;/code&gt;, &lt;code&gt;-c&lt;/code&gt;, etc. It will take some work to get us there, but it would look like&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;Module.callMain([&amp;quot;-e&amp;quot;, &amp;quot;raw_file_buffer&amp;quot;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So how do we store a RAW file buffer as a &lt;code&gt;Uint8Array&lt;/code&gt; in MEMFS, and then pass a reference to it?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;script&amp;gt;
  var Module = {
    onRuntimeInitialized: async () =&amp;gt; {
      const image = await fetch(&amp;quot;IMG_4248.CR2&amp;quot;).then(r =&amp;gt;
        r.arrayBuffer()
      );

      // Cast ArrayBuffer to Uint8Array
      const clampedBuffer = new Uint8Array(image);

      // Create a workspace in MEMFS
      FS.mkdir(&amp;quot;/workspace&amp;quot;);
      FS.chdir(&amp;quot;/workspace&amp;quot;);
      FS.writeFile(&amp;quot;raw_file_buffer&amp;quot;, clampedBuffer);

      // The buffer is stored in FS. Pass a reference to its name.
      Module.callMain([&amp;quot;-e&amp;quot;, &amp;quot;raw_file_buffer&amp;quot;]);

      console.log(FS.readdir(&amp;quot;/workspace&amp;quot;))
      /* Will print
         [ &amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;, &amp;quot;raw_file_buffer&amp;quot;, &amp;quot;raw_file_buffer.thumb.jpg&amp;quot; ]
      */
    }
  };
&amp;lt;/script&amp;gt;

&amp;lt;script async src=&amp;quot;dcraw.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! We can already see the name of the extracted thumbnail in the FS workspace we created! All that&apos;s left is to read this new extracted thumbnail from the workspace, and clean it up.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;script&amp;gt;
  var Module = {
    onRuntimeInitialized: async () =&amp;gt; {
      const image = await fetch(&amp;quot;IMG_4248.CR2&amp;quot;).then(r =&amp;gt;
        r.arrayBuffer()
      );

      // Cast ArrayBuffer to Uint8Array
      const clampedBuffer = new Uint8Array(image);

      // Create a workspace in MEMFS
      FS.mkdir(&amp;quot;/workspace&amp;quot;);
      FS.chdir(&amp;quot;/workspace&amp;quot;);
      FS.writeFile(&amp;quot;raw_file_buffer&amp;quot;, clampedBuffer);

      // The buffer is stored in FS. Pass a reference to its name.
      Module.callMain([&amp;quot;-e&amp;quot;, &amp;quot;raw_file_buffer&amp;quot;]);

      const extracted = FS.readFile(&amp;quot;raw_file_buffer.thumb.jpg&amp;quot;, {encoding: &amp;quot;binary&amp;quot;});

      // Cleanup
      if (extracted &amp;amp;&amp;amp; clampedBuffer) {
        [&amp;quot;raw_file_buffer&amp;quot;, &amp;quot;raw_file_buffer.thumb.jpg&amp;quot;].forEach(item =&amp;gt; {
          FS.unlink(item);
        })
      }
      FS.chdir(&amp;quot;/&amp;quot;);
      FS.rmdir(&amp;quot;/workspace&amp;quot;);

      // Now use `extracted` to create an image Blob to be used in the browser
      const blob = new Blob([extracted], {type: &amp;quot;image/jpeg&amp;quot;});
      const imageUrl = URL.createObjectURL(blob);
      const img = document.createElement(&amp;quot;img&amp;quot;);
      img.src = imageUrl;
      img.width = 600;
      document.body.appendChild(img);
    }
  };
&amp;lt;/script&amp;gt;

&amp;lt;script async src=&amp;quot;dcraw.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there we have it. Our most minimal implementation of &lt;code&gt;dcraw&lt;/code&gt; compiled to WASM, extracting a RAW image thumbnail and rendering it directly in the browser.&lt;/p&gt;
&lt;p&gt;Hopefully the steps outlined are straightforward enough to go forth and compile your own C/C++ libraries to WASM. The piece that I got hung-up on the most was using the browser&apos;s in-memory &lt;code&gt;FS&lt;/code&gt;. It wasn&apos;t obvious to me that WASM was able to write directly in there, until I started logging everything in the workspace I created.&lt;/p&gt;
&lt;p&gt;If you compile anything of your own and want to share with the world, or got stuck on any part of this, just ping me on Twitter &lt;a href=&quot;https://twitter.com/ultrasandwich&quot;&gt;@ultrasandwich&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Example demo&lt;/h2&gt;
&lt;p&gt;If you would like to see the full code example from this article (plus a little extra), you can find the Github repo at &lt;a href=&quot;https://github.com/eschaefer/wasm-dcraw&quot;&gt;https://github.com/eschaefer/wasm-dcraw&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://eschaefer.github.io/wasm-dcraw&quot;&gt;A running demo is also here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.dechifro.org/dcraw&quot;&gt;dcraw home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zfedoran/dcraw.js/blob/master/makefile&quot;&gt;Compiling dcraw to node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/WebAssembly/existing_C_to_wasm&quot;&gt;Compiling an Existing C Module to WebAssembly&lt;/a&gt; - &lt;em&gt;MDN&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_wasm&quot;&gt;Compiling a New C/C++ Module to WebAssembly&lt;/a&gt; - &lt;em&gt;MDN&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://emscripten.org/docs/compiling/Building-Projects.html&quot;&gt;Building projects with Emscripten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/squoosh&quot;&gt;Squoosh&lt;/a&gt; - Open source, encode/decode many image formats in the browser using WASM&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Babel Plugin for React Components Made with Framer X</title><link>https://eric-schaefer.com/blog/2018-10-08-babel-plugin-framer-x/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2018-10-08-babel-plugin-framer-x/</guid><description>Useful for converting Framer X components into production-ready components.</description><pubDate>Sun, 07 Oct 2018 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This weekend I published a Babel plugin that turns React components made with Framer X into production-ready components that you can directly use in Storybook, Styleguidist, Gatsby, or wherever.&lt;/p&gt;
&lt;p&gt;Typically if you created React components with Framer, you need to remove the static &lt;code&gt;propertyControls&lt;/code&gt; and dependencies imported from the &lt;code&gt;framer&lt;/code&gt; npm library by hand, if you want to use them anywhere else.&lt;/p&gt;
&lt;h3&gt;The TLDR;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install --save-dev babel-plugin-framer-x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add it to your Babel configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;plugins&amp;quot;: [&amp;quot;babel-plugin-framer-x&amp;quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you find any issues, please bring them to my attention on Github!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/eschaefer/babel-plugin-framer-x&quot;&gt;https://github.com/eschaefer/babel-plugin-framer-x&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Bulk fixing photo and video dates</title><link>https://eric-schaefer.com/blog/2024-02-29-bulk-update-photo-dates/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2024-02-29-bulk-update-photo-dates/</guid><description>A quick way to mass-update photo and video creation dates.</description><pubDate>Wed, 28 Feb 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have a lot of old digital photos with the correct date in the filename. But that date doesn&apos;t match anymore with the file creation date. This can pose a big problem for a some photo library software out there, which depends on file creation date to correctly organize the photos (looking at you Google Photos).&lt;/p&gt;
&lt;p&gt;Most of my photos generated from a digital camera or phone have dates somewhere in the file names, formatted something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IMG_20161226_115358.jpg
20180101_141437.jpg
VID_20171202_124719.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; a date in there. If only I could parse the date in each file, and manually set the creation date for each one accordingly...&lt;/p&gt;
&lt;h2&gt;Regular expressions to the rescue&lt;/h2&gt;
&lt;p&gt;The date portion of the filename can be represented by the following regular expression:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[0-9]{8}_[0-9]{6}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In simple terms it means match any 8 digits (yyyymmdd) followed by &lt;code&gt;_&lt;/code&gt;, followed by any 6 digits (hhmmss).&lt;/p&gt;
&lt;p&gt;Here&apos;s a quick &lt;a href=&quot;https://regexr.com/7so6l&quot;&gt;regexr playground&lt;/a&gt; to look at it in action.&lt;/p&gt;
&lt;p&gt;Using this in a bash script could be done like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash

filename=&amp;quot;IMG_20161226_115358.jpg&amp;quot;

if [[ &amp;quot;$filename&amp;quot; =~ ([0-9]{8}_[0-9]{6}) ]]; then
  date_from_filename=&amp;quot;${BASH_REMATCH[0]}&amp;quot;
  echo &amp;quot;$date_from_filename&amp;quot; # prints 20161226_115358
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have the date formatted like &lt;code&gt;yyyymmdd_hhmmss&lt;/code&gt;. But we need to convert it into a generic timestamp that can be used. To do that we can use the &lt;code&gt;date&lt;/code&gt; utility to accept a predefined date format, and parse it into a proper system date.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;date -jf &amp;quot;%Y%m%d_%H%M%S&amp;quot; &amp;quot;20161226_115358&amp;quot;
# Mon Dec 26 11:53:58 CET 2016
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also output the parsed date into a format that can be used by the &lt;code&gt;touch&lt;/code&gt; utility, to set the file creation date...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;date -jf &amp;quot;%Y%m%d_%H%M%S&amp;quot; &amp;quot;20161226_115358&amp;quot; +&amp;quot;%Y%m%d%H%M%S&amp;quot;
# 20161226115358
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jumping ahead a few steps, we can assemble these building blocks into a full bash script that can:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Accept a directory of files&lt;/li&gt;
&lt;li&gt;For each file, extract a date from the file name&lt;/li&gt;
&lt;li&gt;Parse the date and reformat it to be used to modify the file creation date&lt;/li&gt;
&lt;li&gt;Gracefully fail in any of these steps with a helpful message&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash

# Usage example: bash date-fixer.sh ~/Pictures/backup/

# Check if the correct number of arguments is provided
if [ &amp;quot;$#&amp;quot; -ne 1 ]; then
  echo &amp;quot;Usage: $0 &amp;lt;directory_path&amp;gt;&amp;quot;
  exit 1
fi

directory_path=&amp;quot;$1&amp;quot;

# Check if the directory exists
if [ ! -d &amp;quot;$directory_path&amp;quot; ]; then
  echo &amp;quot;Error: Directory not found: $directory_path&amp;quot;
  exit 1
fi

# Loop through all files in the directory
for filename in &amp;quot;$directory_path&amp;quot;/*; do
  # Extract just the filename without the full path
  basename=$(basename &amp;quot;$filename&amp;quot;)

   # Skip hidden files
  if [[ &amp;quot;$basename&amp;quot; == .* ]]; then
    continue
  fi

  # Extract date from filename
  if [[ &amp;quot;$basename&amp;quot; =~ ([0-9]{8}_[0-9]{6}) ]]; then
    date_from_filename=&amp;quot;${BASH_REMATCH[0]}&amp;quot;
    echo &amp;quot;Date from filename: $date_from_filename&amp;quot;

    # Convert parsed date and time to timestamp
    timestamp=$(date -jf &amp;quot;%Y%m%d_%H%M%S&amp;quot; &amp;quot;$date_from_filename&amp;quot; +&amp;quot;%Y%m%d%H%M%S&amp;quot;)

    # Check for valid timestamp
    if [[ ! &amp;quot;$timestamp&amp;quot; =~ ^[0-9]+$ ]]; then
      echo &amp;quot;Invalid timestamp for $filename: $timestamp&amp;quot;
      continue
    fi

    # Modify file&apos;s creation time
    touch -t &amp;quot;$timestamp&amp;quot; &amp;quot;$filename&amp;quot;
    echo &amp;quot;Modified creation time of $filename&amp;quot;
  else
    echo &amp;quot;Skipping file without date in filename: $file&amp;quot;
  fi
done
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Building a DIY Air Quality Sensor</title><link>https://eric-schaefer.com/blog/2024-02-01-building-a-diy-air-quality-sensor/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2024-02-01-building-a-diy-air-quality-sensor/</guid><description>Getting some insight into the air quality in Berlin with a DIY sensor network.</description><pubDate>Wed, 31 Jan 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While attending this year&apos;s Chaos Communication Congress in Hamburg, I saw a pretty inspiring talk from two researchers in the field of numerical pollution modeling. They were combining data from all kinds of sources, including a DIY sensor network run by volunteers, to create models that can improve city planning, life quality, and ultimately address the problem of air pollution. I can really recommend watching the whole thing:&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe width=&amp;quot;100%&amp;quot; style=&amp;quot;aspect-ratio: 16/9;&amp;quot; src=&amp;quot;https://media.ccc.de/v/37c3-11975-numerical_air_quality_modeling_systems/oembed&amp;quot; frameborder=&amp;quot;0&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;One of their slides mentioned the website &lt;a href=&quot;https://sensor.community/&quot;&gt;sensor.community&lt;/a&gt; which tracks all volunteer-run air quality sensors, and provides DIY guides to build your own air quality and noise pollution sensors. When I got home after the conference, I bought the pieces to assemble the DIY &lt;a href=&quot;https://sensor.community/en/sensors/airrohr/&quot;&gt;AirRohr device&lt;/a&gt;. I live on a pretty busy street in Berlin, and wanted to get a sense of how much air pollution is affecting my area.&lt;/p&gt;
&lt;p&gt;Here are all the pieces ready to be assembled on my living room floor:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;sensor-community-pieces.jpg&quot; alt=&quot;The kit pieces for DIY sensor.community&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The NodeMCU CPU/WLAN + BME280 temperature sensor&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;nodeMCU-cpu-wlan.jpg&quot; alt=&quot;The NodeMCU CPU/WLAN&quot;&gt;&lt;/p&gt;
&lt;p&gt;This piece is responsible for powering the sensors, and communicating the collected data back to my WiFi router. Flashing the firmware for this was not possible with my Macbook Pro (M2, 2023). I used an old Linux laptop to do this instead, which was much more straightforward.&lt;/p&gt;
&lt;h2&gt;The SDS011 fine dust sensor&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;sds011-fine-dust-sensor.jpg&quot; alt=&quot;The SDS011 fine dust sensor&quot;&gt;&lt;/p&gt;
&lt;p&gt;A clear plastic tube is connected to the nozzle on this piece, and a small fan circulates air through the sensor.&lt;/p&gt;
&lt;h2&gt;Fully assembled kit fixed to my apartment window&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;sensor-community-kit-assembled.jpg&quot; alt=&quot;The assembled kit for sensor.community&quot;&gt;&lt;/p&gt;
&lt;p&gt;The white pipe provides a little housing for the electronics inside. A 3m USB cable runs under the window frame to power the the device. It&apos;s fixed to the window with some zip ties.&lt;/p&gt;
&lt;p&gt;After a little bit of configuration, the device started wirelessly emitting data back to the sensor.community network!&lt;/p&gt;
&lt;h2&gt;Live data: fine dust, 10 µm&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe src=&amp;quot;https://api-rrd.madavi.de:3000/grafana/d-solo/GUaL5aZMz/pm-sensors?orgId=1&amp;amp;var-chipID=esp8266-5627249&amp;amp;var-type=SDS011&amp;amp;var-query0=sensors&amp;amp;from=1706310000000&amp;amp;panelId=5&amp;quot; width=&amp;quot;100%&amp;quot; style=&amp;quot;aspect-ratio: 3/2;&amp;quot; frameborder=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Live data: fine dust, 2.5 µm&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe src=&amp;quot;https://api-rrd.madavi.de:3000/grafana/d-solo/GUaL5aZMz/pm-sensors?orgId=1&amp;amp;var-chipID=esp8266-5627249&amp;amp;var-type=SDS011&amp;amp;var-query0=sensors&amp;amp;from=1706310000000&amp;amp;panelId=13&amp;quot; width=&amp;quot;100%&amp;quot; style=&amp;quot;aspect-ratio: 3/2;&amp;quot; frameborder=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;As of the date of this blog post, the device has been collecting data for about a week. It&apos;s already quite interesting to see the patterns of weekday rush-hour traffic and how they affect the air quality in my area.&lt;/p&gt;
</content:encoded></item><item><title>Repairing Shock Cord on Quarter Dome 2 Tent Poles</title><link>https://eric-schaefer.com/blog/2023-08-05-repairing-quarter-dome-2-tent-pole-elastic-shock-cord/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2023-08-05-repairing-quarter-dome-2-tent-pole-elastic-shock-cord/</guid><description>Bringing an old tent pole assembly back to life.</description><pubDate>Fri, 04 Aug 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently while camping I discovered that the elastic shock cord in my &lt;a href=&quot;https://www.rei.com/product/110819/rei-co-op-quarter-dome-2-tent&quot;&gt;REI Quarter Dome 2&lt;/a&gt; tent poles had become totally brittle and non-elastic, making it nearly impossible to keep all the poles together. This kind of thing happens when storing the tent in a warm place, or if the cord is wet before storing it for a long time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;quarter-dome-2-tent.jpg&quot; alt=&quot;Quarter Dome 2 tent&quot;&gt;&lt;/p&gt;
&lt;p&gt;The tent pole assembly is a little unusual on the Quarter Dome 2. There is a central hub which routes all the cords to the different poles. I found this a little intimidating to try and repair the cords before making a proper diagram of how it all fits together. I could not find any existing guide online for this tent. I hope this helps anyone else out there who has this tent, but found the cable routing a little crazy. It&apos;s really not so bad when it&apos;s broken-down into its smaller parts. I have also color-coded how each of the 4 cord routes should run.&lt;/p&gt;
&lt;h2&gt;Tent pole assembly diagram for the REI Quarter Dome 2&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Click image to see zoomed-in detail view&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&amp;quot;quarter-dome-2-pole-diagram.jpg&amp;quot;&amp;gt;&lt;img src=&quot;quarter-dome-2-pole-diagram.jpg&quot; alt=&quot;Close-up of the Quarter Dome 2 tent pole assembly&quot;&gt;&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Each terminal end of the poles has a silver knob, which can be &lt;strong&gt;twisted off&lt;/strong&gt;. Inside, you can see how the shock cord is tied. Just cut out the old shock cord and replace with a new one. I bought &lt;a href=&quot;https://www.vaude.com/de/en/12860-shock-cord-10-m-tent-shock-cord.html#?colour=1444&amp;amp;size=13424&quot;&gt;this 10m cord from Vaude&lt;/a&gt;, which is slightly thicker and more robust than the original cord, while still fitting nicely.&lt;/p&gt;
&lt;p&gt;For a general idea of how to string the shock-cord through the poles, I &lt;a href=&quot;https://www.ifixit.com/Guide/How+do+I+replace+a+brittle+or+broken+shock+cord-/74524&quot;&gt;recommend this guide from iFixIt&lt;/a&gt;. It includes some close-up videos of how to apply the right amount of tension on the cord as well.&lt;/p&gt;
</content:encoded></item><item><title>How I Learned to Stop Worrying and Log the Noise</title><link>https://eric-schaefer.com/blog/2025-10-19-sirens-at-my-window/</link><guid isPermaLink="true">https://eric-schaefer.com/blog/2025-10-19-sirens-at-my-window/</guid><description>How I recorded and analyzed siren activity outside my window on a busy street in Berlin.</description><pubDate>Sat, 18 Oct 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;;
import Chart from &apos;./chart.astro&apos;;
import Audio from &apos;./audio.astro&apos;;
import siren1 from &apos;./audio/siren_20250730_223059.wav&apos;;
import siren2 from &apos;./audio/siren_20250805_203714.wav&apos;;
import spectrogram1 from &apos;./images/siren_20250730_223059.jpg&apos;;
import spectrogram2 from &apos;./images/siren_20250805_203714.jpg&apos;;&lt;/p&gt;
&lt;p&gt;Living in Berlin means accepting a certain soundtrack. I know what I signed up for when I chose to live on a city artery. Nonetheless I am sensitive to sound, and the constant wail of sirens started to feel... excessive. Was I imagining it? There was only one way to find out... another DIY recording project. I&apos;ve already toyed with some window DIY data projects before, like my &lt;a href=&quot;/blog/2024-02-01-building-a-diy-air-quality-sensor/&quot;&gt;air quality sensor&lt;/a&gt;. This time we&apos;re gonna learn about noise pollution rather than air pollution.&lt;/p&gt;
&lt;p&gt;Berlin has some open data on city noise. &lt;a href=&quot;https://gdi.berlin.de/viewer/main/?MAPS=%7B%22center%22:%5B390022.4138428097,5820501.803771703%5D,%22zoom%22:5%7D&amp;amp;LAYERS=%5B%7B%22id%22:%22hintergrund_default_grau%22,%22visibility%22:true,%22transparency%22:0%7D,%7B%22id%22:%22ua_stratlaerm_2022:bb_strasse_gesamt_den2022%22,%22visibility%22:true,%22transparency%22:0%7D,%7B%22id%22:%22ua_stratlaerm_2022:da_autobahn2022%22,%22visibility%22:true,%22transparency%22:0%7D,%7B%22id%22:%22ua_stratlaerm_2022:db_bundesstrasse2022%22,%22visibility%22:true,%22transparency%22:0%7D,%7B%22id%22:%22ua_stratlaerm_2022:dc_sonstigestr2022%22,%22visibility%22:true,%22transparency%22:0%7D,%7B%22id%22:%22ua_stratlaerm_2022:df_laermschutzeinrichtung2022%22,%22visibility%22:true,%22transparency%22:0%7D%5D&quot;&gt;My humble 237-meter section of Mehringdamm&lt;/a&gt;, as of 2019 (kinda dated), had an average noise level of 75 dB(A) with a staggering 43,140 vehicles passing by daily. To put that in perspective, if you lined those cars up bumper-to-bumper, each averaging 4.5 meters, you&apos;d have a 194-kilometer-long metal snake. No wonder it&apos;s a bit loud.&lt;/p&gt;
&lt;h3&gt;The recording setup&lt;/h3&gt;
&lt;p&gt;Nothing fancy here. I used a spare linux laptop from the closet, and just laid it on living room window sill along with the plants. That&apos;s it. Not even a USB microphone. I cranked up the audio input to max, and then thought about how to record the audio and make sense of what comes in.&lt;/p&gt;
&lt;h3&gt;Making sense of the recorded audio&lt;/h3&gt;
&lt;p&gt;My first attempt to quantify the sirens involved a Fast Fourier Transform. My thought was to isolate the dominant frequency range of sirens, which are predictable. The Python library SciPy has a built-in FFT function, made bootstrapping this relatively easy. But the FFT proved to be a bit too enthusiastic, generating a lot of false positives. My baby daughter&apos;s crying repeatedly showed up in the siren detection, which, I admit, tugged at the heartstrings a little while combing through the recorded samples. Clearly, a more sophisticated approach was needed.&lt;/p&gt;
&lt;h3&gt;TensorFlow&lt;/h3&gt;
&lt;p&gt;I stumbled upon an excellent tutorial in TensorFlow&apos;s documentation for &lt;a href=&quot;https://www.tensorflow.org/hub/tutorials/yamnet&quot;&gt;classifying audio&lt;/a&gt; using the YAMNet model. This model conveniently includes a subset of sirens in its training data. Perfect!&lt;/p&gt;
&lt;p&gt;Processing a live stream turned out not to be possible with TensorFlow though. The solution? Record audio constantly in two-second chunks (enough to capture a full siren cycle), then feed those snippets to the YAMNet model. I also made sure to keep the raw audio recordings for any deeper analysis that might be required later. All detected siren events with timestamps were dutifully logged to a CSV for easy digestion.&lt;/p&gt;
&lt;p&gt;Here&apos;s what I ended up with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np
import sounddevice as sd
import tensorflow as tf
import tensorflow_hub as hub
import pandas as pd
import time
import os
from datetime import datetime
from scipy.io import wavfile
import requests

# --- Configuration ---
MODEL_URL = &amp;quot;https://tfhub.dev/google/yamnet/1&amp;quot;
SAMPLE_RATE = 16000  # YAMNet requires 16000 Hz sample rate
BLOCK_DURATION_S = 2.0 # Process audio in 2-second chunks
BLOCK_SIZE = int(SAMPLE_RATE * BLOCK_DURATION_S)
DETECTION_THRESHOLD = 0.55  # Confidence threshold from 0 to 1
SIREN_CLASS_NAME = &amp;quot;Siren&amp;quot;
SIREN_EVENT_CSV = &amp;quot;siren_events.csv&amp;quot;
SIREN_EVENT_INTERVAL = 30  # seconds
SIREN_SAMPLE_DIR = &amp;quot;siren_samples&amp;quot;
last_siren_time = 0

# --- Load YAMNet Model ---
print(&amp;quot;Loading YAMNet model...&amp;quot;)
model = hub.load(MODEL_URL)
# Load class names from the CSV file referenced by model.class_map_path
class_map_path = model.class_map_path().numpy().decode(&apos;utf-8&apos;)
class_map_df = pd.read_csv(class_map_path)
class_names = class_map_df[&apos;display_name&apos;].values.astype(&apos;U&apos;)
print(&amp;quot;Model loaded.&amp;quot;)

# Ensure the sample directory exists
os.makedirs(SIREN_SAMPLE_DIR, exist_ok=True)

def audio_callback(indata, frames, time_info, status):
    global last_siren_time
    if status:
        print(status)

    waveform = indata[:, 0].astype(np.float32)
    scores, embeddings, spectrogram = model(waveform)
    siren_index = np.where(class_names == SIREN_CLASS_NAME)[0][0]
    siren_score = tf.reduce_mean(scores[:, siren_index]).numpy()

    now = time.time()
    if siren_score &amp;gt; DETECTION_THRESHOLD and (now - last_siren_time) &amp;gt;= SIREN_EVENT_INTERVAL:
        last_siren_time = now
        timestamp = datetime.now().isoformat()
        print(f&amp;quot;🚨 SIREN DETECTED! (Confidence: {siren_score:.2f}) at {timestamp}&amp;quot;)
        # Save WAV sample
        wav_filename = f&apos;siren_{datetime.now().strftime(&amp;quot;%Y%m%d_%H%M%S&amp;quot;)}.wav&apos;
        wav_path = os.path.join(SIREN_SAMPLE_DIR, wav_filename)
        # Convert waveform to int16 for WAV
        wav_data = np.int16(waveform / np.max(np.abs(waveform)) * 32767) if np.max(np.abs(waveform)) &amp;gt; 0 else np.int16(waveform)
        wavfile.write(wav_path, SAMPLE_RATE, wav_data)
        # Append to CSV
        if not os.path.exists(SIREN_EVENT_CSV):
            with open(SIREN_EVENT_CSV, &apos;w&apos;) as f:
                f.write(&apos;timestamp,wav_filename\n&apos;)
        with open(SIREN_EVENT_CSV, &apos;a&apos;) as f:
            f.write(f&apos;{timestamp},{wav_path}\n&apos;)

# --- Main Execution ---
try:
    print(&amp;quot;\nListening for sirens... Press Ctrl+C to stop.&amp;quot;)
    # Start the audio stream
    with sd.InputStream(
        channels=1,
        samplerate=SAMPLE_RATE,
        blocksize=BLOCK_SIZE,
        callback=audio_callback
    ):
        while True:
            pass  # Keep the stream alive
except KeyboardInterrupt:
    print(&amp;quot;\nStopping...&amp;quot;)
except Exception as e:
    print(f&amp;quot;An error occurred: {e}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I let the model run for a month on my little linux laptop, and here&apos;s the result.&lt;/p&gt;
&lt;p&gt;&amp;lt;Chart /&amp;gt;&lt;/p&gt;
&lt;p&gt;At first glace, yeah, that&apos;s a lot of sirens. I&apos;m not crazy! Also interesting that the number of siren events per day varies widely in the &lt;strong&gt;beginning&lt;/strong&gt;, and then the curve kind of stabilizes toward the end. I guess that&apos;s just how convergence and increasing sample size works.&lt;/p&gt;
&lt;p&gt;The first day (July 26th) was also abnormally high, I think because it happened to be Christopher Street Day, a yearly pride parade in Berlin.&lt;/p&gt;
&lt;h3&gt;👮‍♂️ Police siren&lt;/h3&gt;
&lt;p&gt;&amp;lt;Audio path={siren1} title=&amp;quot;Police siren sample&amp;quot; dataPointId={287} /&amp;gt;&lt;/p&gt;
&lt;p&gt;Looking at this audio sample in &lt;a href=&quot;https://www.audacityteam.org/&quot;&gt;Audacity&lt;/a&gt; with the spectrogram view, it&apos;s clear that it&apos;s a police siren. The harmonics are quite distinct, and the overall shape is quite regular.&lt;/p&gt;
&lt;p&gt;&amp;lt;Image src={spectrogram1} alt=&amp;quot;Spectrogram of police siren sample, showing the harmonics of the siren&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Notice how the harmonics are clean multiples of the fundamental frequency, just appearing a little weaker in the spectrum. Pretty cool!&lt;/p&gt;
&lt;h3&gt;🚑 Ambulance siren&lt;/h3&gt;
&lt;p&gt;&amp;lt;Audio path={siren2} title=&amp;quot;Ambulance siren sample&amp;quot; dataPointId={287} /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;Image src={spectrogram2} alt=&amp;quot;Spectrogram of ambulance siren sample, showing the harmonics of the siren&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Lower fundamental frequency here for ambulance sirens is interesting. I learned that these lower frequencies carry better in the dense urban environment, but also, wow, they are freaking loud too. 130 dB(A) is at the human pain threshold.&lt;/p&gt;
&lt;h3&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;This was a fun project, and I learned a lot about noise pollution and sirens. Also vibe-coded enough Python to make it work. Would be cool to also auto-classify the sirens according to vehicle type, but that wasn&apos;t my original goal.&lt;/p&gt;
</content:encoded></item></channel></rss>