Class: Omnizip::Formats::Rar::Rar5::Compression::Lzma
- Inherits:
-
Object
- Object
- Omnizip::Formats::Rar::Rar5::Compression::Lzma
- Defined in:
- lib/omnizip/formats/rar/rar5/compression/lzma.rb
Overview
LZMA compression method for RAR5
This adapter uses the existing LZMA implementation with RAR5-specific parameters and encoding.
RAR5 compression methods (from spec):
- 0: STORE (no compression)
- 1: FASTEST
- 2: FAST
- 3: NORMAL
- 4: GOOD
- 5: BEST
For methods 1-5, RAR5 uses LZMA compression with different dictionary sizes and compression levels.
Defined Under Namespace
Classes: LzmaOptions
Constant Summary collapse
- METHOD_FASTEST =
Compression method identifier for RAR5 Methods 1-5 all use LZMA with different parameters
1- METHOD_FAST =
2- METHOD_NORMAL =
3- METHOD_GOOD =
4- METHOD_BEST =
5
Class Method Summary collapse
-
.build_lzma_options(level) ⇒ LzmaOptions
Build LZMA options based on RAR5 compression level.
-
.compress(data, options = {}) ⇒ Hash
Compress data using LZMA.
-
.compression_info(level = METHOD_NORMAL) ⇒ Integer
Get compression info VINT value.
-
.decompress(data, options = {}) ⇒ String
Decompress LZMA-compressed data.
-
.method_id(level = METHOD_NORMAL) ⇒ Integer
Get compression method identifier for level.
Class Method Details
.build_lzma_options(level) ⇒ LzmaOptions
Build LZMA options based on RAR5 compression level
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/omnizip/formats/rar/rar5/compression/lzma.rb', line 166 def self.(level) # RAR5 compression levels map to LZMA parameters # These are approximations based on typical RAR behavior dict_size = 1 << case level when 1 then 18 # 256 KB (fastest) when 2 then 20 # 1 MB (fast) when 3 then 22 # 4 MB (normal) when 4 then 23 # 8 MB (good) when 5 then 24 # 16 MB (best) else 22 # default: 4 MB end # RAR5 uses specific LZMA parameters: lc=1, lp=2, pb=0 # (Different from standalone LZMA which typically uses lc=3, lp=0, pb=2) LzmaOptions.new(level, dict_size, lc: 1, lp: 2, pb: 0) end |
.compress(data, options = {}) ⇒ Hash
Compress data using LZMA
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/omnizip/formats/rar/rar5/compression/lzma.rb', line 43 def self.compress(data, = {}) level = [:level] || METHOD_NORMAL # Create StringIO streams for LZMA input = StringIO.new(data) output = StringIO.new output.set_encoding(Encoding::BINARY) # Create LZMA encoder with RAR5-appropriate settings lzma = Algorithms::LZMA.new = (level) # Build encoder options hash that LZMA encoder will accept = { dict_size: .dict_size, lc: .lc, lp: .lp, pb: .pb, level: level, } # Compress using LZMA with RAR5 parameters lzma.compress(input, output, ) # The LZMA encoder outputs: # - 1 byte: properties (lc, lp, pb) # - 4 bytes: dictionary size (little-endian) # - 8 bytes: uncompressed size (little-endian) # - remaining: compressed data # # RAR5 stores properties in file header extra area (type 0x03), # so we extract them separately compressed_with_header = output.string if compressed_with_header.bytesize > 13 # Extract LZMA properties for RAR5 extra area # RAR5 stores: property byte (1) + dict size (4) = 5 bytes # NOT the uncompressed size (which is in the next 8 bytes of LZMA header) properties = compressed_with_header[0, 5] # Extract raw LZMA stream (skip 13-byte header) compressed_data = compressed_with_header[13..] { data: compressed_data, properties: properties, } else # Edge case: if compression produced less than 13 bytes (extremely rare), # return original data uncompressed with nil properties warn "LZMA output too small (#{compressed_with_header.bytesize} bytes), using STORE" { data: data, properties: nil, } end end |
.compression_info(level = METHOD_NORMAL) ⇒ Integer
Get compression info VINT value
For RAR5, compression_info encodes:
- Bits 0-5: compression method (1-5 for LZMA)
- Bits 6+: version (0 for now)
157 158 159 160 |
# File 'lib/omnizip/formats/rar/rar5/compression/lzma.rb', line 157 def self.compression_info(level = METHOD_NORMAL) method = method_id(level) method & 0x3F # Bits 0-5 only end |
.decompress(data, options = {}) ⇒ String
Decompress LZMA-compressed data
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/omnizip/formats/rar/rar5/compression/lzma.rb', line 108 def self.decompress(data, = {}) properties = [:properties] uncompressed_size = [:uncompressed_size] # Reconstruct the 13-byte LZMA header if properties are provided if properties && properties.bytesize >= 5 # properties contains: 1 byte props + 4 bytes dict size # We need to add 8 bytes for uncompressed size header = properties.dup header += if uncompressed_size # Add uncompressed size as 8-byte little-endian [uncompressed_size].pack("Q<") else # Use -1 (0xFFFFFFFFFFFFFFFF) to indicate EOS marker mode [0xFFFFFFFFFFFFFFFF].pack("Q<") end full_data = header + data else # Data already includes LZMA header full_data = data end input = StringIO.new(full_data) output = StringIO.new output.set_encoding(Encoding::BINARY) # Use SDK decoder since RAR5 LZMA was compressed with SDK encoder decoder = Implementations::SevenZip::LZMA::Decoder.new(input) decoder.decode_stream(output) output.string end |
.method_id(level = METHOD_NORMAL) ⇒ Integer
Get compression method identifier for level
145 146 147 |
# File 'lib/omnizip/formats/rar/rar5/compression/lzma.rb', line 145 def self.method_id(level = METHOD_NORMAL) level.clamp(METHOD_FASTEST, METHOD_BEST) end |