Skip to content

Instantly share code, notes, and snippets.

@BavYeti
Forked from HikerBoricua/fix_eoi.py
Last active September 20, 2020 15:46
Show Gist options
  • Save BavYeti/a3335a2dd7790b45f28fde1bcae406f5 to your computer and use it in GitHub Desktop.
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
#!/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