Skip to content

Commit

Permalink
Remove Claim Block, add new Store, and create new Claim Block.
Browse files Browse the repository at this point in the history
Signed-off-by: Bofu Chen (bafu) <bofu@numbersprotocol.io>
  • Loading branch information
bafu committed Feb 10, 2021
1 parent e4a6d7e commit 1104657
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 26 deletions.
2 changes: 1 addition & 1 deletion cai/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def insert_xmp_key(data_bytes, store_label='cai/cb.starling_1'):
metadata = pyexiv2.ImageMetadata.from_buffer(data_bytes)
metadata.read()
metadata['Xmp.dcterms.provenance'] = pyexiv2.XmpTag('Xmp.dcterms.provenance',
'self#jumbf=cai/' + store_label)
'self#jumbf=cai/{}/cai.claim'.format(store_label))
metadata.write()
return metadata.buffer

Expand Down
36 changes: 29 additions & 7 deletions cai/jumbf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# along with starling-cai. If not, see <http://www.gnu.org/licenses/>.

import json
import re

'''Implementation of ISO/IEC 19566-5:2019(E)
Information technologies - JPEG systems
Expand Down Expand Up @@ -121,11 +122,11 @@ def print_box(self):


class App11Box(object):
def __init__(self):
def __init__(self, en=1, z=1):
self.marker = 'FFEB'
self.ci = 'JP'.encode('utf-8').hex()
self.en = 1
self.z = 1
self.en = en
self.z = z
self.payload = b''

def get_size(self):
Expand Down Expand Up @@ -156,18 +157,19 @@ def convert_bytes(self):
# Reminder: Superbox = LBox + TBox + Payload
segment_superboxes = self._split_payload()
total_bytes = b''
self.z = 1
current_z = self.z
for superbox in segment_superboxes:
marker = bytes.fromhex(self.marker)
ci = bytes.fromhex(self.ci)
en = self.en.to_bytes(2, byteorder='big')
z = self.z.to_bytes(4, byteorder='big')
z = current_z.to_bytes(4, byteorder='big')

# marker is not included
length = 2 + len(ci) + len(en) + len(z) + len(superbox)
le = length.to_bytes(2, byteorder='big')
total_bytes += marker + le + ci + en + z + superbox
self.z += 1
print('current_z:', current_z)
current_z += 1
return total_bytes


Expand Down Expand Up @@ -201,4 +203,24 @@ def create_codestream_superbox(content=b'', label=''):


def json_to_bytes(json_object):
return json.dumps(json_object, separators=(',',':')).encode('utf-8')
return json.dumps(json_object, separators=(',',':')).encode('utf-8')


def get_app11_marker_segment_headers(data_bytes):
marker = b'\xff\xeb'
offsets = [m.start() for m in re.finditer(marker, data_bytes)]
headers = {}
for offset in offsets:
header = {}
header['le'] = int.from_bytes(data_bytes[offset + 2 : offset + 4], byteorder='big')
header['ci'] = data_bytes[offset + 4 : offset + 6].decode('utf-8')
header['en'] = int.from_bytes(data_bytes[offset + 6 : offset + 8], byteorder='big')
header['z'] = int.from_bytes(data_bytes[offset + 8 : offset + 12], byteorder='big')
header['lbox'] = int.from_bytes(data_bytes[offset + 12 : offset + 16], byteorder='big')
header['tbox'] = data_bytes[offset + 16 : offset + 20].decode('utf-8')
header['offset'] = offset

# passive protection to skip illegal or empty segment
if header['le'] > 10:
headers[header['z']] = header
return headers
166 changes: 148 additions & 18 deletions cai/starling.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from cai.core import insert_xmp_key
from cai.jumbf import create_codestream_superbox
from cai.jumbf import create_json_superbox
from cai.jumbf import get_app11_marker_segment_headers
from cai.jumbf import json_to_bytes

'''Starling CLI tool to generate CAI metadata.
Expand Down Expand Up @@ -87,9 +88,21 @@ def main():
# read media content if injection is enabled
with open(args.inject, 'rb') as f:
raw_bytes = f.read()

# get the label of the last Claim
parent_claim = get_xmp_tag(raw_bytes)
print('parent_claim: ', parent_claim)

# get App11 marker segment headers
app11_headers = get_app11_marker_segment_headers(raw_bytes)
has_app11_headers = True if len(app11_headers) > 0 else False
if has_app11_headers:
print('Find App11 marker segments')
for i in range(1, len(app11_headers) + 1):
print('\t{0}'.format(app11_headers[i]))
else:
print('No existing App11 marker segment.')

