Last active
June 24, 2018 17:31
-
-
Save P1nGu1n/1790f4ab88aa567f8ad1605e44d67387 to your computer and use it in GitHub Desktop.
Rename obfuscated files using a .par2 file for SABnzbd, by parsing the .par2 file and matching the source files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import sys | |
import struct | |
import fnmatch | |
import hashlib | |
from os import path | |
# see: http://parchive.sourceforge.net/docs/specifications/parity-volume-spec/article-spec.html | |
STRUCT_PACKET_HEADER = struct.Struct("<" | |
"8s" # Magic sequence | |
"Q" # Length of the entire packet (including header), must be multiple of 4 | |
"16s" # MD5 Hash of packet | |
"16s" # Recovery Set ID | |
"16s" # Packet type | |
) | |
PACKET_TYPE_FILE_DESC = 'PAR 2.0\x00FileDesc' | |
STRUCT_FILE_DESC_PACKET = struct.Struct("<" | |
"16s" # File ID | |
"16s" # MD5 hash of the entire file | |
"16s" # MD5 hash of the first 16KiB of the file | |
"Q" # Length of the file | |
) | |
def decodePar(dir, parfile): | |
with open(path.join(dir, parfile), 'rb') as file: | |
while (True): | |
header = file.read(STRUCT_PACKET_HEADER.size) | |
if not header: break # file rully read | |
(_, packetLength, _, _, packetType) = STRUCT_PACKET_HEADER.unpack(header) | |
bodyLength = packetLength - STRUCT_PACKET_HEADER.size | |
# only process File Description packets | |
if (packetType != PACKET_TYPE_FILE_DESC): | |
# skip this packet | |
file.seek(bodyLength, os.SEEK_CUR) | |
continue | |
chunck = file.read(STRUCT_FILE_DESC_PACKET.size) | |
(_, _, hash16k, filelength) = STRUCT_FILE_DESC_PACKET.unpack(chunck) | |
# filename makes up for the rest of the packet, padded with null characters | |
targetName = file.read(bodyLength - STRUCT_FILE_DESC_PACKET.size).rstrip('\0') | |
targetPath = path.join(dir, targetName) | |
# file already exists, skip it | |
if (path.exists(targetPath)): | |
print "File already exists: " + targetName | |
continue | |
# find and rename file | |
srcPath = findFile(dir, filelength, hash16k) | |
if (srcPath is not None): | |
os.rename(srcPath, targetPath) | |
print "Renamed file from " + path.basename(srcPath) + " to " + targetName | |
else: | |
print "No match found for: " + targetName | |
def findFile(dir, filelength, hash16k): | |
for filename in os.listdir(dir): | |
filepath = path.join(dir, filename) | |
# check if the size matches as an indication | |
if (path.getsize(filepath) != filelength): continue | |
with open(filepath, 'rb') as file: | |
data = file.read(16 * 1024) | |
m = hashlib.md5() | |
m.update(data) | |
# compare hash to confirm the match | |
if (m.digest() == hash16k): | |
return filepath | |
return None | |
# find par2 files | |
outputDir = os.environ['SAB_COMPLETE_DIR'] | |
for root, dirnames, filenames in os.walk(outputDir): | |
for filename in fnmatch.filter(filenames, '*.par2'): | |
decodePar(root, filename) | |
sys.exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment