HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //home/panomity.de/vr.panomity.com/api/equirect-reconstruct.php
<?php
/**
 * Equirect Rekonstruktion aus Tiles
 */

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; }

$input = file_get_contents('php://input');
$data = json_decode($input, true);
$panoId = isset($data['pano_id']) ? (int)$data['pano_id'] : 0;
if ($panoId <= 0) { http_response_code(400); echo json_encode(['error' => 'Invalid pano_id']); exit; }

$rootDir = realpath(__DIR__ . '/..');
if ($rootDir === false) { $rootDir = dirname(__DIR__); }
$tilesDir = $rootDir . '/panos/p' . $panoId . '.tiles';
if (!is_dir($tilesDir)) { @mkdir($tilesDir, 0777, true); }
$equirectPath = $tilesDir . '/equirect.jpg';

$originals = [
    $rootDir . '/p' . $panoId . '.jpg',
    $rootDir . '/p' . $panoId . '.jpeg',
    $rootDir . '/p' . $panoId . '.png',
    $rootDir . '/p' . $panoId . '.tif',
    $rootDir . '/p' . $panoId . '.tiff',
    $rootDir . '/p' . $panoId . '.psb',
];

$ktool = realpath($rootDir . '/admin/data/bin/krpano/krpanotools');
if ($ktool && is_file($ktool)) { @shell_exec('chmod +x ' . escapeshellarg($ktool)); }

function assemble_face_from_tiles($faceDir, $tmpFacePath) {
    if (!is_dir($faceDir)) return false;
    $yDirs = glob($faceDir . '/*', GLOB_ONLYDIR);
    if (!$yDirs) return false;
    $yIdxs = [];
    foreach ($yDirs as $yd) { $b = basename($yd); if (ctype_digit($b)) $yIdxs[] = (int)$b; }
    if (!$yIdxs) return false;
    sort($yIdxs);
    $tileW = 0; $tileH = 0; $rows = []; $maxX = 0;
    foreach ($yIdxs as $y) {
        $xFiles = glob($faceDir . '/' . $y . '/*.{jpg,jpeg,png}', GLOB_BRACE);
        if (!$xFiles) continue;
        $xIdxs = []; $row = [];
        foreach ($xFiles as $xf) {
            $xb = basename($xf, '.' . pathinfo($xf, PATHINFO_EXTENSION));
            if (ctype_digit($xb)) { $xIdxs[] = (int)$xb; $row[(int)$xb] = $xf; }
        }
        if (!$xIdxs) continue;
        sort($xIdxs);
        $maxX = max($maxX, max($xIdxs));
        $rows[$y] = $row;
        if ($tileW === 0 || $tileH === 0) {
            $sz = @getimagesize($row[$xIdxs[0]]);
            if ($sz) { $tileW = $sz[0]; $tileH = $sz[1]; }
        }
    }
    if ($tileW === 0 || $tileH === 0 || !$rows) return false;
    $maxY = max(array_keys($rows));
    $canvasW = ($maxX + 1) * $tileW;
    $canvasH = ($maxY + 1) * $tileH;
    $canvas = imagecreatetruecolor($canvasW, $canvasH);
    foreach ($rows as $y => $row) {
        foreach ($row as $x => $file) {
            $srcImg = @imagecreatefromjpeg($file);
            if (!$srcImg) $srcImg = @imagecreatefrompng($file);
            if (!$srcImg) continue;
            imagecopy($canvas, $srcImg, $x * $tileW, $y * $tileH, 0, 0, $tileW, $tileH);
            imagedestroy($srcImg);
        }
    }
    imagejpeg($canvas, $tmpFacePath, 90);
    imagedestroy($canvas);
    return file_exists($tmpFacePath);
}

