Skip to content

Instantly share code, notes, and snippets.

@HikerBoricua
Forked from bcyrill/fix_eoi.py
Last active July 25, 2021 21:25
Show Gist options
  • Save HikerBoricua/54aec42ee47f2ecb374ec3208b3a98ee to your computer and use it in GitHub Desktop.
Save HikerBoricua/54aec42ee47f2ecb374ec3208b3a98ee to your computer and use it in GitHub Desktop.
Adds peripheral code to make it easier to work with a folder/directory containing mixed content. Tested with S8+ (trailer versions 103, 105 & 106). Coded for Python 3.6.5 64 bit.
#!/usr/bin/python
# The Samsung trailer format is based on the implementation from ExifTool
# http://www.sno.phy.queensu.ca/~phil/exiftool/
# Forked off https://gist.github.com/bcyrill to fit a specific use case,
# bad pano files mixed with other files in a folder/dir
# The "dump" argument was not tested in this fork
# Meant to be run from a batch like the Windows CMD one in the block comment below
"""
echo off
for %%A in (*.jpg) do python fix_eoi.py "%%A"
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
if (len(sys.argv) < 2) or (len(sys.argv) > 3):
print("Usage: fix_eoi.py <filename> [dump]")
exit()
file_fullname = sys.argv[1]
if len(sys.argv) < 3:
dump = 0
elif sys.argv[2] == "dump":
dump = 1
else:
print("Invalid argument")
print("Usage: fix_eoi.py <filename> [dump]")
exit()
(file_name, file_ext) = os.path.splitext(file_fullname)
file_basename = os.path.basename(file_fullname)
if file_ext != '.jpg':
print(file_basename + ': Doesn\'t have Samsung panoramas standard .jpg extension')
exit()
with open(file_fullname, 'rb') 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))
exit()
# 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]))
exit()
# 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))
exit()
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)
exit()
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 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')
exit()
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')
exit()
else:
print(file_basename + ': Inserting EOI to correct defective Samsung panorama')
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()
shutil.move(file_fullname, file_name + '_orig.jpg')
@frapa
Copy link

frapa commented Jul 25, 2021

I made a rust version which can strip the files of the videos and reduce the size in about half. This version merely makes the file a valid JPEG file but the video remains in there. I also made some binaries if somebody is interested: https://github.com/frapa/sampan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment