Skip to content

Commit 169e079

Browse files
committed
added compression level choices -c, --compression (default, fast & hc), heavily optimized code base and minor improvements added for file processing
1 parent d6e77ac commit 169e079

File tree

5 files changed

+99
-68
lines changed

5 files changed

+99
-68
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ Usage :
5555

5656
$ pydvpl --mode compress --path /path/to/decompress/compress.yaml --threads 10
5757

58+
$ pydvpl --mode compress --path /path/to/decompress/compress.yaml --compression hc
59+
60+
$ pydvpl --mode compress --path /path/to/decompress/ --compression fast
61+
5862
Requirements :
5963

6064
>python 3.10+

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "pydvpl"
33
description = "A CLI Tool Coded In Python3 To Convert WoTB ( Dava ) SmartDLC DVPL File Based On LZ4 High Compression."
44
readme = "README.md"
5-
version = "0.4.0"
5+
version = "0.5.0"
66
authors = [{ name = "RifsxD", email = "support@rxd-mods.xyz" }]
77
license = { text = "MIT License" }
88
requires-python = ">=3.10"

src/pydvpl/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from .converter import (
2-
CompressDVPL,
3-
DecompressDVPL,
4-
ConvertDVPLFiles,
5-
VerifyDVPLFiles,
6-
readDVPLFooter,
7-
createDVPLFooter
2+
compress_dvpl,
3+
decompress_dvpl,
4+
convert_dvpl,
5+
verify_dvpl,
6+
read_dvpl_footer,
7+
create_dvpl_footer
88
)
99

1010
from .__version__ import (
@@ -16,8 +16,8 @@
1616
__author__
1717
)
1818

