[IMP] php: Save artist images locally, fixes for client JS

refactor_total
Brett Spaulding 1 year ago
parent bab2a7bdcc
commit 9385a382ae

@ -18,3 +18,4 @@ yarn-error.log
/.fleet /.fleet
/.idea /.idea
/.vscode /.vscode
public/images/artist

@ -22,10 +22,13 @@ class ApiController extends Controller
'thumbnail' => $artist->thumbnail, 'thumbnail' => $artist->thumbnail,
]; ];
} }
\Log::info('=======================');
$response = json_encode( array('data' => $data)); $response = json_encode( array('data' => $data));
\Log::info($response);
return $response; return $response;
} }
public function queue_artist($id, ArtistQueue $artistQueue)
{
$artistQueue->enqueue($id);
}
} }

@ -4,13 +4,15 @@ namespace App\Http\Controllers;
use App\Models\Artist; use App\Models\Artist;
use App\Models\WebDriver; use App\Models\WebDriver;
use App\Utils\ImageUrl;
use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverExpectedCondition;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Facades\App;
use Nette\Utils\Image;
class SearchController extends Controller class SearchController extends Controller
{ {
@ -36,10 +38,19 @@ class SearchController extends Controller
$artistHref = $artistLink[0]->getAttribute('href'); $artistHref = $artistLink[0]->getAttribute('href');
$artistName = $artistLink[0]->getAttribute('title'); $artistName = $artistLink[0]->getAttribute('title');
// Resize image and save to file, provide path to data
$imageUrl = ImageUrl::modifyGoogleImageUrl($artistThumbnail);
$imageFileUrl = ImageUrl::save_img_url($imageUrl);
\Log::info('============================');
\Log::info($imageUrl);
\Log::info($imageFileUrl);
$data = [ $data = [
'name' => $artistName, 'name' => $artistName,
'thumbnail' => $artistThumbnail, 'thumbnail' => $artistThumbnail,
'url_remote' => $artistHref, 'url_remote' => $artistHref,
'image' => $imageFileUrl,
]; ];
$artist_id = Artist::findOrCreateByName($artistName, $data); $artist_id = Artist::findOrCreateByName($artistName, $data);
return $artist_id->read(); return $artist_id->read();
@ -81,11 +92,21 @@ class SearchController extends Controller
$artistLink = $artist->findElements(WebDriverBy::cssSelector('a')); $artistLink = $artist->findElements(WebDriverBy::cssSelector('a'));
$artistHref = $artistLink[0]->getAttribute('href'); $artistHref = $artistLink[0]->getAttribute('href');
$artistName = $artistLink[0]->getAttribute('aria-label'); $artistName = $artistLink[0]->getAttribute('aria-label');
// Resize image and save to file, provide path to data
$imageUrl = ImageUrl::modifyGoogleImageUrl($artistThumbnail);
$imageFileUrl = ImageUrl::save_img_url($imageUrl);
\Log::info('============================');
\Log::info($imageUrl);
\Log::info($imageFileUrl);
// Create if we don't have it yet // Create if we don't have it yet
$data = [ $data = [
'name' => $artistName, 'name' => $artistName,
'thumbnail' => $artistThumbnail, 'thumbnail' => $artistThumbnail,
'url_remote' => $artistHref, 'url_remote' => $artistHref,
'image' => $imageFileUrl,
]; ];
$artist_id = Artist::findOrCreateByName($artistName, $data); $artist_id = Artist::findOrCreateByName($artistName, $data);
$response[] = $artist_id->read(); $response[] = $artist_id->read();

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AlbumQueue extends Model
{
use HasFactory;
public function enqueue()
{
// Add albums to queue for download
}
public function process_queue()
{
// Either python pings to process the queue or laravel will send the data to python for processing
}
}

@ -14,12 +14,13 @@ class Artist extends Model
return self::where('name', '=', $name)->get(); return self::where('name', '=', $name)->get();
} }
public static function addArtist(string $name, string $thumbnail, string $url_remote) public static function addArtist(string $name, string $thumbnail, string $url_remote, string $image)
{ {
$artist = new Artist(); $artist = new Artist();
$artist->name = $name; $artist->name = $name;
$artist->url_remote = $url_remote; $artist->url_remote = $url_remote;
$artist->thumbnail = $thumbnail; $artist->thumbnail = $thumbnail;
$artist->image = $image;
$artist->save(); $artist->save();
return $artist; return $artist;
} }
@ -28,7 +29,7 @@ class Artist extends Model
{ {
$artist = self::findByName($name)->first(); $artist = self::findByName($name)->first();
if (!$artist && $data) { if (!$artist && $data) {
$artist = self::addArtist($data['name'], $data['thumbnail'], $data['url_remote']); $artist = self::addArtist($data['name'], $data['thumbnail'], $data['url_remote'], $data['image']);
} }
return $artist; return $artist;
} }

