Use 7z for accessing tar files
[bookreader.git] / BookReaderIA / datanode / BookReaderImages.php
1 <?php
2
3 /*
4 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
5
6 This file is part of BookReader.
7
8     BookReader is free software: you can redistribute it and/or modify
9     it under the terms of the GNU Affero General Public License as published by
10     the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12
13     BookReader is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU Affero General Public License for more details.
17
18     You should have received a copy of the GNU Affero General Public License
19     along with BookReader.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 $MIMES = array('jpg' => 'image/jpeg',
23                'png' => 'image/png');
24                
25 $exiftool = '/petabox/sw/books/exiftool/exiftool';
26
27 $zipPath  = $_REQUEST['zip'];
28 $file     = $_REQUEST['file'];
29
30 /*
31  * Approach:
32  * 
33  * Get info about requested image (input)
34  * Get info about requested output format
35  * Determine processing parameters
36  * Process image
37  * Return image data
38  * Clean up temporary files
39  */
40  
41 function getUnarchiveCommand($archivePath, $file)
42 {
43     $lowerPath = strtolower($archivePath);
44     if (preg_match('/\.([^\.]+)$/', $lowerPath, $matches)) {
45         $suffix = $matches[1];
46         
47         if ($suffix == 'zip') {
48             return 'unzip -p '
49                 . escapeshellarg($archivePath)
50                 . ' ' . escapeshellarg($file);
51         } else if ($suffix == 'tar') {
52             return '7z e -so '
53                 . escapeshellarg($archivePath)
54                 . ' ' . escapeshellarg($file);
55         }
56
57     }
58     
59     BRfatal('Incompatible image stack path');
60     
61 }
62  
63 /*
64  * Get the image width, height and depth from a jp2 file in zip.
65  */
66 function getImageSizeAndDepth($zipPath, $file)
67 {
68     global $exiftool;
69     
70     # $$$ will exiftool work for *all* of our images?
71     $cmd = getUnarchiveCommand($zipPath, $file)
72         . ' | '. $exiftool . ' -s -s -s -ImageWidth -ImageHeight -BitsPerComponent -';
73     exec($cmd, $output);
74     
75     preg_match('/^(\d+)/', $output[2], $groups);
76     $bits = intval($groups[1]);
77     
78     $retval = Array('width' => intval($output[0]), 'height' => intval($output[1]),
79         'bits' => $bits);    
80     return $retval;
81 }
82
83 // Unfortunately kakadu requires us to know a priori if the
84 // output file should be .ppm or .pgm.  By decompressing to
85 // .bmp kakadu will write a file we can consistently turn into
86 // .pnm.  Really kakadu should support .pnm as the file output
87 // extension and automatically write ppm or pgm format as
88 // appropriate.
89 $decompressToBmp = true;
90 if ($decompressToBmp) {
91   $stdoutLink = '/tmp/stdout.bmp';
92 } else {
93   $stdoutLink = '/tmp/stdout.ppm';
94 }
95
96 if (isset($_REQUEST['ext'])) {
97   $ext = $_REQUEST['ext'];
98 } else {
99   // Default to jpg
100   $ext = 'jpg';
101 }
102
103 $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
104
105 // Rotate is currently only supported for jp2 since it does not add server load
106 $allowedRotations = array("0", "90", "180", "270");
107 $rotate = $_REQUEST['rotate'];
108 if ( !in_array($rotate, $allowedRotations) ) {
109     $rotate = "0";
110 }
111
112 // Image conversion options
113 $pngOptions = '';
114 $jpegOptions = '-quality 75';
115
116 // The pbmreduce reduction factor produces an image with dimension 1/n
117 // The kakadu reduction factor produceds an image with dimension 1/(2^n)
118
119 if (isset($_REQUEST['height'])) {
120     $ratio = floatval($_REQUEST['origHeight']) / floatval($_REQUEST['height']);
121     if ($ratio <= 2) {
122         $scale = 2;
123         $powReduce = 1;    
124     } else if ($ratio <= 4) {
125         $scale = 4;
126         $powReduce = 2;
127     } else {
128         //$powReduce = 3; //too blurry!
129         $scale = 2;
130         $powReduce = 1;
131     }
132
133 } else {
134     $scale = $_REQUEST['scale'];
135     if (1 >= $scale) {
136         $scale = 1;
137         $powReduce = 0;
138     } else if (2 == $scale) {
139         $powReduce = 1;
140     } else if (4 == $scale) {
141         $powReduce = 2;
142     } else if (8 == $scale) {
143         $powReduce = 3;
144     } else if (16 == $scale) {
145         $powReduce = 4;
146     } else if (32 == $scale) {
147         $powReduce = 5;
148     } else {
149         // $$$ Leaving this in as default though I'm not sure why it is...
150         $scale = 8;
151         $powReduce = 3;
152     }
153 }
154
155 if (!file_exists($stdoutLink)) 
156 {  
157   system('ln -s /dev/stdout ' . $stdoutLink);  
158 }
159
160
161 putenv('LD_LIBRARY_PATH=/petabox/sw/lib/kakadu');
162
163 $unzipCmd  = getUnarchiveCommand($zipPath, $file);
164         
165 if ('jp2' == $fileExt) {
166     $decompressCmd = 
167         " | /petabox/sw/bin/kdu_expand -no_seek -quiet -reduce $powReduce -rotate $rotate -i /dev/stdin -o " . $stdoutLink;
168     if ($decompressToBmp) {
169         $decompressCmd .= ' | bmptopnm ';
170     }
171     
172 } else if ('tif' == $fileExt) {
173     // We need to create a temporary file for tifftopnm since it cannot
174     // work on a pipe (the file must be seekable).
175     // We use the BookReaderTiff prefix to give a hint in case things don't
176     // get cleaned up.
177     $tempFile = tempnam("/tmp", "BookReaderTiff");
178     
179     if (1 != $scale) {
180         if (onPowerNode()) {
181             $pbmReduce = ' | pnmscale -reduce ' . $scale;
182         } else {
183             $pbmReduce = ' | pnmscale -nomix -reduce ' . $scale;
184         }
185     } else {
186         $pbmReduce = '';
187     }
188     
189     $decompressCmd = 
190         ' > ' . $tempFile . ' ; tifftopnm ' . $tempFile . ' 2>/dev/null' . $pbmReduce;
191
192 } else {
193     BRfatal('Unknown source file extension: ' . $fileExt);
194 }
195        
196 // Non-integer scaling is currently disabled on the cluster
197 // if (isset($_REQUEST['height'])) {
198 //     $cmd .= " | pnmscale -height {$_REQUEST['height']} ";
199 // }
200
201 if ('jpg' == $ext) {
202     $compressCmd = ' | pnmtojpeg ' . $jpegOptions;
203 } else if ('png' == $ext) {
204     $compressCmd = ' | pnmtopng ' . $pngOptions;
205 }
206
207 $cmd = $unzipCmd . $decompressCmd . $compressCmd;
208
209 # print $cmd;
210
211
212 header('Content-type: ' . $MIMES[$ext]);
213 header('Cache-Control: max-age=15552000');
214 passthru ($cmd); # cmd returns image data
215
216 if (isset($tempFile)) {
217   unlink($tempFile);
218 }
219
220 function BRFatal($string) {
221     echo "alert('$string')\n";
222     die(-1);
223 }
224
225 // Returns true if using a power node
226 function onPowerNode() {
227     exec("lspci | fgrep -c Realtek", $output, $return);
228     if ("0" != $output[0]) {
229         return true;
230     } else {
231         exec("egrep -q AMD /proc/cpuinfo", $output, $return);
232         if ($return == 0) {
233             return true;
234         }
235     }
236     return false;
237 }
238
239
240 ?>
241