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 +334 -0
- zxtd-0.1.0/Cargo.toml +21 -0
- zxtd-0.1.0/PKG-INFO +130 -0
- zxtd-0.1.0/README.md +112 -0
- zxtd-0.1.0/pyproject.toml +30 -0
- zxtd-0.1.0/src/lib.rs +524 -0
- zxtd-0.1.0/src/python.rs +251 -0
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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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(), ¶ms, 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, ¶ms, 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, ¶ms, 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;
|
zxtd-0.1.0/src/python.rs
ADDED
|
@@ -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, ¶ms.inner, Some(&d.inner))?
|
|
148
|
+
} else {
|
|
149
|
+
compress_with_params(data, ¶ms.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
|
+
}
|