1
0
antisocial-scrobble/album-collage-generator.php
2024-10-11 17:38:13 +00:00

530 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// Prevent direct access to the file
if (!defined('ABSPATH')) {
exit;
}
class AlbumCollageGenerator {
private $db;
private $background_color;
private $text_color;
private $western_font;
private $japanese_font;
private $blacklisted_artists;
public function __construct() {
global $wpdb;
$this->db = $wpdb;
add_action('rest_api_init', array($this, 'register_endpoint'));
$this->background_color = $this->hex_to_rgb('#141617');
$this->text_color = $this->hex_to_rgb('#ececec');
$this->western_font = plugin_dir_path(__FILE__) . 'assets/co1251n.ttf';
$this->japanese_font = plugin_dir_path(__FILE__) . 'assets/BIZUDPMincho-Regular.ttf';
$this->blacklisted_artists = array('Drake', 'Mac Miller'); // placeholder values
}
public function register_endpoint() {
register_rest_route('top-albums/v1', '/last-month', array(
'methods' => 'GET',
'callback' => array($this, 'get_or_generate_image'),
'permission_callback' => '__return_true',
'args' => array(
'month' => array(
'required' => false,
'validate_callback' => array($this, 'is_valid_month'),
),
'year' => array(
'required' => false,
'validate_callback' => array($this, 'is_valid_year'),
),
'overwrite' => array(
'required' => false,
'default' => false,
'validate_callback' => array($this, 'is_valid_overwrite'),
),
),
));
}
public function is_valid_month($param, $request, $key) {
$month = intval($param);
return $month >= 1 && $month <= 12;
}
public function is_valid_year($param, $request, $key) {
$year = intval($param);
$current_year = intval(date('Y'));
return $year >= 2000 && $year <= $current_year;
}
public function is_valid_overwrite($param, $request, $key) {
// Accept boolean or string representations
if (is_bool($param)) {
return true;
}
if (is_string($param)) {
$param = strtolower($param);
return in_array($param, array('1', 'true', 'yes'), true);
}
if (is_numeric($param)) {
return in_array($param, array(1, 0), true);
}
return false;
}
private function interpret_boolean($value) {
if (is_bool($value)) {
return $value;
}
if (is_string($value)) {
$value = strtolower($value);
return in_array($value, array('1', 'true', 'yes'), true);
}
if (is_numeric($value)) {
return (bool)$value;
}
return false;
}
private function hex_to_rgb($hex) {
$hex = str_replace('#', '', $hex);
if(strlen($hex) == 3) {
$r = hexdec(substr($hex,0,1).substr($hex,0,1));
$g = hexdec(substr($hex,1,1).substr($hex,1,1));
$b = hexdec(substr($hex,2,1).substr($hex,2,1));
} else {
$r = hexdec(substr($hex,0,2));
$g = hexdec(substr($hex,2,2));
$b = hexdec(substr($hex,4,2));
}
return array($r, $g, $b);
}
private function get_last_month_data() {
$last_month = date('Y-m', strtotime('last day of previous month'));
return explode('-', $last_month);
}
private function get_top_albums($year, $month) {
// Prepare placeholders for blacklisted artists
$placeholders = array_fill(0, count($this->blacklisted_artists), '%s');
$placeholders_str = implode(', ', $placeholders);
// Merge all parameters for $wpdb->prepare
$query_params = array($year, $month);
$query_params = array_merge($query_params, $this->blacklisted_artists);
$query = $this->db->prepare(
"WITH ranked_covers AS (
SELECT
album_name,
author,
cover_url,
ROW_NUMBER() OVER (PARTITION BY album_name, author ORDER BY time DESC) as rn
FROM song_scrobbles
WHERE cover_url != '' AND cover_url IS NOT NULL
),
album_stats AS (
SELECT
album_name,
author,
COUNT(*) as play_count,
COUNT(DISTINCT song_name) as unique_song_count
FROM song_scrobbles
WHERE YEAR(time) = %d AND MONTH(time) = %d
AND album_name != '' AND album_name IS NOT NULL
-- Exclude blacklisted artists
AND author NOT IN ($placeholders_str)
-- Exclude albums with '¥' symbol in album_name or author
AND album_name NOT LIKE '%%¥%%'
AND author NOT LIKE '%%¥%%'
-- Exclude albums with Cyrillic characters in album_name or author
AND album_name NOT REGEXP '[А-яЁё]'
AND author NOT REGEXP '[А-яЁё]'
GROUP BY album_name, author
HAVING LENGTH(CONCAT(album_name, author)) <= 60 AND unique_song_count >= 1
)
SELECT
a.album_name,
a.author,
r.cover_url,
a.play_count,
a.unique_song_count,
CASE
WHEN a.unique_song_count = 1 THEN 1.0
WHEN a.unique_song_count = 5 THEN 1.25
WHEN a.unique_song_count = 12 THEN 1.6
ELSE 1.0 + (a.unique_song_count * 0.05)
END as multiplier,
(a.play_count *
CASE
WHEN a.unique_song_count = 1 THEN 1.0
WHEN a.unique_song_count = 5 THEN 1.25
WHEN a.unique_song_count = 12 THEN 1.6
ELSE 1.0 + (a.unique_song_count * 0.05)
END
) as rank_score
FROM album_stats a
LEFT JOIN ranked_covers r
ON a.album_name = r.album_name
AND a.author = r.author
AND r.rn = 1
ORDER BY rank_score DESC
LIMIT 25",
...$query_params
);
return $this->db->get_results($query);
}
public function get_or_generate_image($request) {
$month = $request->get_param('month');
$year = $request->get_param('year');
$overwrite = $request->get_param('overwrite'); // Retrieve 'overwrite' parameter
// Convert 'overwrite' to boolean
$overwrite = $this->interpret_boolean($overwrite);
if ($month && $year) {
$month = intval($month);
$year = intval($year);
// Validate that the date is not in the future
$provided_date = strtotime("{$year}-{$month}-01");
$now = time();
if ($provided_date > $now) {
return new WP_Error('invalid_date', 'The provided month and year cannot be in the future.', array('status' => 400));
}
// Ensure the date is valid
if (!checkdate($month, 1, $year)) {
return new WP_Error('invalid_date', 'The provided month and year are invalid.', array('status' => 400));
}
} else {
list($year, $month) = $this->get_last_month_data();
}
$cache_key = "top_albums_{$year}_{$month}";
// Check if we have a cached image
$cached_image_id = get_option($cache_key);
if ($overwrite && $cached_image_id) { // If overwrite is true and cached image exists
// Delete the old attachment
$deleted = wp_delete_attachment($cached_image_id, true);
if ($deleted) {
// Remove the cached option
delete_option($cache_key);
$cached_image_id = false; // Reset to indicate no cached image
} else {
return new WP_Error('deletion_failed', 'Failed to delete the existing image.', array('status' => 500));
}
}
if ($cached_image_id) {
$attachment_path = get_attached_file($cached_image_id);
if ($attachment_path && file_exists($attachment_path)) {
// Serve the cached image directly as a response
$image_data = file_get_contents($attachment_path);
$mime_type = mime_content_type($attachment_path);
// Send appropriate headers
header('Content-Type: ' . $mime_type);
header('Content-Length: ' . strlen($image_data));
header('Content-Disposition: inline; filename="' . basename($attachment_path) . '"');
// Prevent WordPress from sending additional content
remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
echo $image_data;
exit;
}
}
// If no cached image, generate a new one
$image_data = $this->generate_image($year, $month);
// Save the generated image as an attachment
$upload_dir = wp_upload_dir();
$filename = "top_albums_{$year}_{$month}.jpg";
$file_path = $upload_dir['path'] . '/' . $filename;
file_put_contents($file_path, $image_data);
$attachment = array(
'post_mime_type' => 'image/jpeg',
'post_title' => "Top Albums for {$year}-{$month}",
'post_content' => '',
'post_status' => 'inherit'
);
$attach_id = wp_insert_attachment($attachment, $file_path);
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attach_data = wp_generate_attachment_metadata($attach_id, $file_path);
wp_update_attachment_metadata($attach_id, $attach_data);
// Save the attachment ID for future use
update_option($cache_key, $attach_id);
// Serve the newly generated image directly as a response
$mime_type = mime_content_type($file_path);
// Send appropriate headers
header('Content-Type: ' . $mime_type);
header('Content-Length: ' . strlen($image_data));
header('Content-Disposition: inline; filename="' . basename($file_path) . '"');
// Prevent WordPress from sending additional content
remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
echo $image_data;
exit;
}
private function generate_image($year, $month) {
$albums = $this->get_top_albums($year, $month);
$image_width = 2600;
$image_height = 1680;
$album_size = 300;
$gap = 8;
$left_margin = 100;
$top_margin = 100;
$image = imagecreatetruecolor($image_width, $image_height);
$background_color = imagecolorallocate($image, $this->background_color[0], $this->background_color[1], $this->background_color[2]);
imagefill($image, 0, 0, $background_color);
$text_color = imagecolorallocate($image, $this->text_color[0], $this->text_color[1], $this->text_color[2]);
// Add album covers (left side)
for ($i = 0; $i < min(count($albums), 25); $i++) {
$row = floor($i / 5);
$col = $i % 5;
$x = $left_margin + $col * ($album_size + $gap);
$y = $top_margin + $row * ($album_size + $gap);
$album_cover = @imagecreatefromjpeg($albums[$i]->cover_url);
if ($album_cover !== false) {
$this->apply_retro_effects($album_cover);
imagecopyresampled($image, $album_cover, $x, $y, 0, 0, $album_size, $album_size, imagesx($album_cover), imagesy($album_cover));
imagedestroy($album_cover);
} else {
$placeholder_color = imagecolorallocate($image, 100, 100, 100);
imagefilledrectangle($image, $x, $y, $x + $album_size, $y + $album_size, $placeholder_color);
}
}
// Add album titles (right side)
$text_x = $left_margin + 5 * ($album_size + $gap) + 20;
$text_y = $top_margin + 18;
$line_height = 35;
$font_size = 20;
$group_spacing = 50;
for ($i = 0; $i < min(count($albums), 25); $i++) {
$album_text = $albums[$i]->author . ' - ' . $albums[$i]->album_name;
$font = $this->is_japanese($album_text) ? $this->japanese_font : $this->western_font;
$this->add_retro_text($image, $font_size, $text_x, $text_y, $text_color, $font, $album_text);
if (($i + 1) % 5 == 0 && $i < 24) {
$text_y += $line_height + $group_spacing;
} else {
$text_y += $line_height;
}
}
$this->apply_overall_retro_effects($image);
ob_start();
imagejpeg($image);
$image_data = ob_get_clean();
imagedestroy($image);
return $image_data;
}
private function apply_retro_effects(&$image) {
// Add noise
$this->add_noise($image, 20);
// Reduce color depth
$this->reduce_color_depth($image);
// Add slight blur
// imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR);
}
private function add_noise(&$image, $amount) {
$width = imagesx($image);
$height = imagesy($image);
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
if (rand(0, 100) < $amount) {
$rgb = imagecolorat($image, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$noise = rand(-20, 20);
$r = max(0, min(255, $r + $noise));
$g = max(0, min(255, $g + $noise));
$b = max(0, min(255, $b + $noise));
$color = imagecolorallocate($image, $r, $g, $b);
imagesetpixel($image, $x, $y, $color);
}
}
}
}
private function reduce_color_depth(&$image) {
imagetruecolortopalette($image, false, 64);
}
private function add_retro_text(&$image, $font_size, $x, $y, $color, $font, $text) {
// Add a slight shadow
$shadow_color = imagecolorallocatealpha($image, 0, 0, 0, 75);
imagettftext($image, $font_size, 0, $x + 1, $y + 1, $shadow_color, $font, $text);
// Add main text with a slight glow
$glow_color = imagecolorallocatealpha($image, 255, 255, 255, 75);
imagettftext($image, $font_size, 0, $x - 1, $y - 1, $glow_color, $font, $text);
imagettftext($image, $font_size, 0, $x, $y, $color, $font, $text);
}
private function apply_overall_retro_effects(&$image) {
// Add scanlines
$this->add_scanlines($image);
// Add vignette effect
$this->add_vignette($image);
// Add slight color aberration
$this->add_color_aberration($image);
// Add subtle grain
$this->add_grain($image);
}
private function add_scanlines(&$image) {
$height = imagesy($image);
$scanline_color = imagecolorallocatealpha($image, 0, 0, 0, 70);
for ($y = 0; $y < $height; $y += 2) {
imageline($image, 0, $y, imagesx($image), $y, $scanline_color);
}
}
private function add_vignette(&$image) {
$width = imagesx($image);
$height = imagesy($image);
$center_x = $width / 2;
$center_y = $height / 2;
$max_distance = sqrt($center_x * $center_x + $center_y * $center_y);
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$distance = sqrt(pow($x - $center_x, 2) + pow($y - $center_y, 2));
$vignette = 1 - ($distance / $max_distance);
$vignette = max(0, min(1, $vignette));
// Make vignette more subtle
$vignette = 1 - (1 - $vignette) * 0.25; // Adjust 0.25 to control intensity
$rgb = imagecolorat($image, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$r = (int)($r * $vignette);
$g = (int)($g * $vignette);
$b = (int)($b * $vignette);
$color = imagecolorallocate($image, $r, $g, $b);
imagesetpixel($image, $x, $y, $color);
}
}
}
private function add_color_aberration(&$image) {
$width = imagesx($image);
$height = imagesy($image);
$aberration = 1; // Reduced from 2 to 1 for subtler effect
$new_image = imagecreatetruecolor($width, $height);
imagesavealpha($new_image, true);
imagealphablending($new_image, false);
$transparent = imagecolorallocatealpha($new_image, 0, 0, 0, 127);
imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
imagealphablending($new_image, true);
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
// Shift red channel slightly to the left
$red_x = max(0, $x - $aberration);
$red_color = imagecolorallocate($new_image, $r, 0, 0);
imagesetpixel($new_image, $red_x, $y, $red_color);
// Keep green channel in place
$green_color = imagecolorallocate($new_image, 0, $g, 0);
imagesetpixel($new_image, $x, $y, $green_color);
// Shift blue channel slightly to the right
$blue_x = min($width - 1, $x + $aberration);
$blue_color = imagecolorallocate($new_image, 0, 0, $b);
imagesetpixel($new_image, $blue_x, $y, $blue_color);
}
}
// Merge the aberrated image back onto the original with reduced opacity
imagecopymerge($image, $new_image, 0, 0, 0, 0, $width, $height, 3); // Reduced from 50 to 30
imagedestroy($new_image);
}
private function add_grain(&$image) {
$width = imagesx($image);
$height = imagesy($image);
$grain_amount = 8; // Adjust this value to control grain intensity (1-10 recommended)
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
if (rand(0, 100) < $grain_amount) {
$rgb = imagecolorat($image, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$noise = rand(-10, 10);
$r = max(0, min(255, $r + $noise));
$g = max(0, min(255, $g + $noise));
$b = max(0, min(255, $b + $noise));
$color = imagecolorallocate($image, $r, $g, $b);
imagesetpixel($image, $x, $y, $color);
}
}
}
}
private function is_japanese($text) {
return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $text);
}
}
new AlbumCollageGenerator();