-
-
Save BavYeti/a3335a2dd7790b45f28fde1bcae406f5 to your computer and use it in GitHub Desktop.
run fix_eoi.py -h for usage. Added (recursive) folder search + overwrite functionality. Tested with S9+ and Python 3.7.9
This file contains hidden or 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/python | |
#fixes Samsung's broken Panorama jpgs by adding EOI markers at the end of the file | |
#positional arguments: | |
# path file or folderpath | |
#optional arguments: | |
# -h, --help show this help message and exit | |
# -d, --dump dumps Samsung specific data that is added to the image | |
# -R, -r, --recursive | |
# -o, --overwrite overwrite orginial file | |
# -f, --force do not create a backup of the original panorama | |
# The Samsung trailer format is based on the implementation from ExifTool | |
# http://www.sno.phy.queensu.ca/~phil/exiftool/ | |
# Forked from https://gist.github.com/HikerBoricua/ who forked it from https://gist.github.com/bcyrill | |
# Backwards compatible to @HikerBoricua version except the dump argument is now -d flag | |
# Meant to be run from a batch like the Windows CMD one in the block comment below | |
""" | |
echo off | |
python fix_eoi.py "./" | |
echo If everything OK, press any key to delete all *_orig.jpg files | |
set /p answer="Otherwise close this window to prevent loss of originals ..." | |
del *_orig.jpg | |
echo on | |
""" | |
import mmap | |
import struct | |
import os | |
import sys | |
import shutil | |
import argparse | |
def addEOF(files): | |
for file_fullname in files: | |
(file_name, file_ext) = os.path.splitext(file_fullname) | |
file_basename = os.path.basename(file_fullname) | |
with open(file_fullname, 'r+b') as fh: | |
m = mmap.mmap(fh.fileno(), 0, access=mmap.ACCESS_READ) | |
b = bytearray(m) | |
trailer_tail = b[-4:] | |
if trailer_tail != bytearray(b'SEFT'): | |
print('{}: Expected Samsung (b\'SEFT\') trailer but found {}'.format(file_basename, trailer_tail)) | |
continue | |
# else: | |
# print("Found SEFT") | |
length = struct.unpack_from("<I",b[-8:-4])[0] | |
trailer = b[-(8+length):] | |
endPos = len(b) | |
dirPos = endPos-(8+length) | |
if trailer[0:4] != bytearray(b'SEFH'): | |
print('{}: Expected Samsung (b\'SEFH\') trailer but found {}'.format(file_basename, trailer[0:4])) | |
continue | |
# else: | |
# print("Found SEFH") | |
version = struct.unpack_from("<I",trailer[4:8])[0] | |
if version not in set([101, 103, 105, 106]): | |
print('{}: Expected Samsung trailer version in [101,103,105,106] but found {}'.format(file_basename, version)) | |
continue | |
count = struct.unpack_from("<I",trailer[8:12])[0] | |
firstBlock = 0 | |
is_pano = 0 | |
for index in range(0, count): | |
entry = 12 + 12 * index; | |
type = struct.unpack_from("<H",trailer[entry+2:entry+4])[0] | |
noff = struct.unpack_from("<I",trailer[entry+4:entry+8])[0] | |
size = struct.unpack_from("<I",trailer[entry+8:entry+12])[0] | |
if firstBlock < noff: | |
firstBlock = noff | |
entryPos = dirPos - noff | |
entryLen = size | |
data = b[entryPos:entryPos+entryLen] | |
# Validate as the type has to match the SEFH/SEFT entry type | |
entry_type = struct.unpack_from("<H",data[2:4])[0] | |
if type != entry_type: | |
print(file_basename + ': Block type '+ type + ' doesn\'t match entry type ' + entry_type) | |
continue | |
entry_offset = struct.unpack_from("<I",data[4:8])[0] | |
entry_name = data[8:8+entry_offset].decode("utf-8") | |
if entry_name == "Panorama_Shot_Info": | |
is_pano = 1 | |
entry_data = data[8+entry_offset:] | |
if args.dump: | |
print("Dumping: %s" % entry_name) | |
with open(file_name + '_' + entry_name, 'wb') as f: | |
f.write(entry_data) | |
if (not is_pano) and (version < 106): #S8+ stopped adding this entry with trailer v106 | |
print(file_basename + ': Not a Samsung panorama') | |
continue | |
dataPos = dirPos - firstBlock | |
dirLen = endPos - dataPos | |
eoi = struct.unpack_from(">H",b[dataPos-2:dataPos])[0] | |
if eoi == 0xffd9: | |
print(file_basename + ': Already has EOI, not a defective Samsung panorama') | |
continue | |
else: | |
print(file_basename + ': Inserting EOI to correct defective Samsung panorama') | |
if not args.force: | |
shutil.copy(file_fullname, file_name + '_orig.jpg') | |
if args.overwrite: | |
fh.write(b[0:dataPos]) | |
fh.write(bytearray(b'\xff\xd9')) | |
fh.write(b[dataPos:]) | |
else: | |
with open(file_name + '_fix_eoi.jpg', 'wb') as f: | |
f.write(b[0:dataPos]) | |
f.write(bytearray(b'\xff\xd9')) | |
f.write(b[dataPos:]) | |
m.close() | |
fh.close() | |
parser = argparse.ArgumentParser(description='fixes Samsung\'s broken Panorama jpgs by adding EOI markers at the end of the file') | |
parser.add_argument('path', help='file or folderpath') | |
parser.add_argument('-d', '--dump', help='dumps Samsung specific data that is added to the image',action="store_true") | |
parser.add_argument('-R','-r','--recursive',action="store_true") | |
parser.add_argument('-o','--overwrite',help='overwrite orginial file',action="store_true") | |
parser.add_argument('-f','--force',help='do not create a backup of the original panorama',action="store_true") | |
args = parser.parse_args() | |
if os.path.isdir(args.path): | |
filelist=[] | |
if args.recursive: | |
for root, dirs, files in os.walk(args.path): | |
for file in files: | |
if file.endswith(".jpg"): | |
filelist.append(os.path.join(root, file)) | |
else: | |
for file in os.listdir(args.path): | |
if file.endswith(".jpg"): | |
filelist.append(os.path.join(args.path, file)) | |
if len(filelist) > 0: | |
print ("Checking " , len(filelist), " jpg(s)") | |
addEOF(filelist) | |
else: | |
print('Couldn\'t find any files with Samsung\'s panoramas standard .jpg extension') | |
elif os.path.isfile(args.path): | |
if args.path.endswith(".jpg"): | |
addEOF([args.path]) | |
else: | |
print(file_basename + ': Doesn\'t have Samsung\'s panoramas standard .jpg extension') | |
exit() | |
else: | |
print("This is not a valid file or folder path") | |
exit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment