From 00ca1e02d3c7832afbef37745bb0742cf46b6261 Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 21:06:25 +0200 Subject: [PATCH 01/11] Modification of EXIF extraction --- php/Modules/Photo.php | 88 +++++++++++++++++++++++----------------- php/Modules/Settings.php | 7 ++++ 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index 38d82fd..b5d1e14 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -307,7 +307,7 @@ final class Photo { $newUrl2x = LYCHEE_UPLOADS_THUMB . $photoName[0] . '@2x.jpeg'; // Create thumbnails with Imagick - if(extension_loaded('imagick')&&Settings::get()['imagick']==='1') { + if(Settings::hasImagick()) { // Read image $thumb = new Imagick(); @@ -727,48 +727,61 @@ final class Photo { $info = getimagesize($url, $iptcArray); // General information - $return['type'] = $info['mime']; - $return['width'] = $info[0]; - $return['height'] = $info[1]; + $return['type'] = $info['mime']; + $return['width'] = $info[0]; + $return['height'] = $info[1]; + $return['title'] = ''; + $return['description'] = ''; + $return['orientation'] = ''; + $return['iso'] = ''; + $return['aperture'] = ''; + $return['make'] = ''; + $return['model'] = ''; + $return['shutter'] = ''; + $return['focal'] = ''; + $return['takestamp'] = 0; + $return['lens'] = ''; + $return['tags'] = array(); + $return['position'] = ''; + $return['latitude'] = ''; + $return['longitude'] = ''; + $return['altitude'] = ''; // Size $size = filesize($url)/1024; if ($size>=1024) $return['size'] = round($size/1024, 1) . ' MB'; else $return['size'] = round($size, 1) . ' KB'; - // IPTC Metadata Fallback - $return['title'] = ''; - $return['description'] = ''; - // IPTC Metadata + // See https://www.iptc.org/std/IIM/4.2/specification/IIMV4.2.pdf for mapping if(isset($iptcArray['APP13'])) { $iptcInfo = iptcparse($iptcArray['APP13']); if (is_array($iptcInfo)) { - $temp = @$iptcInfo['2#105'][0]; - if (isset($temp)&&strlen($temp)>0) $return['title'] = $temp; + // Title + if (!empty($iptcInfo['2#105'][0])) $return['title'] = $iptcInfo['2#105'][0]; + else if (!empty($iptcInfo['2#005'][0])) $return['title'] = $iptcInfo['2#005'][0]; + + // Description + if (!empty($iptcInfo['2#120'][0])) $return['description'] = $iptcInfo['2#120'][0]; - $temp = @$iptcInfo['2#120'][0]; - if (isset($temp)&&strlen($temp)>0) $return['description'] = $temp; + // Tags + if (!empty($iptcInfo['2#025'])) $return['tags'] = $iptcInfo['2#025']; - $temp = @$iptcInfo['2#005'][0]; - if (isset($temp)&&strlen($temp)>0&&$return['title']==='') $return['title'] = $temp; + // Position + $fields = array(); + if (!empty($iptcInfo['2#090'])) $fields[] = trim($iptcInfo['2#090']); + if (!empty($iptcInfo['2#092'])) $fields[] = trim($iptcInfo['2#092']); + if (!empty($iptcInfo['2#095'])) $fields[] = trim($iptcInfo['2#095']); + if (!empty($iptcInfo['2#101'])) $fields[] = trim($iptcInfo['2#101']); + + if (!empty($fields)) $return['position'] = implode(', ', $fields); } } - // EXIF Metadata Fallback - $return['orientation'] = ''; - $return['iso'] = ''; - $return['aperture'] = ''; - $return['make'] = ''; - $return['model'] = ''; - $return['shutter'] = ''; - $return['focal'] = ''; - $return['takestamp'] = 0; - // Read EXIF if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0); else $exif = false; @@ -779,24 +792,19 @@ final class Photo { if (isset($exif['Orientation'])) $return['orientation'] = $exif['Orientation']; else if (isset($exif['IFD0']['Orientation'])) $return['orientation'] = $exif['IFD0']['Orientation']; - $temp = @$exif['ISOSpeedRatings']; - if (isset($temp)) $return['iso'] = $temp; + if (!empty($exif['ISOSpeedRatings'])) $return['iso'] = $exif['ISOSpeedRatings']; - $temp = @$exif['COMPUTED']['ApertureFNumber']; - if (isset($temp)) $return['aperture'] = $temp; + if (!empty($exif['COMPUTED']['ApertureFNumber'])) $return['aperture'] = $exif['COMPUTED']['ApertureFNumber']; - $temp = @$exif['Make']; - if (isset($temp)) $return['make'] = trim($temp); + if (!empty($exif['Make'])) $return['make'] = trim($exif['Make']); - $temp = @$exif['Model']; - if (isset($temp)) $return['model'] = trim($temp); + if (!empty($exif['Model'])) $return['model'] = trim($exif['Model']); - $temp = @$exif['ExposureTime']; - if (isset($temp)) $return['shutter'] = $exif['ExposureTime'] . ' s'; + if (!empty($exif['ExposureTime'])) $return['shutter'] = $exif['ExposureTime'] . ' s'; $temp = @$exif['FocalLength']; if (isset($temp)) { - if (strpos($temp, '/')!==FALSE) { + if (strpos($temp, '/')!==false) { $temp = explode('/', $temp, 2); $temp = $temp[0] / $temp[1]; $temp = round($temp, 1); @@ -805,8 +813,14 @@ final class Photo { $return['focal'] = $temp . ' mm'; } - $temp = @$exif['DateTimeOriginal']; - if (isset($temp)) $return['takestamp'] = strtotime($temp); + if (!empty($exif['DateTimeOriginal'])) $return['takestamp'] = strtotime($exif['DateTimeOriginal']); + + // Lens field from Lightroom + if (!empty($exif['UndefinedTag:0xA434'])) $return['lens'] = trim($exif['UndefinedTag:0xA434']); + + + $return['latitude'] = ''; + $return['longitude'] = ''; } diff --git a/php/Modules/Settings.php b/php/Modules/Settings.php index 018dc7c..484412d 100755 --- a/php/Modules/Settings.php +++ b/php/Modules/Settings.php @@ -221,6 +221,13 @@ final class Settings { } + /** + * @return array Returns the settings of Lychee. + */ + public static function hasImagick() { + return (bool)(extension_loaded('imagick')&&self::get()['imagick']==='1'); + } + } ?> \ No newline at end of file From 69fc86447189cc5f168a60e8733b259280134c9b Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 21:09:20 +0200 Subject: [PATCH 02/11] Correction of comment --- php/Modules/Settings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/Modules/Settings.php b/php/Modules/Settings.php index 484412d..681b621 100755 --- a/php/Modules/Settings.php +++ b/php/Modules/Settings.php @@ -222,10 +222,10 @@ final class Settings { } /** - * @return array Returns the settings of Lychee. + * @return array Returns the Imagick setting. */ public static function hasImagick() { - return (bool)(extension_loaded('imagick')&&self::get()['imagick']==='1'); + return (bool)(extension_loaded('imagick') && self::get()['imagick'] === '1'); } } From 9d8f9356f284517d020f133a3c0afbae2047ca40 Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 21:36:58 +0200 Subject: [PATCH 03/11] Import photo tags --- php/Modules/Photo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index b5d1e14..00f64e8 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -239,7 +239,7 @@ final class Photo { } // Save to DB - $values = array(LYCHEE_TABLE_PHOTOS, $id, $info['title'], $photo_name, $info['description'], '', $info['type'], $info['width'], $info['height'], $info['size'], $info['iso'], $info['aperture'], $info['make'], $info['model'], $info['shutter'], $info['focal'], $info['takestamp'], $path_thumb, $albumID, $public, $star, $checksum, $medium); + $values = array(LYCHEE_TABLE_PHOTOS, $id, $info['title'], $photo_name, $info['description'], $info['tags'], $info['type'], $info['width'], $info['height'], $info['size'], $info['iso'], $info['aperture'], $info['make'], $info['model'], $info['shutter'], $info['focal'], $info['takestamp'], $path_thumb, $albumID, $public, $star, $checksum, $medium); $query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star, checksum, medium) VALUES ('?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?')", $values); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); @@ -767,7 +767,7 @@ final class Photo { if (!empty($iptcInfo['2#120'][0])) $return['description'] = $iptcInfo['2#120'][0]; // Tags - if (!empty($iptcInfo['2#025'])) $return['tags'] = $iptcInfo['2#025']; + if (!empty($iptcInfo['2#025'])) $return['tags'] = str_replace('; ', ',', $iptcInfo['2#025']); // Position $fields = array(); From 20e22d277799f0c397de88d90675458e280b4ce3 Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 21:45:51 +0200 Subject: [PATCH 04/11] Correction of photo tag formatting --- php/Modules/Photo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index 00f64e8..bb4f5e0 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -767,7 +767,7 @@ final class Photo { if (!empty($iptcInfo['2#120'][0])) $return['description'] = $iptcInfo['2#120'][0]; // Tags - if (!empty($iptcInfo['2#025'])) $return['tags'] = str_replace('; ', ',', $iptcInfo['2#025']); + if (!empty($iptcInfo['2#025'])) $return['tags'] = implode(',', $iptcInfo['2#025']); // Position $fields = array(); From e2509b16570e56ca4a11fffb024459d739d8aa2a Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 21:59:50 +0200 Subject: [PATCH 05/11] Import GPS coordinates from EXIF --- php/Modules/Photo.php | 7 +++++-- php/helpers/getGPSCoordinate.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 php/helpers/getGPSCoordinate.php diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index bb4f5e0..52dead3 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -819,8 +819,11 @@ final class Photo { if (!empty($exif['UndefinedTag:0xA434'])) $return['lens'] = trim($exif['UndefinedTag:0xA434']); - $return['latitude'] = ''; - $return['longitude'] = ''; + // Deal with GPS coordinates + if (!empty($exif['GPSLatitude']) && !empty($exif['GPSLatitudeRef'])) + $return['latitude'] = getGPSCoordinate($exif['GPSLatitude'], $exif['GPSLatitudeRef']); + if (!empty($exif['GPSLongitude']) && !empty($exif['GPSLongitudeRef'])) + $return['longitude'] = getGPSCoordinate($exif['GPSLongitude'], $exif['GPSLongitudeRef']); } diff --git a/php/helpers/getGPSCoordinate.php b/php/helpers/getGPSCoordinate.php new file mode 100644 index 0000000..391c777 --- /dev/null +++ b/php/helpers/getGPSCoordinate.php @@ -0,0 +1,28 @@ + 0 ? gps2Num($coordinate[0]) : 0; + $minutes = count($coordinate) > 1 ? gps2Num($coordinate[1]) : 0; + $seconds = count($coordinate) > 2 ? gps2Num($coordinate[2]) : 0; + + $flip = ($ref == 'W' || $ref == 'S') ? -1 : 1; + + return $flip * ($degrees + (float)$minutes / 60 + (float)$seconds / 3600); +} + +function formattedToFloatGPS($coordinate) { + $parts = explode('/', $coordinate, 2); + + if (count($parts) <= 0) + return 0; + if (count($parts) == 1) + return $parts[0]; + + return (float)$parts[0] / $parts[1]; +} + +?> \ No newline at end of file From ea7bfa606e56da0f722eebda988fedd31f48d9ea Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 22:11:08 +0200 Subject: [PATCH 06/11] Small corrections in EXIF --- php/Modules/Photo.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index 52dead3..dbe8204 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -783,7 +783,7 @@ final class Photo { } // Read EXIF - if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0); + if ($info['mime']=='image/jpeg') $exif = exif_read_data($url, 'EXIF', 0); else $exif = false; // EXIF Metadata @@ -802,15 +802,14 @@ final class Photo { if (!empty($exif['ExposureTime'])) $return['shutter'] = $exif['ExposureTime'] . ' s'; - $temp = @$exif['FocalLength']; - if (isset($temp)) { - if (strpos($temp, '/')!==false) { - $temp = explode('/', $temp, 2); + if (!empty($exif['FocalLength'])) { + if (strpos($exif['FocalLength'], '/')!==false) { + $temp = explode('/', $exif['FocalLength'], 2); $temp = $temp[0] / $temp[1]; $temp = round($temp, 1); $return['focal'] = $temp . ' mm'; } - $return['focal'] = $temp . ' mm'; + $return['focal'] = $exif['FocalLength'] . ' mm'; } if (!empty($exif['DateTimeOriginal'])) $return['takestamp'] = strtotime($exif['DateTimeOriginal']); From 7841cad8f23a4546eca616aef4b935870c766425 Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 22:23:05 +0200 Subject: [PATCH 07/11] Small corrections IPTC data are in the first element of array. GPS coordinate helper added to required files. --- php/Modules/Photo.php | 8 ++++---- php/index.php | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index dbe8204..a7c5d0f 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -771,10 +771,10 @@ final class Photo { // Position $fields = array(); - if (!empty($iptcInfo['2#090'])) $fields[] = trim($iptcInfo['2#090']); - if (!empty($iptcInfo['2#092'])) $fields[] = trim($iptcInfo['2#092']); - if (!empty($iptcInfo['2#095'])) $fields[] = trim($iptcInfo['2#095']); - if (!empty($iptcInfo['2#101'])) $fields[] = trim($iptcInfo['2#101']); + if (!empty($iptcInfo['2#090'])) $fields[] = trim($iptcInfo['2#090'][0]); + if (!empty($iptcInfo['2#092'])) $fields[] = trim($iptcInfo['2#092'][0]); + if (!empty($iptcInfo['2#095'])) $fields[] = trim($iptcInfo['2#095'][0]); + if (!empty($iptcInfo['2#101'])) $fields[] = trim($iptcInfo['2#101'][0]); if (!empty($fields)) $return['position'] = implode(', ', $fields); diff --git a/php/index.php b/php/index.php index 4bacbd7..174c6ef 100755 --- a/php/index.php +++ b/php/index.php @@ -22,6 +22,7 @@ require(__DIR__ . '/autoload.php'); require(__DIR__ . '/helpers/fastImageCopyResampled.php'); require(__DIR__ . '/helpers/generateID.php'); require(__DIR__ . '/helpers/getExtension.php'); +require(__DIR__ . '/helpers/getGPSCoordinate.php'); require(__DIR__ . '/helpers/getGraphHeader.php'); require(__DIR__ . '/helpers/getHashedString.php'); require(__DIR__ . '/helpers/hasPermissions.php'); From 0e6c9e9810c00d3a7fc2cf012420c38f47b24d9b Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 22:24:12 +0200 Subject: [PATCH 08/11] Correction of function name --- php/helpers/getGPSCoordinate.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/php/helpers/getGPSCoordinate.php b/php/helpers/getGPSCoordinate.php index 391c777..2296725 100644 --- a/php/helpers/getGPSCoordinate.php +++ b/php/helpers/getGPSCoordinate.php @@ -5,9 +5,9 @@ * @return string Normalized coordinate as float number (degrees). */ function getGPSCoordinate($coordinate, $ref) { - $degrees = count($coordinate) > 0 ? gps2Num($coordinate[0]) : 0; - $minutes = count($coordinate) > 1 ? gps2Num($coordinate[1]) : 0; - $seconds = count($coordinate) > 2 ? gps2Num($coordinate[2]) : 0; + $degrees = count($coordinate) > 0 ? formattedToFloatGPS($coordinate[0]) : 0; + $minutes = count($coordinate) > 1 ? formattedToFloatGPS($coordinate[1]) : 0; + $seconds = count($coordinate) > 2 ? formattedToFloatGPS($coordinate[2]) : 0; $flip = ($ref == 'W' || $ref == 'S') ? -1 : 1; From 8b25259ed4051738fc974621b77259bbd6daff3f Mon Sep 17 00:00:00 2001 From: Quentin Ligier Date: Sun, 17 Apr 2016 22:32:57 +0200 Subject: [PATCH 09/11] Correction of Focal data extraction --- php/Modules/Photo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index a7c5d0f..6fedbfd 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -809,7 +809,7 @@ final class Photo { $temp = round($temp, 1); $return['focal'] = $temp . ' mm'; } - $return['focal'] = $exif['FocalLength'] . ' mm'; + else $return['focal'] = $exif['FocalLength'] . ' mm'; } if (!empty($exif['DateTimeOriginal'])) $return['takestamp'] = strtotime($exif['DateTimeOriginal']); From 9e59062384c60e90e13e7b10363f963cf341bec2 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Mon, 18 Apr 2016 09:40:52 +0200 Subject: [PATCH 10/11] Syntax adjustments #518 --- php/Modules/Photo.php | 18 ++++++++++++------ php/helpers/getGPSCoordinate.php | 20 +++++++++++--------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/php/Modules/Photo.php b/php/Modules/Photo.php index e5043e2..dc4a9c7 100755 --- a/php/Modules/Photo.php +++ b/php/Modules/Photo.php @@ -789,40 +789,46 @@ final class Photo { // EXIF Metadata if ($exif!==false) { + // Orientation if (isset($exif['Orientation'])) $return['orientation'] = $exif['Orientation']; else if (isset($exif['IFD0']['Orientation'])) $return['orientation'] = $exif['IFD0']['Orientation']; + // ISO if (!empty($exif['ISOSpeedRatings'])) $return['iso'] = $exif['ISOSpeedRatings']; + // Aperture if (!empty($exif['COMPUTED']['ApertureFNumber'])) $return['aperture'] = $exif['COMPUTED']['ApertureFNumber']; + // Make if (!empty($exif['Make'])) $return['make'] = trim($exif['Make']); + // Model if (!empty($exif['Model'])) $return['model'] = trim($exif['Model']); + // Exposure if (!empty($exif['ExposureTime'])) $return['shutter'] = $exif['ExposureTime'] . ' s'; + // Focal Length if (!empty($exif['FocalLength'])) { if (strpos($exif['FocalLength'], '/')!==false) { $temp = explode('/', $exif['FocalLength'], 2); $temp = $temp[0] / $temp[1]; $temp = round($temp, 1); $return['focal'] = $temp . ' mm'; + } else { + $return['focal'] = $exif['FocalLength'] . ' mm'; } - else $return['focal'] = $exif['FocalLength'] . ' mm'; } + // Takestamp if (!empty($exif['DateTimeOriginal'])) $return['takestamp'] = strtotime($exif['DateTimeOriginal']); // Lens field from Lightroom if (!empty($exif['UndefinedTag:0xA434'])) $return['lens'] = trim($exif['UndefinedTag:0xA434']); - // Deal with GPS coordinates - if (!empty($exif['GPSLatitude']) && !empty($exif['GPSLatitudeRef'])) - $return['latitude'] = getGPSCoordinate($exif['GPSLatitude'], $exif['GPSLatitudeRef']); - if (!empty($exif['GPSLongitude']) && !empty($exif['GPSLongitudeRef'])) - $return['longitude'] = getGPSCoordinate($exif['GPSLongitude'], $exif['GPSLongitudeRef']); + if (!empty($exif['GPSLatitude']) && !empty($exif['GPSLatitudeRef'])) $return['latitude'] = getGPSCoordinate($exif['GPSLatitude'], $exif['GPSLatitudeRef']); + if (!empty($exif['GPSLongitude']) && !empty($exif['GPSLongitudeRef'])) $return['longitude'] = getGPSCoordinate($exif['GPSLongitude'], $exif['GPSLongitudeRef']); } diff --git a/php/helpers/getGPSCoordinate.php b/php/helpers/getGPSCoordinate.php index 2296725..93dab4c 100644 --- a/php/helpers/getGPSCoordinate.php +++ b/php/helpers/getGPSCoordinate.php @@ -5,24 +5,26 @@ * @return string Normalized coordinate as float number (degrees). */ function getGPSCoordinate($coordinate, $ref) { + $degrees = count($coordinate) > 0 ? formattedToFloatGPS($coordinate[0]) : 0; - $minutes = count($coordinate) > 1 ? formattedToFloatGPS($coordinate[1]) : 0; - $seconds = count($coordinate) > 2 ? formattedToFloatGPS($coordinate[2]) : 0; + $minutes = count($coordinate) > 1 ? formattedToFloatGPS($coordinate[1]) : 0; + $seconds = count($coordinate) > 2 ? formattedToFloatGPS($coordinate[2]) : 0; + + $flip = ($ref == 'W' || $ref == 'S') ? -1 : 1; - $flip = ($ref == 'W' || $ref == 'S') ? -1 : 1; + return $flip * ($degrees + (float)$minutes / 60 + (float)$seconds / 3600); - return $flip * ($degrees + (float)$minutes / 60 + (float)$seconds / 3600); } function formattedToFloatGPS($coordinate) { + $parts = explode('/', $coordinate, 2); - if (count($parts) <= 0) - return 0; - if (count($parts) == 1) - return $parts[0]; + if (count($parts) <= 0) return 0; + if (count($parts) == 1) return $parts[0]; + + return (float)$parts[0] / $parts[1]; - return (float)$parts[0] / $parts[1]; } ?> \ No newline at end of file From dff48002de492f2f9c04a052aba77a3d714c46ba Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Mon, 18 Apr 2016 09:58:25 +0200 Subject: [PATCH 11/11] Fixed broken URL in Update.md #516 --- docs/Update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Update.md b/docs/Update.md index ad8529c..0995733 100644 --- a/docs/Update.md +++ b/docs/Update.md @@ -11,7 +11,7 @@ Updating Lychee with `git` is the easiest way: ### Update manually -1. Download the [newest Version](https://github.com/electerious/Lychee/release) +1. Download the [newest Version](https://github.com/electerious/Lychee/releases) 2. Replace all existing files, excluding `uploads/` and `data/` 3. Open Lychee (and enter your database details)