-
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
Showing
6 changed files
with
510 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,137 @@ | ||
<?php | ||
|
||
// Debug purpose print $data to browser console | ||
function console_log($data) { | ||
$output = $data; | ||
if (is_array($output)) | ||
$output = implode(',', $output); | ||
|
||
echo "<script>console.log('Debug Objects: " . $output . "' );</script>"; | ||
} | ||
|
||
// check if LastFM user exist | ||
function checkUserExist($lastfmUser, $apiKey) | ||
{ | ||
$query = "https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=$lastfmUser&api_key=$apiKey&format=json"; | ||
try { | ||
$lastfmUserInfo = file_get_contents($query); | ||
|
||
// Check for errors | ||
if ($lastfmUserInfo === false) { | ||
throw new Exception("Unable to fetch Last.fm user info."); | ||
} | ||
|
||
$lastfmUserInfoJson = json_decode($lastfmUserInfo, true); | ||
|
||
if (isset($lastfmUserInfoJson['user']['name'])) { | ||
return true; | ||
} | ||
|
||
} catch (Exception $e) { | ||
// Handle the exception (log, display an error message, etc.) | ||
// For demonstration purposes, we'll return a string with the error message | ||
return $e->getMessage(); | ||
// return false; | ||
} | ||
} | ||
|
||
// Fatch Top Albums | ||
function fetchtopalbums($lastfmuser, $apikey, $period, $limit) { | ||
|
||
// create the url | ||
// https://www.last.fm/api/show/user.gettopalbums | ||
$method = "user.gettopalbums"; | ||
$apiurl = "https://ws.audioscrobbler.com/2.0/"; | ||
|
||
$query = "$apiurl?method=$method&user=$lastfmuser&period=$period&limit=$limit&api_key=$apikey&format=json"; | ||
|
||
$lastfmdata = file_get_contents($query); | ||
$lastfmdatajson = json_decode($lastfmdata, true); | ||
$topalbums = $lastfmdatajson['topalbums']['album']; | ||
|
||
return $topalbums; | ||
} | ||
|
||
// create albums cover array | ||
function createAlbumsCoverArray($topAlbums) | ||
{ | ||
$albumsCoverUrlList = array(); | ||
foreach ($topAlbums as $topAlbum) { | ||
// check if extralarge image exist | ||
$extralarge_image_link = isset($topAlbum['image'][3]['#text']) && !empty($topAlbum['image'][3]['#text']) | ||
? parse_url($topAlbum['image'][3]['#text']) | ||
: null; | ||
|
||
if ($extralarge_image_link) { | ||
$image_filename = pathinfo($extralarge_image_link['path'], PATHINFO_BASENAME); | ||
$original_image_link = "https://" . $extralarge_image_link['host'] . "/i/u/" . $image_filename; | ||
$albumsCoverUrlList[] = $original_image_link; | ||
} | ||
} | ||
|
||
return $albumsCoverUrlList; | ||
} | ||
|
||
function createImagesFromUrls($urls) | ||
{ | ||
|
||
$images = array(); | ||
|
||
foreach ($urls as $url) { | ||
$fileExtension = pathinfo($url, PATHINFO_EXTENSION); | ||
|
||
switch ($fileExtension) { | ||
case 'jpg': | ||
$images[] = imagecreatefromjpeg($url); | ||
break; | ||
case 'png': | ||
$images[] = imagecreatefrompng($url); | ||
break; | ||
case 'gif': | ||
$images[] = imagecreatefromgif($url); | ||
break; | ||
// Add more cases for other supported image formats if needed | ||
} | ||
} | ||
|
||
return $images; | ||
} | ||
|
||
function createPatchwork($imagesSideSize, $patchworkHeight, $patchworkWidth, $noborder, $cols, $rows, $covers) | ||
{ | ||
|
||
// $patchworkWidth = $imagesSideSize * $cols + ($cols - 1); // 299 is the max size of the Last.fm profile left column ;) | ||
// $patchworkHeight = $imagesSideSize * $rows + ($rows - 1); | ||
|
||
// create the "empty" patchwork | ||
$patchwork = imagecreatetruecolor($patchworkWidth, $patchworkHeight); | ||
|
||
if (!$noborder) { | ||
// create a white color (reminds me of SDL ^^) | ||
$white = imagecolorallocate($patchwork, 255, 255, 255); | ||
// we fill our patchwork by the white color | ||
imagefilltoborder($patchwork, 0, 0, $white, $white); | ||
} | ||
|
||
// now we "parse" our images in the patchwork, while resizing them :] | ||
for ($i = 0; $i < $rows; $i++) { | ||
for ($j = 0; $j < $cols; $j++) { | ||
imagecopyresampled($patchwork, $covers[$cols * $i + $j], $j * $imagesSideSize + $j, $i * $imagesSideSize + $i, 0, 0, $imagesSideSize + intval($noborder), $imagesSideSize + intval($noborder), imagesx($covers[$cols * $i + $j]), imagesy($covers[$cols * $i + $j])); | ||
} | ||
} | ||
|
||
return $patchwork; | ||
} | ||
|
||
function createImageJsonData($fileName, $PatchworkWidth, $PatchworkHeight) | ||
{ | ||
$response = [ | ||
'imagePath' => $fileName, | ||
'width' => $PatchworkWidth, | ||
'height' => $PatchworkHeight, | ||
]; | ||
|
||
// header('Content-Type: application/json'); | ||
return json_encode($response); | ||
} | ||
?> |
Empty file.
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,121 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Last.fm top albums patchwork generator</title> | ||
<meta charset="utf-8" /> | ||
<meta name="description" content="A tool that generates a patchwork, an image, based on the covers of your Last.fm top albums. It's simple, free, and it works." /> | ||
<meta name="keywords" content="lastfm top albums generator, last.fm top albums generator, lastfm top albums, last.fm top albums, lastfm, last.fm, top albums" /> | ||
<link href="styles/main.css" rel="stylesheet" type="text/css" /> | ||
<link rel="stylesheet" href="/vendor/picocss/pico/css/pico.min.css"> | ||
<script src="/scripts/main.js"></script> | ||
</head> | ||
<body> | ||
<nav class="container-fluid"> | ||
<ul> | ||
<li><strong>Top Album Patchwork</strong></li> | ||
</ul> | ||
<ul> | ||
<li> | ||
<a class="contrast" href="https://github.com" role="button"> | ||
Fork Me | ||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"> | ||
<path fill="currentColor" d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2a2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82Z" /> | ||
</svg> | ||
</a> | ||
</li> | ||
</ul> | ||
</nav> | ||
<main class="container"> | ||
<article> | ||
<form action="patchwork.php" method="GET" onsubmit="submitForm(event)"> | ||
<label for="username"> | ||
LastFM Username | ||
<input type="text" id="username" name="username" placeholder="username" required> | ||
</label> | ||
<fieldset class="grid"> | ||
<legend>Period</legend> | ||
<div> | ||
<label for="overall"> | ||
<input type="radio" id="overall" name="period" value="overall" checked> | ||
Overall | ||
</label> | ||
<label for="7day"> | ||
<input type="radio" id="7day" name="period" value="7day"> | ||
7 days | ||
</label> | ||
<label for="1month"> | ||
<input type="radio" id="1month" name="period" value="1month"> | ||
1 Month | ||
</label> | ||
</div> | ||
<div> | ||
<label for="3months"> | ||
<input type="radio" id="3months" name="period" value="3months"> | ||
3 Months | ||
</label> | ||
<label for="6months"> | ||
<input type="radio" id="6months" name="period" value="6months"> | ||
6 Months | ||
</label> | ||
<label for="1year"> | ||
<input type="radio" id="1year" name="period" value="12month"> | ||
1 Year | ||
</label> | ||
</div> | ||
</fieldset> | ||
<fieldset class="grid"> | ||
<div> | ||
<label for="cols">Nr. of rows | ||
<output id="rowsOutput">3</output> | ||
</label> | ||
<input type="range" min="1" max="20" value="3" id="rows" name="rows" oninput="rowsOutput.value = rows.value"> | ||
</label> | ||
</div> | ||
<div> | ||
<label for="cols">Nr. of columns | ||
<output id="colsOutput">3</output> | ||
</label> | ||
<input type="range" min="1" max="20" value="3" id="cols" name="cols" oninput="colsOutput.value = cols.value"> | ||
</label> | ||
</div> | ||
</fieldset> | ||
<fieldset class=""> | ||
<label for="imageSize">Images size in pixel</label> | ||
<input type="text" value="150" name="imageSize" id="imagesSize" /> | ||
<label for="noborder"> | ||
<input type="checkbox" name="noborder" id="noborder" /> | ||
No border | ||
</label> | ||
</fieldset> | ||
<button id="submitbtn" type="submit">Generate</button> | ||
</form> | ||
</article> | ||
<article class="hidden" id="resultcontainer"> | ||
<div class=""> | ||
<div class="field-title">Dynamyc Image link :</div> | ||
<div class="img-link"> | ||
<a id="patchworkDynLink" href="" target="_blank"></a> | ||
</div> | ||
<div role="button" class="outline contrast" onclick="copyToClipboard(event, 'patchworkDynLink')">Copy</div> | ||
</div> | ||
<div> | ||
<div class="field-title">Static Image link :</div> | ||
<div class="img-link"> | ||
<a id="patchworkStaticLink" href="" target="_blank"></a> | ||
</div> | ||
<div role="button" class="outline contrast" onclick="copyToClipboard(event, 'patchworkStaticLink')">Copy</div> | ||
</div> | ||
<div class="patchwork"> | ||
<img src="" id="patchworkImg" width="" height="" alt="Patchwork"> | ||
</div> | ||
</article> | ||
</main> | ||
</body> | ||
</html> |
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,106 @@ | ||
<?php | ||
// import functions | ||
require_once 'functions.php'; | ||
|
||
// load Lastfm APIKEY from .env | ||
require_once realpath(__DIR__ . "/vendor/autoload.php"); | ||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); | ||
$dotenv->load(); | ||
|
||
$apiKey = $_ENV['LASTFM_API_KEY']; | ||
|
||
// GET Params; | ||
$lastfmUser = $_GET["username"]; | ||
$period = $_GET["period"]; | ||
$rows = $_GET["rows"]; | ||
$cols = $_GET["cols"]; | ||
$imagesSize = $_GET["imageSize"]; | ||
$noborder = (bool)(isset($_GET["noborder"]) && $_GET["noborder"]); | ||
|
||
// return json data or image file | ||
$json = (bool)(isset($_GET["json"]) && $_GET["json"]); | ||
|
||
// Get 5 more albums incase there isn't an available | ||
// image for one of the requested albums #lazyhackftw | ||
$limit = ($cols * $rows) + 5; | ||
|
||
// Fallback if imagesSize is not set | ||
(isset($imagesSize)) ? $imagesSideSize = $imagesSize : $imagesSideSize = 99; | ||
|
||
// Calculate patchwork size | ||
$patchworkWidth = $imagesSideSize * $cols + ($cols - 1); // 299 is the max size of the Last.fm profile left column ;) | ||
$patchworkHeight = $imagesSideSize * $rows + ($rows - 1); | ||
|
||
do { | ||
// check if username is valid | ||
if (preg_match('/^[a-zA-Z0-9_.-]+$/', $lastfmUser) !== 1) { | ||
$response = [ | ||
'error' => "Invalid Username", | ||
]; | ||
break; | ||
} | ||
// check if username exist | ||
if (!checkUserExist($lastfmUser, $apiKey) === true) { | ||
$response = [ | ||
'error' => checkUserExist($lastfmUser, $apiKey), | ||
]; | ||
break; | ||
} | ||
|
||
// Fetch top albums fron LastFM | ||
$topAlbums = fetchtopalbums($lastfmUser, $apiKey, $period, $limit); | ||
|
||
// Check if albums is not empty | ||
if (empty($topAlbums)) { | ||
$response = [ | ||
'error' => "User does not have scrobbled any albums", | ||
]; | ||
break; | ||
} | ||
|
||
$border = $noborder ? "noborder" : "border"; | ||
// create Hash filename to avoid duplication | ||
$topAlbumsDataHash = hash('md5',json_encode($topAlbums)); | ||
// $fileName = "images/$lastfmUser_$period_$rows_$cols_$imagesSize_$border-hash_$topAlbumsDataHash.jpg"; | ||
$fileName = "images/{$lastfmUser}_{$period}_{$rows}_{$cols}_{$imagesSize}_{$border}-hash_{$topAlbumsDataHash}.jpg"; | ||
// If file exist return existing data or image | ||
if (file_exists($fileName)) { | ||
$response = [ | ||
'imagePath' => $fileName, | ||
'width' => $patchworkWidth, | ||
'height' => $patchworkHeight, | ||
]; | ||
|
||
// $patchwork = file_get_contents($fileName); | ||
$patchwork = imagecreatefromjpeg($fileName); | ||
// console_log($patchwork); | ||
break; | ||
} | ||
|
||
// Else Generate a new patchwork | ||
$albumsCovers = createAlbumsCoverArray($topAlbums); | ||
$covers = createImagesFromUrls($albumsCovers); | ||
$patchwork = createPatchwork($imagesSideSize, $patchworkHeight, $patchworkWidth, $noborder, $cols, $rows, $covers); | ||
// console_log(gettype($patchwork)); | ||
|
||
// save the image into a file | ||
imagejpeg($patchwork, $fileName); | ||
|
||
$response = [ | ||
'imagePath' => $fileName, | ||
'width' => $patchworkWidth, | ||
'height' => $patchworkHeight, | ||
]; | ||
|
||
} while (0); | ||
|
||
// return json if requested else return image | ||
if ($json) { | ||
// return json data | ||
header('Content-Type: application/json'); | ||
echo json_encode($response); | ||
} else { | ||
// display the image | ||
header("Content-type: image/jpg"); | ||
imagejpeg($patchwork); | ||
} |
Oops, something went wrong.