Compare commits

...

17 Commits

Author SHA1 Message Date
sheldonmlee 5e0bb59907 Fix bug where parent directory is omitted 2024-03-11 18:58:28 +08:00
sheldonmlee abec1c242f Ignore hidden files 2024-03-10 15:09:19 +08:00
sheldonmlee 1f52134bd7 Implement basic config file 2023-11-19 15:28:39 +08:00
sheldonmlee 982565bd95 Try to half image width instead of quarter for thumbnail 2023-11-19 13:27:12 +08:00
sheldonmlee 11c3faddb0 Use faster hash algorithum for thumbnail generation 2023-11-19 13:25:21 +08:00
sheldonmlee c5b4dda950 Detect errors from resize-img using substr_count() 2023-11-18 16:42:33 +08:00
sheldonmlee 22a70bd4de Refactor resize-img error printing 2023-11-18 16:41:32 +08:00
sheldonmlee 3c1f996342 Update log messages and set a more dynamic image resize up to a limit 2023-11-18 15:57:36 +08:00
sheldonmlee 737821fa6a Store thumbnails based on the hash of original image 2023-11-18 15:56:05 +08:00
sheldonmlee a6a4d654d4 Update cors log message 2023-11-13 23:25:32 +08:00
sheldonmlee a8bc4fd92e Add cors-handling for testing 2023-11-13 22:50:32 +08:00
sheldonmlee 2bfa19fe24 Slight refactoring 2023-11-13 22:47:14 +08:00
sheldonmlee 792b9c875f Implement thumbnail generation 2023-11-13 02:03:04 +08:00
sheldonmlee a14e2972c6 Ignore php log file 2023-11-12 23:19:11 +08:00
sheldonmlee 76e47a033f Slight refactor and add stub function to create smaller thumbnail images 2023-11-12 23:18:32 +08:00
sheldonmlee f644890e3a Add logging functions 2023-11-12 22:45:19 +08:00
sheldonmlee 39928a2c56 Reformat whitespace 2023-11-12 22:44:35 +08:00
6 changed files with 289 additions and 40 deletions
+3
View File
@@ -1,3 +1,6 @@
php/composer*
php/psalm.xml
php/vendor
php.log
config.json
+6
View File
@@ -0,0 +1,6 @@
{
"ROOT_URL": "http://localhost/",
"ROOT_DIR": "/var/www/html/",
"IMG_DIR": "img/",
"THUMBNAIL_DIR": "thumbnails/"
}
+40
View File
@@ -0,0 +1,40 @@
<?php
include_once __DIR__ . "/logging.php";
/**
* An example CORS-compliant method. It will allow any GET, POST, or OPTIONS requests from any
* origin.
*
* In a production environment, you probably want to be more restrictive, but this gives you
* the general idea of what is involved. For the nitty-gritty low-down, read:
*
* - https://developer.mozilla.org/en/HTTP_access_control
* - https://fetch.spec.whatwg.org/#http-cors-protocol
*
*/
function cors() {
// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
log_print("HTTP_ORIGIN: ".$_SERVER['HTTP_ORIGIN']);
// Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
// you want to allow, and if so:
#if ($_SERVER['HTTP_ORIGIN'] !== 'http://app1.localhost') return;
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// may also be using PUT, PATCH, HEAD etc
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
}
?>
+140 -35
View File
@@ -1,36 +1,16 @@
<?php
define("ROOT_URL", "http://localhost/");
define("IMG_DIR", "/var/www/localhost/img/");
define("IMG_URL", "img/");
include_once __DIR__ . "/logging.php";
include_once __DIR__ . "/cors.php";
cors();
main();
$config_data = get_config();
define("ROOT_URL", $config_data["ROOT_URL"]);
define("ROOT_DIR", $config_data["ROOT_DIR"]);
define("IMG_DIR", $config_data["IMG_DIR"]);
define("THUMBNAIL_DIR", $config_data["THUMBNAIL_DIR"]);
define("THUMBNAIL_SCRIPT", __DIR__ . "/../scripts/resize-img");
function main(): void {
$data = json_decode(file_get_contents('php://input'), true);
$array = [];
$category = $data["category"];
$directory = IMG_DIR.$category;
if (!is_dir($directory)) return;
foreach (scandir($directory) as $file) {
if ($file === ".") continue;
if ($category === "" && $file === "..") continue;
if ($category != "") $file = $category.'/'.$file;
$is_dir = is_dir(IMG_DIR.$file);
if (!$is_dir && !is_valid_file_type($file)) continue;
$file_item = [];
$file_item["is_dir"] = $is_dir;
$file_item["url"] = ROOT_URL.IMG_URL.$file;
array_push($array, $file_item);
}
echo json_encode($array);
}
function is_valid_file_type(string $filename): bool {
$file_extensions = [
// Image extensions
define("IMAGE_EXTENSIONS", [
'jpg',
'jpeg',
'png',
@@ -39,14 +19,139 @@ function is_valid_file_type(string $filename): bool {
'bmp',
'webp',
'svg',
// Video extensions
'mp4',
];
]);
foreach ($file_extensions as $file_extension) {
define("VIDEO_EXTENSIONS", [
'mp4',
]);
define("FILE_EXTENSIONS", array_merge(IMAGE_EXTENSIONS, VIDEO_EXTENSIONS));
function main(): void {
$data = json_decode(file_get_contents('php://input'), true);
$array = [];
$category = $data["category"];
$directory = ROOT_DIR.IMG_DIR.$category;
if (!is_dir($directory)) goto encode;
foreach (scandir($directory) as $file) {
if ($file === ".") continue;
if ($category === "" && $file === "..") continue;
if (substr($file, 0, 1) === "." && $file !== "..") continue;
$path = add_seperator_ab("/", $category, $file);
$is_dir = is_dir(ROOT_DIR.IMG_DIR.$path);
if (!$is_dir && !is_valid_file_type($file)) continue;
$url = ROOT_URL.IMG_DIR.$path;
$thumbnail_url = null;
if (!$is_dir) {
$thumbnail_url = create_thumbnail($category, $file);
}
$file_item = [];
$file_item["is_dir"] = $is_dir;
$file_item["url"] = $url;
$file_item["thumbnail_url"] = $thumbnail_url;
array_push($array, $file_item);
}
encode:
echo json_encode($array);
}
function create_thumbnail(string $category, string $file): string | null {
if (!is_image_file_type($file)) return null;
$path = add_seperator_ab("/", $category, $file);
$original_file = ROOT_DIR.IMG_DIR.$path;
# use fast hashing algo, see: https://github.com/Cyan4973/xxHash
$hash = hash_file("xxh3", $original_file);
$thumbnail_directory = ROOT_DIR.THUMBNAIL_DIR;
$thumbnail_name = "$hash.jpg";
$thumbnail_file = $thumbnail_directory.$thumbnail_name;
if (file_exists($thumbnail_file)) {
log_print("thumbnail for $original_file exists, skipping generation");
goto return_thumbnail;
}
try {
if (!is_dir($thumbnail_directory)) mkdir($thumbnail_directory, 0777, true);
}
catch(Exception $e) {
log_error($e->getMessage());
}
log_print("generating thumbnail for $original_file");
$output = shell_exec(THUMBNAIL_SCRIPT." '$original_file' '$thumbnail_file' 2>&1");
if ($output) log_print($output);
if (substr_count(strtolower((string) $output), "error")) {
log_error("Thumbnail failed");
log_print(ROOT_URL.IMG_DIR.$path);
return ROOT_URL.IMG_DIR.$path;
}
return_thumbnail:
return ROOT_URL.THUMBNAIL_DIR.$thumbnail_name;
}
function add_seperator_ab(string $separator, string $a, string $b): string {
if ($a === "") return $b;
return $a.$separator.$b;
}
function is_valid_file_type(string $filename): bool {
foreach (FILE_EXTENSIONS as $file_extension) {
if (str_ends_with($filename, $file_extension)) return true;
}
return false;
}
?>
function is_image_file_type(string $filename): bool {
foreach(IMAGE_EXTENSIONS as $file_extension) {
if(str_ends_with($filename, $file_extension)) return true;
}
return false;
}
function get_config(): array {
$config_data = file_exists(__DIR__ . "/../config.json") ?
json_decode(file_get_contents(__DIR__ . "/../config.json"), true) : null;
if (!is_valid_config_data($config_data)) {
log_error("Config invalid, falling back to default. Please check config.json");
$config_data = json_decode(file_get_contents(__DIR__ . "/../config.default.json"), true);
if (!is_valid_config_data($config_data))
log_error("Default config invalid. Please check or create config.json");
}
return $config_data;
}
function is_valid_config_data(?array $data): bool {
if (!$data) return false;
$keys = ["ROOT_URL", "ROOT_DIR", "IMG_DIR", "THUMBNAIL_DIR"];
foreach ($keys as $key) {
if (!isset($data[$key])) return false;
switch ($key) {
case "ROOT_URL":
case "THUMBNAIL_DIR":
break;
case "ROOT_DIR":
if (!is_dir($data[$key])) return false;
break;
case "IMG_DIR":
if (!is_dir($data["ROOT_DIR"].$data[$key])) return false;
break;
}
}
return true;
}
main();
?>
+29
View File
@@ -0,0 +1,29 @@
<?php
//define("DEBUG", null);
define("LOG_FILE", __DIR__ . "/../php.log");
define("PREFIX_ERROR", "Error: ");
function log_print($string)
{
if(defined("DEBUG") && !defined("NO_ECHO")) {
echo $string . "<br>";
return;
}
$handle = fopen(LOG_FILE, "a");
if (!$handle) {
echo "Unable to open file.<br>";
return;
}
if (!fwrite($handle, $string . "\n")) {
echo "Unable to write to file.<br>";
}
fclose($handle);
}
function log_error($string)
{
log_print(PREFIX_ERROR . $string);
}
?>
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/python
from PIL import Image
import sys
def main():
argn = len(sys.argv)
if argn == 1:
print_error("You must specify an input file")
exit(1)
elif argn == 2:
print_error("You must specify an output file")
exit(1)
input_image_path = sys.argv[1]
output_image_path = sys.argv[2]
try:
image = Image.open(input_image_path)
except IOError:
print_error(f"Cannot open image file {input_image_path}")
image.close()
exit(1)
width, height = image.size
target_width = int(max(width/2, 1280))
new_height = int(target_width * height / width)
image = image.resize((target_width, new_height), Image.Resampling.BICUBIC)
image = rotate_image(image)
kwargs = {"quality": 50}
if "exif" in image.info.keys():
kwargs["exif"] = image.info["exif"]
try:
image.save(output_image_path, **kwargs)
except IOError:
print_error(f"Cannot save file {output_image_path}")
image.close()
exit(1)
def print_error(string):
print(f"resize-img Error: {string}")
def rotate_image(image):
exif = image.getexif()
if exif is None:
return image
orientation = exif.get(0x0012, 1) # 0x0112 is the EXIF code for orientation
if orientation == 1:
return image
elif orientation == 3:
image = image.rotate(180, expand=True)
elif orientation == 6:
image = image.rotate(270, expand=True)
elif orientation == 8:
image = image.rotate(90, expand=True)
return image
if __name__ == "__main__":
main()