Initial commit

main v0.0.1
Max Nuding 2023-02-19 09:43:49 +01:00
commit 6fde23d551
Signed by: phlaym
GPG Key ID: A06651BAB6777237
13 changed files with 34955 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@ -0,0 +1,45 @@
# Created by https://www.toptal.com/developers/gitignore/api/composer,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=composer,macos
### Composer ###
composer.phar
/vendor/
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
# End of https://www.toptal.com/developers/gitignore/api/composer,macos

7
.nova/Configuration.json Normal file
View File

@ -0,0 +1,7 @@
{
"com.thorlaksson.phpcs.runOnChange" : "onSave",
"com.thorlaksson.phpcs.standard" : "PSR12",
"editor.default_syntax" : "php",
"php.interpreter" : "\/opt\/homebrew\/bin\/php",
"php.validate" : "onSave"
}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
PHP API to interact with a [PhotoPrism](https://www.photoprism.app) instance.
Needs a configuration array to instantiate with the keys: `host`, `username`, and `password`

20
composer.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "phlaym/photoprism-api",
"description": "An API for interacting with PhotoPrism",
"type": "library",
"require": {
"monolog/monolog": "^3.3",
"php": "^7.2 || ^8.0"
},
"autoload": {
"psr-4": {
"Phlaym\\PhotoprismApi\\": "src/"
}
},
"authors": [
{
"name": "Max Nuding",
"email": "max.nuding@icloud.com"
}
]
}

172
composer.lock generated Normal file
View File

@ -0,0 +1,172 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "efa28f7cbc00fac5f1688c4970983720",
"packages": [
{
"name": "monolog/monolog",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "9b5daeaffce5b926cac47923798bba91059e60e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/9b5daeaffce5b926cac47923798bba91059e60e2",
"reference": "9b5daeaffce5b926cac47923798bba91059e60e2",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/log": "^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4.5",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.4",
"phpunit/phpunit": "^9.5.26",
"predis/predis": "^1.1 || ^2",
"ruflin/elastica": "^7",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/3.3.1"
},
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2023-02-06T13:46:10+00:00"
},
{
"name": "psr/log",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/3.0.0"
},
"time": "2021-07-14T16:46:02+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.2 || ^8.0"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

39
src/API/LoggerFactory.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace Phlaym\PhotoprismApi\API;
use Monolog\Logger;
use Monolog\Handler\HandlerInterface;
/** Simple factory to create a logger without needing to set stream handlers every time */
class LoggerFactory
{
private static $handlers = [];
/**
* Add a new handler which is automatically added to the list of handlers
* for all _future_ loggers
*
* @param HandlerInterface $handler The handler to add
*
* @return void
*/
public static function addHandler(HandlerInterface $handler): void
{
self::$handlers[] = $handler;
}
/**
* Create a new Logger with the specified name
*
* @param string $name
*
* @return Logger
*/
public static function create(string $name): Logger
{
$l = new Logger($name);
$l->setHandlers(self::$handlers);
return $l;
}
}

448
src/API/PhotoPrism.php Normal file
View File

