├── .gitignore ├── .htaccess ├── LICENSE ├── README.md ├── archive └── README.md ├── docker ├── README.md └── docker-compose.yml ├── index.php └── php.ini /.gitignore: -------------------------------------------------------------------------------- 1 | /archive/* 2 | counter.txt 3 | 4 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Header set Cache-Control "max-age=0,no-store" 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2021 Gregory Raiz and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # newsprint 2 | 3 | [The blog post and video](https://gregraiz.com/blog/i-made-an-eink-newspaper/) that accompany this code give a general overview of the project. 4 | 5 | Newsprint is a simple web application that will fetch the front page of a newspaper and display it on an eink display. The specific resolutions and sizes have been setup to work with a 32" eInk place & play display from Visionect but can be modified for other screen resolutions. 6 | 7 | There are two portions to getting this up and running. The first is the application server that displays the newspaper. This code is setup to be run on a simple and low cost PHP webhost with very few dependancies. Simply copy the PHP files into a directory on your server and you should be good to go. You will need an "archive" folder on the server and the ability to call the command line "convert" utility to resize/convert images. Most hosts should have this installed as it's fairly standard. 8 | 9 | The current newspapers that I've setup include the Boston Globe, New York Times, Wall St Journal, LA Times, Toronto Star and SF Chronicle. Additional sources are easy to add by looking up the newspaper prefix on freedomforum.org. This is typically two letter State and newspaper abbreviation (NY_NYT or MA_BG). Each newspaper is then adjusted to display as much of the paper as possible. Many newspapers have a boarder or printing margin and I've tried to subtract this out in the samples. 10 | 11 | The second portion of the software needed to get this running is the Visionect server. Information on the [Visionect docker container and server](https://docs.visionect.com/VisionectSoftwareSuite/Installation.html) are available for install. This has to run on the same network as the eInk display. The display itself is not standalone, it's a thin client and requires the Visionect software to act as an HTML rendering engine of sorts. The Visionect server software can be run on any docker server and general installation instructions are on the Visionect site. I was able to get it to run on my Synology server with a slightly modified file. The details of this are in the docker folder of this repo. 12 | 13 | [__Affiliate link to Visionect eInk Display that was used__](https://www.visionect.com/ref/graiz/) 14 | -------------------------------------------------------------------------------- /archive/README.md: -------------------------------------------------------------------------------- 1 | This folder will be populated by generated 2 | JPG files and downloaded PDF's. 3 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | The Visionect software can be downloaded and installed from the Docker container information provided on the visionect website: 2 | > https://docs.visionect.com/VisionectSoftwareSuite/Installation.html 3 | 4 | I was able to install the server on a Synology diskstation using the instructions provided however I needed to make some changes to get this to work. In particular I needed to ssh into my synology and modify the provided ports on the docker-compose.yml file to make this work. 5 | 6 | This seems to be because Synology already has a postgress DB instance and the default ports cause issues. I've included a modified docker-compose file for reference however the only change is the installed port numbers. Consider downloading directly from Visionect and modifying the ports yourself if needed. 7 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | vss: 4 | container_name: vss 5 | image: visionect/visionect-server-v3 6 | privileged: true 7 | cap_add: 8 | - MKNOD 9 | - SYS_ADMIN 10 | devices: 11 | - "/dev/fuse:/dev/fuse" 12 | restart: unless-stopped 13 | links: 14 | - "postgres_db:postgres" 15 | - "redis:redis" 16 | ports: 17 | - 8081:8081 # admin browser 18 | - 11113:11113 19 | - 32991:32991 # GW RPC 20 | - 11114:11114 #STORAGE 21 | - 11115:11115 #NM 22 | - 32989:32989 #NM RPC 23 | - 5559:5559 # broker 1 24 | - 5560:5560 # broker 2 25 | environment: 26 | - DB2_1_PORT_5432_TCP_ADDR=postgres 27 | - DB2_1_PORT_5432_TCP_USER=visionect 28 | - DB2_1_PORT_5432_TCP_PASS=visionect 29 | - DB2_1_PORT_5432_TCP_DB=koala 30 | - REDIS_ADDRESS=redis:6379 31 | volumes: 32 | - /dev/shm:/dev/shm 33 | postgres_db: 34 | container_name: pdb 35 | image: postgres:12 36 | restart: always 37 | ports: 38 | - 5453:5453 39 | environment: 40 | - POSTGRES_USER=visionect 41 | - POSTGRES_DB=koala 42 | - POSTGRES_PASSWORD=visionect 43 | volumes: 44 | - /var/pgdata:/var/lib/postgresql/data 45 | redis: 46 | image: redis:5.0.5 47 | restart: always 48 | 49 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | $maxPapers) { 35 | $x = 0; 36 | } 37 | if (!empty($_REQUEST['index'])) { // Override the counter if there 38 | $x = $_REQUEST['index']; // is a URL parameter for index 39 | } 40 | return($x); 41 | } 42 | function incrementCounter(int $counter){ 43 | // increment the paper for next time 44 | $counter++; 45 | $fp = fopen("counter.txt", "w"); 46 | fwrite($fp, $counter); 47 | fclose($fp); 48 | } 49 | 50 | // fetch a paper and cache in as a JPG. Return the path to the JPG if we found it. 51 | // We can pass in an offset in days to get yesterday or two days ago 52 | function fetchPaper($prefix, $offset=0){ 53 | $pathToPdf = "https://cdn.freedomforum.org/dfp/pdf" . date('j',strtotime("-" . $offset . " days")) . "/" . $prefix . ".pdf"; 54 | $pdffile = "archive/" . $prefix . "_" . date('Ymd',strtotime("-" . $offset . " days")) . ".pdf"; 55 | $pngfile = "archive/" . $prefix . "_" . date('Ymd',strtotime("-" . $offset . " days")) . ".png"; 56 | $rootpath = getcwd() . "/"; 57 | // check if a jpg has already been created 58 | // if not we start checking for the PDF and converting 59 | if (!file_exists($pngfile)){ 60 | $file_headers = @get_headers($pathToPdf); 61 | if(!$file_headers || $file_headers[0] == 'HTTP/1.1 404 Not Found') { 62 | $exists = false; 63 | } 64 | else { 65 | $ch = curl_init($pathToPdf); 66 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 67 | $data = curl_exec($ch); 68 | curl_close($ch); 69 | $exists = true; 70 | $result = file_put_contents($pdffile, $data); 71 | } 72 | if ($exists) { 73 | $command = "convert -density 300 -background white -alpha remove '" . $rootpath . $pdffile . 74 | "' -colorspace Gray -resize 1600 '" . $rootpath . $pngfile . "'"; 75 | exec($command, $output, $response); 76 | } 77 | } else { 78 | $exists = true; 79 | } 80 | if ($exists) { 81 | return $pngfile; 82 | } else { 83 | return false; 84 | } 85 | } 86 | $currentIndex = getCounter(); 87 | if (isset($_REQUEST['index'])) { 88 | $currentIndex = $_REQUEST['index']; 89 | } 90 | 91 | 92 | $imageresult = fetchPaper($news[$currentIndex]['prefix'],0); // Fetch today 93 | if (empty($imageresult)) { 94 | $imageresult = fetchPaper($news[$currentIndex]['prefix'],1); // yesterday 95 | } 96 | if (empty($imageresult)) { 97 | $imageresult = fetchPaper($news[$currentIndex]['prefix'],2); // twesterday 98 | } 99 | ?> 100 | 101 | 102 | 103 | 110 | 111 | 112 | "; 116 | } 117 | ?> 118 | 119 | 120 | 121 | 124 | -------------------------------------------------------------------------------- /php.ini: -------------------------------------------------------------------------------- 1 | extension=imagick.so 2 | --------------------------------------------------------------------------------