X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=BookReaderIA%2Fdatanode%2FBookReaderImages.inc.php;h=9957b40525a744609e52c717cc71a320722514bc;hb=31dbbebab24a4aaa9322352aa534935b481f543f;hp=4a0b769c0709b567bb64e4a8e23f8908ef3f4d08;hpb=9692f7fa8284ec03d30985d11dd6bc417c45c935;p=bookreader.git diff --git a/BookReaderIA/datanode/BookReaderImages.inc.php b/BookReaderIA/datanode/BookReaderImages.inc.php index 4a0b769..9957b40 100644 --- a/BookReaderIA/datanode/BookReaderImages.inc.php +++ b/BookReaderIA/datanode/BookReaderImages.inc.php @@ -60,6 +60,8 @@ class BookReaderImages 'tile' => 'tile', 'w' => 'width', 'h' => 'height', + 'x' => 'x', + 'y' => 'y', 'rotate' => 'rotate' ); @@ -87,7 +89,7 @@ class BookReaderImages try { $metadata = $brm->buildMetadata($_REQUEST['id'], $_REQUEST['itemPath'], $_REQUEST['subPrefix'], $_REQUEST['server']); } catch (Exception $e) { - $this->BRfatal($e->getMessage); + $this->BRfatal($e->getMessage()); } $page = $_REQUEST['page']; @@ -97,7 +99,7 @@ class BookReaderImages // deal with subPrefix if ($_REQUEST['subPrefix']) { - $parts = split('/', $_REQUEST['subPrefix']); + $parts = explode('/', $_REQUEST['subPrefix']); $bookId = $parts[count($parts) - 1 ]; } else { $bookId = $_REQUEST['id']; @@ -107,22 +109,29 @@ class BookReaderImages $basePage = $pageInfo['type']; + $leaf = null; + $region = null; switch ($basePage) { + case 'title': if (! array_key_exists('titleIndex', $metadata)) { $this->BRfatal("No title page asserted in book"); } $imageIndex = $metadata['titleIndex']; break; - + + /* Old 'cover' behaviour where it would show cover 0 if it exists or return 404. + Could be re-added as cover0, cover1, etc case 'cover': if (! array_key_exists('coverIndices', $metadata)) { $this->BRfatal("No cover asserted in book"); } $imageIndex = $metadata['coverIndices'][0]; // $$$ TODO add support for other covers break; - + */ + case 'preview': + case 'cover': // Show our best guess if cover is requested // Preference is: // Cover page if book was published >= 1950 // Title page @@ -165,6 +174,11 @@ class BookReaderImages $imageIndex = $index; break; + case 'leaf': + // Leaf explicitly specified + $leaf = $pageInfo['value']; + break; + default: // Shouldn't be possible $this->BRfatal("Unrecognized page type requested"); @@ -172,12 +186,15 @@ class BookReaderImages } - $leaf = $brm->leafForIndex($imageIndex, $metadata['leafNums']); + if (is_null($leaf)) { + // Leaf was not explicitly set -- look it up + $leaf = $brm->leafForIndex($imageIndex, $metadata['leafNums']); + } $requestEnv = array( 'zip' => $metadata['zip'], 'file' => $brm->imageFilePath($leaf, $metadata['subPrefix'], $metadata['imageFormat']), - 'ext' => 'jpg', + 'ext' => 'jpg', // XXX should pass through ext ); // remove non-passthrough keys from pageInfo @@ -206,6 +223,7 @@ class BookReaderImages * Clean up temporary files */ function serveRequest($requestEnv) { + // Process some of the request parameters $zipPath = $requestEnv['zip']; $file = $requestEnv['file']; @@ -236,6 +254,20 @@ class BookReaderImages // Get the image size and depth $imageInfo = $this->getImageInfo($zipPath, $file); + $region = array(); + foreach (array('x', 'y', 'w', 'h') as $key) { + if (array_key_exists($key, $requestEnv)) { + $region[$key] = $requestEnv[$key]; + } + } + $regionDimensions = $this->getRegionDimensions($imageInfo, $region); + + /* $$$ remove + print_r($imageInfo); + print_r($region); + print_r($regionDimensions); + */ + // Output json if requested if ('json' == $ext) { // $$$ we should determine the output size first based on requested scale @@ -316,6 +348,7 @@ class BookReaderImages // $$$ consider doing a 302 here instead, to make better use of the browser cache // Limit scaling for 1-bit images. See https://bugs.edge.launchpad.net/bookreader/+bug/486011 if (1 == $imageInfo['bits']) { + if ($scale > 1) { $scale /= 2; $powReduce -= 1; @@ -375,23 +408,27 @@ class BookReaderImages $errorMessage = ''; + if (! $this->passthruIfSuccessful($headers, $cmd, $errorMessage)) { // $$$ move to BookReaderRequest // $$$ automated reporting trigger_error('BookReader Processing Error: ' . $cmd . ' -- ' . $errorMessage, E_USER_WARNING); // Try some content-specific recovery - $recovered = false; + $recovered = false; if ($imageInfo['type'] == 'jp2') { $records = $this->getJp2Records($zipPath, $file); - if ($powReduce > intval($records['Clevels'])) { - $powReduce = $records['Clevels']; - $reduce = pow(2, $powReduce); + if (array_key_exists('Clevels', $records)) { + $maxReduce = intval($records['Clevels']); + trigger_error("BookReader using max reduce $maxReduce from jp2 records"); } else { - $reduce = 1; - $powReduce = 0; + $maxReduce = 0; } - + + $powReduce = min($powReduce, $maxReduce); + $reduce = pow(2, $powReduce); + $cmd = $unzipCmd . $this->getDecompressCmd($imageInfo['type'], $powReduce, $rotate, $scale, $stdoutLink) . $compressCmd; + trigger_error('BookReader rerunning with new cmd: ' . $cmd, E_USER_WARNING); if ($this->passthruIfSuccessful($headers, $cmd, $errorMessage)) { // $$$ move to BookReaderRequest $recovered = true; } else { @@ -638,15 +675,34 @@ class BookReaderImages $read = array($stdout, $stderr); $write = NULL; $except = NULL; + $numChanged = stream_select($read, $write, $except, NULL); // $$$ no timeout if (false === $numChanged) { // select failed $errorMessage = 'Select failed'; $retVal = false; - } - if ($read[0] == $stdout && (1 == $numChanged)) { - // Got output first on stdout (only) - // $$$ make sure we get all stdout + error_log('BookReader select failed!'); + } else { + if (in_array($stderr, $read)) { + // Either content in stderr, or stderr is closed (could read 0 bytes) + $error = stream_get_contents($stderr); + if ($error) { + + $errorMessage = $error; + $retVal = false; + + fclose($stderr); + fclose($stdout); + fclose($stdin); + + // It is important that you close any pipes before calling + // proc_close in order to avoid a deadlock + proc_close($process); + return $retVal; + + } + } + $output = fopen('php://output', 'w'); foreach($headers as $header) { header($header); @@ -654,11 +710,6 @@ class BookReaderImages stream_copy_to_stream($pipes[1], $output); fclose($output); // okay since tied to special php://output $retVal = true; - } else { - // Got output on stderr - // $$$ make sure we get all stderr - $errorMessage = stream_get_contents($stderr); - $retVal = false; } fclose($stderr); @@ -683,6 +734,7 @@ class BookReaderImages } // Returns true if using a power node + // XXX change to "on red box" - not working for new Xeon function onPowerNode() { exec("lspci | fgrep -c Realtek", $output, $return); if ("0" != $output[0]) { @@ -709,6 +761,9 @@ class BookReaderImages } function checkPrivs($filename) { + // $$$ we assume here that requests for the title, cover or preview + // come in via BookReaderPreview.php which will be re-run with + // privileges after we return the 403 if (!is_readable($filename)) { header('HTTP/1.1 403 Forbidden'); exit(0); @@ -783,7 +838,8 @@ class BookReaderImages 'n' => 'num', 'cover' => 'single', 'preview' => 'single', - 'title' => 'single' + 'title' => 'single', + 'leaf' => 'num' ); // Look for known page types @@ -835,6 +891,60 @@ class BookReaderImages return $pageInfo; } + function getRegionDimensions($sourceDimensions, $regionDimensions) { + // Return region dimensions as { 'x' => xOffset, 'y' => yOffset, 'w' => width, 'h' => height } + // in terms of full resolution image. + // Note: this will clip the returned dimensions to fit within the source image + + $sourceX = 0; + if (array_key_exists('x', $regionDimensions)) { + $sourceX = intAmount($regionDimensions['x'], $sourceDimensions['width']); + } + $sourceX = $this->clamp(0, $sourceDimensions['width'] - 2, $sourceX); // Allow at least one pixel + + $sourceY = 0; + if (array_key_exists('y', $regionDimensions)) { + $sourceY = intAmount($regionDimensions['y'], $sourceDimensions['height']); + } + $sourceY = $this->clamp(0, $sourceDimensions['height'] - 2, $sourceY); // Allow at least one pixel + + $sourceWidth = $sourceDimensions['width'] - $sourceX; + if (array_key_exists('w', $regionDimensions)) { + $sourceWidth = intAmount($regionDimensions['w'], $sourceDimensions['width']); + } + $sourceWidth = $this->clamp(1, max(1, $sourceDimensions['width'] - $sourceX), $sourceWidth); + + $sourceHeight = $sourceDimensions['height'] - $sourceY; + if (array_key_exists('h', $regionDimensions)) { + $sourceHeight = intAmount($regionDimensions['h'], $sourceDimensions['height']); + } + $sourceHeight = $this->clamp(1, max(1, $sourceDimensions['height'] - $sourceY), $sourceHeight); + + return array('x' => $sourceX, 'y' => $sourceY, 'w' => $sourceWidth, 'h' => $sourceHeight); + } + + function intAmount($stringValue, $maximum) { + // Returns integer amount for string like "5" (5 units) or "0.5" (50%) + if (strpos($stringValue, '.') === false) { + // No decimal, assume int + return intval($stringValue); + } + + return floatval($stringValue) * $maximum + 0.5; + } + + function clamp($minValue, $maxValue, $observedValue) { + if ($observedValue < $minValue) { + return $minValue; + } + + if ($observedValue > $maxValue) { + return $maxValue; + } + + return $observedValue; + } + // Clean up temporary files and resources function cleanup() { foreach($this->tempFiles as $tempFile) {