A project by Evan Madow • June 9, 2022
I put together a project that fetches print editions of my favorite newspapers every morning and showcases them on a frame, rotating throughout the day.
Demo of Meural Newspapers, cycling through content manually with gestures
I'd categorize myself as an amatuer news junkie. I take a bunch of news breaks during the day, scanning headlines from a myriad of publications, digging into the interesting stories in the mornings and evenings. I've found myself particularly interested in how news is covered. During notable news events—e.g. 9/11, election nights, Jan 6th—I irritatingly flip back and forth between networks to see how unfolding coverage differs.
Newspapers are interesting in that they wholly shape the narrative of the notable events that readers digest. For the large number of folks who primarily consume news from a single source, that impact is profound. It's fascinating looking at front pages of various newspapers to see the distinct culminations of each news organization's consideration of what's newsworthy.
One of my favorite museums is the (unfortunately, now defunct) Newseum in Washington D.C. A daily refreshed exhibit they had was a wall of newspapers, showcasing front pages from publications across America and around the world.
The Newseum's Today's Front Pages Exhibit
I wanted to recreate a version of this experience in some way at home so that I could start every morning looking at select front pages, and also frame these pieces as ephemeral art.
I have a Meural (now owned by Netgear) frame, a product that displays artwork—mostly paintings and photos—via a subscription service. It's a neat idea, but I always struggled to find art that spoke to me and looked good enough to display as a centerpiece in my home. The Meural canvas has a matte display and auto-adjusts brightness to make appear less as a screen. It's a perfect device to display newspaper front pages.
To make this system work, I wrote an app in node to handle fetching, preparing and pushing content to the Meural device. To ensure the process runs each morning at 4am, the app executes via a cron job from my trusty raspberry pi:
As a longtime New York Times subscriber, I knew that a PDF of the front page was available daily from nytimes.com. I was curious if other major publications did the same, and after a quick Google search, I learned that a foundation called the Freedom Forum aggregates front pages from 520 participating publications across the world. This was huge!
Programmatically fetching the front pages from the Freedom Forum is a fairly trivial thing. The filename slugs remain constant (e.g. WSJ, CA_SFC, DC_WP), and the path is predictable since there's just one dynamic element corresponding to the day of the month. There are a few quirks when downloading some papers—one is that some don't offer weekend editions, so the process needs to fail gracefully when any of these 404.
In my app, newspaper selections can be added to the rotation via a json config. I've picked 6 papers as a default, but any/all of the 520 supported papers can be listed.
The Freedom Forum offered front page images at a fairly low resolution in JPG format, certainly not suitable for our use case. Amazingly, there are PDF versions which meant that we had infinitely scalable vector text that we could play with.
The Meural doesn't support PDFs (I mean, why would it), so we need to rasterize the PDFs into a high quality image format. Since forever, I've been using ImageMagick for projects that involve image manipulation from a CLI. It seemed like a suitable library this time too. The conversion part is simple. To balance file size and quality, I ended up setting the source to a 225ppi density before exporting:
export const convertImage = async (inputFile: string, outputFile: string) => { return new Promise((resolve, reject) => { im.convert(['-density', '225', inputFile, outputFile], async err => { if (err) reject(err); resolve(true); }); }); };
One problem: the aspect ratio of the Meural was 16:9 (well, 9:16 since it's oriented vertically), and some of these front pages are too long. After determining which images need to be cropped, we just need to truncate the bottoms since a center-oriented crop would chop bits from the both sides (and result in an awkward framing).
This, too, is pretty easy to handle:
export const cropImage = async (file: string, width: number, height: number) => { return new Promise((resolve, reject) => { im.convert(['-gravity', 'North', '-crop', `${width}x${height}+0+0`, file, file], async err => { if (err) reject(err); resolve(true); }); }); }; const desiredAspectRatio = 16 / 9; const threshholdForRatioEnforcement = 0.04; // meural will crop the image which is probably fine, but not beyond 4% const imageProperties = await getImageProperties(convertedFileName); const imageWidth = imageProperties.width; const imageHeight = imageProperties.height; if ( 1 - Math.abs(imageWidth * desiredAspectRatio) / Math.abs(imageHeight) > threshholdForRatioEnforcement ) { await cropImage(convertedFileName, imageWidth, imageWidth * desiredAspectRatio); }
The Meural frame allows for user uploads through a website and mobile app. I had hoped that, through a little bit of client spoofing, I'd be able to use their internal APIs directly. To my surprise, no trickery is actually needed. Amazingly, a blessed soul somewhere in the world thoroughly documented these APIs. That saved me hours of work inspecting traffic and/or setting up a proxy.
Essentially, after a Meural user gets authenticated, a specified playlist (gallery) gets emptied or created if it doesn't exist. We then iterate over converted images and upload them sequentially, adding each one to the gallery. Upon completion, we force a sync of the cloud-hosted content and the device's local storage.
While my app currently supports a single Meural frame as defined in the configuration, it wouldn't take too much effort to extend it to support multiple devices. Maybe in the future, I'll set up a gallery wall in a larger room.
Clone or fork the repo: emadow/meural-newspapers. BYO-Meural though.