-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Zac Colley
committed
Feb 21, 2017
0 parents
commit 044884e
Showing
18 changed files
with
1,172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules/ | ||
|
||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
 | ||
|
||
## 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) | ||
|
||
 | ||
|
||
> 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 not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.