depart | Pour la création des webp et thumbs qui répliquent mon arbo de jpeg principale, j'avais posté ici le script : https://forum.hardware.fr/forum2.ph [...] 0#t6133811
J'ai juste rajouté la création de la bdd :
function storeFolderStructure($folderPath,$sqliteFilePath, $parentID = null)
{
try {
// Création de la base de données SQLite
$db = new PDO('sqlite:'.$sqliteFilePath);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Suppression de la table si elle existe déjà
if($parentID == null) {
$db->exec("DROP TABLE IF EXISTS folders" );
}
// Création de la table pour stocker les dossiers
$db->exec("CREATE TABLE IF NOT EXISTS folders (
id INTEGER PRIMARY KEY,
name TEXT,
folderpath TEXT,
parent_id INTEGER,
FOREIGN KEY (parent_id) REFERENCES folders(id) ON DELETE CASCADE
)" );
// Récupérer le nom du dossier
$folderName = basename($folderPath);
// Insérer le dossier dans la base de données
$stmt = $db->prepare("INSERT INTO folders (name, folderpath, parent_id) VALUES (?, ?, ?)" );
$stmt->execute([$folderName, $folderPath, $parentID]);
// Récupération de l'ID du dossier inséré
$folderID = $db->lastInsertId();
// Récupération des sous-dossiers
$subfolders = glob($folderPath . '/*', GLOB_ONLYDIR);
// Parcours récursif des sous-dossiers
foreach ($subfolders as $subfolder) {
storeFolderStructure($subfolder, $sqliteFilePath, $folderID);
}
} catch (PDOException $e) {
echo "Erreur : " . $e->getMessage();
}
}
storeFolderStructure($webpFolderPath,"/home/monsite/www/archives_photos/photos.sqlite" );
|
Pour le code de la galerie à proprement parler, c'est assez dégueu, ça a été codé à l'arrache, tout dans le même fichier (sauf l'import de photoswype qu'on trouve facilement et une police icomoon avec quelques pictos pour les flèches et ce genre de choses) et j'ai arrêté quand ça m'a suffi. Donc le code est franchement basique et l'output un peu dégueu mais ça marche et ça me convient pour l'instant. Si le mélange camelCase et "_" vous fait vomir, fermez les yeux
La bascule webp/jpg par exemple c'est franchement moche car pas prévu au début, mais l'idée c'était par défaut d'afficher des webp franchement très compressés (suffit 90% du temps) et permet de ne pas cramer de forfait (j'ai pas des masses de data) quand on cherche quelque chose, mais de pouvoir basculer sur les jpeg "moyenne def" que je stocke aussi sur le même serveur si besoin.
Enfin bref, en gros ça donne ça (home/monsite/www/archives_photos/index.php) :
<?php
// Nom de la base de données SQLite
$databaseName = 'photos.sqlite';
$root_webp = "/mnt/pve_storage_p/photos/webp/";
$root_jpg = "/mnt/pve_storage_p/photos/jpegs/"; // chemin de la racine des fichiers grand format (sert pour nettoyer l'arbo pour l'affichage)
// On peut aussi basculer sur les jpegs si on veut des images plus grandes. On garde l'info en session (par défaut webp)
/**
* Sessions
*/
if(session_status() != 2 ) {
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
ini_set("session.cookie_secure", 1);
ini_set("session.cookie_samesite", "Strict" ) ;
ini_set("session.gc_maxlifetime", 4*3600) ; // session de 4 heures pour limiter les expirations
session_start();
}
if (isset($_GET['switch']) && in_array($_GET['switch'], ['jpg', 'webp'])) {
$_SESSION['type'] = $_GET['switch'];
}
if (isset( $_SESSION['type']) && $_SESSION['type'] == 'jpg') {
$dossier_photos = "photos_jpg";
$type = "jpg" ;
$txt_bd = "BD";
$txt_hd = "[HD]";
} else {
$dossier_photos = "photos";
$type = "webp" ;
$txt_bd = "[BD]";
$txt_hd = "HD";
}
// Fonction pour récupérer les sous-dossiers d'un dossier parent, les webp et ce genre de chose
function getFolderDetails($parentID = null)
{
global $databaseName, $type;
try {
// Connexion à la base de données SQLite
$db = new PDO('sqlite:' . $databaseName);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Récupérer les sous-dossiers du dossier parent
if ($parentID === null) {
$query = "SELECT * FROM folders WHERE parent_id IS NULL";
$params = [];
} else {
$query = "SELECT * FROM folders WHERE parent_id = ?";
$params = [$parentID];
}
// Exécution de la requête
$stmt = $db->prepare($query);
$stmt->execute($params);
$folders = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Récupérer les fichiers .webp dans le dossier parent
$stmt = $db->prepare("SELECT folderpath FROM folders WHERE id = ?" );
$stmt->execute([$parentID]);
$folderPath = $stmt->fetchColumn();
if ($folderPath !== false) {
if ($type =='jpg') {
// le dossier en bdd contient le chemin absolu des webp, si on veut utiliser les jpg il faut adapter
$folderPath = str_replace("webp", "jpegs", $folderPath);
$imageFiles = glob($folderPath . '/*.jpg');
} else {
$imageFiles = glob($folderPath . '/*.webp');
}
} else {
$imageFiles = array() ;
}
// Tableau pour stocker les informations sur les images
$imageInfoArray = [];
// Parcourir chaque fichier WebP
foreach ($imageFiles as $imageFile) {
// Récupérer le nom de fichier original
$originalName = $imageFile;
// pour le thumb, même si le fichier large est jpg, le thumb est webp
if ($type =='jpg') {
$thumbName = str_replace(["jpegs", ".jpg"], ["webp", ".webp"], $imageFile);
} else {
$thumbName = $imageFile;
}
// Récupérer les dimensions de l'image
$dimensions = getimagesize($imageFile);
$width = $dimensions[0];
$height = $dimensions[1];
// Ajouter les informations au tableau
$imageInfoArray[] = [
'nomOriginal' => $originalName,
'nomThumb' => $thumbName,
'largeur' => $width,
'hauteur' => $height
];
}
// Récupérer de l'id du parent du dossier courant
$stmt = $db->prepare("SELECT parent_id FROM folders WHERE id = ?" );
$stmt->execute([$parentID]);
$parentID = $stmt->fetchColumn();
return [
'folders' => $folders,
'imageFiles' => $imageInfoArray,
'parentID' => $parentID,
'currentPath' => $folderPath
];
} catch (PDOException $e) {
echo "Erreur : " . $e->getMessage();
return [];
}
}
function stripRoot($filepath,$type = "webp" ) {
// Chemin racine à supprimer
global $root_webp, $root_jpg ;
$type == "jpg" ? $root = $root_jpg : $root = $root_webp ;
// Vérifie si le chemin commence par la racine à supprimer
if (strpos($filepath, $root) === 0) {
// Supprime la racine du chemin
$strippedPath = substr($filepath, strlen($root));
return $strippedPath;
} else {
// Le chemin ne commence pas par la racine, le retourne tel quel
return $filepath;
}
}
// Fonction pour récupérer les sous-dossiers correspondant à un terme de recherche
function searchFolders($searchTerm)
{
global $databaseName;
try {
// Connexion à la base de données SQLite
$db = new PDO('sqlite:' . $databaseName);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Normaliser la chaîne de recherche pour prendre en compte les accents
$searchTermNormalized = iconv('UTF-8', 'ASCII//TRANSLIT', $searchTerm);
// Requête pour rechercher les dossiers correspondant au terme de recherche
$stmt = $db->prepare("SELECT * FROM folders WHERE LOWER(name) LIKE LOWER(?) OR LOWER(name) LIKE LOWER(?)" );
$searchTermLower = strtolower($searchTerm);
$searchTermNormalizedLower = strtolower($searchTermNormalized);
$stmt->execute(["%$searchTermLower%", "%$searchTermNormalizedLower%"]);
$folders = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $folders;
} catch (PDOException $e) {
echo "Erreur lors de la recherche des dossiers : " . $e->getMessage();
return [];
}
}
// Traitement de la recherche si le formulaire est soumis
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['search'])) {
$searchTerm = trim($_POST['search']);
$searchResult = searchFolders($searchTerm);
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Archives photos</title>
<link rel="stylesheet" type="text/css" media="all" href="font-ico/style.css?v=1" />
<!-- Icône pour Android -->
<link rel="icon" type="image/png" sizes="192x192" href="img/bookpile192.png">
<!-- Icône pour iPhone -->
<link rel="apple-touch-icon" sizes="180x180" href="img/bookpile180.png">
<!-- Favicon en PNG -->
<link rel="icon" type="image/png" sizes="32x32" href="img/bookpile32.png">
<style>
html, body {
margin: 0;
padding: 0;
}
body {
background: #000;
color: #fff;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 20px;
}
#form_recherche {
padding: 10px;
}
.form-container {
display: flex;
width: 100%;
}
input[type="submit"] {
background-color: #4CAF50; /* Couleur de fond */
color: white; /* Couleur du texte */
padding: 9px 24px; /* Espacement intérieur */
border: none; /* Suppression de la bordure */
border-radius: 4px; /* Arrondi des coins */
cursor: pointer; /* Curseur pointer */
transition: background-color 0.3s; /* Transition de couleur */
font-size: 16px; /* Taille de la police */
}
/* Effet au survol */
input[type="submit"]:hover {
background-color: #45a049; /* Changement de couleur de fond au survol */
}
/* Effet au clic */
input[type="submit"]:active {
background-color: #4CAF50; /* Retour à la couleur de fond initiale */
}
/* Styles de base pour l'input */
.material-input {
background-color: #fff; /* Couleur de fond */
color: #212121; /* Couleur du texte */
padding: 8px 12px; /* Espacement intérieur */
margin: 0 4px 0 0 ;
border: 1px solid #ccc; /* Bordure */
border-radius: 4px; /* Arrondi des coins */
font-size: 16px; /* Taille de la police */
width: 200px; /* Largeur de l'input */
transition: border-color 0.3s; /* Transition de la couleur de la bordure */
}
.material-input.flex-grow {
flex-grow: 1;
}
/* Effet au survol */
.material-input:hover {
border-color: #9e9e9e; /* Changement de couleur de bordure au survol */
}
/* Effet au focus */
.material-input:focus {
border-color: #2196F3; /* Couleur de la bordure lorsqu'il est en focus */
outline: none; /* Suppression de l'outline par défaut */
}
ul {
list-style-type: none;
padding-left: 20px;
}
ul li {
margin-bottom: 16px;
}
h1 {
margin: 0;
}
#dossiers {
padding: 4px;
}
#dossiers a, #form_recherche a {
color: #fff;
text-decoration: none;
}
#dossiers a:hover, #form_recherche a:hover {
text-decoration: underline;
}
.boutons_retour {
font-size: 24px;
}
.boutons_retour a {
padding: 2px 10px;
}
.flex-container {
display: flex;
flex-wrap: wrap;
margin: 2px;
}
.square {
max-width: calc(33.33% - 4px); /* Ajustez selon vos besoins, -20px pour prendre en compte les marges */
aspect-ratio: 1; /* Définit le rapport d'aspect à 1:1 */
margin: 2px; /* Marge entre les éléments */
position: relative;
overflow: hidden;
}
.square img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.boutons_retour_bas {
align-items: center; /* Centrer verticalement le contenu */
justify-content: center; /* Centrer horizontalement le contenu */
width: 80px;
position: fixed;
bottom:0;
right:0 ;
}
.boutons_retour_bas p {
text-align: right; padding: 0 20px 0 0 ;
}
.boutons_retour_bas p a {
color: #fff;
font-size: 48px;
font-weight: bold;
text-decoration: none;
display: block;
background-color: #4CAF50;
border-radius: 4px; /* Arrondi des coins */
padding: 2px 8px 0 0 ;
}
/* Effet au survol */
.boutons_retour_bas p a:hover {
background-color: #45a049; /* Changement de couleur de fond au survol */
}
.resolutions {
padding: 20px;
text-align: center;
}
.resolutions a {
color: white;
text-decoration: none;
}
</style>
<link rel="stylesheet" href="PhotoSwipe-5.4.2/dist/photoswipe.css">
<script type="module">
import PhotoSwipeLightbox from './PhotoSwipe-5.4.2/dist/photoswipe-lightbox.esm.js';
const lightbox = new PhotoSwipeLightbox({
gallery: '#my-gallery',
children: 'a',
showHideAnimationType: 'zoom',
showAnimationDuration: 100,
hideAnimationDuration: 100,
imageClickAction: 'close',
tapAction: 'close',
loop: false,
pswpModule: () => import('./PhotoSwipe-5.4.2/dist/photoswipe.esm.js')
});
lightbox.init();
</script>
</head>
<body>
<!-- Formulaire de recherche -->
<div id="form_recherche">
<form method="post" class="form-container" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="text" class="material-input flex-grow" name="search" id="search" placeholder="Recherche">
<input type="submit" value="OK">
</form>
<?php
// Affichage des résultats de la recherche
if (isset($searchResult) && !empty($searchResult)) {
echo "<p>Résultats de la recherche :</p>";
echo "<ul>";
foreach ($searchResult as $folder) {
echo "<li><a href='?folder=" . $folder['id'] . "'>" . $folder['name'] . "</a></li>";
}
echo "</ul>";
} elseif (isset($searchResult) && empty($searchResult)) {
echo "<p>Aucun résultat trouvé pour : $searchTerm</p>";
}
?>
</div>
<div id="dossiers">
<?php
// Traitement du dossier demandé (s'il est spécifié dans l'URL)
$folderID = isset($_GET['folder']) ? $_GET['folder'] : null;
// Affichage de l'arborescence à partir du dossier spécifié
if ($folderID == null) {
$folderID = 1 ;
}
$scanReturn = getFolderDetails($folderID);
$subfolders = $scanReturn['folders'] ;
$imageFilesArray = $scanReturn['imageFiles'] ;
$parentID = $scanReturn['parentID'] ;
$currentPath = $scanReturn['currentPath'] ;
if (isset($parentID) && $parentID != ''){
echo "<h1><span class=\"boutons_retour\" ><a href='?folder=" . $parentID . "' title=\"Remonter au dossier parent\"><span class=\"icon-arrow-up2\"></span></a></span>".stripRoot($currentPath,$type)."</h1>";
} else {
echo "<h1>Dossiers</h1>";
}
if ($folderID != 1) {
echo "<p class=\"boutons_retour\" ><a href=\"javascript:history.go(-1)\" title=\"Revenir en arrière\"><span class=\"icon-arrow-left2\"></span></a></p>";
}
if (!empty($subfolders)) {
//echo "<p>Contenu du dossier :</p>";
echo "<ul>";
foreach ($subfolders as $folder) {
echo "<li><a href='?folder=" . $folder['id'] . "'>" . $folder['name'] . "</a></li>";
}
echo "</ul>";
} else {
//echo "<p>Ce dossier ne contient pas de sous-dossiers.</p>";
}
?>
</div>
<?php
// Affichage des fichiers .webp
// /photos et /photos_thumbs sont 2 alias définis dans le fichier de config Apache de l'hôte if (!empty($imageFilesArray)) {
//echo "<p>Fichiers .webp de :".$currentPath."</p>";
echo "<div class='flex-container' id='my-gallery'>";
foreach ($imageFilesArray as $file) {
echo "<div class=\"square\">";
echo "<a href=\"".$dossier_photos."/".stripRoot($file['nomOriginal'],$type)."\" data-pswp-width=\"".$file['largeur']."\" data-pswp-height=\"".$file['hauteur']."\" data-cropped=\"true\">";
echo "<img class=\"thumb\" src=\"photos_thumbs/".stripRoot($file['nomThumb'])."\" alt=\"\">";
echo "</a>";
echo "</div>";
}
echo "</div>";
if ($folderID != 1) {
echo "<div class='flex-container'>";
echo "<div class=\"boutons_retour_bas\">";
echo "<p>";
echo "<a href=\"javascript:history.go(-1)\" title=\"Revenir en arrière\"><span class=\"icon-arrow-left2\"></span></a> ";
//echo " <a href='?folder=" . $parentID . "' title=\"Remonter au dossier parent\"><span class=\"icon-arrow-up2\"></span></a>
echo "</p>";
echo "</div>";
echo "</div>";
}
} else {
// echo "<p>Ce dossier ne contient pas de fichiers .webp.</p>";
}
// Récupération des paramètres de l'URL courante
$currentUrl = strtok($_SERVER["REQUEST_URI"],'?');
$queryParams = $_SERVER['QUERY_STRING'];
// Supprimer le paramètre "switch" s'il est déjà présent
if(isset($_GET['switch'])) {
parse_str($queryParams, $params);
unset($params['switch']);
$queryParams = http_build_query($params);
}
// Construction des liens en conservant les paramètres d'URL existants
$linkWebp = $currentUrl . '?' . ($queryParams ? $queryParams . '&' : '') . 'switch=webp';
$linkJpg = $currentUrl . '?' . ($queryParams ? $queryParams . '&' : '') . 'switch=jpg';
?>
<br>
<div class="resolutions">
<?php echo '<a href="' . htmlspecialchars($linkWebp) . '">' . $txt_bd . '</a> / <a href="' . htmlspecialchars($linkJpg) . '">' . $txt_hd . '</a><br>'; ?>
</div>
</body>
</html>
|
Résultat :
Quelques pistes de réflexion :
- Désormais Lightroom permet l'export en avif (pour ceux qui ne connaissent pas, c'est un "équivalent" du webp, meilleure compression que le jpeg, et désormais pris en charge dans quasi tous les navigateurs), donc on peut si on préfère répliquer directement sa structure lightroom en export avif (et les thumbs aussi via une autre publication) via le plugin de friedl folder publisher. On peut sans souci réduire la qualité à 45 (sur 100) pour les images et encore moins pour les thumbs. En faisant ça vous n'avez plus besoin de la tâche planifiée qui mirrore le dossier de jpegs.
- J'ai créé des alias pour "monter" des dossiers qui sont hors racine de mon serveur web, mais si vous voulez/pouvez, vous pouvez directement publier vos photos dans un dossier directement accessible par le serveur web, genre /home/monsite/www/archives_photos/photos_avif par exemple. Ca simplifiera encore
- Faites en sorte que votre serveur web soit http2, ça accélère les requêtes lorsqu'on affiche des centaines de thumbs d'un coup. Message édité par depart le 07-05-2025 à 12:20:23
|