@ -8,4 +8,18 @@ use Illuminate\Database\Eloquent\Model;
class ArtistQueue extends Model class ArtistQueue extends Model
{ {
use HasFactory; use HasFactory;
public function enqueue($artist_id)
{
$this->artist_id = $artist_id;
$this->save();
}
public function process_queue()
{
// Scrape the artist page for image, and album data (image, url, name)
}
} }

@ -0,0 +1,67 @@
<?php
namespace App\Utils;
class ImageUrl
{
/**
* Modify the width and height values in a Google Image URL.
*
* @param string $url The original URL.
* @return string The modified URL with new width and height values.
*/
public static function modifyGoogleImageUrl(string $url, int $size = 1024): string
{
// Regular expression pattern to match width and height values in the URL
$pattern = '/w(\\d+)-h(\\d+)/';
// Use preg_replace_callback() to replace the matched values with the provided integer
return preg_replace_callback($pattern, function ($matches) use ($size) {
return "w{$size}-h{$size}";
}, $url);
}
/**
* Save an image from a Google Image URL to the local filesystem.
*
* @param string $url The modified URL of the image to download.
* @return string The path to the saved image file, or empty string if the file already exists.
*/
public static function save_img_url(string $url): string
{
// Get the filename from the URL
$filename = basename($url);
// Create a directory for the images (if it doesn't exist)
$imagesDir = public_path('images/artist');
if (!is_dir($imagesDir)) {
mkdir($imagesDir, 0777, true);
}
// Check if the file already exists
$imagePath = $imagesDir . '/' . $filename . '.jpg';
if (file_exists($imagePath)) {
return ''; // File already exists, don't save again
}
// Download the image from the URL using curl
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode !== 200) {
// Handle HTTP error (e.g. file not found)
return '';
}
// Save the image to disk
file_put_contents($imagePath, $response);
return $imagePath;
}
}

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('album_queues', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('album_id')->nullable()->constrained('albums');
$table->enum('state', [
'pending',
'in_progress',
'done',
])->default('pending');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('album_queues');
}
};