@ -0,0 +1,448 @@
<?php
namespace Phlaym\PhotoprismApi\API;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Psr\Log\LoggerInterface;
use Phlaym\PhotoprismApi\Exceptions\NetworkException;
use Phlaym\PhotoprismApi\Exceptions\AuthenticationException;
use Phlaym\PhotoprismApi\Entities\Album;
use Phlaym\PhotoprismApi\Entities\Photo;
/**
* The main API class to interface with PhotoPrism
*/
class PhotoPrism
{
/** @var string $base_url Base URL of the PhotoPrism instance */
public string $base_url = 'https://photos.phlaym.net';
/** @var string $api_url API URL of the PhotoPrism instance */
public string $api_url = '';
/** @var string|null $session_id Session id of the currently logged in user */
protected ?string $session_id = null;
/** @var string|null $download_token Token for downloading files */
protected ?string $download_token = null;
/** @var string|null $preview_token Token for thumbnails files */
protected ?string $preview_token = null;
/** @var array $config Configuration options */
protected array $config;
/** @var LoggerInterface $logger Logger object */
protected LoggerInterface $logger;
/**
* Creates a new Photoprism API object from the configuration
*
* @param array $config Configuration dictionary
* @param string|null $log_path Path where the log files end up in.
* Will be set to `$log_path = __DIR__.'/logs/log.log';` if empty.
*
* @return void
*/
public function __construct(
array $config,
?string $log_path = null
) {
if (isset($config['host'])) {
$this->base_url = $str = rtrim($config['host'], '/');
}
$this->api_url = $this->base_url . '/api/v1';
$this->config = $config;
if (empty($log_path)) {
$log_path = __DIR__ . '/logs/log.log';
}
LoggerFactory::addHandler(new RotatingFileHandler($log_path, 5, Logger::DEBUG, true));
$this->logger = LoggerFactory::create('Phlaym\PhotoprismApi');
if (isset($_SESSION['pp_sessionid'])) {
$this->session_id = $_SESSION['pp_sessionid'];
$this->preview_token = $_SESSION['pp_preview_token'];
$this->download_token = $_SESSION['pp_download_token'];
}
}
/**
* Parse headers from a cURL HTTP response.
* Returns an array with the keys `headers` and `content`.
* The former is an array containing the headers (header name as key, value as value).
* The latter is a string with the body of the response
*
* @param string $response The complete response, containing the headers and body
*
* @return array
*/
private function parseHeaders(string $response): array
{
$response = explode("\r\n\r\n", $response, 2);
if (count($response) === 3) {
$dropped = array_shift($response);
$this->logger->warning('Dropping start of response:' . $dropped);
}
$headers = $response[0];
if ($headers === 'HTTP/1.1 100 Continue') {
$response = explode("\r\n\r\n", $response[1], 2);
$headers = $response[0];
}
if (isset($response[1])) {
$content = $response[1];
} else {
$content = '';
}
// this is not a good way to parse http headers
// it will not (for example) take into account multiline headers
// but what we're looking for is pretty basic, so we can ignore those shortcomings
$response_headers = explode("\r\n", $headers);
$header_arr = [];
foreach ($response_headers as $header) {
$header = explode(': ', $header, 2);
if (count($header) < 2) {
continue;
}
list($k,$v) = $header;
$header_arr[$k] = $v;
}
return ['headers' => $header_arr, 'content' => $content];
}
/**
* Sends a HTTP request using cURL
* Returns the body of the response
*
* @param string $method -The HTTP method to use
* @param string $path The HTTP request path without the API URL
* @param array $data Request data to send
* @param string $content_type Content-Type to use
*
* @throws NetworkException on failure
*
* @return string The response body
*/
private function makeRequest(
string $method,
string $path,
array $data = [],
string $content_type = 'application/json'
): string {
$url = $this->api_url . $path;
$method = strtolower($method);
$query_data = [];
$headers = [];
$result = null;
try {
if (is_array($data) && $method !== 'post-raw') {
if ($content_type === 'application/json') {
$query_data = json_encode($data);
} elseif ($content_type === 'multipart/form-data') {
$query_data = $data;
} else {
$query_data = http_build_query($data);
}
}
$ch = curl_init();
if ($method === 'get' && !empty($query_data)) {
$url .= '?' . $query_data;
}
if ($method !== 'get') {
$headers[] = 'Content-Type: ' . $content_type;
}
if (!empty($this->session_id)) {
$headers[] = 'X-Session-Id: ' . $this->session_id;
}
$this->logger->info($method . ' request to ' . $url);
$this->logger->debug('Headers ' . json_encode($headers));
$this->logger->debug('postfields data ' . json_encode($data));
$this->logger->debug('postfields ' . json_encode($query_data));
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, $method !== 'get');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
if ($method !== 'get' && !empty($query_data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $query_data);
}
$output = curl_exec($ch);
// $request = curl_getinfo($ch, CURLINFO_HEADER_OUT);
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($output === false) {
throw new NetworkException("Error sending request to " . $url, 0);
}
if (empty($output) || $output === false) {
$e = new NetworkException("No answer from" . $url, 0);
$this->logger->error(
"Error sending request. No answer from server",
['Exception' => print_r($e, true)]
);
throw $e;
}
if ($http_status === 0) {
throw new NetworkException('Unable to connect to API ' . $url);
}
if ($http_status >= 400) {
throw new NetworkException('Invalid response ' . $url . ': ' . $http_status);
}
$result = $this->parseHeaders($output);
} catch (\Exception $e) {
$this->logger->error("Error sending request", ['Exception' => print_r($e, true)]);
throw new NetworkException("Error sending request to " . $url, 0, $e);
}
return $result['content'];
}
/**
* Log in to PhotoPrism.
* If already logged in nothing happens.
* No check whether the session is still valid is performed
* @throws AuthenticationException on failure
*
* @param bool $force -Force re-login even if already logged in
*/
public function login(bool $force = false): void
{
if (!empty($this->session_id) && !$force) {
$this->logger->info('Skipping login, already logged in');
return;
}
$data = [
'username' => $this->config['username'],
'password' => $this->config['password'],
];
$res = $this->makeRequest('POST', '/session', $data);
$this->logger->info('Login result: ' . $res);
$response = json_decode($res, true);
if (!empty($response['error'])) {
throw new AuthenticationException($response['error']);
}
$this->session_id = $response['id'];
$this->download_token = $response['config']['downloadToken'];
$this->preview_token = $response['config']['previewToken'];
$_SESSION['pp_sessionid'] = $this->session_id;
$_SESSION['pp_preview_token'] = $this->preview_token;
$_SESSION['pp_download_token'] = $this->download_token;
$this->logger->debug('Session ID: ' . $this->session_id . ', preview token: ' . $this->preview_token);
}
/**
* Fetches albums from PhotoPrism
* @throws NetworkException on failure
*
* @param int $count -Maximum amount of albums to fetch
* @param int $offset -Number of albums to skip
* @return Album[]
*/
public function getAlbums(int $count = 1000, int $offset = 0): array
{
$data = [
'count' => $count,
'offset' => $offset,
'type' => 'album'
];
$res = $this->makeRequest('GET', '/albums', $data, 'text/plain');
$response = json_decode($res, true);
if (!empty($response['error'])) {
throw new NetworkException($response['error']);
}
$albums = array_map(
function ($entry) {
return new Album($entry);
},
$response
);
$this->logger->debug('Albums' . json_encode($albums));
return $albums;
}
/**
* Fetches albums from PhotoPrism which can be viewed with the provided tokens
* @throws NetworkException on failure
* @param string[] $tokens -A list of tokens by which the albums are filtered
* @param int $count -Maximum amount of albums to fetch
* @param int $offset -Number of albums to skip
* @return Album[]
*/
public function getAlbumsByTokens(array $tokens, int $count = 1000, int $offset = 0): array
{
$this->logger->debug('getAlbumsByToken');
$albums = $this->getAlbums($count, $offset);
$visibleAlbums = [];
foreach ($albums as $album) {
$token = $this->getAlbumToken($album);
$album->token = $token;
if ($token != null && in_array($album->token, $tokens)) {
$visibleAlbums[] = $album;
} else {
$this->logger->debug('Skipping album without access: ' . $album->title);
}
}
$this->logger->debug('getAlbumsByToken' . json_encode($visibleAlbums));
return $visibleAlbums;
}
/**
* Fetches an album from PhotoPrism which can be viewed with the provided token
* @throws NetworkException on failure
* @param string $token -A token by which the albums are filtered
* @return Album|null
*/
public function getAlbumByToken(string $token): ?Album
{
$this->logger->debug('getAlbumByToken');
$albums = $this->getAlbumsByTokens([$token]);
if (empty($albums)) {
$this->logger->debug('Could not find album for token' . $token);
return null;
}
$this->logger->debug('getAlbumByToken done');
return $albums[0];
}
/**
* Fetches an album from PhotoPrism with the specified id. Needs an access token!
* @throws NetworkException on failure
* @param string $id -id of the album
* @param string $token -Access token
* @return Album|null
*/
public function getAlbumById(string $id, string $token): ?Album
{
$this->logger->debug('getAlbumById', ['id' => $id]);
$res = $this->makeRequest('GET', '/albums/' . $id, [], 'text/plain');
$response = json_decode($res, true);
if (!empty($response['error'])) {
throw new NetworkException($response['error']);
}
$album = new Album($response);
$album_token = $this->getAlbumToken($album);
$album->token = $album_token;
if ($album_token != null && $album_token == $token) {
return $album;
}
$this->logger->warning('getAlbumById: Invalid token');
return null;
}
/**
* Fetches the secret token of an album
* @throws NetworkException on failure
*
* @param Album $album -The album which's toke should be fetched
* @return string|null Album token or null if the album is private
*/
public function getAlbumToken($album): ?string
{
$uid = is_string($album) ? $album : $album->uid;
$res = $this->makeRequest('GET', '/albums/' . $uid . '/links');
/** @var array $response */
$this->logger->debug('Token response: ' . $res);
$response_array = json_decode($res, true);
if (empty($response_array)) {
$this->logger->warning('Empty response: ' . json_encode($res));
return null;
}
$response = $response_array[0];
if (!empty($response['error'])) {
throw new NetworkException($response['error']);
}
if (!isset($response['Token'])) {
return null;
}
return $response['Token'];
}
/**
* Upload photos, optionally add them to a specific album
* @throws NetworkException on failure
* @param string|null $album -The album UID to which the photos should be aded
*/
public function uploadPhotos(?string $album = null): void
{
$path = time();
$url = '/upload/' . $path;
$import_url = '/import' . $url;
foreach (array_keys($_FILES['files']['tmp_name']) as $key) {
$file_tmpname = $_FILES['files']['tmp_name'][$key];
$this->logger->info('Uploading ' . $file_tmpname . ' to ' . $url);
$filename = basename($_FILES['files']['name'][$key]);
$cFile = curl_file_create($file_tmpname, $_FILES['files']['type'][$key], $filename);
$data = ['files' => $cFile];
$res = $this->makeRequest('POST', $url, $data, 'multipart/form-data');
$this->logger->info('Upload result: ' . $res);
}
$this->logger->info('Importing files');
/** @var string[] $albums */
$albums = empty($album) ? [] : [$album];
$import_data = ["move" => true, "albums" => $albums];
$res = $this->makeRequest('POST', $import_url, $import_data);
$this->logger->info('Import result: ' . $res);
}
/**
* Retrieves the photos of an album
* @throws NetworkException on failure
* @param Album $album - The album to which the photos should be loaded
* @param int $count - How many photos should be loaded
* @param int $offset - Offset for paging
* @return Photo[] - Photos in the album
*/
public function getAlbumPhotos(Album $album, int $count = 60, int $offset = 0): array
{
$this->logger->debug('getAlbumPhotos');
$data = [
'album' => $album->uid,
'count' => $count,
'offset' => $offset
];
$res = $this->makeRequest('GET', '/photos', $data, 'text/plain');
$response = json_decode($res, true);
if (!empty($response['error'])) {
throw new NetworkException($response['error']);
}
$this->logger->debug('getAlbumPhotos response');
$photos = array_map(
function ($entry) {
$photo = new Photo($entry);
$photo->thumbnail_token = $this->preview_token;
return $photo;
},
$response
);
$this->logger->info('Got photos:' . json_encode($photos));
$photos = array_filter(
$photos,
function ($obj) {
static $idList = array();
if (in_array($obj->uid, $idList)) {
return false;
}
$idList[] = $obj->uid;
return true;
}
);
$this->logger->info('unique photos:' . json_encode($photos));
return $photos;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

62
src/Entities/Album.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace Phlaym\PhotoprismApi\Entities;
use Phlaym\PhotoprismApi\API\LoggerFactory;
use Psr\Log\LoggerInterface;
/** A PhotoPrism Album */
class Album
{
/** @var string $uid Unique Id of the album */
public string $uid = '';
/** @var string $slug URL slug of the album */
public string $slug = '';
/** @var string $title Title of the album */
public string $title = '';
/** @var string|null $token Secret token of the album. Needs to be set by the API */
public ?string $token = null;
/** @var int $photoCount Number of photos in Album */
public int $photoCount = 0;
/** @var LoggerInterface $logger Logger object */
private LoggerInterface $logger;
/**
* Creates a new album from the api response
*
* @param array $response Photoprism API response containing an album object
*
* @return void
*/
public function __construct(
array $response
) {
$this->uid = $response['UID'];
$this->slug = $response['Slug'];
$this->title = $response['Title'];
if (isset($response['PhotoCount'])) {
$this->photoCount = $response['PhotoCount'];
}
$this->logger = LoggerFactory::create('Phlaym\PhotoprismApi.Album');
}
/**
* Gets the URL path for this album.
* Starts with a leading /
* Returns null if the album's token is not set
*
* @return string|null
*/
public function getUrlPath(): ?string
{
if (empty($this->token)) {
return null;
}
return "/s/{$this->token}/{$this->slug}";
}
}

77
src/Entities/Photo.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace Phlaym\PhotoprismApi\Entities;
use Phlaym\PhotoprismApi\API\LoggerFactory;
use Psr\Log\LoggerInterface;
/** A PhotoPrism Photo */
class Photo
{
/** @var string $thumbnail_token Token for fetching the thumbnail*/
public string $thumbnail_token = '';
/** @var string $uid Unique Id of the photo */
public string $uid = '';
/** @var string $id Id of the photo */
public int $id = 0;
/** @var string $type Type of the photo (image, video, etc) */
public string $type = '';
public string $fileUID;
public string $fileRoot;
public string $fileName;
public string $hash;
public int $width;
public int $height;
public array $files;
/** @var LoggerInterface $logger Logger object */
private LoggerInterface $logger;
/**
* Creates a new photo from the api response
*
* @param array $response Photoprism API response containing a photo object
*
* @return void
*/
public function __construct(
array $response
) {
$this->uid = $response['UID'];
$this->id = intval($response['ID']);
$this->type = $response['Type'];
$this->fileUID = $response['FileUID'];
$this->fileRoot = $response['FileRoot'];
$this->fileName = $response['FileName'];
$this->hash = $response['Hash'];
$this->width = intval($response['Width']);
$this->height = intval($response['Height']);
$this->logger = LoggerFactory::create('Phlaym\PhotoprismApi.Photo');
}
/**
* Gets the thumbnail URL path for this image.
* Starts with a leading /
*
* @return string
*/
public function getThumbnailUrl(string $size = 'tile_500'): string
{
return "/t/{$this->hash}/{$this->thumbnail_token}/{$size}";
}
/**
* Gets the thumbnail URL path for this image.
* Starts with a leading /
*
* @return string
*/
public function getEmbedUrl(string $size = 'fit_1920'): string
{
return "/api/v1/t/{$this->hash}/{$this->thumbnail_token}/{$size}";
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Phlaym\PhotoprismApi\Exceptions;
class AuthenticationException extends \Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Phlaym\PhotoprismApi\Exceptions;
class NetworkException extends \Exception
{
}