From cd2dcf28731e9756ade38816b738e7a3ed6757e3 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Thu, 1 Nov 2018 22:15:20 -0400 Subject: [PATCH] Various refactoring, better webp image handling --- console | 4 + src/API/JsonAPI.php | 12 ++- src/API/Kitsu/Model.php | 17 ++-- .../Kitsu/Transformer/AnimeTransformer.php | 4 +- src/AnimeClient.php | 38 +++++++-- src/Command/ClearThumbnails.php | 59 +++++++++++++ src/Command/UpdateThumbnails.php | 28 +----- src/Dispatcher.php | 10 ++- src/Helper/Picture.php | 85 +++++++++++++++++++ src/Model/Anime.php | 17 +--- src/constants.php | 11 ++- 11 files changed, 222 insertions(+), 63 deletions(-) create mode 100644 src/Command/ClearThumbnails.php create mode 100644 src/Helper/Picture.php diff --git a/console b/console index 19eb3956..1631e100 100755 --- a/console +++ b/console @@ -17,6 +17,10 @@ try (new Console([ 'cache:clear' => Command\CacheClear::class, 'cache:refresh' => Command\CachePrime::class, + 'clear:cache' => Command\CacheClear::class, + 'clear:thumbnails' => Command\ClearThumbnails::class, + 'refresh:cache' => Command\CachePrime::class, + 'refresh:thumbnails' => Command\UpdateThumbnails::class, 'regenerate-thumbnails' => Command\UpdateThumbnails::class, 'lists:sync' => Command\SyncLists::class, 'mal_id:check' => Command\MALIDCheck::class, diff --git a/src/API/JsonAPI.php b/src/API/JsonAPI.php index 178fb976..f2dc8d60 100644 --- a/src/API/JsonAPI.php +++ b/src/API/JsonAPI.php @@ -141,6 +141,8 @@ final class JsonAPI { $relationship[$dataType][$idKey][$j] = $included[$dataType][$idKey]; } + + unset($item['relationships'][$relType]['data']); } } } @@ -221,6 +223,11 @@ final class JsonAPI { continue; } + if ( ! array_key_exists($dataType, $organized)) + { + $organized[$dataType] = []; + } + if (array_key_exists($idKey, $organized[$dataType])) { $relationship[$dataType][$idKey] = $organized[$dataType][$idKey]; @@ -298,7 +305,10 @@ final class JsonAPI { $type = $item['type']; $id = $item['id']; - $organized[$type][$id] = $item['attributes']; + if (array_key_exists('attributes', $item)) + { + $organized[$type][$id] = $item['attributes']; + } if (array_key_exists('relationships', $item)) { diff --git a/src/API/Kitsu/Model.php b/src/API/Kitsu/Model.php index bca83c9c..93de3fe6 100644 --- a/src/API/Kitsu/Model.php +++ b/src/API/Kitsu/Model.php @@ -235,15 +235,20 @@ final class Model { { $data = $this->getRequest("people/{$id}", [ 'query' => [ + 'filter' => [ + 'id' => $id, + ], 'fields' => [ 'characters' => 'canonicalName,slug,image', + 'characterVoices' => 'mediaCharacter', 'anime' => 'canonicalTitle,titles,slug,posterImage', 'manga' => 'canonicalTitle,titles,slug,posterImage', + 'mediaCharacters' => 'role,media,character', + 'mediaStaff' => 'role,media,person', ], - 'include' => 'castings.character,castings.media' + 'include' => 'voices.mediaCharacter.media,voices.mediaCharacter.character,staff.media', ], ]); - $cacheItem->set($data); $cacheItem->save(); } @@ -268,7 +273,7 @@ final class Model { 'fields' => [ 'anime' => 'slug,canonicalTitle,posterImage', 'manga' => 'slug,canonicalTitle,posterImage', - 'characters' => 'slug,canonicalName,image' + 'characters' => 'slug,canonicalName,image', ], 'include' => 'waifu,favorites.item,stats' ] @@ -364,13 +369,13 @@ final class Model { * @param string $slug * @return Anime */ - public function getAnime(string $slug): Anime + public function getAnime(string $slug) { $baseData = $this->getRawMediaData('anime', $slug); if (empty($baseData)) { - return new Anime(); + return (new Anime([]))->toArray(); } return $this->animeTransformer->transform($baseData); @@ -966,7 +971,7 @@ final class Model { 'mediaCharacters' => 'character,role', ], 'include' => ($type === 'anime') - ? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character' + ? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character,characters.character' : 'staff,staff.person,categories,mappings,characters.character', ] ]; diff --git a/src/API/Kitsu/Transformer/AnimeTransformer.php b/src/API/Kitsu/Transformer/AnimeTransformer.php index 2a4ee36a..f39dc52c 100644 --- a/src/API/Kitsu/Transformer/AnimeTransformer.php +++ b/src/API/Kitsu/Transformer/AnimeTransformer.php @@ -40,7 +40,9 @@ final class AnimeTransformer extends AbstractTransformer { sort($item['genres']); $title = $item['canonicalTitle']; - $titles = array_unique(array_diff($item['titles'], [$title])); + + $titles = Kitsu::filterTitles($item); + // $titles = array_unique(array_diff($item['titles'], [$title])); return new Anime([ 'age_rating' => $item['ageRating'], diff --git a/src/AnimeClient.php b/src/AnimeClient.php index 4d365042..b4f15536 100644 --- a/src/AnimeClient.php +++ b/src/AnimeClient.php @@ -71,7 +71,14 @@ function loadTomlFile(string $filename): array return Toml::parseFile($filename); } -function _iterateToml(TomlBuilder $builder, $data, $parentKey = NULL): void +/** + * Recursively create a toml file from a data array + * + * @param TomlBuilder $builder + * @param iterable $data + * @param null $parentKey + */ +function _iterateToml(TomlBuilder $builder, iterable $data, $parentKey = NULL): void { foreach ($data as $key => $value) { @@ -107,7 +114,7 @@ function _iterateToml(TomlBuilder $builder, $data, $parentKey = NULL): void * @param mixed $data * @return string */ -function arrayToToml($data): string +function arrayToToml(iterable $data): string { $builder = new TomlBuilder(); @@ -197,28 +204,31 @@ function checkFolderPermissions(ConfigInterface $config): array } /** - * Generate the path for the cached image from the original iamge + * Generate the path for the cached image from the original image * * @param string $kitsuUrl + * @param bool $webp * @return string */ -function getLocalImg ($kitsuUrl): string +function getLocalImg ($kitsuUrl, $webp = TRUE): string { if ( ! is_string($kitsuUrl)) { - return 'images/404/404.png'; + return 'images/placeholder.webp'; } $parts = parse_url($kitsuUrl); if ($parts === FALSE) { - return 'images/404/404.png'; + return 'images/placeholder.webp'; } $file = basename($parts['path']); $fileParts = explode('.', $file); $ext = array_pop($fileParts); + $ext = $webp ? 'webp' : $ext; + $segments = explode('/', trim($parts['path'], '/')); $type = $segments[0] === 'users' ? $segments[1] : $segments[0]; @@ -241,12 +251,15 @@ function createPlaceholderImage ($path, $width, $height, $text = 'Image Unavaila $width = $width ?? 200; $height = $height ?? 200; - $img = imagecreate($width, $height); + $img = imagecreatetruecolor($width, $height); + imagealphablending($img, TRUE); $path = rtrim($path, '/'); // Background is the first color by default - imagecolorallocatealpha($img, 255, 255, 255, 127); + $fillColor = imagecolorallocatealpha($img, 255, 255, 255, 127); + imagefill($img, 0, 0, $fillColor); + $textColor = imagecolorallocate($img, 64, 64, 64); imagealphablending($img, TRUE); @@ -266,6 +279,13 @@ function createPlaceholderImage ($path, $width, $height, $text = 'Image Unavaila // Save the images imagesavealpha($img, TRUE); imagepng($img, $path . '/placeholder.png', 9); - imagedestroy($img); + + $pngImage = imagecreatefrompng($path . '/placeholder.png'); + imagealphablending($pngImage, TRUE); + imagesavealpha($pngImage, TRUE); + + imagewebp($pngImage, $path . '/placeholder.webp'); + + imagedestroy($pngImage); } \ No newline at end of file diff --git a/src/Command/ClearThumbnails.php b/src/Command/ClearThumbnails.php new file mode 100644 index 00000000..b3345534 --- /dev/null +++ b/src/Command/ClearThumbnails.php @@ -0,0 +1,59 @@ + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Command; + +/** + * Clears out image cache directories + */ +class ClearThumbnails extends BaseCommand { + + public function execute(array $args, array $options = []): void + { + $this->clearThumbs(); + $this->echoBox('All cached images have been removed'); + } + + public function clearThumbs() + { + $imgDir = realpath(__DIR__ . '/../../public/images'); + + $paths = [ + 'avatars/*.gif', + 'avatars/*.jpg', + 'avatars/*.png', + 'avatars/*.webp', + 'anime/*.jpg', + 'anime/*.png', + 'anime/*.webp', + 'manga/*.jpg', + 'manga/*.png', + 'manga/*.webp', + 'characters/*.jpg', + 'characters/*.png', + 'characters/*.webp', + 'people/*.jpg', + 'people/*.png', + 'people/*.webp', + ]; + + foreach($paths as $path) + { + $cmd = "rm -rf {$imgDir}/{$path}"; + exec($cmd); + } + } +} \ No newline at end of file diff --git a/src/Command/UpdateThumbnails.php b/src/Command/UpdateThumbnails.php index 5819b7a7..7291eeb6 100644 --- a/src/Command/UpdateThumbnails.php +++ b/src/Command/UpdateThumbnails.php @@ -23,7 +23,7 @@ use Aviat\AnimeClient\Controller\Index; * Clears out image cache directories, then re-creates the image cache * for manga and anime */ -final class UpdateThumbnails extends BaseCommand { +final class UpdateThumbnails extends ClearThumbnails { /** * Model for making requests to Kitsu API * @var \Aviat\AnimeClient\API\Kitsu\Model @@ -43,13 +43,11 @@ final class UpdateThumbnails extends BaseCommand { $this->controller = new Index($this->container); $this->kitsuModel = $this->container->get('kitsu-model'); - $this->clearThumbs(); + // Clear the existing thunbnails + parent::execute($args, $options); $ids = $this->getImageList(); - // print_r($ids); - // echo json_encode($ids, \JSON_PRETTY_PRINT); - // Resave the images foreach($ids as $type => $typeIds) { @@ -64,26 +62,6 @@ final class UpdateThumbnails extends BaseCommand { $this->echoBox('Finished regenerating all thumbnails'); } - public function clearThumbs() - { - $imgDir = realpath(__DIR__ . '/../../public/images'); - - $paths = [ - 'anime/*.jpg', - 'anime/*.webp', - 'manga/*.jpg', - 'manga/*.webp', - 'characters/*.jpg', - 'characters/*.webp', - ]; - - foreach($paths as $path) - { - $cmd = "rm -rf {$imgDir}/{$path}"; - exec($cmd); - } - } - public function getImageList() { $mangaList = $this->kitsuModel->getFullRawMangaList(); diff --git a/src/Dispatcher.php b/src/Dispatcher.php index d0050f4b..4777d5af 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -363,9 +363,15 @@ final class Dispatcher extends RoutingBase { ? $controllerMap[$routeType] : DEFAULT_CONTROLLER; - if (array_key_exists($routeType, $controllerMap)) + // If there's an explicit controller, try to find + // the full namespaced class name + if (array_key_exists('controller', $route)) { - $controllerClass = $controllerMap[$routeType]; + $controllerKey = $route['controller']; + if (array_key_exists($controllerKey, $controllerMap)) + { + $controllerClass = $controllerMap[$controllerKey]; + } } // Prepend the controller to the route parameters diff --git a/src/Helper/Picture.php b/src/Helper/Picture.php new file mode 100644 index 00000000..d37335fd --- /dev/null +++ b/src/Helper/Picture.php @@ -0,0 +1,85 @@ + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Helper; + +use Aviat\Ion\Di\ContainerAware; + +/** + * Simplify picture elements + */ +final class Picture { + + use ContainerAware; + + /** + * Create the html f + * + * @param string $webp + * @param string $fallbackExt + * @param array $picAttrs + * @param array $imgAttrs + * @return string + */ + public function __invoke(string $webp, string $fallbackExt = 'jpg', $picAttrs = [], $imgAttrs = []): string + { + $urlGenerator = $this->container->get('url-generator'); + $helper = $this->container->get('html-helper'); + + // If it is a placeholder image, make the + // fallback a png, not a jpg + if (strpos($webp, 'placeholder') !== FALSE) + { + $fallbackExt = 'png'; + } + + if (strpos($webp, '//') === FALSE) + { + $webp = $urlGenerator->assetUrl($webp); + } + + + $urlParts = explode('.', $webp); + $ext = array_pop($urlParts); + $path = implode('.', $urlParts); + + $mime = $ext === 'jpg' + ? 'image/jpeg' + : "image/{$ext}"; + $fallbackMime = $fallbackExt === 'jpg' + ? 'image/jpeg' + : "image/{$fallbackExt}"; + + $fallbackImg = "{$path}.{$fallbackExt}"; + + $pictureChildren = [ + $helper->void('source', [ + 'srcset' => $webp, + 'type' => $mime, + ]), + $helper->void('source', [ + 'srcset' => $fallbackImg, + 'type' => $fallbackMime + ]), + $helper->img($fallbackImg, array_merge(['alt' => ''], $imgAttrs)), + ]; + + $sources = implode('', $pictureChildren); + + return $helper->elementRaw('picture', $sources, $picAttrs); + } +} +// End of Picture.php \ No newline at end of file diff --git a/src/Model/Anime.php b/src/Model/Anime.php index e358cf8f..42538dec 100644 --- a/src/Model/Anime.php +++ b/src/Model/Anime.php @@ -108,7 +108,7 @@ class Anime extends API { * @param string $slug * @return AnimeType */ - public function getAnime(string $slug): AnimeType + public function getAnime(string $slug) { return $this->kitsuModel->getAnime($slug); } @@ -173,14 +173,6 @@ class Anime extends API { $results = $requester->makeRequests(); - // Debug info - /* $body = Json::decode($results['anilist']); - if ($body['errors']) - { - dump($body); - die(); - } */ - return count($results) > 0; } @@ -261,13 +253,6 @@ class Anime extends API { $results = $requester->makeRequests(); - // Debug info - /* $body = Json::decode($results['anilist']); - if (isset($body['errors'])) { - dump($body); - die(); - } */ - return count($results) > 0; } } \ No newline at end of file diff --git a/src/constants.php b/src/constants.php index d3e3be96..e2fe0b9d 100644 --- a/src/constants.php +++ b/src/constants.php @@ -16,7 +16,7 @@ namespace Aviat\AnimeClient; -const DEFAULT_CONTROLLER = Controller\Index::class; +const DEFAULT_CONTROLLER = Controller\Misc::class; const DEFAULT_CONTROLLER_METHOD = 'index'; const DEFAULT_CONTROLLER_NAMESPACE = Controller::class; const DEFAULT_LIST_CONTROLLER = Controller\Anime::class; @@ -24,7 +24,12 @@ const ERROR_MESSAGE_METHOD = 'errorPage'; const NOT_FOUND_METHOD = 'notFound'; const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth'; const SRC_DIR = __DIR__; -const USER_AGENT = "Tim's Anime Client/4.0"; +const USER_AGENT = "Tim's Anime Client/4.1"; + +// Regex patterns +const ALPHA_SLUG_PATTERN = '[a-z_]+'; +const NUM_PATTERN = '[0-9]+'; +const SLUG_PATTERN = '[a-z0-9\-]+'; // Why doesn't this already exist? const MILLI_FROM_NANO = 1000 * 1000; @@ -129,7 +134,7 @@ const SETTINGS_MAP = [ ], ], ], - /*'options' => [ + /* 'options' => [ 'type' => 'subfield', 'title' => 'Options', 'fields' => [],