@ -2,39 +2,66 @@ console.log('Version 1:20:2');
const appModal = $('#modalDownloadQueue'); const appModal = $('#modalDownloadQueue');
const loader = $("#loader-wrapper"); const loader = $("#loader-wrapper");
function construct_artist_result_html(artist_list) { function template_artist_result(element) {
let html = '<h3>Found Artist!</h3>'; return `
let index = 0; <div class="card w-100 p-2 mb-2">
artist_list.forEach((element) => { <div class="container-fluid">
index += 1; <div class="row">
html += ` <div class="col-3">
<div class="card w-100 p-2 mb-2"> <img src="${element.thumbnail}" width="72px" height="72px" style="border-radius: 12px;"/>
<div class="container-fluid"> </div>
<div class="row"> <div class="col-9 m-auto">
<div class="col-3"> <h4>${element.name}</h4>
<img src="${element.thumbnail}" width="72px" height="72px" style="border-radius: 12px;"/>
</div>
<div class="col-9 m-auto">
<h4>${element.name}</h4>
</div>
</div> </div>
</div> </div>
</div> </div>
` </div>
if (index === 1 && artist_list.length > 1) { `
html += '<hr/>'; }
html += '<h6>Suggested Artists</h6>'
html += '<hr/>'; function construct_artist_result_html(artist_list) {
} let html = '<h3>Found Artist!</h3>';
}) let index = 0;
if (artist_list.length > 1) {
artist_list.forEach((element) => {
index += 1;
html += template_artist_result(element);
if (index === 1 && artist_list.length > 1) {
html += '<hr/>';
html += '<h6>Suggested Artists</h6>'
html += '<hr/>';
}
})
} else {
html += template_artist_result(artist_list);
}
return html return html
} }
function proc_notification(icon, html, text) { function proc_notification(icon, title, html) {
Swal.fire({ Swal.fire({
html: html,
icon: icon, icon: icon,
text: text title: title,
html: html
})
}
function artist_queue_toggle(element) {
let self = $(element);
console.log(self);
console.log(self.data('artist_id'));
let artist_name = self.data('artist_name');
self.prop('disabled', true)
$.ajax({
url: `/api/artist/queue/${self.data('artist_id')}`,
success: () => {
proc_notification('success', 'Queued Download', `Artist ${artist_name} Queued for Download!`);
},
error: (response) => {
console.log(response);
proc_notification('error', 'What the flip?!', `Failed to queue artist ${artist_name} <br/><br/> <strong>${response.status}: ${response.statusText}</strong>`);
self.prop('disabled', false);
}
}) })
} }
@ -68,8 +95,8 @@ $('#download_btn').on('click', () => {
console.log(response); console.log(response);
console.log('==========='); console.log('===========');
icon = 'success'; icon = 'success';
title = construct_artist_result_html(response); let html = construct_artist_result_html(response);
proc_notification(icon, title, 'Artist found'); proc_notification(icon, title, html);
$('#search_bar').val(''); $('#search_bar').val('');
loader.fadeOut(700); loader.fadeOut(700);
}, },
@ -95,13 +122,13 @@ document.addEventListener('alpine:init', () => {
Alpine.store('app', { Alpine.store('app', {
init() { init() {
// TODO: Poll for artists and queue // TODO: Poll for artists and queue
this.Artists = []; // this.Artists = [];
this.Queue = []; this.Queue = [];
this.ArtistResults = []; // this.ArtistResults = [];
}, },
Artists: [], // Rendered in the 'Artists' modal // Artists: [], // Rendered in the 'Artists' modal
ArtistResults: [], // Rendered in the SWAL popup // ArtistResults: [], // Rendered in the SWAL popup
Queue: [], // Rendered in the 'Queue' modal Queue: [], // Rendered in the 'Queue' modal
}); });
@ -116,14 +143,25 @@ $(document).ready(function () {
type: 'get', type: 'get',
dataType: 'json', dataType: 'json',
columns: [ columns: [
{data: 'thumbnail', render: (data) => { return `<img src="${data}" height=48 width="48" style="border-radius: 6px;"/>`}}, {
data: 'thumbnail', orderable: false, render: (data) => {
return `<img src="${data}" height=48 width="48" style="border-radius: 6px;"/>`
}
},
{data: 'name'}, {data: 'name'},
{title: 'Channel', data: 'url_remote', render: (data) => {return `<a href="https://music.youtube.com/${data}" class="btn btn-danger" target="_blank"><i class="lab la-youtube"></i></a>`}}, {
title: 'Channel', data: 'url_remote', render: (data) => {
return `<a href="https://music.youtube.com/${data}" class="btn btn-danger" target="_blank"><i class="lab la-youtube"></i></a>`
}
},
{data: 'state'}, {data: 'state'},
{data: 'id', render: (data, row) => { {
let stateDiable = row.state === 'in_progress' ? 'disabled': ''; data: 'id', orderable: false, render: (data, type, row) => {
let stateClass = row.state === 'in_progress' ? '': 'btn-primary'; let stateDiable = row.state === 'in_progress' ? 'disabled' : '';
return `<button class="btn ${stateClass}" hx-get="/api/artist/toggle" ${stateDiable}><i class="las la-cloud-download-alt"></i> Download</button>`} let stateClass = row.state === 'in_progress' ? '' : 'btn-primary';
let artist_name = row.name;
return `<button class="btn ${stateClass}" style="float: right;" data-artist_name="${artist_name}" data-artist_id="${data}" onclick="artist_queue_toggle(this)" ${stateDiable}><i class="las la-cloud-download-alt"></i> Download</button>`
}
} }
], ],
}); });

@ -30,8 +30,8 @@
<a id="download_btn" class="btn btn-outline-secondary action-item-home btn-modal" <a id="download_btn" class="btn btn-outline-secondary action-item-home btn-modal"
href="#download" href="#download"
data-action="download"> data-action="download">
<i class="las la-download"></i> {{-- <i class="las la-download"></i>--}}
Download Search
</a> </a>
</div> </div>

@ -11,3 +11,4 @@ Route::get('/', function () {
Route::get('/artist/{artist}', [SearchController::class, 'search_artist'])->name('api.search.artist'); Route::get('/artist/{artist}', [SearchController::class, 'search_artist'])->name('api.search.artist');
Route::get('api/artists/', [ApiController::class, 'get_artists'])->name('api.artist'); Route::get('api/artists/', [ApiController::class, 'get_artists'])->name('api.artist');
Route::get('api/artists/queue/{id}', [ApiController::class, 'queue_artist'])->name('api.artist.queue');

Loading…
Cancel
Save