Changeset 3757771
- Timestamp:
- 10/21/2026 06:09:32 AM (5 days ago)
- Location:
- bytcrush-image-compression
- Files:
-
- 2 added
- 2 deleted
- 22 edited
- 1 copied
-
tags/1.0.1 (copied) (copied from bytcrush-image-compression/trunk )
-
tags/1.0.1/assets/js/admin.js (modified) (1 diff)
-
tags/1.0.1/assets/js/background.js (modified) (1 diff)
-
tags/1.0.1/bytcrush-image-compression.php (modified) (2 diffs)
-
tags/1.0.1/includes/Admin/Admin.php (modified) (2 diffs)
-
tags/1.0.1/includes/Engine/Background.php (modified) (8 diffs)
-
tags/1.0.1/includes/Engine/Compressor.php (modified) (2 diffs)
-
tags/1.0.1/includes/Engine/Driver/ImagickDriver.php (modified) (2 diffs)
-
tags/1.0.1/includes/Engine/Optimizer.php (modified) (2 diffs)
-
tags/1.0.1/includes/Engine/Resources.php (added)
-
tags/1.0.1/includes/Rest/Controller.php (modified) (3 diffs)
-
tags/1.0.1/languages/bytcrush-image-compression.pot (modified) (11 diffs)
-
tags/1.0.1/readme.txt (modified) (2 diffs)
-
tags/1.0.1/real-pc.csv (deleted)
-
trunk/assets/js/admin.js (modified) (1 diff)
-
trunk/assets/js/background.js (modified) (1 diff)
-
trunk/bytcrush-image-compression.php (modified) (2 diffs)
-
trunk/includes/Admin/Admin.php (modified) (2 diffs)
-
trunk/includes/Engine/Background.php (modified) (8 diffs)
-
trunk/includes/Engine/Compressor.php (modified) (2 diffs)
-
trunk/includes/Engine/Driver/ImagickDriver.php (modified) (2 diffs)
-
trunk/includes/Engine/Optimizer.php (modified) (2 diffs)
-
trunk/includes/Engine/Resources.php (added)
-
trunk/includes/Rest/Controller.php (modified) (3 diffs)
-
trunk/languages/bytcrush-image-compression.pot (modified) (11 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/real-pc.csv (deleted)
Legend:
- Unmodified
- Added
- Removed
-
bytcrush-image-compression/tags/1.0.1/assets/js/admin.js
r3753311 r3757771 321 321 return; 322 322 } 323 if ( res.cooldown > 0 ) { 324 // The server is overloaded: give it a break, then carry on. 325 sub.textContent = i18n.cooling; 326 for ( var waited = 0; waited < res.cooldown && run.active; waited++ ) { 327 await sleep( 1000 ); 328 } 329 if ( run.active ) { 330 sub.textContent = i18n.subRunning; 331 } 332 } 323 333 } catch ( err ) { 324 334 failures++; -
bytcrush-image-compression/tags/1.0.1/assets/js/background.js
r3753311 r3757771 89 89 } 90 90 91 if ( res.stalled ) { 91 if ( res.cooldown > 0 ) { 92 // Server overloaded: the run is backing off and resumes by itself. 93 d.setSub( t.subCooling ); 94 await sleep( Math.min( res.cooldown, 10 ) * 1000 ); 95 } else if ( res.stalled ) { 92 96 // Loopback blocked or chain broken: this tab does the work. 93 97 d.setSub( t.subFallback ); -
bytcrush-image-compression/tags/1.0.1/bytcrush-image-compression.php
r3753311 r3757771 5 5 * Plugin URI: https://warcal.dev/wordpress/plugins/bytcrush-image-compression 6 6 * Description: Optimize every image in your Media Library in one click with the BytCrush compression engine. Originals are always kept safe, and WebP/AVIF versions are served automatically. 7 * Version: 1.0. 07 * Version: 1.0.1 8 8 * Requires at least: 5.3 9 9 * Requires PHP: 7.2 … … 41 41 * --------------------------------------------------------------------------- 42 42 */ 43 define( 'BYTC_VERSION', '1.0. 0' );43 define( 'BYTC_VERSION', '1.0.1' ); 44 44 define( 'BYTC_FILE', __FILE__ ); 45 45 define( 'BYTC_DIR', plugin_dir_path( __FILE__ ) ); -
bytcrush-image-compression/tags/1.0.1/includes/Admin/Admin.php
r3753311 r3757771 157 157 'subBackground' => __( 'Running on your server. You can close this tab and come back any time.', 'bytcrush-image-compression' ), 158 158 'subFallback' => __( 'Your server is blocking background requests, so this tab is doing the work. Keep it open to continue.', 'bytcrush-image-compression' ), 159 'subCooling' => __( 'Your server is busy right now, so BytCrush is taking a short break to keep your site fast. It continues automatically.', 'bytcrush-image-compression' ), 159 160 ), 160 161 ) ); … … 230 231 'unsaved' => __( 'You have unsaved changes', 'bytcrush-image-compression' ), 231 232 'saveFailed' => __( 'Could not save settings', 'bytcrush-image-compression' ), 233 'cooling' => __( 'Your server is busy right now, so BytCrush is taking a short break to keep your site fast. It continues automatically.', 'bytcrush-image-compression' ), 232 234 'leaveWarning' => __( 'BytCrush is still optimizing. Leaving will pause the run.', 'bytcrush-image-compression' ), 233 235 ); -
bytcrush-image-compression/tags/1.0.1/includes/Engine/Background.php
r3753311 r3757771 101 101 'halted' => '', 102 102 'finished' => 0, 103 'resume_at' => 0, 103 104 ) 104 105 ); … … 128 129 'halted' => '', 129 130 'finished' => 0, 131 'resume_at' => 0, 130 132 ) 131 133 ); … … 216 218 return; 217 219 } 218 if ( self::stalled() ) { 220 $state = self::state(); 221 if ( self::stalled() || ( $state['resume_at'] && ! self::cooling() && ! Lock::since( self::LOCK ) ) ) { 219 222 self::work( true ); 220 223 } … … 227 230 */ 228 231 public static function stalled() { 232 if ( self::cooling() ) { 233 return false; // Deliberate back-off, not a broken chain. 234 } 229 235 $locked = Lock::since( self::LOCK ); 230 236 if ( $locked && ( time() - $locked ) < 5 * MINUTE_IN_SECONDS ) { … … 241 247 */ 242 248 public static function work( $chain ) { 243 if ( ! self::running() || ! Lock::acquire( self::LOCK, 5 * MINUTE_IN_SECONDS ) ) { 249 // Cooling down after an overloaded batch: the cron job resumes the run. 250 if ( ! self::running() || self::cooling() || ! Lock::acquire( self::LOCK, 5 * MINUTE_IN_SECONDS ) ) { 244 251 return array(); 245 252 } 246 253 247 $items = array(); 248 $halted = ''; 254 $items = array(); 255 $halted = ''; 256 $cooldown = 0; 249 257 250 258 try { 251 259 self::beat(); 252 $budget = (float) apply_filters( 'bytcrush_bulk_time_budget', 20 );253 $start = microtime( true );254 260 $seen = array(); 255 261 $streak = 0; 256 262 257 do { 258 if ( ! self::running() ) { 259 break; 260 } 261 $ids = Stats::pending_ids( 1, $seen ); 262 if ( ! $ids ) { 263 break; 264 } 265 $seen[] = $ids[0]; 266 $result = Optimizer::optimize( $ids[0] ); 267 $items[] = self::record( Controller::item( $ids[0], $result ) ); 268 269 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 270 if ( $streak >= 10 ) { 271 $halted = $result['message']; 272 break; 273 } 274 } while ( microtime( true ) - $start < $budget ); 263 // Time-boxed to the host's limits and paced by server load. 264 $batch = Resources::batch( 265 static function () use ( &$seen ) { 266 $ids = Stats::pending_ids( 1, $seen ); 267 return $ids ? $ids[0] : null; 268 }, 269 static function ( $id ) use ( &$seen, &$items, &$streak, &$halted ) { 270 $seen[] = $id; 271 $result = Optimizer::optimize( $id ); 272 $items[] = self::record( Controller::item( $id, $result ) ); 273 274 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 275 if ( $streak >= 10 ) { 276 $halted = $result['message']; 277 return true; 278 } 279 return false; 280 }, 281 array( __CLASS__, 'running' ) 282 ); 283 $cooldown = $batch['cooldown']; 275 284 } finally { 276 285 Lock::release( self::LOCK ); … … 281 290 } elseif ( ! Stats::pending_ids( 1 ) ) { 282 291 self::stop(); 292 } elseif ( $cooldown ) { 293 // Server overloaded: back off; cron (or the open tab) resumes after. 294 $state = self::state(); 295 $state['resume_at'] = time() + $cooldown; 296 $state['beat'] = time(); 297 self::save_state( $state ); 283 298 } elseif ( $chain && self::running() ) { 284 299 self::dispatch(); … … 286 301 287 302 return $items; 303 } 304 305 /** 306 * Whether the run is backing off because the server was overloaded. 307 * 308 * @return bool 309 */ 310 public static function cooling() { 311 return (int) self::state()['resume_at'] > time(); 288 312 } 289 313 … … 301 325 'running' => $running, 302 326 'stalled' => $running && self::stalled(), 327 'cooldown' => $running ? max( 0, (int) $state['resume_at'] - time() ) : 0, 303 328 'processed' => (int) $state['processed'], 304 329 'elapsed' => $state['started'] ? max( 1, time() - (int) $state['started'] ) : 0, -
bytcrush-image-compression/tags/1.0.1/includes/Engine/Compressor.php
r3753311 r3757771 164 164 sprintf( __( 'This server cannot create %s images.', 'bytcrush-image-compression' ), strtoupper( $format ) ) 165 165 ); 166 } 167 168 // Check memory before decoding: GD holds every pixel in PHP memory, and a 169 // huge image would kill the request. Imagick works outside PHP's limit 170 // (with its own capped budget that spills to disk), so hand it over; 171 // with no alternative, skip the image instead of crashing. 172 if ( GdDriver::class === $class ) { 173 $size = @getimagesize( $src ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- header read only. 174 if ( $size ) { 175 list( $target_w, $target_h ) = self::fitted_size( (int) $size[0], (int) $size[1], (int) $o['max_width'], (int) $o['max_height'] ); 176 $needed = Resources::gd_memory_needed( (int) $size[0], (int) $size[1], $target_w, $target_h ); 177 $available = Resources::memory_available(); 178 if ( $needed > $available ) { 179 if ( ImagickDriver::available() && ImagickDriver::can_encode( $format ) ) { 180 $class = ImagickDriver::class; 181 } else { 182 return new \WP_Error( 183 'bytcrush_too_large', 184 sprintf( 185 /* translators: 1: image size in megapixels, 2: memory needed, 3: memory available. */ 186 __( 'Skipped to keep your site safe: this %1$s MP image needs about %2$s of memory and the server allows %3$s.', 'bytcrush-image-compression' ), 187 number_format_i18n( $size[0] * $size[1] / 1000000, 1 ), 188 size_format( $needed ), 189 size_format( $available ) 190 ) 191 ); 192 } 193 } 194 } 166 195 } 167 196 … … 270 299 */ 271 300 private static function fit( Driver $driver, $max_width, $max_height ) { 272 if ( $max_width <= 0 && $max_height <= 0 ) { 301 list( $width, $height ) = self::fitted_size( $driver->width(), $driver->height(), $max_width, $max_height ); 302 if ( $width === $driver->width() && $height === $driver->height() ) { 273 303 return false; 274 304 } 275 $w = $driver->width(); 276 $h = $driver->height(); 305 $driver->resize( $width, $height ); 306 return true; 307 } 308 309 /** 310 * Dimensions after fitting inside a box (unchanged when already inside). 311 * 312 * @param int $w Width. 313 * @param int $h Height. 314 * @param int $max_width Max width (0 = unbounded). 315 * @param int $max_height Max height (0 = unbounded). 316 * @return int[] { width, height } 317 */ 318 private static function fitted_size( $w, $h, $max_width, $max_height ) { 319 if ( ( $max_width <= 0 && $max_height <= 0 ) || $w < 1 || $h < 1 ) { 320 return array( $w, $h ); 321 } 277 322 $box_w = $max_width > 0 ? $max_width : $w; 278 323 $box_h = $max_height > 0 ? $max_height : $h; 279 324 if ( $w <= $box_w && $h <= $box_h ) { 280 return false;325 return array( $w, $h ); 281 326 } 282 327 $ratio = min( $box_w / $w, $box_h / $h ); 283 $driver->resize( max( 1, (int) round( $w * $ratio ) ), max( 1, (int) round( $h * $ratio ) ) ); 284 return true; 328 return array( max( 1, (int) round( $w * $ratio ) ), max( 1, (int) round( $h * $ratio ) ) ); 285 329 } 286 330 -
bytcrush-image-compression/tags/1.0.1/includes/Engine/Driver/ImagickDriver.php
r3753311 r3757771 54 54 } 55 55 56 self::limit_resources(); 56 57 $im = new \Imagick(); 57 58 $im->readImage( $path ); … … 63 64 64 65 $this->im = $im; 66 } 67 68 /** 69 * Keep ImageMagick within what the server can spare. Its memory is not 70 * counted against PHP's memory_limit and it uses every CPU core by default, 71 * which on shared hosting can get the process throttled or killed. Cap it 72 * to one thread (as WordPress core does) and to a memory budget that 73 * matches PHP's; above that, its pixel cache spills to disk instead. 74 * 75 * @return void 76 */ 77 private static function limit_resources() { 78 static $done = false; 79 if ( $done || ! method_exists( '\Imagick', 'setResourceLimit' ) ) { 80 return; 81 } 82 $done = true; 83 84 /** 85 * Threads ImageMagick may use per image (0 = leave its default). 86 * 87 * @param int $threads Threads. 88 */ 89 $threads = (int) apply_filters( 'bytcrush_imagick_threads', 1 ); 90 if ( $threads > 0 && defined( '\Imagick::RESOURCETYPE_THREAD' ) ) { 91 \Imagick::setResourceLimit( \Imagick::RESOURCETYPE_THREAD, $threads ); 92 } 93 94 $memory = (int) min( 256 * MB_IN_BYTES, max( 32 * MB_IN_BYTES, \BytCrush\Engine\Resources::memory_available() ) ); 95 if ( defined( '\Imagick::RESOURCETYPE_MEMORY' ) ) { 96 \Imagick::setResourceLimit( \Imagick::RESOURCETYPE_MEMORY, $memory ); 97 } 98 if ( defined( '\Imagick::RESOURCETYPE_MAP' ) ) { 99 \Imagick::setResourceLimit( \Imagick::RESOURCETYPE_MAP, $memory * 2 ); 100 } 65 101 } 66 102 -
bytcrush-image-compression/tags/1.0.1/includes/Engine/Optimizer.php
r3753311 r3757771 277 277 278 278 if ( is_wp_error( $res ) ) { 279 // Animated GIF/PNG: skip the image (as the WebP step does), don't flag an error.280 if ( 'bytcrush_animated' === $res->get_error_code() ) {279 // Animated, or too large for this server's memory: skip the image with the reason, don't flag an error. 280 if ( in_array( $res->get_error_code(), array( 'bytcrush_animated', 'bytcrush_too_large' ), true ) ) { 281 281 return self::finish( $id, 'skipped', $res->get_error_message(), $meta ); 282 282 } … … 341 341 342 342 if ( is_wp_error( $bytes ) ) { 343 if ( 'bytcrush_animated' === $bytes->get_error_code() ) {343 if ( in_array( $bytes->get_error_code(), array( 'bytcrush_animated', 'bytcrush_too_large' ), true ) ) { 344 344 return self::finish( $id, 'skipped', $bytes->get_error_message(), $meta ); 345 345 } -
bytcrush-image-compression/tags/1.0.1/includes/Rest/Controller.php
r3753311 r3757771 18 18 use BytCrush\Engine\Backup; 19 19 use BytCrush\Engine\Optimizer; 20 use BytCrush\Engine\Resources; 20 21 use BytCrush\Engine\Stats; 21 22 use WP_REST_Request; … … 164 165 bytcrush_keep_running(); 165 166 166 /**167 * Seconds of work per bulk request. Keep well under the host's168 * max_execution_time / proxy timeout.169 *170 * @param float $seconds Budget.171 */172 $budget = (float) apply_filters( 'bytcrush_bulk_time_budget', 20 );173 $start = microtime( true );174 167 $items = array(); 175 168 $seen = array(); … … 177 170 $halted = ''; 178 171 179 do { 180 $ids = Stats::pending_ids( 1, $seen ); 181 if ( ! $ids ) { 182 break; 172 // Time-boxed to the host's limits and paced by server load. 173 $batch = Resources::batch( 174 static function () use ( &$seen ) { 175 $ids = Stats::pending_ids( 1, $seen ); 176 return $ids ? $ids[0] : null; 177 }, 178 static function ( $id ) use ( &$seen, &$items, &$streak, &$halted ) { 179 $seen[] = $id; 180 $result = Optimizer::optimize( $id ); 181 $items[] = self::item( $id, $result ); 182 183 // A run of consecutive failures means a site-wide problem (disk 184 // full, permissions): stop instead of failing the whole library. 185 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 186 if ( $streak >= 10 ) { 187 $halted = $result['message']; 188 return true; 189 } 190 return false; 183 191 } 184 $seen[] = $ids[0]; 185 $result = Optimizer::optimize( $ids[0] ); 186 $items[] = self::item( $ids[0], $result ); 187 188 // A run of consecutive failures means a site-wide problem (disk 189 // full, permissions): stop instead of failing the whole library. 190 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 191 if ( $streak >= 10 ) { 192 $halted = $result['message']; 193 break; 194 } 195 } while ( microtime( true ) - $start < $budget ); 196 197 return rest_ensure_response( 198 array( 199 'items' => $items, 200 'stats' => Stats::get(), 201 'done' => ! Stats::pending_ids( 1 ), 202 'halted' => $halted, 192 ); 193 194 return rest_ensure_response( 195 array( 196 'items' => $items, 197 'stats' => Stats::get(), 198 'done' => ! Stats::pending_ids( 1 ), 199 'halted' => $halted, 200 'cooldown' => $batch['cooldown'], 203 201 ) 204 202 ); -
bytcrush-image-compression/tags/1.0.1/languages/bytcrush-image-compression.pot
r3753311 r3757771 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: BytCrush Image Compression 1.0. 0\n"5 "Project-Id-Version: BytCrush Image Compression 1.0.1\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/app\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-10- 14T15:53:40+00:00\n"12 "POT-Creation-Date: 2026-10-21T04:14:25+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.12.0\n" … … 17 17 #. Plugin Name of the plugin 18 18 #: bytcrush-image-compression.php 19 #: includes/Admin/Admin.php:6 119 #: includes/Admin/Admin.php:62 20 20 #: views/dashboard.php:46 21 21 msgid "BytCrush Image Compression" … … 48 48 msgstr "" 49 49 50 #: includes/Admin/Admin.php:6 250 #: includes/Admin/Admin.php:63 51 51 #: includes/Admin/MediaColumn.php:38 52 52 #: includes/Admin/MediaColumn.php:65 … … 54 54 msgstr "" 55 55 56 #: includes/Admin/Admin.php:6 856 #: includes/Admin/Admin.php:69 57 57 #: views/header.php:15 58 58 msgid "Dashboard" 59 59 msgstr "" 60 60 61 #: includes/Admin/Admin.php:7 161 #: includes/Admin/Admin.php:72 62 62 #: views/header.php:20 63 63 msgid "Page audit" 64 64 msgstr "" 65 65 66 #: includes/Admin/Admin.php:7 367 #: includes/Admin/Admin.php: 29266 #: includes/Admin/Admin.php:74 67 #: includes/Admin/Admin.php:301 68 68 #: views/header.php:16 69 69 msgid "Settings" 70 70 msgstr "" 71 71 72 #: includes/Admin/Admin.php:7 672 #: includes/Admin/Admin.php:77 73 73 #: views/pro.php:33 74 74 msgid "BytCrush Pro" 75 75 msgstr "" 76 76 77 #: includes/Admin/Admin.php:7 678 #: includes/Admin/Admin.php: 29577 #: includes/Admin/Admin.php:77 78 #: includes/Admin/Admin.php:304 79 79 #: views/header.php:23 80 80 msgid "Go Pro" 81 81 msgstr "" 82 82 83 #: includes/Admin/Admin.php:13 383 #: includes/Admin/Admin.php:135 84 84 msgid "Running on your server. You can close this tab and come back any time." 85 85 msgstr "" 86 86 87 #: includes/Admin/Admin.php:13 487 #: includes/Admin/Admin.php:136 88 88 msgid "Your server is blocking background requests, so this tab is doing the work. Keep it open to continue." 89 89 msgstr "" 90 90 91 #. translators: %s: number of images. 91 #: includes/Admin/Admin.php:137 92 #: includes/Admin/Admin.php:201 93 msgid "Your server is busy right now, so BytCrush is taking a short break to keep your site fast. It continues automatically." 94 msgstr "" 95 92 96 #: includes/Admin/Admin.php:179 93 #: views/dashboard.php:34 94 #, php-format 95 msgid "Optimize %s images in one click" 97 msgid "Crushing bytes…" 96 98 msgstr "" 97 99 98 100 #: includes/Admin/Admin.php:180 99 msgid " Crushing bytes…"101 msgid "Paused" 100 102 msgstr "" 101 103 102 104 #: includes/Admin/Admin.php:181 103 msgid "Paused"104 msgstr ""105 106 #: includes/Admin/Admin.php:182107 105 #: views/dashboard.php:35 108 106 msgid "Your library is fully optimized" 109 107 msgstr "" 110 108 111 #. translators: %s: number of images. 112 #: includes/Admin/Admin.php:184 113 #: views/dashboard.php:32 114 #, php-format 115 msgid "%s images could not be optimized" 116 msgstr "" 117 118 #: includes/Admin/Admin.php:185 109 #: includes/Admin/Admin.php:182 119 110 #: views/dashboard.php:39 120 111 msgid "The reasons are listed below. Fix the cause, then press Retry." 121 112 msgstr "" 122 113 114 #: includes/Admin/Admin.php:183 115 msgid "Paused after 10 failures in a row. " 116 msgstr "" 117 118 #: includes/Admin/Admin.php:184 119 msgid "Restoring originals…" 120 msgstr "" 121 122 #: includes/Admin/Admin.php:185 123 msgid "Keep this tab open. You can pause at any time and pick up where you left off." 124 msgstr "" 125 123 126 #: includes/Admin/Admin.php:186 124 msgid "Paused after 10 failures in a row. "125 msgstr ""126 127 #: includes/Admin/Admin.php:187128 msgid "Restoring originals…"129 msgstr ""130 131 #: includes/Admin/Admin.php:188132 msgid "Keep this tab open. You can pause at any time and pick up where you left off."133 msgstr ""134 135 #. translators: %s: number of images.136 #: includes/Admin/Admin.php:190137 #, php-format138 msgid "%s images left. Resume whenever you are ready."139 msgstr ""140 141 #: includes/Admin/Admin.php:191142 127 #: views/dashboard.php:41 143 128 msgid "New uploads are optimized automatically. Originals are safe in backups." 144 129 msgstr "" 145 130 146 #: includes/Admin/Admin.php:1 92131 #: includes/Admin/Admin.php:187 147 132 #: views/dashboard.php:93 148 133 msgid "Optimize all images" 149 134 msgstr "" 150 135 151 #: includes/Admin/Admin.php:1 93136 #: includes/Admin/Admin.php:188 152 137 msgid "Pause" 153 138 msgstr "" 154 139 155 #: includes/Admin/Admin.php:1 94140 #: includes/Admin/Admin.php:189 156 141 msgid "Resume" 157 142 msgstr "" 158 143 159 #: includes/Admin/Admin.php:19 5144 #: includes/Admin/Admin.php:190 160 145 #: views/dashboard.php:93 161 146 msgid "All images optimized" 162 147 msgstr "" 163 148 164 #. translators: 1: processed count, 2: total count. 165 #: includes/Admin/Admin.php:197 166 #, php-format 167 msgid "%1$s of %2$s images" 168 msgstr "" 169 170 #. translators: %s: time, e.g. 2m 10s. 171 #: includes/Admin/Admin.php:199 172 #, php-format 173 msgid "about %s left" 174 msgstr "" 175 176 #: includes/Admin/Admin.php:200 149 #: includes/Admin/Admin.php:191 177 150 #: includes/Admin/MediaColumn.php:128 178 151 #: views/dashboard.php:220 … … 180 153 msgstr "" 181 154 182 #: includes/Admin/Admin.php: 201155 #: includes/Admin/Admin.php:192 183 156 #: includes/Admin/MediaColumn.php:128 184 157 msgid "Skipped" 185 158 msgstr "" 186 159 187 #: includes/Admin/Admin.php: 202160 #: includes/Admin/Admin.php:193 188 161 #: includes/Admin/MediaColumn.php:141 189 162 msgid "Optimizing…" 190 163 msgstr "" 191 164 192 #: includes/Admin/Admin.php: 203165 #: includes/Admin/Admin.php:194 193 166 msgid "Restoring…" 194 167 msgstr "" 195 168 169 #: includes/Admin/Admin.php:195 170 msgid "The server stopped responding. The bulk run has been paused. Press Resume to continue." 171 msgstr "" 172 173 #: includes/Admin/Admin.php:196 174 msgid "Restore the original image? The optimized version and its WebP/AVIF copies will be removed." 175 msgstr "" 176 177 #: includes/Admin/Admin.php:197 178 msgid "Restore ALL original images? Every optimized image goes back to its original file and all WebP/AVIF copies are removed. This can take a while." 179 msgstr "" 180 181 #: includes/Admin/Admin.php:198 182 msgid "Settings saved" 183 msgstr "" 184 185 #: includes/Admin/Admin.php:199 186 msgid "You have unsaved changes" 187 msgstr "" 188 189 #: includes/Admin/Admin.php:200 190 msgid "Could not save settings" 191 msgstr "" 192 193 #: includes/Admin/Admin.php:202 194 msgid "BytCrush is still optimizing. Leaving will pause the run." 195 msgstr "" 196 197 #. translators: %s: number of images. 198 #: includes/Admin/Admin.php:205 199 #: views/dashboard.php:34 200 #, php-format 201 msgid "Optimize %s images in one click" 202 msgstr "" 203 204 #. translators: %s: number of images. 205 #: includes/Admin/Admin.php:207 206 #: views/dashboard.php:32 207 #, php-format 208 msgid "%s images could not be optimized" 209 msgstr "" 210 211 #. translators: %s: number of images. 212 #: includes/Admin/Admin.php:209 213 #, php-format 214 msgid "%s images left. Resume whenever you are ready." 215 msgstr "" 216 217 #. translators: 1: processed count, 2: total count. 218 #: includes/Admin/Admin.php:211 219 #, php-format 220 msgid "%1$s of %2$s images" 221 msgstr "" 222 223 #. translators: %s: time, e.g. 2m 10s. 224 #: includes/Admin/Admin.php:213 225 #, php-format 226 msgid "about %s left" 227 msgstr "" 228 196 229 #. translators: 1: bytes saved, 2: image count. 197 #: includes/Admin/Admin.php:2 05230 #: includes/Admin/Admin.php:215 198 231 #, php-format 199 232 msgid "Done! You saved %1$s across %2$s images." 200 233 msgstr "" 201 234 202 #: includes/Admin/Admin.php:206 203 msgid "The server stopped responding. The bulk run has been paused. Press Resume to continue." 204 msgstr "" 205 206 #: includes/Admin/Admin.php:207 207 msgid "Restore the original image? The optimized version and its WebP/AVIF copies will be removed." 208 msgstr "" 209 210 #: includes/Admin/Admin.php:208 211 msgid "Restore ALL original images? Every optimized image goes back to its original file and all WebP/AVIF copies are removed. This can take a while." 212 msgstr "" 213 214 #. translators: %s: number of images. 215 #: includes/Admin/Admin.php:210 235 #. translators: %s: number of images. 236 #: includes/Admin/Admin.php:217 216 237 #, php-format 217 238 msgid "Restored %s images to their originals." … … 219 240 220 241 #. translators: %s: number of images. 221 #: includes/Admin/Admin.php:21 2242 #: includes/Admin/Admin.php:219 222 243 #, php-format 223 244 msgid "%s failed images were put back in the queue." 224 245 msgstr "" 225 246 226 #: includes/Admin/Admin.php:213 227 msgid "Settings saved" 228 msgstr "" 229 230 #: includes/Admin/Admin.php:214 231 msgid "You have unsaved changes" 232 msgstr "" 233 234 #: includes/Admin/Admin.php:215 235 msgid "Could not save settings" 236 msgstr "" 237 238 #: includes/Admin/Admin.php:216 239 msgid "BytCrush is still optimizing. Leaving will pause the run." 240 msgstr "" 241 242 #: includes/Admin/Admin.php:261 247 #: includes/Admin/Admin.php:270 243 248 msgid "BytCrush is ready." 244 249 msgstr "" 245 250 246 251 #. translators: %s: number of images. 247 #: includes/Admin/Admin.php:2 63252 #: includes/Admin/Admin.php:272 248 253 #, php-format 249 254 msgid "%s image in your Media Library can be optimized in one click. Originals are kept safe." … … 252 257 msgstr[1] "" 253 258 254 #: includes/Admin/Admin.php:2 65259 #: includes/Admin/Admin.php:274 255 260 msgid "Optimize my images" 256 261 msgstr "" 257 262 258 #: includes/Admin/Admin.php:2 67263 #: includes/Admin/Admin.php:276 259 264 msgid "Dismiss" 260 265 msgstr "" 261 266 262 #: includes/Admin/Admin.php: 291267 #: includes/Admin/Admin.php:300 263 268 msgid "Optimize" 264 269 msgstr "" 265 270 266 #: includes/Admin/Admin.php:3 34271 #: includes/Admin/Admin.php:343 267 272 msgid "picture tags" 268 273 msgstr "" 269 274 270 #: includes/Admin/Admin.php:3 35275 #: includes/Admin/Admin.php:344 271 276 msgid "server rewrite" 272 277 msgstr "" 273 278 274 #: includes/Admin/Admin.php:3 36279 #: includes/Admin/Admin.php:345 275 280 msgid "not served" 276 281 msgstr "" 277 282 278 #: includes/Admin/Admin.php:3 41283 #: includes/Admin/Admin.php:350 279 284 msgid "Compression" 280 285 msgstr "" 281 286 282 #: includes/Admin/Admin.php:3 43287 #: includes/Admin/Admin.php:352 283 288 #: views/settings.php:99 284 289 msgid "Next-gen formats" … … 286 291 287 292 #. translators: 1: width, 2: height. 288 #: includes/Admin/Admin.php:344289 #: includes/Admin/Admin.php:350290 293 #: includes/Admin/Admin.php:353 291 #: includes/Admin/Admin.php:354 292 #: includes/Admin/Admin.php:355 293 #: includes/Admin/Admin.php:356 294 #: includes/Admin/Admin.php:359 295 #: includes/Admin/Admin.php:362 296 #: includes/Admin/Admin.php:363 297 #: includes/Admin/Admin.php:364 298 #: includes/Admin/Admin.php:365 294 299 #: views/settings.php:121 295 300 msgid "Off" 296 301 msgstr "" 297 302 298 #: includes/Admin/Admin.php:3 48303 #: includes/Admin/Admin.php:357 299 304 msgid "Resize large images" 300 305 msgstr "" 301 306 302 307 #. translators: 1: width, 2: height. 303 #: includes/Admin/Admin.php:35 0308 #: includes/Admin/Admin.php:359 304 309 #, php-format 305 310 msgid "Max %1$s × %2$s px" 306 311 msgstr "" 307 312 308 #: includes/Admin/Admin.php:3 53313 #: includes/Admin/Admin.php:362 309 314 #: views/settings.php:156 310 315 msgid "Strip metadata" 311 316 msgstr "" 312 317 313 #: includes/Admin/Admin.php:3 53318 #: includes/Admin/Admin.php:362 314 319 msgid "On, colour profile kept" 315 320 msgstr "" 316 321 317 #: includes/Admin/Admin.php:3 54322 #: includes/Admin/Admin.php:363 318 323 msgid "Auto-optimize uploads" 319 324 msgstr "" 320 325 321 #: includes/Admin/Admin.php:3 54322 #: includes/Admin/Admin.php:3 55323 #: includes/Admin/Admin.php:3 56326 #: includes/Admin/Admin.php:363 327 #: includes/Admin/Admin.php:364 328 #: includes/Admin/Admin.php:365 324 329 msgid "On" 325 330 msgstr "" 326 331 327 #: includes/Admin/Admin.php:3 55332 #: includes/Admin/Admin.php:364 328 333 msgid "Keep originals" 329 334 msgstr "" 330 335 331 #: includes/Admin/Admin.php:3 56336 #: includes/Admin/Admin.php:365 332 337 msgid "Background runs" 333 338 msgstr "" … … 390 395 msgstr "" 391 396 392 #: includes/Engine/Background.php:4 00397 #: includes/Engine/Background.php:425 393 398 msgid "Every minute (BytCrush)" 394 399 msgstr "" … … 445 450 msgstr "" 446 451 447 #: includes/Engine/Compressor.php:222 452 #. translators: 1: image size in megapixels, 2: memory needed, 3: memory available. 453 #: includes/Engine/Compressor.php:186 454 #, php-format 455 msgid "Skipped to keep your site safe: this %1$s MP image needs about %2$s of memory and the server allows %3$s." 456 msgstr "" 457 458 #: includes/Engine/Compressor.php:251 448 459 msgid "The encoder returned an empty image." 449 460 msgstr "" 450 461 451 462 #. translators: %s: file name. 452 #: includes/Engine/Compressor.php:2 47463 #: includes/Engine/Compressor.php:276 453 464 #, php-format 454 465 msgid "Could not write %s. Check the uploads folder permissions." 455 466 msgstr "" 456 467 457 #: includes/Engine/Compressor.php:2 54468 #: includes/Engine/Compressor.php:283 458 469 msgid "Animated images are left untouched." 459 470 msgstr "" -
bytcrush-image-compression/tags/1.0.1/readme.txt
r3753311 r3757771 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.2 7 Stable tag: 1.0. 07 Stable tag: 1.0.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 88 88 == Changelog == 89 89 90 = 1.0.1 = 91 * Server-friendly by design: BytCrush now checks how much memory an image needs before opening it, and skips images that are too large for the server (or hands them to ImageMagick) instead of risking an error. 92 * Batches adapt to your host's time limit and proxy timeouts, and never start an image that could not finish in time. 93 * Optimization is paced by server load: it pauses between images when the server is busy, backs off when it is overloaded, and uses at most half the CPU when the load cannot be measured, so visitors keep priority. 94 * ImageMagick is limited to one thread and a memory budget that matches PHP's. 95 90 96 = 1.0.0 = 91 97 * Initial release. -
bytcrush-image-compression/trunk/assets/js/admin.js
r3753311 r3757771 321 321 return; 322 322 } 323 if ( res.cooldown > 0 ) { 324 // The server is overloaded: give it a break, then carry on. 325 sub.textContent = i18n.cooling; 326 for ( var waited = 0; waited < res.cooldown && run.active; waited++ ) { 327 await sleep( 1000 ); 328 } 329 if ( run.active ) { 330 sub.textContent = i18n.subRunning; 331 } 332 } 323 333 } catch ( err ) { 324 334 failures++; -
bytcrush-image-compression/trunk/assets/js/background.js
r3753311 r3757771 89 89 } 90 90 91 if ( res.stalled ) { 91 if ( res.cooldown > 0 ) { 92 // Server overloaded: the run is backing off and resumes by itself. 93 d.setSub( t.subCooling ); 94 await sleep( Math.min( res.cooldown, 10 ) * 1000 ); 95 } else if ( res.stalled ) { 92 96 // Loopback blocked or chain broken: this tab does the work. 93 97 d.setSub( t.subFallback ); -
bytcrush-image-compression/trunk/bytcrush-image-compression.php
r3753311 r3757771 5 5 * Plugin URI: https://warcal.dev/wordpress/plugins/bytcrush-image-compression 6 6 * Description: Optimize every image in your Media Library in one click with the BytCrush compression engine. Originals are always kept safe, and WebP/AVIF versions are served automatically. 7 * Version: 1.0. 07 * Version: 1.0.1 8 8 * Requires at least: 5.3 9 9 * Requires PHP: 7.2 … … 41 41 * --------------------------------------------------------------------------- 42 42 */ 43 define( 'BYTC_VERSION', '1.0. 0' );43 define( 'BYTC_VERSION', '1.0.1' ); 44 44 define( 'BYTC_FILE', __FILE__ ); 45 45 define( 'BYTC_DIR', plugin_dir_path( __FILE__ ) ); -
bytcrush-image-compression/trunk/includes/Admin/Admin.php
r3753311 r3757771 157 157 'subBackground' => __( 'Running on your server. You can close this tab and come back any time.', 'bytcrush-image-compression' ), 158 158 'subFallback' => __( 'Your server is blocking background requests, so this tab is doing the work. Keep it open to continue.', 'bytcrush-image-compression' ), 159 'subCooling' => __( 'Your server is busy right now, so BytCrush is taking a short break to keep your site fast. It continues automatically.', 'bytcrush-image-compression' ), 159 160 ), 160 161 ) ); … … 230 231 'unsaved' => __( 'You have unsaved changes', 'bytcrush-image-compression' ), 231 232 'saveFailed' => __( 'Could not save settings', 'bytcrush-image-compression' ), 233 'cooling' => __( 'Your server is busy right now, so BytCrush is taking a short break to keep your site fast. It continues automatically.', 'bytcrush-image-compression' ), 232 234 'leaveWarning' => __( 'BytCrush is still optimizing. Leaving will pause the run.', 'bytcrush-image-compression' ), 233 235 ); -
bytcrush-image-compression/trunk/includes/Engine/Background.php
r3753311 r3757771 101 101 'halted' => '', 102 102 'finished' => 0, 103 'resume_at' => 0, 103 104 ) 104 105 ); … … 128 129 'halted' => '', 129 130 'finished' => 0, 131 'resume_at' => 0, 130 132 ) 131 133 ); … … 216 218 return; 217 219 } 218 if ( self::stalled() ) { 220 $state = self::state(); 221 if ( self::stalled() || ( $state['resume_at'] && ! self::cooling() && ! Lock::since( self::LOCK ) ) ) { 219 222 self::work( true ); 220 223 } … … 227 230 */ 228 231 public static function stalled() { 232 if ( self::cooling() ) { 233 return false; // Deliberate back-off, not a broken chain. 234 } 229 235 $locked = Lock::since( self::LOCK ); 230 236 if ( $locked && ( time() - $locked ) < 5 * MINUTE_IN_SECONDS ) { … … 241 247 */ 242 248 public static function work( $chain ) { 243 if ( ! self::running() || ! Lock::acquire( self::LOCK, 5 * MINUTE_IN_SECONDS ) ) { 249 // Cooling down after an overloaded batch: the cron job resumes the run. 250 if ( ! self::running() || self::cooling() || ! Lock::acquire( self::LOCK, 5 * MINUTE_IN_SECONDS ) ) { 244 251 return array(); 245 252 } 246 253 247 $items = array(); 248 $halted = ''; 254 $items = array(); 255 $halted = ''; 256 $cooldown = 0; 249 257 250 258 try { 251 259 self::beat(); 252 $budget = (float) apply_filters( 'bytcrush_bulk_time_budget', 20 );253 $start = microtime( true );254 260 $seen = array(); 255 261 $streak = 0; 256 262 257 do { 258 if ( ! self::running() ) { 259 break; 260 } 261 $ids = Stats::pending_ids( 1, $seen ); 262 if ( ! $ids ) { 263 break; 264 } 265 $seen[] = $ids[0]; 266 $result = Optimizer::optimize( $ids[0] ); 267 $items[] = self::record( Controller::item( $ids[0], $result ) ); 268 269 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 270 if ( $streak >= 10 ) { 271 $halted = $result['message']; 272 break; 273 } 274 } while ( microtime( true ) - $start < $budget ); 263 // Time-boxed to the host's limits and paced by server load. 264 $batch = Resources::batch( 265 static function () use ( &$seen ) { 266 $ids = Stats::pending_ids( 1, $seen ); 267 return $ids ? $ids[0] : null; 268 }, 269 static function ( $id ) use ( &$seen, &$items, &$streak, &$halted ) { 270 $seen[] = $id; 271 $result = Optimizer::optimize( $id ); 272 $items[] = self::record( Controller::item( $id, $result ) ); 273 274 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 275 if ( $streak >= 10 ) { 276 $halted = $result['message']; 277 return true; 278 } 279 return false; 280 }, 281 array( __CLASS__, 'running' ) 282 ); 283 $cooldown = $batch['cooldown']; 275 284 } finally { 276 285 Lock::release( self::LOCK ); … … 281 290 } elseif ( ! Stats::pending_ids( 1 ) ) { 282 291 self::stop(); 292 } elseif ( $cooldown ) { 293 // Server overloaded: back off; cron (or the open tab) resumes after. 294 $state = self::state(); 295 $state['resume_at'] = time() + $cooldown; 296 $state['beat'] = time(); 297 self::save_state( $state ); 283 298 } elseif ( $chain && self::running() ) { 284 299 self::dispatch(); … … 286 301 287 302 return $items; 303 } 304 305 /** 306 * Whether the run is backing off because the server was overloaded. 307 * 308 * @return bool 309 */ 310 public static function cooling() { 311 return (int) self::state()['resume_at'] > time(); 288 312 } 289 313 … … 301 325 'running' => $running, 302 326 'stalled' => $running && self::stalled(), 327 'cooldown' => $running ? max( 0, (int) $state['resume_at'] - time() ) : 0, 303 328 'processed' => (int) $state['processed'], 304 329 'elapsed' => $state['started'] ? max( 1, time() - (int) $state['started'] ) : 0, -
bytcrush-image-compression/trunk/includes/Engine/Compressor.php
r3753311 r3757771 164 164 sprintf( __( 'This server cannot create %s images.', 'bytcrush-image-compression' ), strtoupper( $format ) ) 165 165 ); 166 } 167 168 // Check memory before decoding: GD holds every pixel in PHP memory, and a 169 // huge image would kill the request. Imagick works outside PHP's limit 170 // (with its own capped budget that spills to disk), so hand it over; 171 // with no alternative, skip the image instead of crashing. 172 if ( GdDriver::class === $class ) { 173 $size = @getimagesize( $src ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- header read only. 174 if ( $size ) { 175 list( $target_w, $target_h ) = self::fitted_size( (int) $size[0], (int) $size[1], (int) $o['max_width'], (int) $o['max_height'] ); 176 $needed = Resources::gd_memory_needed( (int) $size[0], (int) $size[1], $target_w, $target_h ); 177 $available = Resources::memory_available(); 178 if ( $needed > $available ) { 179 if ( ImagickDriver::available() && ImagickDriver::can_encode( $format ) ) { 180 $class = ImagickDriver::class; 181 } else { 182 return new \WP_Error( 183 'bytcrush_too_large', 184 sprintf( 185 /* translators: 1: image size in megapixels, 2: memory needed, 3: memory available. */ 186 __( 'Skipped to keep your site safe: this %1$s MP image needs about %2$s of memory and the server allows %3$s.', 'bytcrush-image-compression' ), 187 number_format_i18n( $size[0] * $size[1] / 1000000, 1 ), 188 size_format( $needed ), 189 size_format( $available ) 190 ) 191 ); 192 } 193 } 194 } 166 195 } 167 196 … … 270 299 */ 271 300 private static function fit( Driver $driver, $max_width, $max_height ) { 272 if ( $max_width <= 0 && $max_height <= 0 ) { 301 list( $width, $height ) = self::fitted_size( $driver->width(), $driver->height(), $max_width, $max_height ); 302 if ( $width === $driver->width() && $height === $driver->height() ) { 273 303 return false; 274 304 } 275 $w = $driver->width(); 276 $h = $driver->height(); 305 $driver->resize( $width, $height ); 306 return true; 307 } 308 309 /** 310 * Dimensions after fitting inside a box (unchanged when already inside). 311 * 312 * @param int $w Width. 313 * @param int $h Height. 314 * @param int $max_width Max width (0 = unbounded). 315 * @param int $max_height Max height (0 = unbounded). 316 * @return int[] { width, height } 317 */ 318 private static function fitted_size( $w, $h, $max_width, $max_height ) { 319 if ( ( $max_width <= 0 && $max_height <= 0 ) || $w < 1 || $h < 1 ) { 320 return array( $w, $h ); 321 } 277 322 $box_w = $max_width > 0 ? $max_width : $w; 278 323 $box_h = $max_height > 0 ? $max_height : $h; 279 324 if ( $w <= $box_w && $h <= $box_h ) { 280 return false;325 return array( $w, $h ); 281 326 } 282 327 $ratio = min( $box_w / $w, $box_h / $h ); 283 $driver->resize( max( 1, (int) round( $w * $ratio ) ), max( 1, (int) round( $h * $ratio ) ) ); 284 return true; 328 return array( max( 1, (int) round( $w * $ratio ) ), max( 1, (int) round( $h * $ratio ) ) ); 285 329 } 286 330 -
bytcrush-image-compression/trunk/includes/Engine/Driver/ImagickDriver.php
r3753311 r3757771 54 54 } 55 55 56 self::limit_resources(); 56 57 $im = new \Imagick(); 57 58 $im->readImage( $path ); … … 63 64 64 65 $this->im = $im; 66 } 67 68 /** 69 * Keep ImageMagick within what the server can spare. Its memory is not 70 * counted against PHP's memory_limit and it uses every CPU core by default, 71 * which on shared hosting can get the process throttled or killed. Cap it 72 * to one thread (as WordPress core does) and to a memory budget that 73 * matches PHP's; above that, its pixel cache spills to disk instead. 74 * 75 * @return void 76 */ 77 private static function limit_resources() { 78 static $done = false; 79 if ( $done || ! method_exists( '\Imagick', 'setResourceLimit' ) ) { 80 return; 81 } 82 $done = true; 83 84 /** 85 * Threads ImageMagick may use per image (0 = leave its default). 86 * 87 * @param int $threads Threads. 88 */ 89 $threads = (int) apply_filters( 'bytcrush_imagick_threads', 1 ); 90 if ( $threads > 0 && defined( '\Imagick::RESOURCETYPE_THREAD' ) ) { 91 \Imagick::setResourceLimit( \Imagick::RESOURCETYPE_THREAD, $threads ); 92 } 93 94 $memory = (int) min( 256 * MB_IN_BYTES, max( 32 * MB_IN_BYTES, \BytCrush\Engine\Resources::memory_available() ) ); 95 if ( defined( '\Imagick::RESOURCETYPE_MEMORY' ) ) { 96 \Imagick::setResourceLimit( \Imagick::RESOURCETYPE_MEMORY, $memory ); 97 } 98 if ( defined( '\Imagick::RESOURCETYPE_MAP' ) ) { 99 \Imagick::setResourceLimit( \Imagick::RESOURCETYPE_MAP, $memory * 2 ); 100 } 65 101 } 66 102 -
bytcrush-image-compression/trunk/includes/Engine/Optimizer.php
r3753311 r3757771 277 277 278 278 if ( is_wp_error( $res ) ) { 279 // Animated GIF/PNG: skip the image (as the WebP step does), don't flag an error.280 if ( 'bytcrush_animated' === $res->get_error_code() ) {279 // Animated, or too large for this server's memory: skip the image with the reason, don't flag an error. 280 if ( in_array( $res->get_error_code(), array( 'bytcrush_animated', 'bytcrush_too_large' ), true ) ) { 281 281 return self::finish( $id, 'skipped', $res->get_error_message(), $meta ); 282 282 } … … 341 341 342 342 if ( is_wp_error( $bytes ) ) { 343 if ( 'bytcrush_animated' === $bytes->get_error_code() ) {343 if ( in_array( $bytes->get_error_code(), array( 'bytcrush_animated', 'bytcrush_too_large' ), true ) ) { 344 344 return self::finish( $id, 'skipped', $bytes->get_error_message(), $meta ); 345 345 } -
bytcrush-image-compression/trunk/includes/Rest/Controller.php
r3753311 r3757771 18 18 use BytCrush\Engine\Backup; 19 19 use BytCrush\Engine\Optimizer; 20 use BytCrush\Engine\Resources; 20 21 use BytCrush\Engine\Stats; 21 22 use WP_REST_Request; … … 164 165 bytcrush_keep_running(); 165 166 166 /**167 * Seconds of work per bulk request. Keep well under the host's168 * max_execution_time / proxy timeout.169 *170 * @param float $seconds Budget.171 */172 $budget = (float) apply_filters( 'bytcrush_bulk_time_budget', 20 );173 $start = microtime( true );174 167 $items = array(); 175 168 $seen = array(); … … 177 170 $halted = ''; 178 171 179 do { 180 $ids = Stats::pending_ids( 1, $seen ); 181 if ( ! $ids ) { 182 break; 172 // Time-boxed to the host's limits and paced by server load. 173 $batch = Resources::batch( 174 static function () use ( &$seen ) { 175 $ids = Stats::pending_ids( 1, $seen ); 176 return $ids ? $ids[0] : null; 177 }, 178 static function ( $id ) use ( &$seen, &$items, &$streak, &$halted ) { 179 $seen[] = $id; 180 $result = Optimizer::optimize( $id ); 181 $items[] = self::item( $id, $result ); 182 183 // A run of consecutive failures means a site-wide problem (disk 184 // full, permissions): stop instead of failing the whole library. 185 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 186 if ( $streak >= 10 ) { 187 $halted = $result['message']; 188 return true; 189 } 190 return false; 183 191 } 184 $seen[] = $ids[0]; 185 $result = Optimizer::optimize( $ids[0] ); 186 $items[] = self::item( $ids[0], $result ); 187 188 // A run of consecutive failures means a site-wide problem (disk 189 // full, permissions): stop instead of failing the whole library. 190 $streak = 'error' === $result['status'] ? $streak + 1 : 0; 191 if ( $streak >= 10 ) { 192 $halted = $result['message']; 193 break; 194 } 195 } while ( microtime( true ) - $start < $budget ); 196 197 return rest_ensure_response( 198 array( 199 'items' => $items, 200 'stats' => Stats::get(), 201 'done' => ! Stats::pending_ids( 1 ), 202 'halted' => $halted, 192 ); 193 194 return rest_ensure_response( 195 array( 196 'items' => $items, 197 'stats' => Stats::get(), 198 'done' => ! Stats::pending_ids( 1 ), 199 'halted' => $halted, 200 'cooldown' => $batch['cooldown'], 203 201 ) 204 202 ); -
bytcrush-image-compression/trunk/languages/bytcrush-image-compression.pot
r3753311 r3757771 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: BytCrush Image Compression 1.0. 0\n"5 "Project-Id-Version: BytCrush Image Compression 1.0.1\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/app\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-10- 14T15:53:40+00:00\n"12 "POT-Creation-Date: 2026-10-21T04:14:25+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.12.0\n" … … 17 17 #. Plugin Name of the plugin 18 18 #: bytcrush-image-compression.php 19 #: includes/Admin/Admin.php:6 119 #: includes/Admin/Admin.php:62 20 20 #: views/dashboard.php:46 21 21 msgid "BytCrush Image Compression" … … 48 48 msgstr "" 49 49 50 #: includes/Admin/Admin.php:6 250 #: includes/Admin/Admin.php:63 51 51 #: includes/Admin/MediaColumn.php:38 52 52 #: includes/Admin/MediaColumn.php:65 … … 54 54 msgstr "" 55 55 56 #: includes/Admin/Admin.php:6 856 #: includes/Admin/Admin.php:69 57 57 #: views/header.php:15 58 58 msgid "Dashboard" 59 59 msgstr "" 60 60 61 #: includes/Admin/Admin.php:7 161 #: includes/Admin/Admin.php:72 62 62 #: views/header.php:20 63 63 msgid "Page audit" 64 64 msgstr "" 65 65 66 #: includes/Admin/Admin.php:7 367 #: includes/Admin/Admin.php: 29266 #: includes/Admin/Admin.php:74 67 #: includes/Admin/Admin.php:301 68 68 #: views/header.php:16 69 69 msgid "Settings" 70 70 msgstr "" 71 71 72 #: includes/Admin/Admin.php:7 672 #: includes/Admin/Admin.php:77 73 73 #: views/pro.php:33 74 74 msgid "BytCrush Pro" 75 75 msgstr "" 76 76 77 #: includes/Admin/Admin.php:7 678 #: includes/Admin/Admin.php: 29577 #: includes/Admin/Admin.php:77 78 #: includes/Admin/Admin.php:304 79 79 #: views/header.php:23 80 80 msgid "Go Pro" 81 81 msgstr "" 82 82 83 #: includes/Admin/Admin.php:13 383 #: includes/Admin/Admin.php:135 84 84 msgid "Running on your server. You can close this tab and come back any time." 85 85 msgstr "" 86 86 87 #: includes/Admin/Admin.php:13 487 #: includes/Admin/Admin.php:136 88 88 msgid "Your server is blocking background requests, so this tab is doing the work. Keep it open to continue." 89 89 msgstr "" 90 90 91 #. translators: %s: number of images. 91 #: includes/Admin/Admin.php:137 92 #: includes/Admin/Admin.php:201 93 msgid "Your server is busy right now, so BytCrush is taking a short break to keep your site fast. It continues automatically." 94 msgstr "" 95 92 96 #: includes/Admin/Admin.php:179 93 #: views/dashboard.php:34 94 #, php-format 95 msgid "Optimize %s images in one click" 97 msgid "Crushing bytes…" 96 98 msgstr "" 97 99 98 100 #: includes/Admin/Admin.php:180 99 msgid " Crushing bytes…"101 msgid "Paused" 100 102 msgstr "" 101 103 102 104 #: includes/Admin/Admin.php:181 103 msgid "Paused"104 msgstr ""105 106 #: includes/Admin/Admin.php:182107 105 #: views/dashboard.php:35 108 106 msgid "Your library is fully optimized" 109 107 msgstr "" 110 108 111 #. translators: %s: number of images. 112 #: includes/Admin/Admin.php:184 113 #: views/dashboard.php:32 114 #, php-format 115 msgid "%s images could not be optimized" 116 msgstr "" 117 118 #: includes/Admin/Admin.php:185 109 #: includes/Admin/Admin.php:182 119 110 #: views/dashboard.php:39 120 111 msgid "The reasons are listed below. Fix the cause, then press Retry." 121 112 msgstr "" 122 113 114 #: includes/Admin/Admin.php:183 115 msgid "Paused after 10 failures in a row. " 116 msgstr "" 117 118 #: includes/Admin/Admin.php:184 119 msgid "Restoring originals…" 120 msgstr "" 121 122 #: includes/Admin/Admin.php:185 123 msgid "Keep this tab open. You can pause at any time and pick up where you left off." 124 msgstr "" 125 123 126 #: includes/Admin/Admin.php:186 124 msgid "Paused after 10 failures in a row. "125 msgstr ""126 127 #: includes/Admin/Admin.php:187128 msgid "Restoring originals…"129 msgstr ""130 131 #: includes/Admin/Admin.php:188132 msgid "Keep this tab open. You can pause at any time and pick up where you left off."133 msgstr ""134 135 #. translators: %s: number of images.136 #: includes/Admin/Admin.php:190137 #, php-format138 msgid "%s images left. Resume whenever you are ready."139 msgstr ""140 141 #: includes/Admin/Admin.php:191142 127 #: views/dashboard.php:41 143 128 msgid "New uploads are optimized automatically. Originals are safe in backups." 144 129 msgstr "" 145 130 146 #: includes/Admin/Admin.php:1 92131 #: includes/Admin/Admin.php:187 147 132 #: views/dashboard.php:93 148 133 msgid "Optimize all images" 149 134 msgstr "" 150 135 151 #: includes/Admin/Admin.php:1 93136 #: includes/Admin/Admin.php:188 152 137 msgid "Pause" 153 138 msgstr "" 154 139 155 #: includes/Admin/Admin.php:1 94140 #: includes/Admin/Admin.php:189 156 141 msgid "Resume" 157 142 msgstr "" 158 143 159 #: includes/Admin/Admin.php:19 5144 #: includes/Admin/Admin.php:190 160 145 #: views/dashboard.php:93 161 146 msgid "All images optimized" 162 147 msgstr "" 163 148 164 #. translators: 1: processed count, 2: total count. 165 #: includes/Admin/Admin.php:197 166 #, php-format 167 msgid "%1$s of %2$s images" 168 msgstr "" 169 170 #. translators: %s: time, e.g. 2m 10s. 171 #: includes/Admin/Admin.php:199 172 #, php-format 173 msgid "about %s left" 174 msgstr "" 175 176 #: includes/Admin/Admin.php:200 149 #: includes/Admin/Admin.php:191 177 150 #: includes/Admin/MediaColumn.php:128 178 151 #: views/dashboard.php:220 … … 180 153 msgstr "" 181 154 182 #: includes/Admin/Admin.php: 201155 #: includes/Admin/Admin.php:192 183 156 #: includes/Admin/MediaColumn.php:128 184 157 msgid "Skipped" 185 158 msgstr "" 186 159 187 #: includes/Admin/Admin.php: 202160 #: includes/Admin/Admin.php:193 188 161 #: includes/Admin/MediaColumn.php:141 189 162 msgid "Optimizing…" 190 163 msgstr "" 191 164 192 #: includes/Admin/Admin.php: 203165 #: includes/Admin/Admin.php:194 193 166 msgid "Restoring…" 194 167 msgstr "" 195 168 169 #: includes/Admin/Admin.php:195 170 msgid "The server stopped responding. The bulk run has been paused. Press Resume to continue." 171 msgstr "" 172 173 #: includes/Admin/Admin.php:196 174 msgid "Restore the original image? The optimized version and its WebP/AVIF copies will be removed." 175 msgstr "" 176 177 #: includes/Admin/Admin.php:197 178 msgid "Restore ALL original images? Every optimized image goes back to its original file and all WebP/AVIF copies are removed. This can take a while." 179 msgstr "" 180 181 #: includes/Admin/Admin.php:198 182 msgid "Settings saved" 183 msgstr "" 184 185 #: includes/Admin/Admin.php:199 186 msgid "You have unsaved changes" 187 msgstr "" 188 189 #: includes/Admin/Admin.php:200 190 msgid "Could not save settings" 191 msgstr "" 192 193 #: includes/Admin/Admin.php:202 194 msgid "BytCrush is still optimizing. Leaving will pause the run." 195 msgstr "" 196 197 #. translators: %s: number of images. 198 #: includes/Admin/Admin.php:205 199 #: views/dashboard.php:34 200 #, php-format 201 msgid "Optimize %s images in one click" 202 msgstr "" 203 204 #. translators: %s: number of images. 205 #: includes/Admin/Admin.php:207 206 #: views/dashboard.php:32 207 #, php-format 208 msgid "%s images could not be optimized" 209 msgstr "" 210 211 #. translators: %s: number of images. 212 #: includes/Admin/Admin.php:209 213 #, php-format 214 msgid "%s images left. Resume whenever you are ready." 215 msgstr "" 216 217 #. translators: 1: processed count, 2: total count. 218 #: includes/Admin/Admin.php:211 219 #, php-format 220 msgid "%1$s of %2$s images" 221 msgstr "" 222 223 #. translators: %s: time, e.g. 2m 10s. 224 #: includes/Admin/Admin.php:213 225 #, php-format 226 msgid "about %s left" 227 msgstr "" 228 196 229 #. translators: 1: bytes saved, 2: image count. 197 #: includes/Admin/Admin.php:2 05230 #: includes/Admin/Admin.php:215 198 231 #, php-format 199 232 msgid "Done! You saved %1$s across %2$s images." 200 233 msgstr "" 201 234 202 #: includes/Admin/Admin.php:206 203 msgid "The server stopped responding. The bulk run has been paused. Press Resume to continue." 204 msgstr "" 205 206 #: includes/Admin/Admin.php:207 207 msgid "Restore the original image? The optimized version and its WebP/AVIF copies will be removed." 208 msgstr "" 209 210 #: includes/Admin/Admin.php:208 211 msgid "Restore ALL original images? Every optimized image goes back to its original file and all WebP/AVIF copies are removed. This can take a while." 212 msgstr "" 213 214 #. translators: %s: number of images. 215 #: includes/Admin/Admin.php:210 235 #. translators: %s: number of images. 236 #: includes/Admin/Admin.php:217 216 237 #, php-format 217 238 msgid "Restored %s images to their originals." … … 219 240 220 241 #. translators: %s: number of images. 221 #: includes/Admin/Admin.php:21 2242 #: includes/Admin/Admin.php:219 222 243 #, php-format 223 244 msgid "%s failed images were put back in the queue." 224 245 msgstr "" 225 246 226 #: includes/Admin/Admin.php:213 227 msgid "Settings saved" 228 msgstr "" 229 230 #: includes/Admin/Admin.php:214 231 msgid "You have unsaved changes" 232 msgstr "" 233 234 #: includes/Admin/Admin.php:215 235 msgid "Could not save settings" 236 msgstr "" 237 238 #: includes/Admin/Admin.php:216 239 msgid "BytCrush is still optimizing. Leaving will pause the run." 240 msgstr "" 241 242 #: includes/Admin/Admin.php:261 247 #: includes/Admin/Admin.php:270 243 248 msgid "BytCrush is ready." 244 249 msgstr "" 245 250 246 251 #. translators: %s: number of images. 247 #: includes/Admin/Admin.php:2 63252 #: includes/Admin/Admin.php:272 248 253 #, php-format 249 254 msgid "%s image in your Media Library can be optimized in one click. Originals are kept safe." … … 252 257 msgstr[1] "" 253 258 254 #: includes/Admin/Admin.php:2 65259 #: includes/Admin/Admin.php:274 255 260 msgid "Optimize my images" 256 261 msgstr "" 257 262 258 #: includes/Admin/Admin.php:2 67263 #: includes/Admin/Admin.php:276 259 264 msgid "Dismiss" 260 265 msgstr "" 261 266 262 #: includes/Admin/Admin.php: 291267 #: includes/Admin/Admin.php:300 263 268 msgid "Optimize" 264 269 msgstr "" 265 270 266 #: includes/Admin/Admin.php:3 34271 #: includes/Admin/Admin.php:343 267 272 msgid "picture tags" 268 273 msgstr "" 269 274 270 #: includes/Admin/Admin.php:3 35275 #: includes/Admin/Admin.php:344 271 276 msgid "server rewrite" 272 277 msgstr "" 273 278 274 #: includes/Admin/Admin.php:3 36279 #: includes/Admin/Admin.php:345 275 280 msgid "not served" 276 281 msgstr "" 277 282 278 #: includes/Admin/Admin.php:3 41283 #: includes/Admin/Admin.php:350 279 284 msgid "Compression" 280 285 msgstr "" 281 286 282 #: includes/Admin/Admin.php:3 43287 #: includes/Admin/Admin.php:352 283 288 #: views/settings.php:99 284 289 msgid "Next-gen formats" … … 286 291 287 292 #. translators: 1: width, 2: height. 288 #: includes/Admin/Admin.php:344289 #: includes/Admin/Admin.php:350290 293 #: includes/Admin/Admin.php:353 291 #: includes/Admin/Admin.php:354 292 #: includes/Admin/Admin.php:355 293 #: includes/Admin/Admin.php:356 294 #: includes/Admin/Admin.php:359 295 #: includes/Admin/Admin.php:362 296 #: includes/Admin/Admin.php:363 297 #: includes/Admin/Admin.php:364 298 #: includes/Admin/Admin.php:365 294 299 #: views/settings.php:121 295 300 msgid "Off" 296 301 msgstr "" 297 302 298 #: includes/Admin/Admin.php:3 48303 #: includes/Admin/Admin.php:357 299 304 msgid "Resize large images" 300 305 msgstr "" 301 306 302 307 #. translators: 1: width, 2: height. 303 #: includes/Admin/Admin.php:35 0308 #: includes/Admin/Admin.php:359 304 309 #, php-format 305 310 msgid "Max %1$s × %2$s px" 306 311 msgstr "" 307 312 308 #: includes/Admin/Admin.php:3 53313 #: includes/Admin/Admin.php:362 309 314 #: views/settings.php:156 310 315 msgid "Strip metadata" 311 316 msgstr "" 312 317 313 #: includes/Admin/Admin.php:3 53318 #: includes/Admin/Admin.php:362 314 319 msgid "On, colour profile kept" 315 320 msgstr "" 316 321 317 #: includes/Admin/Admin.php:3 54322 #: includes/Admin/Admin.php:363 318 323 msgid "Auto-optimize uploads" 319 324 msgstr "" 320 325 321 #: includes/Admin/Admin.php:3 54322 #: includes/Admin/Admin.php:3 55323 #: includes/Admin/Admin.php:3 56326 #: includes/Admin/Admin.php:363 327 #: includes/Admin/Admin.php:364 328 #: includes/Admin/Admin.php:365 324 329 msgid "On" 325 330 msgstr "" 326 331 327 #: includes/Admin/Admin.php:3 55332 #: includes/Admin/Admin.php:364 328 333 msgid "Keep originals" 329 334 msgstr "" 330 335 331 #: includes/Admin/Admin.php:3 56336 #: includes/Admin/Admin.php:365 332 337 msgid "Background runs" 333 338 msgstr "" … … 390 395 msgstr "" 391 396 392 #: includes/Engine/Background.php:4 00397 #: includes/Engine/Background.php:425 393 398 msgid "Every minute (BytCrush)" 394 399 msgstr "" … … 445 450 msgstr "" 446 451 447 #: includes/Engine/Compressor.php:222 452 #. translators: 1: image size in megapixels, 2: memory needed, 3: memory available. 453 #: includes/Engine/Compressor.php:186 454 #, php-format 455 msgid "Skipped to keep your site safe: this %1$s MP image needs about %2$s of memory and the server allows %3$s." 456 msgstr "" 457 458 #: includes/Engine/Compressor.php:251 448 459 msgid "The encoder returned an empty image." 449 460 msgstr "" 450 461 451 462 #. translators: %s: file name. 452 #: includes/Engine/Compressor.php:2 47463 #: includes/Engine/Compressor.php:276 453 464 #, php-format 454 465 msgid "Could not write %s. Check the uploads folder permissions." 455 466 msgstr "" 456 467 457 #: includes/Engine/Compressor.php:2 54468 #: includes/Engine/Compressor.php:283 458 469 msgid "Animated images are left untouched." 459 470 msgstr "" -
bytcrush-image-compression/trunk/readme.txt
r3753311 r3757771 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.2 7 Stable tag: 1.0. 07 Stable tag: 1.0.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 88 88 == Changelog == 89 89 90 = 1.0.1 = 91 * Server-friendly by design: BytCrush now checks how much memory an image needs before opening it, and skips images that are too large for the server (or hands them to ImageMagick) instead of risking an error. 92 * Batches adapt to your host's time limit and proxy timeouts, and never start an image that could not finish in time. 93 * Optimization is paced by server load: it pauses between images when the server is busy, backs off when it is overloaded, and uses at most half the CPU when the load cannot be measured, so visitors keep priority. 94 * ImageMagick is limited to one thread and a memory budget that matches PHP's. 95 90 96 = 1.0.0 = 91 97 * Initial release.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)