function assemble_face_from_flat_tiles($faceDir, $tmpFacePath) {
    if (!is_dir($faceDir)) return false;
    $files = glob($faceDir . '/*.{jpg,jpeg,png}', GLOB_BRACE);
    if (!$files) return false;
    $tiles = [];
    $xs = []; $ys = [];
    foreach ($files as $f) {
        $bn = basename($f);
        if (preg_match('/(\d+)[._-](\d+)\.(?:jpg|jpeg|png)$/i', $bn, $m)) {
            $a = (int)$m[1]; $b = (int)$m[2];
            // Versuch 1: a=x, b=y
            $tiles[] = ['x'=>$a,'y'=>$b,'f'=>$f,'try'=>1];
            $xs[$a] = true; $ys[$b] = true;
            // optional: Versuch 2: a=y, b=x wird weiter unten getestet
        }
    }
    if (!$tiles) return false;
    ksort($xs); ksort($ys);
    $xList = array_keys($xs); $yList = array_keys($ys);
    $tileW = 0; $tileH = 0;
    $sz = @getimagesize($tiles[0]['f']); if ($sz) { $tileW = $sz[0]; $tileH = $sz[1]; }
    if ($tileW === 0 || $tileH === 0) return false;
    $canvasW = (count($xList)) * $tileW; $canvasH = (count($yList)) * $tileH;
    $canvas = imagecreatetruecolor($canvasW, $canvasH);
    foreach ($tiles as $t) {
        $srcImg = @imagecreatefromjpeg($t['f']); if (!$srcImg) $srcImg = @imagecreatefrompng($t['f']); if (!$srcImg) continue;
        $xi = array_search($t['x'], $xList, true); $yi = array_search($t['y'], $yList, true);
        if ($xi === false || $yi === false) { imagedestroy($srcImg); continue; }
        imagecopy($canvas, $srcImg, $xi * $tileW, $yi * $tileH, 0, 0, $tileW, $tileH);
        imagedestroy($srcImg);
    }
    imagejpeg($canvas, $tmpFacePath, 90);
    imagedestroy($canvas);
    if (file_exists($tmpFacePath)) return true;

    return false;
}

function split_strip_into_faces_any($imgPath, $outDir, &$facesOut) {
    $img = @imagecreatefromjpeg($imgPath);
    if (!$img) $img = @imagecreatefrompng($imgPath);
    if (!$img) return false;
    $w = imagesx($img); $h = imagesy($img);
    $orders = [ ['l','f','r','b','u','d'], ['f','r','b','l','u','d'], ['r','b','l','f','u','d'], ['b','l','f','r','u','d'] ];
    if ($h % 6 === 0 && abs(($h/6) - $w) < 20) {
        $size = (int)round($h/6);
        foreach ($orders as $order) {
            $tmpFaces = [];
            for ($i=0; $i<6; $i++) {
                $crop = imagecreatetruecolor($size, $size);
                imagecopy($crop, $img, 0, 0, 0, $i*$size, $size, $size);
                $p = $outDir . '/' . $order[$i] . '.jpg'; imagejpeg($crop, $p, 90); imagedestroy($crop);
                $tmpFaces[$order[$i]] = $p;
            }
            $facesOut = $tmpFaces; imagedestroy($img); return true;
        }
    }
    if ($w % 6 === 0 && abs(($w/6) - $h) < 20) {
        $size = (int)round($w/6);
        foreach ($orders as $order) {
            $tmpFaces = [];
            for ($i=0; $i<6; $i++) {
                $crop = imagecreatetruecolor($size, $size);
                imagecopy($crop, $img, 0, 0, $i*$size, 0, $size, $size);
                $p = $outDir . '/' . $order[$i] . '.jpg'; imagejpeg($crop, $p, 90); imagedestroy($crop);
                $tmpFaces[$order[$i]] = $p;
            }
            $facesOut = $tmpFaces; imagedestroy($img); return true;
        }
    }
    imagedestroy($img); return false;
}

