Skip to content

Instantly share code, notes, and snippets.

@P1nGu1n
Last active June 24, 2018 17:31
Show Gist options
  • Save P1nGu1n/1790f4ab88aa567f8ad1605e44d67387 to your computer and use it in GitHub Desktop.
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.
#!/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