-
Notifications
You must be signed in to change notification settings - Fork 1
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
1 parent
4e18d5f
commit a5cda89
Showing
8 changed files
with
341 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,2 @@ | ||
# SpotiPixel | ||
a simple nodeJS software that fetch your current playing song and display it to you divoo pixoo device |
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,57 @@ | ||
const axios = require('axios'); | ||
const crypto = require('crypto'); | ||
const config = require('./config'); | ||
|
||
const apiURL = config.app.PixooUrl; | ||
const checkSongUrl = `${config.app.SpotiPixelServerUrl}/check-song`; | ||
let previousSongName = ""; | ||
|
||
async function fetchSongName() { | ||
try { | ||
const response = await axios.get(checkSongUrl); | ||
return response.data; | ||
} catch (error) { | ||
console.error('Error fetching song name:', error); | ||
return null; | ||
} | ||
} | ||
|
||
async function fetchGif(url) { | ||
try { | ||
const response = await axios({ url, responseType: 'arraybuffer' }); | ||
const gifBuffer = response.data; | ||
return { gifBuffer }; | ||
} catch (error) { | ||
console.error('Error downloading GIF:', error); | ||
return null; | ||
} | ||
} | ||
|
||
async function playNetGif(gifUrl) { | ||
const songName = await fetchSongName(); | ||
if (songName === previousSongName) { | ||
console.log('The song has not changed or an error has occurred. No need to check the GIF.'); | ||
return; | ||
} | ||
const gifData = await fetchGif(gifUrl); | ||
if (!gifData) return; | ||
|
||
try { | ||
const response = await axios.post(apiURL, { | ||
Command: "Device/PlayTFGif", | ||
FileType: 2, | ||
FileName: gifUrl, | ||
}); | ||
|
||
console.log(response.data); | ||
previousSongName = songName; | ||
} catch (error) { | ||
console.error('Error sending request to Pixoo API:', error); | ||
} | ||
} | ||
|
||
function startGifCheckLoop() { | ||
setInterval(() => playNetGif(`${config.app.SpotiPixelServerUrl}/cover.gif`), 1500); | ||
} | ||
|
||
startGifCheckLoop(); |
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,161 @@ | ||
const fs = require('fs'); | ||
const express = require('express'); | ||
const SpotifyWebApi = require('spotify-web-api-node'); | ||
const { createCanvas, loadImage } = require('canvas'); | ||
const GIFEncoder = require('gifencoder'); | ||
const config = require('./config'); | ||
|
||
const spotifyApi = new SpotifyWebApi({ | ||
clientId: config.app.clientId, | ||
clientSecret: config.app.clientSecret, | ||
redirectUri: config.app.redirectUri | ||
}); | ||
|
||
const tokenFilePath = './spotify-token.json'; | ||
const gifFilePath = './album-cover.gif'; | ||
|
||
let currentTrackId = ''; | ||
let currentTrackInfo = "No Track Playing"; | ||
|
||
function refreshTokenIfNeeded(callback) { | ||
const { refreshToken } = JSON.parse(fs.readFileSync(tokenFilePath, 'utf8')); | ||
spotifyApi.setRefreshToken(refreshToken); | ||
|
||
spotifyApi.refreshAccessToken().then( | ||
function(data) { | ||
console.log('Access token has been successfully refreshed!'); | ||
spotifyApi.setAccessToken(data.body['access_token']); | ||
|
||
fs.writeFileSync(tokenFilePath, JSON.stringify({ | ||
accessToken: data.body['access_token'], | ||
refreshToken | ||
}), 'utf8'); | ||
|
||
if (callback) callback(); | ||
}, | ||
function(err) { | ||
console.log('Could not refresh access token!', err); | ||
} | ||
); | ||
} | ||
|
||
function fetchCurrentPlayingTrack() { | ||
spotifyApi.getMyCurrentPlayingTrack() | ||
.then(function(data) { | ||
if (data.body && data.body.item && data.body.item.id !== currentTrackId) { | ||
currentTrackId = data.body.item.id; | ||
currentTrackInfo = `${data.body.item.name} - ${data.body.item.artists.map(artist => artist.name).join(', ')}`; | ||
|
||
console.log('Title: ', data.body.item.name); | ||
console.log('Artist: ', data.body.item.artists.map(artist => artist.name).join(', ')); | ||
console.log('Album cover: ', data.body.item.album.images[0].url); | ||
createGif(data.body.item.name, data.body.item.artists.map(artist => artist.name).join(', '), data.body.item.album.images[0].url); | ||
} else if (!data.body || !data.body.item) { | ||
console.log('No track is currently playing.'); | ||
currentTrackInfo = "No Track Playing"; | ||
} | ||
}, function(err) { | ||
console.error('Error fetching the current track:', err); | ||
currentTrackInfo = "No Track Playing"; | ||
if (err.statusCode === 401) { | ||
refreshTokenIfNeeded(fetchCurrentPlayingTrack); | ||
} | ||
}); | ||
} | ||
|
||
function createGif(title, artist, coverUrl) { | ||
const width = 64; | ||
const height = 64; | ||
const canvas = createCanvas(width, height); | ||
const ctx = canvas.getContext('2d'); | ||
const encoder = new GIFEncoder(width, height); | ||
|
||
encoder.start(); | ||
encoder.setRepeat(0); | ||
encoder.setDelay(265); | ||
encoder.setQuality(10); | ||
|
||
loadImage(coverUrl).then(image => { | ||
const aspectRatio = image.width / image.height; | ||
const canvasAspectRatio = width / height; | ||
let renderWidth, renderHeight, offsetX, offsetY; | ||
|
||
if (aspectRatio > canvasAspectRatio) { | ||
renderHeight = height; | ||
renderWidth = image.width * (renderHeight / image.height); | ||
offsetX = (width - renderWidth) / 2; | ||
offsetY = 0; | ||
} else { | ||
renderWidth = width; | ||
renderHeight = image.height * (renderWidth / image.width); | ||
offsetX = 0; | ||
offsetY = (height - renderHeight) / 2; | ||
} | ||
|
||
const text = `${title} - ${artist}`; | ||
ctx.font = '8px Arial'; | ||
const textWidth = ctx.measureText(text).width; | ||
let textOffset = width; | ||
const initialTextOffset = textOffset; | ||
|
||
const totalFrames = Math.ceil((initialTextOffset + textWidth) / 4); | ||
|
||
for (let i = 0; i < totalFrames; i++) { | ||
ctx.fillStyle = '#000'; | ||
ctx.fillRect(0, 0, width, height); | ||
ctx.drawImage(image, offsetX, offsetY, renderWidth, renderHeight); | ||
|
||
ctx.fillStyle = '#000'; | ||
ctx.fillRect(0, height - 10, width, 10); | ||
ctx.fillStyle = '#fff'; | ||
ctx.fillText(text, textOffset, height - 2); | ||
encoder.addFrame(ctx); | ||
|
||
textOffset -= 4; | ||
|
||
if (textOffset + textWidth < 0) { | ||
textOffset = width; | ||
} | ||
} | ||
|
||
encoder.finish(); | ||
|
||
const buffer = encoder.out.getData(); | ||
fs.writeFileSync(gifFilePath, buffer, 'binary'); | ||
console.log('GIF successfully created!'); | ||
}); | ||
} | ||
|
||
function start() { | ||
const tokenData = JSON.parse(fs.readFileSync(tokenFilePath, 'utf8')); | ||
if (tokenData.accessToken) { | ||
spotifyApi.setAccessToken(tokenData.accessToken); | ||
} else if (tokenData.refreshToken) { | ||
refreshTokenIfNeeded(fetchCurrentPlayingTrack); | ||
} else { | ||
console.log('No token available. Please authenticate.'); | ||
} | ||
|
||
const app = express(); | ||
|
||
app.get('/cover.gif', (req, res) => { | ||
res.sendFile(`${__dirname}/${gifFilePath}`, err => { | ||
if (err) { | ||
res.status(404).send('GIF not found. Make sure the script has generated the GIF.'); | ||
} | ||
}); | ||
}); | ||
app.get('/check-song', (req, res) => { | ||
res.send(currentTrackInfo); | ||
}); | ||
|
||
app.listen(config.app.SpotiPixelServerPort, () => { | ||
console.log(`Server started on http://localhost:${config.app.SpotiPixelServerPort}`); | ||
}); | ||
|
||
setInterval(() => { | ||
fetchCurrentPlayingTrack(); | ||
}, 1000); | ||
} | ||
|
||
start(); |
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 @@ | ||
module.exports = { | ||
app: { | ||
clientId: 'iMAnHaRDtOFIndClieNTiD', | ||
clientSecret: 'iMAnHaRDtOFIndClieNTSeCReT', | ||
redirectUri: 'http://localhost:8080/callback', | ||
InitPort: 8080, | ||
SpotiPixelServerMode: true, | ||
SpotiPixelServerPort: 25567, | ||
SpotiPixelClientMode: true, | ||
PixooUrl: 'http://XXX.XXX.XXX.XXX/post', | ||
SpotiPixelServerUrl: 'http://localhost:25567' | ||
} | ||
}; | ||
// Copyright © ArtichautDev 2024 All Rights Reserved |
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,9 @@ | ||
const config = require('./config'); | ||
|
||
if (config.app.SpotiPixelClientMode === true) { | ||
require('./SpotiClient'); | ||
} | ||
|
||
if (config.app.SpotiPixelServerMode === true) { | ||
require('./SpotiServer'); | ||
} |
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,60 @@ | ||
const express = require('express'); | ||
const session = require('express-session'); | ||
const passport = require('passport'); | ||
const fs = require('fs'); | ||
const config = require('./config'); | ||
|
||
const SpotifyStrategy = require('passport-spotify').Strategy; | ||
|
||
const app = express(); | ||
|
||
app.use(session({ | ||
secret: 'SHSHHZJZKZKXIZKskziskISKQ', | ||
resave: false, | ||
saveUninitialized: true, | ||
cookie: { secure: false } | ||
})); | ||
|
||
app.use(passport.initialize()); | ||
app.use(passport.session()); | ||
|
||
const tokenFilePath = './spotify-token.json'; | ||
|
||
|
||
passport.use(new SpotifyStrategy({ | ||
clientID: config.app.clientId, | ||
clientSecret: config.app.clientSecret, | ||
callbackURL: config.app.redirectUri | ||
}, | ||
function(accessToken, refreshToken, expires_in, profile, done) { | ||
fs.writeFileSync(tokenFilePath, JSON.stringify({ accessToken, refreshToken }), 'utf8'); | ||
|
||
done(null, profile); | ||
})); | ||
|
||
app.use(passport.initialize()); | ||
|
||
app.get('/auth/spotify', passport.authenticate('spotify', { scope: ['user-read-playback-state', 'user-read-currently-playing'], showDialog: true })); | ||
|
||
app.get('/callback', passport.authenticate('spotify', { failureRedirect: '/login' }), function(req, res) { | ||
res.send('Authentication successful! You can close this window.'); | ||
}); | ||
|
||
function checkTokenAndStart() { | ||
if (fs.existsSync(tokenFilePath)) { | ||
const tokenData = JSON.parse(fs.readFileSync(tokenFilePath, 'utf8')); | ||
if (tokenData.accessToken) { | ||
console.log('Token found, you can use the Spotify API.'); | ||
} else { | ||
startAuthServer(); | ||
} | ||
} else { | ||
startAuthServer(); | ||
} | ||
} | ||
|
||
function startAuthServer() { | ||
app.listen(config.app.InitPort, () => console.log(`Server started on http://localhost:${config.app.InitPort}/auth/spotify. Please authenticate.`)); | ||
} | ||
|
||
checkTokenAndStart(); |
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,37 @@ | ||
{ | ||
"name": "spotipixel", | ||
"version": "1.0.0", | ||
"description": "This JavaScript project bridges the power of Spotify's API with Divoom's Pixoo to transform music album covers into captivating pixel art GIFs. Designed with a split architecture, it operates with distinct client and server sides. The server side interacts with Spotify's API to fetch album cover images based on user preferences or current playback. Once retrieved, the album covers are processed into pixel art, embracing the nostalgic aesthetic of Divoom's Pixoo displays. The client side, responsible for user interaction, allows users to select their Spotify tracks and view the pixel art transformation in real time. This integration not only celebrates music and art but also offers a unique way to visually experience your favorite albums.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node index.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/ArtichautDev/SpotiPixel.git" | ||
}, | ||
"keywords": [ | ||
"spotify", | ||
"divoom", | ||
"pixoo", | ||
"Pixoo64" | ||
], | ||
"author": "ArtichautDev", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/ArtichautDev/SpotiPixel/issues" | ||
}, | ||
"homepage": "https://github.com/ArtichautDev/SpotiPixel#readme", | ||
"dependencies": { | ||
"axios": "^1.6.8", | ||
"canvas": "^2.11.2", | ||
"express": "^4.19.2", | ||
"express-session": "^1.18.0", | ||
"gifencoder": "^2.0.1", | ||
"node-fetch": "^3.3.2", | ||
"node-gyp": "^10.1.0", | ||
"passport": "^0.7.0", | ||
"passport-spotify": "^2.0.0", | ||
"spotify-web-api-node": "^5.0.2" | ||
} | ||
} |
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 @@ | ||
{} |