# create CAI metadata
assertions = []
for filepath, label in zip(assertion_filepaths, assertion_labels):
Expand All @@ -104,37 +117,154 @@ def main():
raise Exception(
'Unknown assertion type {0} from {1}'.format(fileext, filepath))

# private key for signature
if key_filepath != '':
with open(key_filepath, 'rb') as f:
key = f.read()
else:
key = []

# create a new Store
cai_store = CaiStore(label=store_label,
assertions=assertions,
recorder=recorder,
parent_claim=parent_claim,
key=key)
cai_claim_block = CaiClaimBlock()
cai_claim_block.content_boxes.append(cai_store)
cai_segment = App11Box()
cai_segment.payload = cai_claim_block.convert_bytes()
if has_app11_headers:
# get last segment header information
header_number = len(app11_headers)
last_le = app11_headers[header_number]['le']
last_en = app11_headers[header_number]['en']
last_z = app11_headers[header_number]['z']
last_lbox = app11_headers[header_number]['lbox']
last_tbox = app11_headers[header_number]['tbox']
last_offset = app11_headers[header_number]['offset']
print('Last Le: {0}, En: {1}, Z: {2}, LBox: {3}, TBox: {4}, offset: {5}'.format(
last_le, last_en, last_z, last_lbox, last_tbox, last_offset))

# output CAI metadata
if len(args.output) == 0:
print(cai_segment.convert_bytes().hex())
# re-construct Claim Block payload
claim_block_payload = bytearray()
for i in range(1, header_number + 1):
payload_start = app11_headers[i]['offset'] + 20
payload_end = payload_start + (app11_headers[i]['le'] - 18)
payload = raw_bytes[payload_start : payload_end]
claim_block_payload += payload
print('Claim Block payload size (Description/Content Boxes): {}'.format(len(claim_block_payload)))

print('Claim Block')
print('\tSuperbox')
print('\t\tLBox: {0}, TBox: {1}'.format(last_lbox, last_tbox))

print('\tDescription Box')
offset_d = 0
lbox_d = int.from_bytes(claim_block_payload[offset_d: offset_d + 4], byteorder='big')
tbox_d = claim_block_payload[offset_d + 4 : offset_d + 8].decode('utf-8')
print('\t\toffset: {}'.format(offset_d))
print('\t\tLBox: {0}, TBox: {1}'.format(lbox_d, tbox_d))
print('\t\tType: {}'.format(claim_block_payload[offset_d + 8 : offset_d + 24].hex()))
print('\t\tToggles: {}'.format(claim_block_payload[offset_d + 24 : offset_d + 25].hex()))
print('\t\tLabel: {}'.format(claim_block_payload[offset_d + 25 : lbox_d].decode('utf-8')))

print('\tContent Box (Stores)')
print('\t\tStore #1')
print('\t\tSuperbox')
offset_s = lbox_d
lbox_s = int.from_bytes(claim_block_payload[offset_s: offset_s + 4], byteorder='big')
tbox_s = claim_block_payload[offset_s + 4 : offset_s + 8].decode('utf-8')
print('\t\t\toffset: {}'.format(offset_s))
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_s, tbox_s))
print('\t\tDescription Box')
offset_d = offset_s + 8
lbox_d = int.from_bytes(claim_block_payload[offset_d: offset_d + 4], byteorder='big')
tbox_d = claim_block_payload[offset_d + 4 : offset_d + 8].decode('utf-8')
print('\t\t\toffset: {}'.format(offset_d))
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_d, tbox_d))
print('\t\t\tType: {}'.format(claim_block_payload[offset_d + 8 : offset_d + 24].hex()))
print('\t\t\tToggles: {}'.format(claim_block_payload[offset_d + 24 : offset_d + 25].hex()))
print('\t\t\tLabel: {}'.format(claim_block_payload[offset_d + 25 : offset_d + lbox_d].decode('utf-8')))
print('\t\tContent Box')
offset_c = offset_d + lbox_d
lbox_c = int.from_bytes(claim_block_payload[offset_c: offset_c + 4], byteorder='big')
tbox_c = claim_block_payload[offset_c + 4 : offset_c + 8].decode('utf-8')
print('\t\t\toffset: {}'.format(offset_c))
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_c, tbox_c))
print('\t\t\tType: {}'.format(claim_block_payload[offset_c + 8 : offset_c + 24].hex()))