function run_cube2sphere($ktool, $faces, $outPath, &$debugOut = null) {
    if (!$ktool || !is_file($ktool)) {
        $debugOut = ['error' => 'ktool not found or not a file', 'ktool' => $ktool];
        return false;
    }
    $need = ['l','r','f','b','u','d'];
    foreach ($need as $n) {
        if (empty($faces[$n])) {
            $debugOut = ['error' => 'Missing face: ' . $n, 'faces' => $faces];
            return false;
        }
    }
    
    // krpanotools arbeitet besser mit relativem Arbeitsverzeichnis
    // Kopiere alle Faces in ein tmp-Verzeichnis und wechsle dorthin
    $workDir = sys_get_temp_dir() . '/krpano_work_' . uniqid();
    @mkdir($workDir, 0777, true);
    
    // Bestimme die Cube-Größe aus dem ersten Face
    $firstFace = $faces['l'];
    $faceSize = null;
    $imgInfo = @getimagesize($firstFace);
    if ($imgInfo) {
        $faceSize = $imgInfo[0]; // Breite = Cube-Größe
    }
    
    // krpanotools erwartet Suffixe wie _l, _f, _r, etc. ODER explizite -l= -f= Syntax
    // Wir verwenden die explizite Syntax
    $localFaces = [];
    foreach ($faces as $key => $srcPath) {
        $localPath = $workDir . '/face_' . $key . '.jpg';
        if (!@copy($srcPath, $localPath)) {
            $debugOut = ['error' => 'Failed to copy face to workdir', 'face' => $key, 'src' => $srcPath];
            return false;
        }
        $localFaces[$key] = 'face_' . $key . '.jpg';
    }
    
    $localOut = $workDir . '/equirect.jpg';
    
    // Explizite Syntax: -l=file -f=file -r=file -b=file -u=file -d=file
    $cmd = 'cd ' . escapeshellarg($workDir) . ' && '
        . escapeshellarg($ktool)
        . ' cube2sphere'
        . ' -l=' . escapeshellarg($localFaces['l'])
        . ' -f=' . escapeshellarg($localFaces['f'])
        . ' -r=' . escapeshellarg($localFaces['r'])
        . ' -b=' . escapeshellarg($localFaces['b'])
        . ' -u=' . escapeshellarg($localFaces['u'])
        . ' -d=' . escapeshellarg($localFaces['d'])
        . ' -o=equirect.jpg';
    
    $output = shell_exec($cmd . ' 2>&1');
    
    $success = false;
    if (file_exists($localOut)) {
        if (@copy($localOut, $outPath)) {
            $success = true;
        }
    }
    
    // Cleanup
    @unlink($localOut);
    foreach ($localFaces as $lf) { @unlink($workDir . '/' . $lf); }
    @rmdir($workDir);
    
    $debugOut = [
        'cmd' => $cmd,
        'output' => $output,
        'outPath' => $outPath,
        'localOut' => $localOut,
        'exists' => file_exists($outPath)
    ];
    
    return $success;
}

function find_single_image_in_dir($dir) {
    $files = glob($dir . '/*.{jpg,jpeg,png}', GLOB_BRACE);
    if (!$files) return null;
    // wähle die größte Datei
    usort($files, function($a,$b){ return filesize($b) <=> filesize($a); });
    return $files[0];
}

// Original -> equirect
$src = null; foreach ($originals as $cand) { if (file_exists($cand)) { $src = $cand; break; } }
if ($src) {
    $ext = strtolower(pathinfo($src, PATHINFO_EXTENSION));
    if (in_array($ext, ['jpg','jpeg'])) { @copy($src, $equirectPath); }
    else { $img = null; if ($ext === 'png') $img = @imagecreatefrompng($src); if ($img) { @imagejpeg($img, $equirectPath, 90); @imagedestroy($img); } else { @copy($src, $equirectPath); } }
    if (file_exists($equirectPath)) { echo json_encode(['success' => true, 'file' => 'panos/p' . $panoId . '.tiles/equirect.jpg', 'mode' => 'original']); exit; }
}

$levelDirs = glob($tilesDir . '/l*', GLOB_ONLYDIR);
$levelNums = [];
if ($levelDirs) { foreach ($levelDirs as $ld) { $bn = basename($ld); if ($bn !== '' && $bn[0] === 'l' && ctype_digit(substr($bn,1))) { $levelNums[] = (int)substr($bn,1); } } }
rsort($levelNums);
$tmpDir = sys_get_temp_dir() . '/p' . $panoId . '_faces_' . uniqid(); @mkdir($tmpDir, 0777, true);
$faces = ['l'=>null,'r'=>null,'f'=>null,'b'=>null,'u'=>null,'d'=>null];

// Level-Grid
foreach ($levelNums as $ln) {
    $base = $tilesDir . '/l' . $ln; $okAll = true;
    foreach (array_keys($faces) as $face) { $out = $tmpDir . '/' . $face . '.jpg'; if (!assemble_face_from_tiles($base . '/' . $face, $out)) { $okAll = false; break; } $faces[$face] = $out; }
    if ($okAll && run_cube2sphere($ktool, $faces, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'grid-level']); exit; }
}

// Flaches Tile-Muster in Face-Ordnern (x_y.jpg)
$facesFlat = [];
$okFlat = true;
foreach (array_keys($faces) as $fc) {
    $out = $tmpDir . '/' . $fc . '.jpg';
    if (!assemble_face_from_flat_tiles($tilesDir . '/' . $fc, $out)) { $okFlat = false; break; }
    $facesFlat[$fc] = $out;
}
if ($okFlat && run_cube2sphere($ktool, $facesFlat, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'flat-faces']); exit; }

