Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac Colley committed Feb 21, 2017
0 parents commit 044884e
Show file tree
Hide file tree
Showing 18 changed files with 1,172 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .env-sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
LASTFM_API=
LASTFM_SECRET=
LASTFM_USERAGENT=
LASTFM_USERNAME=

PRINTER_USB=
PRINTER_BAUDRATE=
PRINTER_ROTATION=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/

.env
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# now playing printer

_🎧🔥🖨️🖼️ print out the album art music you're currently listening to_

Uses Last.fm to get the tracks you're listening to and then prints them out on a thermal receipt printer.

![Picture of a song printed out](printer.jpg)

## what you need

+ [thermal printer](http://www.hobbytronics.co.uk/thermal-printer)
+ 57mm thermal paper
+ Arduino Uno (Works with other devices that can use serial port too use the corresponding RX and TX)
+ [Arduino IDE](https://www.arduino.cc/en/Main/Software)
+ some jumper wires
+ 9V power supply (splice wires and shove into thermal printer)
+ [Last.fm API key](http://www.last.fm/api)

## installation

1. upload the `StandardFirmata` example code to your Arduino Uno (`File/Examples/Firmata/StandardFirmata` -> `Upload`)
2. [set-up your thermal printer](https://learn.adafruit.com/mini-thermal-receipt-printer)

![Visual set-up](setup.png)

> you will probably need to tweak the settings for your printer, [see the thermalprinter module comments](https://github.com/xseignard/thermalPrinter/blob/master/src/printer.js#L12)
3. add environment variables. (Copy `.env-sample` to `.env`)

```
LASTFM_API= # sign-up for a key at http://www.last.fm/api
LASTFM_SECRET= # also at http://www.last.fm/api
LASTFM_USERAGENT= # this is to tell last.fm who is requesting info
LASTFM_USERNAME= # your last fm username to track what music is playing
PRINTER_USB= # this is where the arduino is mounted at, see your Arduino IDE (Tools/Port)
PRINTER_BAUDRATE= # hold the power button and plug the power in, the baudrate is printed on the test page
PRINTER_ROTATION= # this changes the orientation of the print, 180 is upside down for example
```

4. install non-node dependencies: [GraphicsMagick](http://www.graphicsmagick.org/)

```
sudo apt-get update
sudo apt-get install graphicsmagick
```

5. install node dependencies

```
npm install
```

## run

listen to music and then run the script:

```
sudo node main.js
```

everytime Last.fm picks up a new song streaming it'll print it out!

> you probably need to run a root to get access to the USB port
Binary file added fonts/source-sans-pro-600.ttf
Binary file not shown.
Binary file added fonts/source-sans-pro-700-italic.ttf
Binary file not shown.
Binary file added fonts/source-sans-pro-700.ttf
Binary file not shown.
Empty file added images/.gitkeep
Empty file.
Binary file added images/2665355ed0464a5acaec960028c7bc69.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/31663ce47f7b48b2ae70e5c7b6cb4f47.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/42f1d32849a24965c968302427f98706.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/624c27c4d14f2c6aa16b12a9ad2288ba.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/ab548097a5dc4a1aca3d99aa2fef9bae.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/f83cecb46e494419c48675528ad758cc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
198 changes: 198 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
require('dotenv').config();

const {
LASTFM_API,
LASTFM_SECRET,
LASTFM_USERAGENT,
LASTFM_USERNAME,
PRINTER_USB,
PRINTER_BAUDRATE,
PRINTER_ROTATION
} = process.env;

if (
!LASTFM_API ||
!LASTFM_SECRET ||
!LASTFM_USERAGENT ||
!LASTFM_USERNAME ||
!PRINTER_USB ||
!PRINTER_BAUDRATE ||
!PRINTER_ROTATION
) {
return console.error('Missing environment variabes.');
}

const fs = require('fs');

const SerialPort = require('serialport');
const serialPort = new SerialPort(PRINTER_USB, { baudrate: +PRINTER_BAUDRATE });
const Printer = require('thermalprinter');

const PRINT_WIDTH = 384;

const request = require('request');
const LastFmNode = require('lastfm').LastFmNode;
const lastfm = new LastFmNode({
api_key: LASTFM_API, // sign-up for a key at http://www.last.fm/api
secret: LASTFM_SECRET,
useragent: LASTFM_USERAGENT
});

const gm = require('gm');

serialPort.on('open', _ => {
console.log('Serial port open!');

/*
maxPrintingDots = 0-255. Max heat dots, Unit (8dots), Default: 7 (64 dots)
heatingTime = 3-255. Heating time, Unit (10us), Default: 80 (800us)
heatingInterval = 0-255. Heating interval, Unit (10µs), Default: 2 (20µs)
The more max heating dots, the more peak current will cost when printing,
the faster printing speed. The max heating dots is 8*(n+1).
The more heating time, the more density, but the slower printing speed.
If heating time is too short, blank page may occur.
The more heating interval, the more clear, but the slower printing speed.
*/

const printer = new Printer(serialPort, {
maxPrintingDots: 10,
heatingTime: 150,
heatingInterval: 120,
commandDelay: 2
});

printer.on('ready', _ => {
console.log('Printer ready!');

const trackStream = lastfm.stream(LASTFM_USERNAME);

trackStream.on('nowPlaying', track => {
console.log('Paused track stream');
trackStream.stop();

getTrackInfo(track)
.then(getAndDitherImage)
.then(imagePath => {
console.log(`Image save to: ${imagePath}`);

printer.printImage(imagePath).lineFeed(2).print(_ => {
console.log('Printed image');
});

console.log('Resumed track stream');
trackStream.start();
})
.catch(error => {
console.error(error);

console.log('Resumed track stream');
trackStream.start();
});
});

trackStream.start();
});
});

const getAndDitherImage = (track) => new Promise((resolve, reject) => {
console.log('Dithering album art');

if (!track) {
return reject('Missing `track`');
}

const imageFilename = track.imageUrl.substr(track.imageUrl.lastIndexOf('/'));
const imagePath = `${__dirname}/images/${imageFilename}`;

if (fs.existsSync(imagePath)) {
console.log('File already exists');
return resolve(imagePath);
}

const FONT_SIZE = 16;
const TEXT_MAX_LENGTH = 22;

function processText(text) {
text = text.toUpperCase();

if (text.length <= TEXT_MAX_LENGTH) {
return text;
}

return text.substr(0, TEXT_MAX_LENGTH - 1) + '…';
}

function niceDate() {
let date = new Date().toISOString();
date = date.replace('T', ' ').replace('Z', ' ');
date = date.substr(0, date.lastIndexOf(':'));
return date.trim();
}

gm(request(track.imageUrl), imageFilename)
// make the image cripser, black and white and then dither
.sharpen(5)
.monochrome()

.dither()

.borderColor('#000')
.border(1, 1)

// center on a white background the size of the printer paper
.gravity('Center')
.extent(PRINT_WIDTH, PRINT_WIDTH)

.fill('#000')
.font(`${__dirname}/fonts/source-sans-pro-700.ttf`, FONT_SIZE)

// put text on each side
.drawText(0, FONT_SIZE, processText(track.name), 'North').rotate('#fff', 90)

.font(`${__dirname}/fonts/source-sans-pro-700-italic.ttf`, FONT_SIZE)
.drawText(0, FONT_SIZE, processText(track.artist), 'North').rotate('#fff', -180)
.drawText(0, FONT_SIZE, processText(track.album), 'North').rotate('#fff', 90)

.font(`${__dirname}/fonts/source-sans-pro-600.ttf`, FONT_SIZE)
.drawText(0, FONT_SIZE, niceDate(), 'South')

// finally rotate, depending on the orientation of the printer
.rotate('#fff', PRINTER_ROTATION)

.write(imagePath, error => {
if (error) {
return reject(error);
}

resolve(imagePath);
});
});

const getTrackInfo = (data) => new Promise((resolve, reject) => {
console.log('Getting track info');

if (!data || !data.image || data.image.length <= 0) {
return reject('No album art');
}

const track = {
name: data.name,
imageUrl: data.image[3]['#text'],
artist: data.artist['#text'],
album: data.album['#text']
};


// if album art
if (!track || track.imageUrl.length <= 0) {
return reject('No album art');
}

console.log(`Track info: ${track.name} - ${track.artist} - ${track.album}`);
console.log(`Track image url: ${track.imageUrl}`);

resolve(track);
});
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "now-playing-printer",
"version": "1.0.0",
"description": "🎧🔥🖨️🖼️ print out the album art music you're currently listening to",
"main": "main.js",
"dependencies": {
"dotenv": "^4.0.0",
"gm": "^1.23.0",
"lastfm": "^0.9.2",
"request": "^2.79.0",
"serialport": "^4.0.7",
"thermalprinter": "^0.3.8"
}
}
Binary file added printer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added setup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 044884e

Please sign in to comment.