print('\t\tStore #2')
print('\t\tSuperbox')
offset_s = offset_s + lbox_s
lbox_s = int.from_bytes(claim_block_payload[offset_s: offset_s + 4], byteorder='big')
tbox_s = claim_block_payload[offset_s + 4 : offset_s + 8].decode('utf-8')
print('\t\t\toffset: {}'.format(offset_s))
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_s, tbox_s))
print('\t\tDescription Box')
offset_d = offset_s + 8
lbox_d = int.from_bytes(claim_block_payload[offset_d: offset_d + 4], byteorder='big')
tbox_d = claim_block_payload[offset_d + 4 : offset_d + 8].decode('utf-8')
print('\t\t\toffset: {}'.format(offset_d))
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_d, tbox_d))
print('\t\t\tType: {}'.format(claim_block_payload[offset_d + 8 : offset_d + 24].hex()))
print('\t\t\tToggles: {}'.format(claim_block_payload[offset_d + 24 : offset_d + 25].hex()))
print('\t\t\tLabel: {}'.format(claim_block_payload[offset_d + 25 : offset_d + lbox_d].decode('utf-8')))

print('\t\tStore #3 (newly created)')
print('\t\tSuperbox')
store_bytes = cai_store.convert_bytes()
offset_s = 0
lbox_s = int.from_bytes(store_bytes[offset_s: offset_s + 4], byteorder='big')
tbox_s = store_bytes[offset_s + 4 : offset_s + 8].decode('utf-8')
print('\t\t\toffset: {}'.format(offset_s))
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_s, tbox_s))
print('\t\tDescription Box')
offset_d = offset_s + 8
lbox_d = int.from_bytes(store_bytes[offset_d: offset_d + 4], byteorder='big')
tbox_d = store_bytes[offset_d + 4 : offset_d + 8].decode('utf-8')
print('\t\t\tLBox: {0}, TBox: {1}'.format(lbox_d, tbox_d))
print('\t\t\tType: {}'.format(store_bytes[offset_d + 8 : offset_d + 24].hex()))
print('\t\t\tToggles: {}'.format(store_bytes[offset_d + 24 : offset_d + 25].hex()))
print('\t\t\tLabel: {}'.format(store_bytes[offset_d + 25 : offset_d + lbox_d].decode('utf-8')))

# append new Store bytes
updated_claim_block_payload = claim_block_payload + store_bytes
updated_lbox = last_lbox + len(store_bytes)
#updated_tbox = last_tbox
updated_claim_block_bytes = updated_lbox.to_bytes(4, byteorder='big') + b'jumb' + updated_claim_block_payload
updated_app11_segment = App11Box(en=last_en)
updated_app11_segment.payload = updated_claim_block_bytes

update_range_s = app11_headers[1]['offset']
update_range_e = app11_headers[1]['offset'] + last_lbox + 16
print('Update range: {0} - {1}'.format(update_range_s, update_range_e))

if len(args.inject) > 0:
fname, fext = os.path.splitext(args.inject)
fpath = fname + '-cai' + fext
with open(fpath, 'wb') as f:
data_bytes = raw_bytes[:update_range_s] + updated_app11_segment.convert_bytes() + raw_bytes[update_range_e:]
cai_data_bytes = insert_xmp_key(data_bytes, store_label=store_label)
f.write(cai_data_bytes)
else:
with open(args.output, 'w') as f:
f.write(cai_segment.convert_bytes().hex())

# inject CAI metadata
if len(args.inject) > 0:
fname, fext = os.path.splitext(args.inject)
fpath = fname + '-cai' + fext
with open(fpath, 'wb') as f:
data_bytes = raw_bytes[0:2] + cai_segment.convert_bytes() + raw_bytes[2:]
cai_data_bytes = insert_xmp_key(data_bytes, store_label=store_label)
f.write(cai_data_bytes)
# create a new Claim Block Box
cai_claim_block = CaiClaimBlock()
cai_claim_block.content_boxes.append(cai_store)
cai_segment = App11Box()
cai_segment.payload = cai_claim_block.convert_bytes()

# inject CAI metadata
if len(args.inject) > 0:
fname, fext = os.path.splitext(args.inject)
fpath = fname + '-cai' + fext
with open(fpath, 'wb') as f:
data_bytes = raw_bytes[0:2] + cai_segment.convert_bytes() + raw_bytes[2:]
cai_data_bytes = insert_xmp_key(data_bytes, store_label=store_label)
f.write(cai_data_bytes)

# output CAI metadata
#if len(args.output) == 0:
# print(cai_segment.convert_bytes().hex())
#else:
# with open(args.output, 'w') as f:
# f.write(cai_segment.convert_bytes().hex())


if __name__ == "__main__":
Expand Down

0 comments on commit 1104657

Please sign in to comment.