// Direkte Face-Dateien im Root (Groß-/Klein-/Synonyme)
$map = [ 'l'=>['l','L','left'], 'r'=>['r','R','right'], 'f'=>['f','F','front'], 'b'=>['b','B','back'], 'u'=>['u','U','up','top'], 'd'=>['d','D','down','bottom'] ];
$df = []; foreach ($map as $key=>$cands) { foreach ($cands as $n) { foreach (glob($tilesDir . '/' . $n . '.{jpg,jpeg,png}', GLOB_BRACE) as $f) { $df[$key]=$f; break 2; } } }
if (count($df) === 6 && run_cube2sphere($ktool, $df, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'root-faces']); exit; }

// Fallback: Unterordner 'vr' durchsuchen
$vr = $tilesDir . '/vr';
$vrDebug = ['exists' => is_dir($vr), 'path' => $vr];
if (is_dir($vr)) {
    // pano_*.jpg Dateien im vr-Root (häufigste Variante)
    $vrFaces = [];
    $vrMap = ['l'=>'pano_l','r'=>'pano_r','f'=>'pano_f','b'=>'pano_b','u'=>'pano_u','d'=>'pano_d'];
    foreach ($vrMap as $key => $name) {
        $pattern = $vr . '/' . $name . '.{jpg,jpeg,png}';
        $matches = glob($pattern, GLOB_BRACE);
        $vrDebug['glob_'.$key] = ['pattern' => $pattern, 'matches' => $matches];
        if ($matches) {
            $vrFaces[$key] = $matches[0];
        }
    }
    $vrDebug['found_faces'] = $vrFaces;
    $vrDebug['count'] = count($vrFaces);
    $vrDebug['ktool_exists'] = ($ktool && is_file($ktool));
    
    if (count($vrFaces) === 6) {
        $c2sDebug = null;
        $result = run_cube2sphere($ktool, $vrFaces, $equirectPath, $c2sDebug);
        $vrDebug['cube2sphere_result'] = $result;
        $vrDebug['cube2sphere_debug'] = $c2sDebug;
        $vrDebug['equirect_exists'] = file_exists($equirectPath);
        if ($result) {
            echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-pano']);
            exit;
        }
    }
    
    // Faces in vr/<face>/ (flach)
    $facesV = [];
    $okV = true;
    foreach (array_keys($faces) as $fc) {
        $out = $tmpDir . '/vr_' . $fc . '.jpg';
        if (!assemble_face_from_flat_tiles($vr . '/' . $fc, $out)) { $okV = false; break; }
        $facesV[$fc] = $out;
    }
    if ($okV && run_cube2sphere($ktool, $facesV, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-flat']); exit; }
    
    // Direktdateien in vr-Root (generisch)
    $df2 = []; foreach ($map as $key=>$cands) { foreach ($cands as $n) { foreach (glob($vr . '/' . $n . '.{jpg,jpeg,png}', GLOB_BRACE) as $f) { $df2[$key]=$f; break 2; } } }
    if (count($df2) === 6 && run_cube2sphere($ktool, $df2, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-root']); exit; }
    
    // preview im vr
    foreach (['preview.jpg','preview.jpeg','preview.png'] as $prev) {
        $preview = $vr . '/' . $prev;
        if (file_exists($preview)) {
            $faces2 = [];
            if (split_strip_into_faces_any($preview, $tmpDir, $faces2)) {
                if (run_cube2sphere($ktool, $faces2, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-strip']); exit; }
            }
        }
    }
}

$ls = @scandir($tilesDir) ?: [];
$debug = ['root' => $ls];
// Face-Ordner inspizieren
foreach (['l','r','f','b','u','d'] as $fc) {
    $fdir = $tilesDir . '/' . $fc;
    if (is_dir($fdir)) {
        $debug[$fc] = @scandir($fdir) ?: [];
    }
}
// vr-Ordner inspizieren
if (is_dir($tilesDir . '/vr')) {
    $debug['vr'] = @scandir($tilesDir . '/vr') ?: [];
    foreach (['l','r','f','b','u','d'] as $fc) {
        $fdir = $tilesDir . '/vr/' . $fc;
        if (is_dir($fdir)) {
            $debug['vr/'.$fc] = @scandir($fdir) ?: [];
        }
    }
}
$debug['vr_debug'] = $vrDebug ?? null;
http_response_code(404);
echo json_encode(['error'=>'Failed to reconstruct from tiles or preview','debug'=>$debug,'krpano'=>(string)$ktool]);
exit;
?>