First dev version v.0.1
This commit is contained in:
49
src/includes/admin_actions.php
Normal file
49
src/includes/admin_actions.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// includes/admin_actions.php
|
||||
require_once __DIR__ . '/config.php';
|
||||
session_start();
|
||||
|
||||
if (empty($_SESSION['is_admin'])) {
|
||||
http_response_code(403);
|
||||
exit('Access denied');
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
switch ($action) {
|
||||
case 'rebuild_gallery':
|
||||
require_once __DIR__ . '/api.php';
|
||||
echo "✅ Gallery cache rebuilt successfully.";
|
||||
break;
|
||||
|
||||
case 'rebuild_exif':
|
||||
require_once __DIR__ . '/exif_helper.php';
|
||||
$dir = $CONFIG['gallery_dir'];
|
||||
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
|
||||
$count = 0;
|
||||
foreach ($rii as $file) {
|
||||
if ($file->isFile() && preg_match('/\.jpe?g$/i', $file->getFilename())) {
|
||||
get_exif_cached($file->getPathname(), $CONFIG['exif_dir'], 0);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
echo "✅ Rebuilt EXIF cache for {$count} images.";
|
||||
break;
|
||||
|
||||
case 'clean_thumbs':
|
||||
$thumb_dir = $CONFIG['thumb_dir'];
|
||||
$removed = 0;
|
||||
foreach (glob($thumb_dir . '/*.jpg') as $t) {
|
||||
unlink($t);
|
||||
$removed++;
|
||||
}
|
||||
echo "🧹 Removed {$removed} thumbnails.";
|
||||
break;
|
||||
|
||||
case 'restart_php':
|
||||
shell_exec('pgrep php-fpm | xargs kill -USR2 2>/dev/null');
|
||||
echo "🔄 PHP-FPM reload signal sent.";
|
||||
break;
|
||||
|
||||
default:
|
||||
echo "Unknown action.";
|
||||
}
|
||||
32
src/includes/admin_auth.php
Normal file
32
src/includes/admin_auth.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// includes/admin_auth.php
|
||||
session_start();
|
||||
require_once __DIR__ . '/config.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'check':
|
||||
echo json_encode(['logged_in' => !empty($_SESSION['is_admin'])]);
|
||||
break;
|
||||
|
||||
case 'login':
|
||||
if (password_verify($password, $CONFIG['upload_password'])) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'logout':
|
||||
session_destroy();
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['error' => 'Invalid action']);
|
||||
}
|
||||
|
||||
84
src/includes/api.php
Normal file
84
src/includes/api.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// includes/api.php – gallery JSON API with EXIF summary + permanent cache
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/exif_helper.php';
|
||||
require_once __DIR__ . '/thumbnail_helper.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Pagination setup
|
||||
$page = max(1, intval($_GET['page'] ?? 1));
|
||||
$per_page = $CONFIG['max_per_page'] ?? 100;
|
||||
|
||||
// Cache file setup
|
||||
$cache_file = $CONFIG['cache_file'];
|
||||
|
||||
// Serve cached gallery JSON if fresh
|
||||
if (file_exists($cache_file)) {
|
||||
$data = json_decode(file_get_contents($cache_file), true);
|
||||
} else {
|
||||
$data = buildGalleryCache($CONFIG);
|
||||
if (!is_dir(dirname($cache_file))) @mkdir(dirname($cache_file), 0775, true);
|
||||
file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Paginate
|
||||
$total = count($data['images']);
|
||||
$total_blocks = ceil($total / $per_page);
|
||||
$slice = array_slice($data['images'], ($page - 1) * $per_page, $per_page);
|
||||
|
||||
// Output JSON
|
||||
echo json_encode([
|
||||
'total_blocks' => $total_blocks,
|
||||
'blocks_returned' => count($slice),
|
||||
'blocks' => $slice
|
||||
], JSON_PRETTY_PRINT);
|
||||
|
||||
|
||||
// --- FUNCTIONS ---
|
||||
|
||||
function buildGalleryCache($CONFIG) {
|
||||
$dir = $CONFIG['gallery_dir'];
|
||||
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
|
||||
$images = [];
|
||||
|
||||
foreach ($rii as $file) {
|
||||
if ($file->isFile() && preg_match('/\.jpe?g$/i', $file->getFilename())) {
|
||||
$path = $file->getPathname();
|
||||
$hash = md5($path);
|
||||
$rel = str_replace($CONFIG['base_dir'], '', $path);
|
||||
$thumb = str_replace($CONFIG['base_dir'], '', $CONFIG['thumb_dir']) ."/" . $hash . '.' . strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
// Get EXIF (cached full dump)
|
||||
$info = [];
|
||||
$exif = [];
|
||||
$summary = [];
|
||||
$info = get_exif_cached($path, $CONFIG['exif_dir'], $hash);
|
||||
$exif_file = $info['file'];
|
||||
$exif = $info['info'];
|
||||
unset($info);
|
||||
$summary = extractExifSummary($exif);
|
||||
// Generate the thumbnails
|
||||
if (!file_exists($thumb)) {
|
||||
generate_thumbnail_imagick($path,$hash,$CONFIG['thumb_dir'],$CONFIG['thumb_max_size'],$CONFIG['thumb_quality']);
|
||||
}
|
||||
|
||||
$images[] = [
|
||||
'filename' => basename($path),
|
||||
'publicPath' => $rel,
|
||||
'thumbPublic' => $thumb,
|
||||
'date' => date('Y-m-d', filemtime($path)),
|
||||
'MD5' => $hash,
|
||||
'exif_file' => $exif_file,
|
||||
'exif' => $summary
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'generated' => date('c'),
|
||||
'count' => count($images),
|
||||
'images' => $images
|
||||
];
|
||||
}
|
||||
|
||||
39
src/includes/config.php
Normal file
39
src/includes/config.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// includes/config.php
|
||||
// Global configuration for the site. Edit values as needed.
|
||||
$ROOT_DIR='/home/reclusejay/repos/camera-gallery/src';
|
||||
$CONFIG = [
|
||||
// === General Site Info ===
|
||||
'site_name' => 'My Photo Gallery',
|
||||
'nav_order' => ['home', 'gallery', 'upload'],
|
||||
'nav_admin' => ['shell', 'gethash'],
|
||||
'nav_hidden' => ['admin','infophp'],
|
||||
'default_theme' => 'dark', // 'dark' or 'light'
|
||||
'timezone' => 'UTC',
|
||||
|
||||
// === Paths ===
|
||||
'base_dir' => $ROOT_DIR,
|
||||
'gallery_dir' => $ROOT_DIR.'/img/sorted/jpg',
|
||||
'thumb_dir' => $ROOT_DIR.'/cache/thumbs',
|
||||
'cache_dir' => $ROOT_DIR.'/cache',
|
||||
'log_dir' => $ROOT_DIR.'/logs',
|
||||
'upload_dir' => $ROOT_DIR.'/img/uploads',
|
||||
'index_file' => $ROOT_DIR.'/img/.index',
|
||||
|
||||
// === Gallery & Caching ===
|
||||
'max_per_page' => 100,
|
||||
'thumb_max_size' => 300,
|
||||
'thumb_quality' => 80,
|
||||
'cache_file' => $ROOT_DIR.'/cache/gallery.json',
|
||||
'remove_nodes' => ['MAKERNOTE', 'INTEROP', 'THUMBNAIL'],
|
||||
'remove_tags' => ['SectionsFound','UserComment','UserCommentEncoding','Thumbnail.FileType','Thumbnail.MimeType','ComponentsConfiguration','FileSource','SceneType'],
|
||||
'exif_dir' => $ROOT_DIR.'/cache/exif',
|
||||
|
||||
// === Uploads ===
|
||||
'upload_password' => '$2y$12$Fb7u3H.428PoPCy/pxhqpu.poqjmDbiyzJtRJs/CEcCEPPMOYBLCm', // bcrypt hash
|
||||
'max_parallel_uploads' => 2, // max simultaneous uploads
|
||||
];
|
||||
|
||||
// Apply timezone globally
|
||||
date_default_timezone_set($CONFIG['timezone']);
|
||||
|
||||
123
src/includes/exif_helper.php
Normal file
123
src/includes/exif_helper.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// includes/exif_helper.php
|
||||
// Full EXIF extractor and permanent cache
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
function get_exif_cached($path, $exif_dir, $hash) {
|
||||
global $CONFIG;
|
||||
if (!file_exists($path)) return [];
|
||||
|
||||
$json_path = rtrim($exif_dir, '/') . '/' . $hash . '.json';
|
||||
|
||||
// Return cached EXIF if it exists (and expiry = 0 means never refresh)
|
||||
if (file_exists($json_path)) {
|
||||
$exif = json_decode(file_get_contents($json_path), true);
|
||||
if (is_array($exif)) return [ 'file' => $json_path, 'info' => $exif ];
|
||||
}
|
||||
|
||||
// Extract fresh EXIF
|
||||
$exif = @exif_read_data($path, null, true);
|
||||
|
||||
//clean exif data remove unwanted tags
|
||||
foreach ($CONFIG['remove_nodes'] as $node) {
|
||||
unset($exif[$node]);
|
||||
}
|
||||
|
||||
if (!$exif || !is_array($exif)) return [];
|
||||
// Clean up binary / overly large data
|
||||
foreach ($exif as $section => &$entries) {
|
||||
foreach ($entries as $key => $val) {
|
||||
if (is_string($val) && (strlen($val) > 256 || !mb_check_encoding($val, 'UTF-8'))) { unset($entries[$key]); }
|
||||
if (preg_match('/^UndefinedTag:0x[0-9A-Fa-f]+$/', $key)) { unset($entries[$key]); }
|
||||
if (in_array($key, $CONFIG['remove_tags'])) { unset($entries[$key]); }
|
||||
}
|
||||
}
|
||||
// Save the entire structure as JSON
|
||||
if (!is_dir($exif_dir)) @mkdir($exif_dir, 0775, true);
|
||||
file_put_contents($json_path, json_encode($exif, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return [ 'file' => $json_path, 'info' => $exif ];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
function extractExifSummary($exif) {
|
||||
if (!is_array($exif)) return [];
|
||||
|
||||
// Safely extract key EXIF tags if available
|
||||
return array_filter([
|
||||
'Camera' => $exif['IFD0']['MAKE'].'-'.$exif['IFD0']['Model'] ?? null,
|
||||
'Lens' => $exif['EXIF']['LensModel'] ?? null,
|
||||
'Aperture' => isset($exif['EXIF']['FNumber']) ? 'f/' . round(exifFractionToFloat($exif['EXIF']['FNumber']), 1) : null,
|
||||
'Shutter' => $exif['EXIF']['ExposureTime'] ?? null,
|
||||
'ISO' => $exif['EXIF']['ISOSpeedRatings'] ?? null,
|
||||
'WhiteBalance' => ($exif['EXIF']['WhiteBalance'] ?? 0) ? 'Manual' : 'Auto',
|
||||
'DateTaken' => $exif['EXIF']['DateTimeOriginal'] ?? null
|
||||
]);
|
||||
}*/
|
||||
|
||||
function extractExifSummary(array $exif): array {
|
||||
// Exif mapped values
|
||||
$programMap = [
|
||||
0 => 'Not defined', 1 => 'Manual', 2 => 'Normal program',
|
||||
3 => 'Aperture priority', 4 => 'Shutter priority', 5 => 'Creative program',
|
||||
6 => 'Action program', 7 => 'Portrait', 8 => 'Landscape',
|
||||
];
|
||||
$colorSpaceMap = [
|
||||
1 => 'sRGB', 65535 => 'AdobeRGB'
|
||||
];
|
||||
$meteringModeMap = [
|
||||
0 => 'Unknown', 1 => 'Average', 2 => 'Center-weighted', 3 => 'Spot', 4 => 'Multi-spot',
|
||||
5 => 'Pattern', 6 => 'Partial', 255 => 'Other'
|
||||
];
|
||||
$orientationMap = [
|
||||
1 => 'Top-left', 2 => 'Top-right', 3 => 'Bottom-right', 4 => 'Bottom-left',
|
||||
5 => 'Left-top', 6 => 'Right-top', 7 => 'Right-bottom', 8 => 'Left-bottom'
|
||||
];
|
||||
$sceneCaptureMap = [0 => 'Standard', 1 => 'Landscape', 2 => 'Portrait', 3 => 'Night scene'];
|
||||
|
||||
// Helper to convert EXIF fraction to float
|
||||
$fractionToFloat = function($val) {
|
||||
if (is_string($val) && strpos($val, '/') !== false) {
|
||||
[$num, $den] = explode('/', $val);
|
||||
return (float)$num / (float)$den;
|
||||
}
|
||||
return (float)$val;
|
||||
};
|
||||
|
||||
// Helper to safely get nested array values
|
||||
$get = function(array $arr, string ...$keys) {
|
||||
foreach ($keys as $key) {
|
||||
if (isset($arr[$key])) {
|
||||
$arr = $arr[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
};
|
||||
|
||||
return array_filter([
|
||||
'Camera' => $get($exif, 'IFD0','Make').'-'.$get($exif, 'IFD0', 'Model'),
|
||||
'Lens' => $get($exif, 'EXIF', 'LensModel'),
|
||||
'ExposureProgram' => $programMap[$get($exif, 'EXIF', 'ExposureProgram')] ?? 0,
|
||||
'Aperture' => ($f = $get($exif, 'EXIF', 'FNumber')) ? 'f/' . round($fractionToFloat($f), 1) : null,
|
||||
'Shutter' => $get($exif, 'EXIF', 'ExposureTime'),
|
||||
'ISO' => $get($exif, 'EXIF', 'ISOSpeedRatings'),
|
||||
'FocalLength' => ($fl = $get($exif, 'EXIF', 'FocalLength')) ? round($fractionToFloat($fl), 1) . 'mm' : null,
|
||||
'ExposureBiasValue' => ($ev = $get($exif, 'EXIF', 'ExposureBiasValue')) ? round($fractionToFloat($ev), 1) . ' EV' : null,
|
||||
'WhiteBalance' => ($wb = $get($exif, 'EXIF', 'WhiteBalance') ?? 0) ? 'Manual' : 'Auto',
|
||||
'DateTaken' => $get($exif, 'EXIF', 'DateTimeOriginal'),
|
||||
'Orientation' => $orientationMap[$get($exif, 'IFD0', 'Orientation') ?? 1],
|
||||
'ImageWidth' => $get($exif, 'EXIF', 'PixelXDimension') ?? $get($exif, 'IFD0', 'ImageWidth'),
|
||||
'ImageHeight' => $get($exif, 'EXIF', 'PixelYDimension') ?? $get($exif, 'IFD0', 'ImageLength'),
|
||||
'ColorSpace' => $colorSpaceMap[$get($exif, 'EXIF', 'ColorSpace') ?? 65535],
|
||||
'MeteringMode' => $meteringModeMap[$get($exif, 'EXIF', 'MeteringMode') ?? 0],
|
||||
'SceneCaptureType' => $sceneCaptureMap[$get($exif, 'EXIF', 'SceneCaptureType') ?? 0],
|
||||
'SubjectDistance' => $get($exif, 'EXIF', 'SubjectDistance'),
|
||||
'Contrast' => $get($exif, 'EXIF', 'Contrast'),
|
||||
'Saturation' => $get($exif, 'EXIF', 'Saturation'),
|
||||
'Sharpness' => $get($exif, 'EXIF', 'Sharpness'),
|
||||
]);
|
||||
}
|
||||
|
||||
74
src/includes/stats.php
Normal file
74
src/includes/stats.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// includes/stats.php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// --- CPU usage ---
|
||||
$load = sys_getloadavg()[0];
|
||||
$cpu_usage = round(($load / shell_exec('nproc')) * 100, 1);
|
||||
|
||||
// --- Memory usage ---
|
||||
$meminfo = @file('/proc/meminfo', FILE_IGNORE_NEW_LINES);
|
||||
$mem = [];
|
||||
foreach ($meminfo as $line) {
|
||||
[$key, $val] = array_map('trim', explode(':', $line));
|
||||
$mem[$key] = (int) filter_var($val, FILTER_SANITIZE_NUMBER_INT);
|
||||
}
|
||||
$ram_total = $mem['MemTotal'] ?? 0;
|
||||
$ram_free = ($mem['MemAvailable'] ?? 0);
|
||||
$ram_used = $ram_total - $ram_free;
|
||||
$ram_usage = $ram_total ? round(($ram_used / $ram_total) * 100, 1) : 0;
|
||||
|
||||
$swap_total = $mem['SwapTotal'] ?? 0;
|
||||
$swap_free = $mem['SwapFree'] ?? 0;
|
||||
$swap_used = $swap_total - $swap_free;
|
||||
$swap_usage = $swap_total ? round(($swap_used / $swap_total) * 100, 1) : 0;
|
||||
|
||||
// Optional: check ZRAM usage
|
||||
$zram_used = 0;
|
||||
$zram_total = 0;
|
||||
if (is_dir('/sys/block')) {
|
||||
foreach (glob('/sys/block/zram*/mm_stat') as $zram) {
|
||||
$stats = explode(' ', file_get_contents($zram));
|
||||
$zram_used += (int)$stats[1];
|
||||
$zram_total += (int)$stats[2];
|
||||
}
|
||||
}
|
||||
$zram_usage = $zram_total ? round(($zram_used / $zram_total) * 100, 1) : 0;
|
||||
|
||||
// --- Disk usage ---
|
||||
$disk_total = disk_total_space('/');
|
||||
$disk_free = disk_free_space('/');
|
||||
$disk_used = $disk_total - $disk_free;
|
||||
$disk_usage = round(($disk_used / $disk_total) * 100, 1);
|
||||
$disk0_total = disk_total_space('/home');
|
||||
$disk0_free = disk_free_space('/home');
|
||||
$disk0_used = $disk0_total - $disk0_free;
|
||||
$disk0_usage = round(($disk0_used / $disk0_total) * 100, 1);
|
||||
|
||||
// --- Gallery image count ---
|
||||
// --- Image count from cache/gallery.json ---
|
||||
$jsonFile = __DIR__ . '/../cache/gallery.json';
|
||||
if (file_exists($jsonFile)) {
|
||||
$gallery = json_decode(file_get_contents($jsonFile), true);
|
||||
$count = $gallery['count'] ?? 0;
|
||||
} else {
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
|
||||
// --- Response ---
|
||||
echo json_encode([
|
||||
'cpu' => $cpu_usage,
|
||||
'ram' => $ram_usage,
|
||||
'ram_total' => $ram_total * 1024, // convert KB → bytes
|
||||
'ram_used' => $ram_used * 1024,
|
||||
'zram' => $zram_usage,
|
||||
'swap' => $swap_usage,
|
||||
'disk' => $disk_usage,
|
||||
'disk_total' => $disk_total,
|
||||
'disk_used' => $disk_used,
|
||||
'disk0' => $disk0_usage,
|
||||
'disk0_total' => $disk0_total,
|
||||
'disk0_used' => $disk0_used,
|
||||
'images' => $count,
|
||||
]);
|
||||
90
src/includes/thumbnail_helper.php
Normal file
90
src/includes/thumbnail_helper.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/** includes/thumbnail_helper.php
|
||||
* Generate a thumbnail using Imagick
|
||||
* @param string $src_path Path to the source image
|
||||
* @param string $hash MD5 filename
|
||||
* @param string $thumb_path Path to save the generated thumbnail
|
||||
* @param int $max_size Max thumbnail size (pixels)
|
||||
* @param int $quality Output image quality (1–100)
|
||||
* @return bool True on success, false on failure
|
||||
*/
|
||||
|
||||
|
||||
function generate_thumbnail_imagick($src_path, $hash, $thumb_path, $max_size, $quality) {
|
||||
$ext = strtolower(pathinfo($src_path, PATHINFO_EXTENSION));
|
||||
$img = new Imagick($src_path);
|
||||
if (method_exists($img, 'autoOrientImage')) {
|
||||
// Modern Imagick versions
|
||||
$img->setImageOrientation(Imagick::ORIENTATION_UNDEFINED);
|
||||
$img->autoOrientImage();
|
||||
} else {
|
||||
// Manual fallback for older Imagick builds
|
||||
switch ($img->getImageOrientation()) {
|
||||
case Imagick::ORIENTATION_TOPLEFT: // normal
|
||||
break;
|
||||
case Imagick::ORIENTATION_TOPRIGHT:
|
||||
$img->flopImage(); // horizontal flip
|
||||
break;
|
||||
case Imagick::ORIENTATION_BOTTOMRIGHT:
|
||||
$img->rotateImage(new ImagickPixel(), 180);
|
||||
break;
|
||||
case Imagick::ORIENTATION_BOTTOMLEFT:
|
||||
$img->flopImage();
|
||||
$img->rotateImage(new ImagickPixel(), 180);
|
||||
break;
|
||||
case Imagick::ORIENTATION_LEFTTOP:
|
||||
$img->flopImage();
|
||||
$img->rotateImage(new ImagickPixel(), 90);
|
||||
break;
|
||||
case Imagick::ORIENTATION_RIGHTTOP:
|
||||
$img->rotateImage(new ImagickPixel(), 90);
|
||||
break;
|
||||
case Imagick::ORIENTATION_RIGHTBOTTOM:
|
||||
$img->flopImage();
|
||||
$img->rotateImage(new ImagickPixel(), 270);
|
||||
break;
|
||||
case Imagick::ORIENTATION_LEFTBOTTOM:
|
||||
$img->rotateImage(new ImagickPixel(), 270);
|
||||
break;
|
||||
}
|
||||
$img->setImageOrientation(Imagick::ORIENTATION_UNDEFINED);
|
||||
}
|
||||
|
||||
// Get original dimensions
|
||||
$width = $img->getImageWidth();
|
||||
$height = $img->getImageHeight();
|
||||
|
||||
// Calculate proportional scaling based on longest side
|
||||
if ($width > $height) {
|
||||
$new_width = $max_size;
|
||||
$new_height = (int) round($height * ($max_size / $width));
|
||||
} else {
|
||||
$new_height = $max_size;
|
||||
$new_width = (int) round($width * ($max_size / $height));
|
||||
}
|
||||
|
||||
$img->setImageCompressionQuality($quality);
|
||||
$img->setBackgroundColor(new ImagickPixel('white'));
|
||||
$img->thumbnailImage($new_width, $new_height, true);
|
||||
$img->stripImage(); // remove metadata (saves space)
|
||||
|
||||
// Infer output format from extension
|
||||
switch ($ext) {
|
||||
case 'png':
|
||||
$img->setImageFormat('png');
|
||||
break;
|
||||
case 'webp':
|
||||
$img->setImageFormat('webp');
|
||||
break;
|
||||
default:
|
||||
$img->setImageFormat('jpeg');
|
||||
break;
|
||||
}
|
||||
|
||||
$thumbnail=$thumb_path . '/' . $hash . '.' . $ext;
|
||||
|
||||
$img->writeImage($thumbnail);
|
||||
$img->destroy();
|
||||
|
||||
return true;
|
||||
}
|
||||
58
src/includes/upload_handler.php
Normal file
58
src/includes/upload_handler.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// includes/upload_handler.php – backend for multi-file upload
|
||||
require_once __DIR__ . '/config.php';
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$uploadDir = '/srv/www/uploads';
|
||||
$indexFile = $CONFIG['index_file'];
|
||||
$successCount = 0;
|
||||
|
||||
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
|
||||
|
||||
if (!isset($_FILES['files'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'No files uploaded.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach ($_FILES['files']['tmp_name'] as $i => $tmp) {
|
||||
if (!is_uploaded_file($tmp)) continue;
|
||||
|
||||
$origName = $_FILES['files']['name'][$i];
|
||||
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
|
||||
if ($ext !== 'jpg' && $ext !== 'jpeg') continue;
|
||||
|
||||
// Extract EXIF date + shutter count if available
|
||||
$exif = @exif_read_data($tmp, 'EXIF', true);
|
||||
$date = $exif['EXIF']['DateTimeOriginal'] ?? date('Y:m:d H:i:s');
|
||||
[$Y,$m,$d,$H,$M,$S] = sscanf($date, "%4d:%2d:%2d %2d:%2d:%2d");
|
||||
|
||||
$yearDir = sprintf("%s/%04d", $uploadDir, $Y);
|
||||
$monthDir = sprintf("%s/%02d", $yearDir, $m);
|
||||
$dayDir = sprintf("%s/%02d", $monthDir, $d);
|
||||
if (!is_dir($dayDir)) mkdir($dayDir, 0755, true);
|
||||
|
||||
$time = sprintf("%02d%02d%02d", $H, $M, $S);
|
||||
|
||||
$shutter = '';
|
||||
if (!empty($exif['EXIF']['ShutterCount'])) {
|
||||
$shutter = 'sc' . $exif['EXIF']['ShutterCount'];
|
||||
} elseif (!empty($exif['MakerNote']['ShutterCount'])) {
|
||||
$shutter = 'sc' . $exif['MakerNote']['ShutterCount'];
|
||||
} else {
|
||||
$shutter = 'rn' . rand(100000, 999999);
|
||||
}
|
||||
|
||||
// Read / increment index
|
||||
$idx = 1;
|
||||
if (file_exists($indexFile)) {
|
||||
$idx = intval(trim(file_get_contents($indexFile))) + 1;
|
||||
}
|
||||
file_put_contents($indexFile, $idx);
|
||||
|
||||
$newName = sprintf("%s_%sidx%d.%s", $time, $shutter, $idx, $ext);
|
||||
$dest = sprintf("%s/%s", $dayDir, $newName);
|
||||
|
||||
if (move_uploaded_file($tmp, $dest)) $successCount++;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'count' => $successCount]);
|
||||
Reference in New Issue
Block a user