. # # SingleFile () saves complete web pages as monolithic HTML # files with all assets (images, fonts, scripts) embedded as data URIs. This works but produces # bloated files that are slow to parse and cannot benefit from incremental loading. This script # reverses that embedding: it extracts each data URI to a separate file in an asset directory, # rewrites the HTML to use relative URLs, and applies optional image optimizations. # (Images/GIFs are recompressed losslessly & lossily, and PNGs are converted to JPEG if minimal PSNR distortion, # due to the many extremely large photographic PNGs on websites which should be JPEGS.) # # The gwtar format (`--create-gwtar=1`) is a self-extracting HTML archive for long-term preservation: # - Opens as a normal HTML page with embedded JavaScript decoder # - Contains a ustar tarball appended after the HTML # - Includes SHA-256 hashes in the asset manifest for integrity verification # - Optionally includes PAR2 parity data (25% redundancy) for forward error correction # The result is a single file that browsers can render directly while remaining extractable with # standard tools (tail + tar) and recoverable from partial corruption. # (See for detailed description and design rationale.) # # Usage: # php deconstruct_singlefile.php foo.html [options] # # Options: # --memory-limit=1024M PHP memory limit (default: 1024M) # --backtrack-limit=5000000 PCRE backtrack limit for regex on large files # --jpg-quality=80% JPEG quality when converting from PNG # --optimize-images=1 Run 'compressGIF'/'compressPNG'/'compressJPG' if available # --save-original=1 Keep original SingleFile with '.bak' extension # --create-gwtar=0 Create self-extracting '.gwtar.html' archive # --add-fec-data=1 Append PAR2 parity files (requires '--create-gwtar') # --keep-original=1 Retain original '.html' file when creating the Gwtar # --debug=0 Print diagnostic messages (PNG conversion decisions, etc.) # # (Boolean arguments may also be set to true by passing them without a value.) # # Dependencies: `php-cli` (with `pcre`, `json`), ImageMagick (`identify`, `convert`, `compare`), `tar` # Optional: `par2create` (`par2` package, for `--add-fec-data`), Gwern.net utilities: # `compressGIF`/`compressPNG`/`compressJPG` (for `--optimize-images`) # Required files in script directory: `gwtar.js`, `gwtar_noscript.html`. ## Gwtar version: $gwtar_version_string = 'v1'; ## Command line arguments. $args = [ ]; $boolean_arg_names = [ '--optimize-images', '--save-original', '--create-gwtar', '--add-fec-data', '--keep-original', '--debug' ]; for ($i = 1; $i < $argc; $i++) { if (str_starts_with($argv[$i], '--')) { $parts = explode('=', $argv[$i]); if (count($parts) == 2) { if (in_array($parts[0], $boolean_arg_names)) { $args[$parts[0]] = ($parts[1] != '0'); } else { $args[$parts[0]] = $parts[1]; } } else { if (in_array($parts[0], $boolean_arg_names)) { $args[$parts[0]] = true; } else { echo "ERROR: Argument ‘{$args[$parts[0]]}’ must have a value! Exiting.\n"; die; } } } else if (!isset($args['file'])) { $args['file'] = $argv[$i]; } } ## Check for file existing. if (file_exists($args['file']) == false) { echo "ERROR: file ‘{$args['file']}’ does not exist! Exiting.\n"; die; } ## The JS file that does the client-side processing. $js_script_path = __DIR__ . '/gwtar.js'; if (file_exists($js_script_path) == false) { echo "ERROR: JS script file (‘gwtar.js’) must be in the same directory as this script! Exiting.\n"; die; } ## The