19-
__all__ = ['CompressDVPL', 'DecompressDVPL', 'ConvertDVPLFiles',
20-
'VerifyDVPLFiles', 'readDVPLFooter', 'createDVPLFooter',
19+
__all__ = ['compress_dvpl', 'decompress_dvpl', 'convert_dvpl',
20+
'verify_dvpl', 'read_dvpl_footer', 'create_dvpl_footer',
2121
'__description__', '__title__', '__version__',
2222
'__author__', '__date__', '__repo__']
2323

src/pydvpl/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = "PyDVPL"
22
__description__ = "A CLI Tool Coded In Python3 To Convert WoTB ( Dava ) SmartDLC DVPL File Based On LZ4 High Compression."
3-
__version__ = "0.4.0"
3+
__version__ = "0.5.0"
44
__date__ = "2024-03-14"
55
__author__ = "RifsxD"
66
__repo__ = "https://github.com/rifsxd/pydvpl"

src/pydvpl/converter.py

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
import sys
88
import multiprocessing
99
import queue
10+
from pathlib import Path
1011
from functools import partial
1112
from .__version__ import __version__, __description__, __title__, __date__, __repo__, __author__
1213

1314

14-
1515
class Color:
1616
RED = '\033[31m'
1717
GREEN = '\033[32m'
@@ -48,7 +48,7 @@ def __init__(self, original_size, compressed_size, crc32, type_val):
4848
self.type = type_val
4949

5050

51-
def createDVPLFooter(input_size, compressed_size, crc32_val, type_val):
51+
def create_dvpl_footer(input_size, compressed_size, crc32_val, type_val):
5252
result = bytearray(DVPL_FOOTER_SIZE)
5353
result[:4] = input_size.to_bytes(4, 'little')
5454
result[4:8] = compressed_size.to_bytes(4, 'little')
@@ -58,7 +58,7 @@ def createDVPLFooter(input_size, compressed_size, crc32_val, type_val):
5858
return result
5959

6060

61-
def readDVPLFooter(buffer):
61+
def read_dvpl_footer(buffer):
6262
if len(buffer) < DVPL_FOOTER_SIZE:
6363
raise ValueError(Color.RED + "InvalidDVPLFooter: Buffer size is smaller than expected" + Color.RESET)
6464

@@ -75,14 +75,21 @@ def readDVPLFooter(buffer):
7575
return DVPLFooter(original_size, compressed_size, crc32_val, type_val)
7676

7777

78-
def CompressDVPL(buffer):
79-
compressed_block = lz4.block.compress(buffer, store_size=False)
80-
footer_buffer = createDVPLFooter(len(buffer), len(compressed_block), zlib.crc32(compressed_block), DVPL_TYPE_LZ4)
78+
def compress_dvpl(buffer, compression_type="default"):
79+
if compression_type == "fast":
80+
mode = "fast"
81+
elif compression_type == "hc":
82+
mode = "high_compression"
83+
else:
84+
mode = "default"
85+
86+
compressed_block = lz4.block.compress(buffer, store_size=False, mode=mode)
87+
footer_buffer = create_dvpl_footer(len(buffer), len(compressed_block), zlib.crc32(compressed_block), DVPL_TYPE_LZ4)
8188
return compressed_block + footer_buffer
8289

8390

84-
def DecompressDVPL(buffer):
85-
footer_data = readDVPLFooter(buffer)
91+
def decompress_dvpl(buffer):
92+
footer_data = read_dvpl_footer(buffer)
8693
target_block = buffer[:-DVPL_FOOTER_SIZE]
8794

8895
if len(target_block) != footer_data.compressed_size:
@@ -96,10 +103,10 @@ def DecompressDVPL(buffer):
96103
raise ValueError(Color.RED + "DVPLTypeSizeMismatch" + Color.RESET)
97104
return target_block
98105
elif footer_data.type == DVPL_TYPE_LZ4:
99-
deDVPL_block = lz4.block.decompress(target_block, uncompressed_size=footer_data.original_size)
100-
if len(deDVPL_block) != footer_data.original_size:
106+
de_dvpl_block = lz4.block.decompress(target_block, uncompressed_size=footer_data.original_size)
107+
if len(de_dvpl_block) != footer_data.original_size:
101108
raise ValueError(Color.RED + "DVPLDecodeSizeMismatch" + Color.RESET)
102-
return deDVPL_block
109+
return de_dvpl_block
103110
else:
104111
raise ValueError(Color.RED + "UNKNOWN DVPL FORMAT" + Color.RESET)
105112

@@ -115,16 +122,15 @@ def print_progress_bar(processed_files, total_files):
115122
sys.stdout.flush()
116123

117124

118-
from pathlib import Path
119-
120125
def count_total_files(directory):
121126
total_files = 0
122127
for path in Path(directory).rglob('*'):
123128
if path.is_file():
124129
total_files += 1
125130
return total_files
126131

127-
def ConvertDVPLFiles(directory_or_file, config, total_files=None, processed_files=None):
132+
133+
def convert_dvpl(directory_or_file, config, total_files=None, processed_files=None):
128134
if total_files is None:
129135
total_files = count_total_files(directory_or_file)
130136
if processed_files is None:
@@ -134,10 +140,13 @@ def ConvertDVPLFiles(directory_or_file, config, total_files=None, processed_file
134140
failure_count = 0
135141
ignored_count = 0
136142

143+
if not os.path.exists(directory_or_file):
144+
raise FileNotFoundError(f"File or directory '{directory_or_file}' not found.")
145+
137146
if Path(directory_or_file).is_dir():
138147
for file_path in Path(directory_or_file).rglob('*'):
139148
if file_path.is_file():
140-
succ, fail, ignored = ConvertDVPLFiles(str(file_path), config, total_files, processed_files)
149+
succ, fail, ignored = convert_dvpl(str(file_path), config, total_files, processed_files)
141150
success_count += succ
142151
failure_count += fail
143152
ignored_count += ignored
@@ -154,26 +163,38 @@ def ConvertDVPLFiles(directory_or_file, config, total_files=None, processed_file
154163
if not should_ignore and (is_decompression or is_compression):
155164
file_path = directory_or_file
156165
try:
157-
with open(file_path, "rb") as f:
158-
file_data = f.read()
159-
160-
if is_compression:
161-
processed_block = CompressDVPL(file_data)
162-
new_name = file_path + ".dvpl"
166+
# Check if the file exists before attempting to open it
167+
if os.path.exists(file_path):
168+
with open(file_path, "rb") as f:
169+
file_data = f.read()
170+
171+
if is_compression:
172+
if config.compression == "fast":
173+
processed_block = compress_dvpl(file_data, "fast")
174+
elif config.compression == "hc":
175+
processed_block = compress_dvpl(file_data, "hc")
176+
else:
177+
processed_block = compress_dvpl(file_data)
178+
new_name = file_path + ".dvpl"
179+
else:
180+
processed_block = decompress_dvpl(file_data)
181+
new_name = os.path.splitext(file_path)[0]
182+
183+
with open(new_name, "wb") as f:
184+
f.write(processed_block)
185+
186+
if not config.keep_originals:
187+
os.remove(file_path)
188+
189+
success_count += 1
190+
if config.verbose:
191+
with output_lock:
192+
print(f"{Color.GREEN}\nFile{Color.RESET} {file_path} has been successfully {Color.GREEN}{'compressed' if is_compression else 'decompressed'}{Color.RESET} into {Color.GREEN}{new_name}{Color.RESET}")
163193
else:
164-
processed_block = DecompressDVPL(file_data)
165-
new_name = os.path.splitext(file_path)[0]
166-
167-
with open(new_name, "wb") as f:
168-
f.write(processed_block)
169-
170-
if not config.keep_originals:
171-
os.remove(file_path)
172-
173-
success_count += 1
174-
if config.verbose:
175-
with output_lock:
176-
print(f"{Color.GREEN}\nFile{Color.RESET} {file_path} has been successfully {Color.GREEN}{'compressed' if is_compression else 'decompressed'}{Color.RESET} into {Color.GREEN}{new_name}{Color.RESET}")
194+
if config.verbose:
195+
with output_lock:
196+
print(f"{Color.RED}\nError{Color.RESET}: File {file_path} does not exist.")
197+
failure_count += 1
177198
except Exception as e:
178199
failure_count += 1
179200
if config.verbose:
@@ -187,7 +208,8 @@ def ConvertDVPLFiles(directory_or_file, config, total_files=None, processed_file
187208

188209
return success_count, failure_count, ignored_count
189210

190-
def VerifyDVPLFiles(directory_or_file, config, total_files=None, processed_files=None):
211+
212+
def verify_dvpl(directory_or_file, config, total_files=None, processed_files=None):
191213
if total_files is None:
192214
total_files = count_total_files(directory_or_file)
193215
if processed_files is None:
@@ -197,10 +219,13 @@ def VerifyDVPLFiles(directory_or_file, config, total_files=None, processed_files
197219
failure_count = 0
198220
ignored_count = 0
199221

222+
if not os.path.exists(directory_or_file):
223+
raise FileNotFoundError(f"File or directory '{directory_or_file}' not found.")
224+
200225
if Path(directory_or_file).is_dir():
201226
for file_path in Path(directory_or_file).rglob('*'):
202227
if file_path.is_file() and file_path.suffix == '.dvpl':
203-
succ, fail, ignored = VerifyDVPLFiles(str(file_path), config, total_files, processed_files)
228+
succ, fail, ignored = verify_dvpl(str(file_path), config, total_files, processed_files)
204229
success_count += succ
205230
failure_count += fail
206231
ignored_count += ignored
@@ -219,7 +244,7 @@ def VerifyDVPLFiles(directory_or_file, config, total_files=None, processed_files
219244
with open(file_path, "rb") as f:
220245
file_data = f.read()
221246

222-
footer_data = readDVPLFooter(file_data)
247+
footer_data = read_dvpl_footer(file_data)
223248

224249
target_block = file_data[:-DVPL_FOOTER_SIZE]
225250

@@ -233,8 +258,8 @@ def VerifyDVPLFiles(directory_or_file, config, total_files=None, processed_files
233258
if footer_data.original_size != footer_data.compressed_size or footer_data.type != DVPL_TYPE_NONE:
234259
raise ValueError(Color.RED + "DVPLTypeSizeMismatch" + Color.RESET)
235260
elif footer_data.type == DVPL_TYPE_LZ4:
236-
deDVPL_block = lz4.block.decompress(target_block, uncompressed_size=footer_data.original_size)
237-
if len(deDVPL_block) != footer_data.original_size:
261+
de_dvpl_block = lz4.block.decompress(target_block, uncompressed_size=footer_data.original_size)
262+
if len(de_dvpl_block) != footer_data.original_size:
238263
raise ValueError(Color.RED + "DVPLDecodeSizeMismatch" + Color.RESET)
239264
else:
240265
raise ValueError(Color.RED + "UNKNOWN DVPL FORMAT" + Color.RESET)
@@ -259,19 +284,20 @@ def VerifyDVPLFiles(directory_or_file, config, total_files=None, processed_files
259284
return success_count, failure_count, ignored_count
260285

261286

262-
def process_func(directory_or_file, config, total_files, processed_files):
287+
288+
def process_mode(directory_or_file, config):
263289
if config.mode in ["compress", "decompress"]:
264-
return ConvertDVPLFiles(directory_or_file, config)
290+
return convert_dvpl(directory_or_file, config)
265291
elif config.mode == "verify":
266-
return VerifyDVPLFiles(directory_or_file, config)
292+
return verify_dvpl(directory_or_file, config)
267293
elif config.mode == "help":
268-
PrintHelpMessage()
269-
return (0, 0, 0) # Return (0, 0, 0) when in help mode
294+
print_help_message()
295+
return 0, 0, 0
270296
else:
271297
raise ValueError("Incorrect mode selected. Use '--help' for information.")
272298

273299

274-
def ParseCommandLineArgs():
300+
def parse_command_line_args():
275301
parser = argparse.ArgumentParser()
276302
parser.add_argument("-m", "--mode",
277303
help="mode can be 'c' or 'compress' / 'd' or 'decompress' / 'v' or 'verify' / 'h' or 'help' (for an extended help guide).")
@@ -284,6 +310,8 @@ def ParseCommandLineArgs():
284310
help="Comma-separated list of file extensions to ignore during compression.")
285311
parser.add_argument("-t", "--threads", type=int, default=1,
286312
help="Number of threads to use for processing. Default is 1.")
313+
parser.add_argument("-c", "--compression", choices=['default', 'fast', 'hc'],
314+
help="Select compression level: 'default' for default compression, 'fast' for fast compression, 'hc' for high compression.")
287315
args = parser.parse_args()
288316

289317
if not args.mode:
@@ -300,14 +328,14 @@ def ParseCommandLineArgs():
300328
'h': 'help'
301329
}
302330

303-
# If mode argument is provided and it matches a short form, replace it with the full mode name
331+
# If mode argument is provided, and it matches a short form, replace it with the full mode name
304332
if args.mode in mode_mapping:
305333
args.mode = mode_mapping[args.mode]
306334

307335
return args
308336

309337

310-
def PrintHelpMessage():
338+
def print_help_message():
311339
print('''$ pydvpl [--mode] [--keep-originals] [--path] [--verbose] [--ignore] [--threads]
312340
313341
• flags can be one of the following:
@@ -344,7 +372,7 @@ def PrintHelpMessage():
344372
345373
$ pydvpl --mode decompress --keep-originals --path /path/to/decompress/compress.yaml.dvpl
346374
347-
$ pydvpl --mode dcompress --keep-originals --path /path/to/decompress/compress.yaml
375+
$ pydvpl --mode decompress --keep-originals --path /path/to/decompress/compress.yaml
348376
349377
$ pydvpl --mode compress --path /path/to/decompress --ignore .exe,.dll
350378
@@ -359,10 +387,14 @@ def PrintHelpMessage():
359387
$ pydvpl --mode decompress --path /path/to/decompress/compress.yaml.dvpl --threads 10
360388
361389
$ pydvpl --mode compress --path /path/to/decompress/compress.yaml --threads 10
390+
391+
$ pydvpl --mode compress --path /path/to/decompress/compress.yaml --compression hc
392+
393+
$ pydvpl --mode compress --path /path/to/decompress/ --compression fast
362394
''')
363395

364396

365-
def PrintElapsedTime(elapsed_time):
397+
def print_elapsed_time(elapsed_time):
366398
if elapsed_time < 1:
367399
print(f"\nProcessing took {Color.GREEN}{int(elapsed_time * 1000)} ms{Color.RESET}\n")
368400
elif elapsed_time < 60:
@@ -380,19 +412,14 @@ def main():
380412
print(f"{Color.BLUE}• Info:{Color.RESET} {Meta.INFO}\n")
381413

382414
start_time = time.time()
383-
config = ParseCommandLineArgs()
415+
config = parse_command_line_args()
384416

385417
if config.threads <= 0:
386418
print(f"\n{Color.YELLOW}No threads specified.{Color.RESET} No processing will be done.\n")
387419
return
388420

389-
total_files = count_total_files(config.path)
390-
manager = multiprocessing.Manager()
391-
processed_files = manager.Value('i', 0) # Define processed_files using a Manager
392-
393421
try:
394-
process_func_partial = partial(process_func, config=config, total_files=total_files,
395-
processed_files=processed_files)
422+
process_func_partial = partial(process_mode, config=config)
396423

397424
if config.threads > 1:
398425
with multiprocessing.Pool(config.threads) as pool:
@@ -414,8 +441,8 @@ def main():
414441
print(f"\n\n{Color.RED}{config.mode.upper()} FAILED{Color.RESET}: {e}\n")
415442

416443
elapsed_time = time.time() - start_time
417-
PrintElapsedTime(elapsed_time)
444+
print_elapsed_time(elapsed_time)
418445

419446

420447
if __name__ == "__main__":
421-
main()
448+
main()

0 commit comments

Comments
 (0)