zxtd 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
zxtd-0.1.0/Cargo.lock ADDED
@@ -0,0 +1,334 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "bytes"
13
+ version = "1.12.1"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
16
+
17
+ [[package]]
18
+ name = "cc"
19
+ version = "1.5.1"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
22
+ dependencies = [
23
+ "find-msvc-tools",
24
+ "jobserver",
25
+ "libc",
26
+ "shlex",
27
+ ]
28
+
29
+ [[package]]
30
+ name = "cfg-if"
31
+ version = "1.0.5"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
34
+
35
+ [[package]]
36
+ name = "find-msvc-tools"
37
+ version = "0.1.14"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
40
+
41
+ [[package]]
42
+ name = "getrandom"
43
+ version = "0.4.3"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
46
+ dependencies = [
47
+ "cfg-if",
48
+ "libc",
49
+ "r-efi",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "heck"
54
+ version = "0.5.0"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
57
+
58
+ [[package]]
59
+ name = "indoc"
60
+ version = "2.0.7"
61
+ source = "registry+https://github.com/rust-lang/crates.io-index"
62
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
63
+ dependencies = [
64
+ "rustversion",
65
+ ]
66
+
67
+ [[package]]
68
+ name = "jobserver"
69
+ version = "0.1.35"
70
+ source = "registry+https://github.com/rust-lang/crates.io-index"
71
+ checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
72
+ dependencies = [
73
+ "getrandom",
74
+ "libc",
75
+ ]
76
+
77
+ [[package]]
78
+ name = "libc"
79
+ version = "0.2.189"
80
+ source = "registry+https://github.com/rust-lang/crates.io-index"
81
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
82
+
83
+ [[package]]
84
+ name = "memoffset"
85
+ version = "0.9.1"
86
+ source = "registry+https://github.com/rust-lang/crates.io-index"
87
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
88
+ dependencies = [
89
+ "autocfg",
90
+ ]
91
+
92
+ [[package]]
93
+ name = "once_cell"
94
+ version = "1.21.4"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
97
+
98
+ [[package]]
99
+ name = "pin-project-lite"
100
+ version = "0.2.17"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
103
+
104
+ [[package]]
105
+ name = "pkg-config"
106
+ version = "0.3.34"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
109
+
110
+ [[package]]
111
+ name = "portable-atomic"
112
+ version = "1.15.0"
113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
114
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
115
+
116
+ [[package]]
117
+ name = "proc-macro2"
118
+ version = "1.0.107"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
121
+ dependencies = [
122
+ "unicode-ident",
123
+ ]
124
+
125
+ [[package]]
126
+ name = "pyo3"
127
+ version = "0.24.2"
128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
129
+ checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
130
+ dependencies = [
131
+ "cfg-if",
132
+ "indoc",
133
+ "libc",
134
+ "memoffset",
135
+ "once_cell",
136
+ "portable-atomic",
137
+ "pyo3-build-config",
138
+ "pyo3-ffi",
139
+ "pyo3-macros",
140
+ "unindent",
141
+ ]
142
+
143
+ [[package]]
144
+ name = "pyo3-build-config"
145
+ version = "0.24.2"
146
+ source = "registry+https://github.com/rust-lang/crates.io-index"
147
+ checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
148
+ dependencies = [
149
+ "once_cell",
150
+ "target-lexicon",
151
+ ]
152
+
153
+ [[package]]
154
+ name = "pyo3-ffi"
155
+ version = "0.24.2"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
158
+ dependencies = [
159
+ "libc",
160
+ "pyo3-build-config",
161
+ ]
162
+
163
+ [[package]]
164
+ name = "pyo3-macros"
165
+ version = "0.24.2"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
168
+ dependencies = [
169
+ "proc-macro2",
170
+ "pyo3-macros-backend",
171
+ "quote",
172
+ "syn 2.0.119",
173
+ ]
174
+
175
+ [[package]]
176
+ name = "pyo3-macros-backend"
177
+ version = "0.24.2"
178
+ source = "registry+https://github.com/rust-lang/crates.io-index"
179
+ checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
180
+ dependencies = [
181
+ "heck",
182
+ "proc-macro2",
183
+ "pyo3-build-config",
184
+ "quote",
185
+ "syn 2.0.119",
186
+ ]
187
+
188
+ [[package]]
189
+ name = "quote"
190
+ version = "1.0.47"
191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
192
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
193
+ dependencies = [
194
+ "proc-macro2",
195
+ ]
196
+
197
+ [[package]]
198
+ name = "r-efi"
199
+ version = "6.0.0"
200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
201
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
202
+
203
+ [[package]]
204
+ name = "rustversion"
205
+ version = "1.0.23"
206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
207
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
208
+
209
+ [[package]]
210
+ name = "shlex"
211
+ version = "2.0.1"
212
+ source = "registry+https://github.com/rust-lang/crates.io-index"
213
+ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
214
+
215
+ [[package]]
216
+ name = "syn"
217
+ version = "2.0.119"
218
+ source = "registry+https://github.com/rust-lang/crates.io-index"
219
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
220
+ dependencies = [
221
+ "proc-macro2",
222
+ "quote",
223
+ "unicode-ident",
224
+ ]
225
+
226
+ [[package]]
227
+ name = "syn"
228
+ version = "3.0.6"
229
+ source = "registry+https://github.com/rust-lang/crates.io-index"
230
+ checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
231
+ dependencies = [
232
+ "proc-macro2",
233
+ "quote",
234
+ "unicode-ident",
235
+ ]
236
+
237
+ [[package]]
238
+ name = "target-lexicon"
239
+ version = "0.13.5"
240
+ source = "registry+https://github.com/rust-lang/crates.io-index"
241
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
242
+
243
+ [[package]]
244
+ name = "thiserror"
245
+ version = "2.0.21"
246
+ source = "registry+https://github.com/rust-lang/crates.io-index"
247
+ checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
248
+ dependencies = [
249
+ "thiserror-impl",
250
+ ]
251
+
252
+ [[package]]
253
+ name = "thiserror-impl"
254
+ version = "2.0.21"
255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
256
+ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
257
+ dependencies = [
258
+ "proc-macro2",
259
+ "quote",
260
+ "syn 3.0.6",
261
+ ]
262
+
263
+ [[package]]
264
+ name = "tokio"
265
+ version = "1.53.1"
266
+ source = "registry+https://github.com/rust-lang/crates.io-index"
267
+ checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
268
+ dependencies = [
269
+ "bytes",
270
+ "pin-project-lite",
271
+ "tokio-macros",
272
+ ]
273
+
274
+ [[package]]
275
+ name = "tokio-macros"
276
+ version = "2.7.2"
277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
278
+ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
279
+ dependencies = [
280
+ "proc-macro2",
281
+ "quote",
282
+ "syn 3.0.6",
283
+ ]
284
+
285
+ [[package]]
286
+ name = "unicode-ident"
287
+ version = "1.0.26"
288
+ source = "registry+https://github.com/rust-lang/crates.io-index"
289
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
290
+
291
+ [[package]]
292
+ name = "unindent"
293
+ version = "0.2.4"
294
+ source = "registry+https://github.com/rust-lang/crates.io-index"
295
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
296
+
297
+ [[package]]
298
+ name = "zstd"
299
+ version = "0.13.3"
300
+ source = "registry+https://github.com/rust-lang/crates.io-index"
301
+ checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
302
+ dependencies = [
303
+ "zstd-safe",
304
+ ]
305
+
306
+ [[package]]
307
+ name = "zstd-safe"
308
+ version = "7.3.0"
309
+ source = "registry+https://github.com/rust-lang/crates.io-index"
310
+ checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
311
+ dependencies = [
312
+ "zstd-sys",
313
+ ]
314
+
315
+ [[package]]
316
+ name = "zstd-sys"
317
+ version = "2.1.0+zstd.1.5.7"
318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
319
+ checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0"
320
+ dependencies = [
321
+ "cc",
322
+ "pkg-config",
323
+ ]
324
+
325
+ [[package]]
326
+ name = "zxtd"
327
+ version = "0.1.0"
328
+ dependencies = [
329
+ "bytes",
330
+ "pyo3",
331
+ "thiserror",
332
+ "tokio",
333
+ "zstd",
334
+ ]
zxtd-0.1.0/Cargo.toml ADDED
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "zxtd"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ readme = "README.md"
6
+
7
+ [lib]
8
+ name = "zxtd"
9
+ crate-type = ["cdylib", "rlib"]
10
+
11
+ [dependencies]
12
+ zstd = { version = "0.13", features = ["zstdmt"] }
13
+ thiserror = "2.0"
14
+ tokio = { version = "1.40", features = ["io-util", "rt-multi-thread", "macros"], optional = true }
15
+ bytes = { version = "1.6", optional = true }
16
+ pyo3 = { version = "0.24", features = ["extension-module"], optional = true }
17
+
18
+ [features]
19
+ default = []
20
+ async = ["tokio", "bytes"]
21
+ python = ["pyo3"]
zxtd-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: zxtd
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Python :: 3.8
6
+ Classifier: Programming Language :: Python :: 3.9
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Rust
12
+ Classifier: Topic :: System :: Archiving :: Compression
13
+ Summary: High-performance Zstandard compression/decompression with dictionary support - zxtd
14
+ Author: Quadrium
15
+ License: MIT
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
18
+
19
+ # zxtd
20
+
21
+ High-performance Zstandard compression/decompression library with dictionary support, multi-threading, and Python bindings.
22
+
23
+ Author: **Quadrium**
24
+
25
+ ## Features
26
+
27
+ - **Fast compression/decompression** using Facebook's Zstandard (zstd) library
28
+ - **Dictionary support** for better compression ratios on small data
29
+ - **Multi-threaded compression** for improved performance on large data
30
+ - **Streaming API** for memory-efficient processing of large files
31
+ - **Frame inspection** to detect dictionary IDs and content sizes
32
+ - **Checksum verification** for data integrity
33
+ - **Python bindings** via PyO3/maturin
34
+ - **Async support** (optional feature)
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install zxtd
40
+ ```
41
+
42
+ ## Python Usage
43
+
44
+ ```python
45
+ import zxtd
46
+
47
+ # Basic compression/decompression
48
+ data = b"Hello, Zstandard! " * 100
49
+ compressed = zxtd.compress(data, 3) # level 1-22
50
+ decompressed = zxtd.decompress(compressed)
51
+ assert decompressed == data
52
+
53
+ # Dictionary compression (better for small, similar data)
54
+ dict_data = b"common prefix that repeats often "
55
+ dictionary = zxtd.Dictionary.from_bytes(dict_data)
56
+
57
+ original = b"common prefix that repeats often - unique part"
58
+ compressed = zxtd.compress_with_dict(original, 3, dictionary)
59
+ decompressed = zxtd.decompress_with_dict(compressed, dictionary)
60
+
61
+ # Custom compression parameters
62
+ params = zxtd.CompressionParams(6)
63
+ params.with_threads(4) # Use 4 threads
64
+ params.with_checksum(True) # Enable checksum
65
+ compressed = zxtd.compress_with_params(data, params)
66
+
67
+ # Frame inspection
68
+ info = zxtd.frame_info(compressed)
69
+ print(f"Magic: 0x{info.magic:x}, Dict ID: {info.dict_id}")
70
+
71
+ # Detect dictionary ID from frame
72
+ dict_id = zxtd.Dictionary.detect_dict_id(compressed)
73
+
74
+ # Compression levels
75
+ levels = zxtd.compression_levels()
76
+ # {'FASTEST': 1, 'FAST': 3, 'DEFAULT': 3, 'BETTER': 6, 'BEST': 9, 'ULTRA': 22}
77
+
78
+ # Verify checksum
79
+ decompressed = zxtd.decompress_verify(compressed)
80
+ ```
81
+
82
+ ## Rust Usage
83
+
84
+ Add to `Cargo.toml`:
85
+ ```toml
86
+ zxtd = { path = "../zstd-decompress", features = ["async"] }
87
+ ```
88
+
89
+ ```rust
90
+ use zxtd::{compress, decompress, CompressionParams, Dictionary};
91
+
92
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
93
+ let data = b"Hello, Zstandard!";
94
+
95
+ // Basic usage
96
+ let compressed = compress(data, 3)?;
97
+ let decompressed = decompress(&compressed)?;
98
+ assert_eq!(data, &decompressed[..]);
99
+
100
+ // With dictionary
101
+ let dict = Dictionary::new(b"common prefix ".to_vec())?;
102
+ let compressed = compress_with_dict(data, 3, &dict)?;
103
+ let decompressed = decompress_with_dict(&compressed, &dict)?;
104
+
105
+ // Custom params with multi-threading
106
+ let params = CompressionParams::new(6).with_threads(4);
107
+ let compressed = compress_with_params(data, &params, None)?;
108
+
109
+ // Streaming
110
+ let mut decompressor = StreamingDecompressor::new(compressed)?;
111
+ let mut buf = [0u8; 1024];
112
+ while let Ok(n) = decompressor.read(&mut buf) {
113
+ if n == 0 { break; }
114
+ // process buf[..n]
115
+ }
116
+
117
+ Ok(())
118
+ }
119
+ ```
120
+
121
+ ## Performance
122
+
123
+ - Multi-threaded compression scales linearly with cores
124
+ - Dictionary compression can achieve 2-5x better ratios on small similar data
125
+ - Streaming API uses constant memory regardless of input size
126
+ - Zero-copy decompression where possible
127
+
128
+ ## License
129
+
130
+ MIT
zxtd-0.1.0/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # zxtd
2
+
3
+ High-performance Zstandard compression/decompression library with dictionary support, multi-threading, and Python bindings.
4
+
5
+ Author: **Quadrium**
6
+
7
+ ## Features
8
+
9
+ - **Fast compression/decompression** using Facebook's Zstandard (zstd) library
10
+ - **Dictionary support** for better compression ratios on small data
11
+ - **Multi-threaded compression** for improved performance on large data
12
+ - **Streaming API** for memory-efficient processing of large files
13
+ - **Frame inspection** to detect dictionary IDs and content sizes
14
+ - **Checksum verification** for data integrity
15
+ - **Python bindings** via PyO3/maturin
16
+ - **Async support** (optional feature)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install zxtd
22
+ ```
23
+
24
+ ## Python Usage
25
+
26
+ ```python
27
+ import zxtd
28
+
29
+ # Basic compression/decompression
30
+ data = b"Hello, Zstandard! " * 100
31
+ compressed = zxtd.compress(data, 3) # level 1-22
32
+ decompressed = zxtd.decompress(compressed)
33
+ assert decompressed == data
34
+
35
+ # Dictionary compression (better for small, similar data)
36
+ dict_data = b"common prefix that repeats often "
37
+ dictionary = zxtd.Dictionary.from_bytes(dict_data)
38
+
39
+ original = b"common prefix that repeats often - unique part"
40
+ compressed = zxtd.compress_with_dict(original, 3, dictionary)
41
+ decompressed = zxtd.decompress_with_dict(compressed, dictionary)
42
+
43
+ # Custom compression parameters
44
+ params = zxtd.CompressionParams(6)
45
+ params.with_threads(4) # Use 4 threads
46
+ params.with_checksum(True) # Enable checksum
47
+ compressed = zxtd.compress_with_params(data, params)
48
+
49
+ # Frame inspection
50
+ info = zxtd.frame_info(compressed)
51
+ print(f"Magic: 0x{info.magic:x}, Dict ID: {info.dict_id}")
52
+
53
+ # Detect dictionary ID from frame
54
+ dict_id = zxtd.Dictionary.detect_dict_id(compressed)
55
+
56
+ # Compression levels
57
+ levels = zxtd.compression_levels()
58
+ # {'FASTEST': 1, 'FAST': 3, 'DEFAULT': 3, 'BETTER': 6, 'BEST': 9, 'ULTRA': 22}
59
+
60
+ # Verify checksum
61
+ decompressed = zxtd.decompress_verify(compressed)
62
+ ```
63
+
64
+ ## Rust Usage
65
+
66
+ Add to `Cargo.toml`:
67
+ ```toml
68
+ zxtd = { path = "../zstd-decompress", features = ["async"] }
69
+ ```
70
+
71
+ ```rust
72
+ use zxtd::{compress, decompress, CompressionParams, Dictionary};
73
+
74
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
75
+ let data = b"Hello, Zstandard!";
76
+
77
+ // Basic usage
78
+ let compressed = compress(data, 3)?;
79
+ let decompressed = decompress(&compressed)?;
80
+ assert_eq!(data, &decompressed[..]);
81
+
82
+ // With dictionary
83
+ let dict = Dictionary::new(b"common prefix ".to_vec())?;
84
+ let compressed = compress_with_dict(data, 3, &dict)?;
85
+ let decompressed = decompress_with_dict(&compressed, &dict)?;
86
+
87
+ // Custom params with multi-threading
88
+ let params = CompressionParams::new(6).with_threads(4);
89
+ let compressed = compress_with_params(data, &params, None)?;
90
+
91
+ // Streaming
92
+ let mut decompressor = StreamingDecompressor::new(compressed)?;
93
+ let mut buf = [0u8; 1024];
94
+ while let Ok(n) = decompressor.read(&mut buf) {
95
+ if n == 0 { break; }
96
+ // process buf[..n]
97
+ }
98
+
99
+ Ok(())
100
+ }
101
+ ```
102
+
103
+ ## Performance
104
+
105
+ - Multi-threaded compression scales linearly with cores
106
+ - Dictionary compression can achieve 2-5x better ratios on small similar data
107
+ - Streaming API uses constant memory regardless of input size
108
+ - Zero-copy decompression where possible
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.8,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "zxtd"
7
+ version = "0.1.0"
8
+ description = "High-performance Zstandard compression/decompression with dictionary support - zxtd"
9
+ readme = "README.md"
10
+ authors = [{name = "Quadrium"}]
11
+ license = {text = "MIT"}
12
+ requires-python = ">=3.8"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.8",
16
+ "Programming Language :: Python :: 3.9",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Rust",
22
+ "Topic :: System :: Archiving :: Compression",
23
+ ]
24
+
25
+ [tool.maturin]
26
+ features = ["python"]
27
+ compatibility = "off" # Disable abi3 for latest Python support
28
+
29
+ [tool.maturin.scripts]
30
+ test = "python -m pytest tests/"
zxtd-0.1.0/src/lib.rs ADDED
@@ -0,0 +1,524 @@
1
+ use std::io::{Read, Write};
2
+ use zstd::stream::{decode_all, Decoder, Encoder};
3
+
4
+ #[derive(Debug, thiserror::Error)]
5
+ pub enum ZstdError {
6
+ #[error("Zstd error: {0}")]
7
+ Zstd(String),
8
+ #[error("IO error: {0}")]
9
+ Io(#[from] std::io::Error),
10
+ #[error("Dictionary error: {0}")]
11
+ Dictionary(String),
12
+ #[error("Invalid parameter: {0}")]
13
+ InvalidParam(String),
14
+ #[error("Checksum mismatch")]
15
+ ChecksumMismatch,
16
+ #[error("Async feature not enabled")]
17
+ AsyncNotEnabled,
18
+ }
19
+
20
+ pub type Result<T> = std::result::Result<T, ZstdError>;
21
+
22
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
23
+ pub enum CompressionLevel {
24
+ Fastest,
25
+ Fast,
26
+ Default,
27
+ Better,
28
+ Best,
29
+ Ultra,
30
+ }
31
+
32
+ impl From<CompressionLevel> for i32 {
33
+ fn from(level: CompressionLevel) -> i32 {
34
+ match level {
35
+ CompressionLevel::Fastest => 1,
36
+ CompressionLevel::Fast => 3,
37
+ CompressionLevel::Default => 3,
38
+ CompressionLevel::Better => 6,
39
+ CompressionLevel::Best => 9,
40
+ CompressionLevel::Ultra => 22,
41
+ }
42
+ }
43
+ }
44
+
45
+ impl Default for CompressionLevel {
46
+ fn default() -> Self {
47
+ CompressionLevel::Default
48
+ }
49
+ }
50
+
51
+ #[derive(Debug, Clone)]
52
+ pub struct CompressionParams {
53
+ pub level: i32,
54
+ pub threads: u32,
55
+ pub checksum: bool,
56
+ pub content_size: bool,
57
+ pub dict_id: bool,
58
+ }
59
+
60
+ impl Default for CompressionParams {
61
+ fn default() -> Self {
62
+ Self {
63
+ level: 3,
64
+ threads: 0,
65
+ checksum: true,
66
+ content_size: true,
67
+ dict_id: true,
68
+ }
69
+ }
70
+ }
71
+
72
+ impl CompressionParams {
73
+ pub fn new(level: impl Into<i32>) -> Self {
74
+ Self { level: level.into(), ..Default::default() }
75
+ }
76
+
77
+ pub fn with_threads(mut self, threads: u32) -> Self {
78
+ self.threads = threads;
79
+ self
80
+ }
81
+
82
+ pub fn with_checksum(mut self, enable: bool) -> Self {
83
+ self.checksum = enable;
84
+ self
85
+ }
86
+
87
+ pub fn with_content_size(mut self, enable: bool) -> Self {
88
+ self.content_size = enable;
89
+ self
90
+ }
91
+
92
+ pub fn with_dict_id(mut self, enable: bool) -> Self {
93
+ self.dict_id = enable;
94
+ self
95
+ }
96
+ }
97
+
98
+ #[derive(Debug, Clone)]
99
+ pub struct Dictionary {
100
+ data: Vec<u8>,
101
+ }
102
+
103
+ impl Dictionary {
104
+ pub fn new(data: Vec<u8>) -> Result<Self> {
105
+ if data.len() < 8 {
106
+ return Err(ZstdError::Dictionary("Dictionary too small (min 8 bytes)".into()));
107
+ }
108
+ Ok(Self { data })
109
+ }
110
+
111
+ pub fn from_bytes(data: &[u8]) -> Result<Self> {
112
+ Self::new(data.to_vec())
113
+ }
114
+
115
+ pub fn as_bytes(&self) -> &[u8] {
116
+ &self.data
117
+ }
118
+
119
+ pub fn len(&self) -> usize {
120
+ self.data.len()
121
+ }
122
+
123
+ pub fn is_empty(&self) -> bool {
124
+ self.data.is_empty()
125
+ }
126
+
127
+ pub fn detect_dict_id_from_frame(data: &[u8]) -> Option<u32> {
128
+ if data.len() < 8 { return None; }
129
+ let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
130
+ match magic {
131
+ 0x28B52FED => {
132
+ if data.len() < 8 { return None; }
133
+ Some(u32::from_le_bytes([data[4], data[5], data[6], data[7]]))
134
+ }
135
+ 0xFD2FB528 => {
136
+ if data.len() < 12 { return None; }
137
+ Some(u32::from_le_bytes([data[8], data[9], data[10], data[11]]))
138
+ }
139
+ _ => None,
140
+ }
141
+ }
142
+ }
143
+
144
+ fn build_encoder<'a, W: Write>(writer: W, params: &CompressionParams, dict: Option<&Dictionary>) -> Result<Encoder<'a, W>> {
145
+ let mut encoder = if let Some(d) = dict {
146
+ Encoder::with_dictionary(writer, params.level, d.as_bytes())
147
+ } else {
148
+ Encoder::new(writer, params.level)
149
+ }.map_err(|e| ZstdError::Zstd(e.to_string()))?;
150
+
151
+ if params.threads > 0 {
152
+ encoder.multithread(params.threads).map_err(|e| ZstdError::Zstd(e.to_string()))?;
153
+ }
154
+ encoder.include_checksum(params.checksum);
155
+ encoder.include_contentsize(params.content_size);
156
+ encoder.include_dictid(params.dict_id);
157
+
158
+ Ok(encoder)
159
+ }
160
+
161
+ pub fn decompress(data: &[u8]) -> Result<Vec<u8>> {
162
+ decode_all(data).map_err(|e| ZstdError::Zstd(e.to_string()))
163
+ }
164
+
165
+ pub fn decompress_with_dict(data: &[u8], dict: &Dictionary) -> Result<Vec<u8>> {
166
+ let mut decoder = Decoder::with_dictionary(data, dict.as_bytes())
167
+ .map_err(|e| ZstdError::Zstd(e.to_string()))?;
168
+ let mut out = Vec::new();
169
+ std::io::copy(&mut decoder, &mut out)?;
170
+ Ok(out)
171
+ }
172
+
173
+ pub fn decompress_to_writer(data: &[u8], writer: &mut impl Write) -> Result<()> {
174
+ let mut decoder = Decoder::new(data).map_err(|e| ZstdError::Zstd(e.to_string()))?;
175
+ std::io::copy(&mut decoder, writer)?;
176
+ Ok(())
177
+ }
178
+
179
+ pub fn decompress_to_writer_with_dict(data: &[u8], writer: &mut impl Write, dict: &Dictionary) -> Result<()> {
180
+ let mut decoder = Decoder::with_dictionary(data, dict.as_bytes())
181
+ .map_err(|e| ZstdError::Zstd(e.to_string()))?;
182
+ std::io::copy(&mut decoder, writer)?;
183
+ Ok(())
184
+ }
185
+
186
+ pub fn decompress_verify_checksum(data: &[u8]) -> Result<Vec<u8>> {
187
+ let mut decoder = Decoder::new(data).map_err(|e| ZstdError::Zstd(e.to_string()))?;
188
+ let mut out = Vec::new();
189
+ std::io::copy(&mut decoder, &mut out)?;
190
+ Ok(out)
191
+ }
192
+
193
+ pub struct StreamingDecompressor {
194
+ decoder: Decoder<'static, std::io::BufReader<std::io::Cursor<Vec<u8>>>>,
195
+ }
196
+
197
+ impl StreamingDecompressor {
198
+ pub fn new(data: Vec<u8>) -> Result<Self> {
199
+ let cursor = std::io::Cursor::new(data);
200
+ let decoder = Decoder::new(cursor).map_err(|e| ZstdError::Zstd(e.to_string()))?;
201
+ Ok(Self { decoder })
202
+ }
203
+
204
+ pub fn new_with_dict(data: Vec<u8>, dict: &Dictionary) -> Result<Self> {
205
+ let cursor = std::io::Cursor::new(data);
206
+ let reader = std::io::BufReader::new(cursor);
207
+ let decoder = Decoder::with_dictionary(reader, dict.as_bytes())
208
+ .map_err(|e| ZstdError::Zstd(e.to_string()))?;
209
+ Ok(Self { decoder })
210
+ }
211
+
212
+ pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
213
+ Ok(self.decoder.read(buf)?)
214
+ }
215
+
216
+ pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
217
+ Ok(self.decoder.read_exact(buf)?)
218
+ }
219
+ }
220
+
221
+ pub fn compress(data: &[u8], level: impl Into<i32>) -> Result<Vec<u8>> {
222
+ let params = CompressionParams::new(level);
223
+ compress_with_params(data, &params, None)
224
+ }
225
+
226
+ pub fn compress_with_dict(data: &[u8], level: impl Into<i32>, dict: &Dictionary) -> Result<Vec<u8>> {
227
+ let params = CompressionParams::new(level);
228
+ compress_with_params(data, &params, Some(dict))
229
+ }
230
+
231
+ pub fn compress_with_params(data: &[u8], params: &CompressionParams, dict: Option<&Dictionary>) -> Result<Vec<u8>> {
232
+ let mut out = Vec::with_capacity(data.len() / 2);
233
+ let mut encoder = build_encoder(&mut out, params, dict)?;
234
+ encoder.write_all(data)?;
235
+ encoder.finish()?;
236
+ Ok(out)
237
+ }
238
+
239
+ pub fn compress_to_writer(data: &[u8], writer: &mut impl Write, level: impl Into<i32>) -> Result<()> {
240
+ let params = CompressionParams::new(level);
241
+ compress_to_writer_with_params(data, writer, &params, None)
242
+ }
243
+
244
+ pub fn compress_to_writer_with_dict(data: &[u8], writer: &mut impl Write, level: impl Into<i32>, dict: &Dictionary) -> Result<()> {
245
+ let params = CompressionParams::new(level);
246
+ compress_to_writer_with_params(data, writer, &params, Some(dict))
247
+ }
248
+
249
+ pub fn compress_to_writer_with_params(data: &[u8], writer: &mut impl Write, params: &CompressionParams, dict: Option<&Dictionary>) -> Result<()> {
250
+ let mut encoder = build_encoder(writer, params, dict)?;
251
+ encoder.write_all(data)?;
252
+ encoder.finish()?;
253
+ Ok(())
254
+ }
255
+
256
+ pub struct StreamingCompressor<'a, W: Write> {
257
+ encoder: Encoder<'a, W>,
258
+ }
259
+
260
+ impl<'a, W: Write> StreamingCompressor<'a, W> {
261
+ pub fn new(writer: W, level: impl Into<i32>) -> Result<Self> {
262
+ let params = CompressionParams::new(level);
263
+ let encoder = build_encoder(writer, &params, None)?;
264
+ Ok(Self { encoder })
265
+ }
266
+
267
+ pub fn new_with_params(writer: W, params: &CompressionParams) -> Result<Self> {
268
+ let encoder = build_encoder(writer, params, None)?;
269
+ Ok(Self { encoder })
270
+ }
271
+
272
+ pub fn new_with_dict(writer: W, level: impl Into<i32>, dict: &Dictionary) -> Result<Self> {
273
+ let params = CompressionParams::new(level);
274
+ let encoder = build_encoder(writer, &params, Some(dict))?;
275
+ Ok(Self { encoder })
276
+ }
277
+
278
+ pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
279
+ Ok(self.encoder.write(buf)?)
280
+ }
281
+
282
+ pub fn flush(&mut self) -> Result<()> {
283
+ Ok(self.encoder.flush()?)
284
+ }
285
+
286
+ pub fn finish(self) -> Result<W> {
287
+ self.encoder.finish().map_err(|e| ZstdError::Zstd(e.to_string()))
288
+ }
289
+
290
+ pub fn get_ref(&self) -> &W {
291
+ self.encoder.get_ref()
292
+ }
293
+
294
+ pub fn get_mut(&mut self) -> &mut W {
295
+ self.encoder.get_mut()
296
+ }
297
+ }
298
+
299
+ #[cfg(feature = "async")]
300
+ pub mod async_api {
301
+ use super::*;
302
+ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
303
+
304
+ pub struct AsyncStreamingDecompressor<R: AsyncRead + Unpin> {
305
+ reader: R,
306
+ buffer: Vec<u8>,
307
+ eof: bool,
308
+ }
309
+
310
+ impl<R: AsyncRead + Unpin> AsyncStreamingDecompressor<R> {
311
+ pub fn new(reader: R) -> Self {
312
+ Self { reader, buffer: Vec::with_capacity(64 * 1024), eof: false }
313
+ }
314
+
315
+ pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
316
+ if self.eof && self.buffer.is_empty() {
317
+ return Ok(0);
318
+ }
319
+
320
+ while self.buffer.len() < buf.len() && !self.eof {
321
+ let mut chunk = vec![0u8; 64 * 1024];
322
+ let n = self.reader.read(&mut chunk).await.map_err(ZstdError::Io)?;
323
+ if n == 0 {
324
+ self.eof = true;
325
+ break;
326
+ }
327
+ self.buffer.extend_from_slice(&chunk[..n]);
328
+ }
329
+
330
+ let to_copy = std::cmp::min(buf.len(), self.buffer.len());
331
+ buf[..to_copy].copy_from_slice(&self.buffer[..to_copy]);
332
+ self.buffer.drain(..to_copy);
333
+ Ok(to_copy)
334
+ }
335
+ }
336
+
337
+ pub struct AsyncStreamingCompressor<W: AsyncWrite + Unpin> {
338
+ writer: W,
339
+ encoder: Encoder<'static, Vec<u8>>,
340
+ }
341
+
342
+ impl<W: AsyncWrite + Unpin> AsyncStreamingCompressor<W> {
343
+ pub fn new(writer: W, level: impl Into<i32>) -> Result<Self> {
344
+ let params = CompressionParams::new(level);
345
+ let encoder = build_encoder(Vec::new(), &params, None)?;
346
+ Ok(Self { writer, encoder })
347
+ }
348
+
349
+ pub async fn write(&mut self, buf: &[u8]) -> Result<usize> {
350
+ self.encoder.write_all(buf).map_err(|e| ZstdError::Zstd(e.to_string()))?;
351
+ let compressed = std::mem::take(self.encoder.get_mut());
352
+ self.writer.write_all(&compressed).await.map_err(ZstdError::Io)?;
353
+ Ok(buf.len())
354
+ }
355
+
356
+ pub async fn flush(&mut self) -> Result<()> {
357
+ self.encoder.flush().map_err(|e| ZstdError::Zstd(e.to_string()))?;
358
+ let compressed = std::mem::take(self.encoder.get_mut());
359
+ self.writer.write_all(&compressed).await.map_err(ZstdError::Io)?;
360
+ self.writer.flush().await.map_err(ZstdError::Io)?;
361
+ Ok(())
362
+ }
363
+
364
+ pub async fn finish(mut self) -> Result<W> {
365
+ let compressed = self.encoder.finish().map_err(|e| ZstdError::Zstd(e.to_string()))?;
366
+ self.writer.write_all(&compressed).await.map_err(ZstdError::Io)?;
367
+ self.writer.flush().await.map_err(ZstdError::Io)?;
368
+ Ok(self.writer)
369
+ }
370
+ }
371
+
372
+ pub async fn compress_async(data: &[u8], level: impl Into<i32> + Send + 'static) -> Result<Vec<u8>> {
373
+ let data = data.to_vec();
374
+ let level = level.into();
375
+ tokio::task::spawn_blocking(move || compress(&data, level)).await.unwrap()
376
+ }
377
+
378
+ pub async fn decompress_async(data: &[u8]) -> Result<Vec<u8>> {
379
+ let data = data.to_vec();
380
+ tokio::task::spawn_blocking(move || decompress(&data)).await.unwrap()
381
+ }
382
+ }
383
+
384
+ pub fn estimate_compressed_size(data: &[u8], level: i32) -> usize {
385
+ let mut encoder = Encoder::new(Vec::new(), level).unwrap();
386
+ let _ = encoder.write(data);
387
+ let finished = encoder.finish().unwrap();
388
+ finished.len()
389
+ }
390
+
391
+ pub fn get_frame_info(data: &[u8]) -> Result<FrameInfo> {
392
+ if data.len() < 8 {
393
+ return Err(ZstdError::InvalidParam("Data too small for frame header".into()));
394
+ }
395
+ let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
396
+
397
+ let dict_id = match magic {
398
+ 0x28B52FED => {
399
+ if data.len() < 8 { return Err(ZstdError::InvalidParam("Invalid frame".into())); }
400
+ Some(u32::from_le_bytes([data[4], data[5], data[6], data[7]]))
401
+ }
402
+ 0xFD2FB528 => {
403
+ if data.len() < 12 { return Err(ZstdError::InvalidParam("Invalid skippable frame".into())); }
404
+ Some(u32::from_le_bytes([data[8], data[9], data[10], data[11]]))
405
+ }
406
+ _ => return Err(ZstdError::InvalidParam("Not a valid zstd frame".into())),
407
+ };
408
+
409
+ Ok(FrameInfo { magic, dict_id })
410
+ }
411
+
412
+ #[derive(Debug, Clone)]
413
+ pub struct FrameInfo {
414
+ pub magic: u32,
415
+ pub dict_id: Option<u32>,
416
+ }
417
+
418
+ #[cfg(test)]
419
+ mod tests {
420
+ use super::*;
421
+
422
+ #[test]
423
+ fn roundtrip() {
424
+ let original = b"Hello, Zstandard! This is a test string for compression.";
425
+ let compressed = compress(original, 3).unwrap();
426
+ let decompressed = decompress(&compressed).unwrap();
427
+ assert_eq!(original.to_vec(), decompressed);
428
+ }
429
+
430
+ #[test]
431
+ fn roundtrip_with_params() {
432
+ let original = b"Testing compression with custom params ".repeat(50);
433
+ let params = CompressionParams::new(6)
434
+ .with_checksum(true)
435
+ .with_content_size(true);
436
+ let compressed = compress_with_params(&original, &params, None).unwrap();
437
+ let decompressed = decompress(&compressed).unwrap();
438
+ assert_eq!(original.to_vec(), decompressed);
439
+ }
440
+
441
+ #[test]
442
+ fn multithread_compression() {
443
+ let original = b"Multi-threaded compression test ".repeat(1000);
444
+ let params = CompressionParams::new(3).with_threads(4);
445
+ let compressed = compress_with_params(&original, &params, None).unwrap();
446
+ let decompressed = decompress(&compressed).unwrap();
447
+ assert_eq!(original.to_vec(), decompressed);
448
+ assert!(compressed.len() < original.len());
449
+ }
450
+
451
+ #[test]
452
+ fn dictionary_roundtrip() {
453
+ let dict_data = b"common prefix data that repeats often in our dataset ";
454
+ let dict = Dictionary::new(dict_data.to_vec()).unwrap();
455
+
456
+ let original = b"common prefix data that repeats often in our dataset - unique part 1";
457
+ let compressed = compress_with_dict(original, 3, &dict).unwrap();
458
+ let decompressed = decompress_with_dict(&compressed, &dict).unwrap();
459
+ assert_eq!(original.to_vec(), decompressed);
460
+ }
461
+
462
+ #[test]
463
+ fn streaming_compressor() {
464
+ let original = b"Streaming compression test ".repeat(50);
465
+ let mut compressed = Vec::new();
466
+
467
+ {
468
+ let mut compressor = StreamingCompressor::new(&mut compressed, 3).unwrap();
469
+ compressor.write(&original[..200]).unwrap();
470
+ compressor.write(&original[200..400]).unwrap();
471
+ compressor.write(&original[400..]).unwrap();
472
+ compressor.finish().unwrap();
473
+ }
474
+
475
+ let decompressed = decompress(&compressed).unwrap();
476
+ assert_eq!(original.to_vec(), decompressed);
477
+ }
478
+
479
+ #[test]
480
+ fn frame_info() {
481
+ let original = b"Test data for frame info";
482
+ let compressed = compress(original, 3).unwrap();
483
+ let info = get_frame_info(&compressed).unwrap();
484
+ assert_eq!(info.magic, 0xFD2FB528); // Skippable frame magic
485
+ assert!(info.dict_id.is_some());
486
+ }
487
+
488
+ #[test]
489
+ fn checksum_verification() {
490
+ let original = b"Data with checksum";
491
+ let compressed = compress(original, 3).unwrap();
492
+ let decompressed = decompress_verify_checksum(&compressed).unwrap();
493
+ assert_eq!(original.to_vec(), decompressed);
494
+ }
495
+
496
+ #[test]
497
+ fn compression_level_enum() {
498
+ let original = b"Test data for compression level enum ".repeat(100);
499
+
500
+ let compressed_default = compress(&original, CompressionLevel::Default).unwrap();
501
+ let compressed_best = compress(&original, CompressionLevel::Best).unwrap();
502
+ let compressed_fastest = compress(&original, CompressionLevel::Fastest).unwrap();
503
+
504
+ assert!(compressed_best.len() <= compressed_default.len());
505
+ assert!(compressed_default.len() <= compressed_fastest.len());
506
+
507
+ for compressed in [compressed_default, compressed_best, compressed_fastest] {
508
+ let decompressed = decompress(&compressed).unwrap();
509
+ assert_eq!(original.to_vec(), decompressed);
510
+ }
511
+ }
512
+
513
+ #[cfg(feature = "async")]
514
+ #[tokio::test(flavor = "multi_thread")]
515
+ async fn async_compress_decompress() {
516
+ let original = b"Async compression test ".repeat(100);
517
+ let compressed = async_api::compress_async(&original, 3).await.unwrap();
518
+ let decompressed = async_api::decompress_async(&compressed).await.unwrap();
519
+ assert_eq!(original.to_vec(), decompressed);
520
+ }
521
+ }
522
+
523
+ #[cfg(feature = "python")]
524
+ mod python;
@@ -0,0 +1,251 @@
1
+ #[cfg(feature = "python")]
2
+ use pyo3::prelude::*;
3
+ #[cfg(feature = "python")]
4
+ use pyo3::types::{PyBytes, PyDict, PyList};
5
+ #[cfg(feature = "python")]
6
+ use std::io::Cursor;
7
+
8
+ #[cfg(feature = "python")]
9
+ use crate::{
10
+ compress, compress_with_dict, compress_with_params, decompress, decompress_with_dict,
11
+ decompress_verify_checksum, estimate_compressed_size, get_frame_info,
12
+ CompressionParams, CompressionLevel, Dictionary, FrameInfo, ZstdError,
13
+ };
14
+
15
+ #[cfg(feature = "python")]
16
+ #[pyclass(name = "CompressionParams")]
17
+ struct PyCompressionParams {
18
+ inner: CompressionParams,
19
+ }
20
+
21
+ #[cfg(feature = "python")]
22
+ #[pymethods]
23
+ impl PyCompressionParams {
24
+ #[new]
25
+ #[pyo3(signature = (level = 3))]
26
+ fn new(level: i32) -> Self {
27
+ Self { inner: CompressionParams::new(level) }
28
+ }
29
+
30
+ #[staticmethod]
31
+ fn from_level(level: i32) -> Self {
32
+ Self { inner: CompressionParams::new(level) }
33
+ }
34
+
35
+ fn with_threads(&mut self, threads: u32) -> PyResult<()> {
36
+ self.inner = self.inner.clone().with_threads(threads);
37
+ Ok(())
38
+ }
39
+
40
+ fn with_checksum(&mut self, enable: bool) -> PyResult<()> {
41
+ self.inner = self.inner.clone().with_checksum(enable);
42
+ Ok(())
43
+ }
44
+
45
+ fn with_content_size(&mut self, enable: bool) -> PyResult<()> {
46
+ self.inner = self.inner.clone().with_content_size(enable);
47
+ Ok(())
48
+ }
49
+
50
+ fn with_dict_id(&mut self, enable: bool) -> PyResult<()> {
51
+ self.inner = self.inner.clone().with_dict_id(enable);
52
+ Ok(())
53
+ }
54
+
55
+ fn __repr__(&self) -> String {
56
+ format!("CompressionParams(level={}, threads={}, checksum={})",
57
+ self.inner.level, self.inner.threads, self.inner.checksum)
58
+ }
59
+ }
60
+
61
+ #[cfg(feature = "python")]
62
+ #[pyclass(name = "Dictionary")]
63
+ struct PyDictionary {
64
+ inner: Dictionary,
65
+ }
66
+
67
+ #[cfg(feature = "python")]
68
+ #[pymethods]
69
+ impl PyDictionary {
70
+ #[new]
71
+ fn new(data: &[u8]) -> PyResult<Self> {
72
+ Ok(Self { inner: Dictionary::new(data.to_vec())? })
73
+ }
74
+
75
+ #[staticmethod]
76
+ fn from_bytes(data: &[u8]) -> PyResult<Self> {
77
+ Ok(Self { inner: Dictionary::from_bytes(data)? })
78
+ }
79
+
80
+ #[staticmethod]
81
+ fn detect_dict_id(data: &[u8]) -> PyResult<Option<u32>> {
82
+ Ok(Dictionary::detect_dict_id_from_frame(data))
83
+ }
84
+
85
+ fn as_bytes(&self) -> &[u8] {
86
+ self.inner.as_bytes()
87
+ }
88
+
89
+ fn __len__(&self) -> usize {
90
+ self.inner.len()
91
+ }
92
+
93
+ fn __repr__(&self) -> String {
94
+ format!("Dictionary(len={})", self.inner.len())
95
+ }
96
+ }
97
+
98
+ #[cfg(feature = "python")]
99
+ #[pyclass(name = "FrameInfo")]
100
+ struct PyFrameInfo {
101
+ inner: FrameInfo,
102
+ }
103
+
104
+ #[cfg(feature = "python")]
105
+ #[pymethods]
106
+ impl PyFrameInfo {
107
+ #[getter]
108
+ fn magic(&self) -> u32 {
109
+ self.inner.magic
110
+ }
111
+
112
+ #[getter]
113
+ fn dict_id(&self) -> Option<u32> {
114
+ self.inner.dict_id
115
+ }
116
+
117
+ fn __repr__(&self) -> String {
118
+ format!("FrameInfo(magic=0x{:x}, dict_id={:?})", self.inner.magic, self.inner.dict_id)
119
+ }
120
+ }
121
+
122
+ #[cfg(feature = "python")]
123
+ #[pyfunction]
124
+ #[pyo3(signature = (data, level = 3), name = "compress")]
125
+ fn compress_py(data: &[u8], level: i32) -> PyResult<PyObject> {
126
+ let compressed = compress(data, level)?;
127
+ Python::with_gil(|py| Ok(PyBytes::new(py, &compressed).into()))
128
+ }
129
+
130
+ #[cfg(feature = "python")]
131
+ #[pyfunction]
132
+ #[pyo3(signature = (data, level = 3, dict = None), name = "compress_with_dict")]
133
+ fn compress_with_dict_py(data: &[u8], level: i32, dict: Option<&PyDictionary>) -> PyResult<PyObject> {
134
+ let compressed = if let Some(d) = dict {
135
+ compress_with_dict(data, level, &d.inner)?
136
+ } else {
137
+ compress(data, level)?
138
+ };
139
+ Python::with_gil(|py| Ok(PyBytes::new(py, &compressed).into()))
140
+ }
141
+
142
+ #[cfg(feature = "python")]
143
+ #[pyfunction]
144
+ #[pyo3(signature = (data, params, dict = None), name = "compress_with_params")]
145
+ fn compress_with_params_py(data: &[u8], params: &PyCompressionParams, dict: Option<&PyDictionary>) -> PyResult<PyObject> {
146
+ let compressed = if let Some(d) = dict {
147
+ compress_with_params(data, &params.inner, Some(&d.inner))?
148
+ } else {
149
+ compress_with_params(data, &params.inner, None)?
150
+ };
151
+ Python::with_gil(|py| Ok(PyBytes::new(py, &compressed).into()))
152
+ }
153
+
154
+ #[cfg(feature = "python")]
155
+ #[pyfunction]
156
+ #[pyo3(name = "decompress")]
157
+ fn decompress_py(data: &[u8]) -> PyResult<PyObject> {
158
+ let decompressed = decompress(data)?;
159
+ Python::with_gil(|py| Ok(PyBytes::new(py, &decompressed).into()))
160
+ }
161
+
162
+ #[cfg(feature = "python")]
163
+ #[pyfunction]
164
+ #[pyo3(signature = (data, dict = None), name = "decompress_with_dict")]
165
+ fn decompress_with_dict_py(data: &[u8], dict: Option<&PyDictionary>) -> PyResult<PyObject> {
166
+ let decompressed = if let Some(d) = dict {
167
+ decompress_with_dict(data, &d.inner)?
168
+ } else {
169
+ decompress(data)?
170
+ };
171
+ Python::with_gil(|py| Ok(PyBytes::new(py, &decompressed).into()))
172
+ }
173
+
174
+ #[cfg(feature = "python")]
175
+ #[pyfunction]
176
+ #[pyo3(name = "decompress_verify")]
177
+ fn decompress_verify_py(data: &[u8]) -> PyResult<PyObject> {
178
+ let decompressed = decompress_verify_checksum(data)?;
179
+ Python::with_gil(|py| Ok(PyBytes::new(py, &decompressed).into()))
180
+ }
181
+
182
+ #[cfg(feature = "python")]
183
+ #[pyfunction]
184
+ #[pyo3(name = "estimate_size")]
185
+ fn estimate_size_py(data: &[u8], level: i32) -> PyResult<usize> {
186
+ Ok(estimate_compressed_size(data, level))
187
+ }
188
+
189
+ #[cfg(feature = "python")]
190
+ #[pyfunction]
191
+ #[pyo3(name = "frame_info")]
192
+ fn frame_info_py(data: &[u8]) -> PyResult<PyFrameInfo> {
193
+ Ok(PyFrameInfo { inner: get_frame_info(data)? })
194
+ }
195
+
196
+ #[cfg(feature = "python")]
197
+ #[pyfunction]
198
+ #[pyo3(name = "compression_levels")]
199
+ fn compression_levels() -> PyResult<PyObject> {
200
+ Python::with_gil(|py| {
201
+ let dict = PyDict::new(py);
202
+ dict.set_item("FASTEST", CompressionLevel::Fastest as i32)?;
203
+ dict.set_item("FAST", CompressionLevel::Fast as i32)?;
204
+ dict.set_item("DEFAULT", CompressionLevel::Default as i32)?;
205
+ dict.set_item("BETTER", CompressionLevel::Better as i32)?;
206
+ dict.set_item("BEST", CompressionLevel::Best as i32)?;
207
+ dict.set_item("ULTRA", CompressionLevel::Ultra as i32)?;
208
+ Ok(dict.into())
209
+ })
210
+ }
211
+
212
+ #[cfg(feature = "python")]
213
+ #[pyfunction]
214
+ #[pyo3(name = "strategies")]
215
+ fn strategies() -> PyResult<PyObject> {
216
+ Python::with_gil(|py| {
217
+ let list = PyList::new(py, &[
218
+ "FAST", "DFAST", "GREEDY", "LAZY", "LAZY2",
219
+ "BTLZ2", "BTOPT", "BTULTRA", "BTULTRA2"
220
+ ])?;
221
+ Ok(list.into())
222
+ })
223
+ }
224
+
225
+ #[cfg(feature = "python")]
226
+ #[pymodule]
227
+ fn zxtd(m: &Bound<'_, PyModule>) -> PyResult<()> {
228
+ m.add_class::<PyCompressionParams>()?;
229
+ m.add_class::<PyDictionary>()?;
230
+ m.add_class::<PyFrameInfo>()?;
231
+
232
+ m.add_function(wrap_pyfunction!(compress_py, m)?)?;
233
+ m.add_function(wrap_pyfunction!(compress_with_dict_py, m)?)?;
234
+ m.add_function(wrap_pyfunction!(compress_with_params_py, m)?)?;
235
+ m.add_function(wrap_pyfunction!(decompress_py, m)?)?;
236
+ m.add_function(wrap_pyfunction!(decompress_with_dict_py, m)?)?;
237
+ m.add_function(wrap_pyfunction!(decompress_verify_py, m)?)?;
238
+ m.add_function(wrap_pyfunction!(estimate_size_py, m)?)?;
239
+ m.add_function(wrap_pyfunction!(frame_info_py, m)?)?;
240
+ m.add_function(wrap_pyfunction!(compression_levels, m)?)?;
241
+ m.add_function(wrap_pyfunction!(strategies, m)?)?;
242
+
243
+ Ok(())
244
+ }
245
+
246
+ #[cfg(feature = "python")]
247
+ impl From<ZstdError> for PyErr {
248
+ fn from(err: ZstdError) -> PyErr {
249
+ PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(err.to_string())
250
+ }
251
+ }