-
Notifications
You must be signed in to change notification settings - Fork 0
/
checksums
175 lines (148 loc) · 5.79 KB
/
checksums
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env python3
# Copyright (C) 2014 Matthew Turnbull. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of Caligon Studios nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys, re, zlib, shutil, textwrap
from optparse import OptionParser, IndentedHelpFormatter
from os import path
version = """\
%prog 2020.05.03.00.16
Copyright (C) 2020 Matthew Turnbull. All rights reserved.
Use of this source code is governed by the 3-clause BSD License
that can be found in the %prog source.
"""
usage="Usage: %prog [options] FILES"
description = """\
%prog is a utility to verify the integrity of a file using a CRC32 checksum \
stored in the file name. A CRC32 checksum is an 8 character hexadecimal string. \
This is a common practice used by Anime sub releases.
When checking the checksum, the following file name formats are recognized:
...[checksum]...
...(checksum)...
..._checksum_...
When appending the checksum to the file name, the bracketed (first) format is used.\
"""
epilog = """
FILES is the list of files to preform the action on. This can be a single file, \
multiple files, or a glob if your shell supports it.
Examples:
%prog -a report.txt image.png *.xml
%prog -c *.mkv
%prog video-\[1004456E\].3gp
"""
# Constants
rcsum = re.compile("((?<=\[)[a-fA-F0-9]{8}(?=\]))|((?<=\()[a-fA-F0-9]{8}(?=\)))|((?<=_)[a-fA-F0-9]{8}(?=_))")
c_null = "[00;00m"
c_red = "[31;01m"
c_green = "[32;01m"
# GRRR... Keep newlines in the description text.
class IndentedHelpFormatterNL(IndentedHelpFormatter):
def format_description(self, description):
if not description:
return description
desc_width = self.width - self.current_indent
indent = " "*self.current_indent
bits = description.split("\n")
formatted_bits = [
textwrap.fill(bit,
desc_width,
initial_indent=indent,
subsequent_indent=indent)
for bit in bits]
return "\n".join(formatted_bits) + "\n"
format_epilog = format_description
# Generator to read the file in chunks
def chunked_read(file_object, chunk_size=1024):
while True:
data = file_object.read(chunk_size)
if not data:
break;
yield data
# Caclulate file CRC32 or return None on failure
def crc32_checksum(filename):
if not path.isfile(filename):
return None
with open(filename, "rb") as f:
csum = 0
for chunk in chunked_read(f, 4096):
csum = zlib.crc32(chunk, csum)
return "%.8X" % (csum & 0xFFFFFFFF)
return None
# Make the replacement handler function for re.sub
def makeSubHandler(csum, hasMatch):
def subHandler(match):
msum = match.group(0)
if msum.upper() == csum:
return c_green + msum + c_null
if not hasMatch:
return c_red + msum + c_null
return msum
return subHandler
# Process the files listed on the command line
def main():
# Handle command line
parser = OptionParser(version=version, usage=usage, description=description,
formatter=IndentedHelpFormatterNL())
parser.epilog=parser.expand_prog_name(epilog)
parser.add_option("-c", "--check", action="store_true", default=False,
help="Compute the checksum and try to match it to the file name using one of the formats listed above.")
parser.add_option("-a", "--append", action="store_true", default=False,
help="Compute the checksum and append it to the the file name, just before the file extension.")
parser.add_option("-v", "--verbose", action="store_true", default=False,
help="Print extra information")
(opts, args) = parser.parse_args()
if opts.append and opts.check:
parser.error("Options 'append' and 'check' are mutually exclusive")
if not opts.append and not opts.check:
opts.check = True
if opts.append:
opts.verbose = True
for filename in args:
if opts.verbose:
print("Professing file: %s" % filename)
csum = crc32_checksum(filename)
if not csum:
print("Unable to calculate checksum for file: %s" % filename)
continue
if opts.append:
src = filename
(root, ext) = path.splitext(src)
dst = "%s[%s]%s" % (root, csum, ext)
shutil.move(src, dst)
print("Moved to: %s" % dst)
elif opts.check:
(fpath, fname) = path.split(filename)
c_in = c_red
hasMatch = False
if fname.upper().find("[%s]" % csum) >= 0 \
or fname.upper().find("(%s)" % csum) >= 0 \
or fname.upper().find("_%s_" % csum) >= 0:
c_in = c_green
hasMatch = True
c_fname = rcsum.sub(makeSubHandler(csum, hasMatch), fname)
print("%s%s%s %s" % (c_in, csum, c_null, c_fname))
if opts.verbose:
print("Done")
if __name__ == "__main__":
main()