Forum Archive

Photo captions

sgspecker

Is there a way to get and set captions on Photos using Pythonista?

I didn't see a property listed in the Photos module API. Hopefully, I just missed it?

Thanks,
Scott

JonB

There are similar questions in the apple developer forums and stackoverflow .. it seems that apple did not expose captions via PhotoKit. So I don't think you can get it, even using objc.

cvp

@sgspecker First of all, I don't know anything about this matter, BUT, after some Google searches and a lot of tests, I've discovered that the caption is passed into the shared asset.
Try to share a photo where you have typed a caption to this little Pythonista script.

@JonB never say never 😀

For your info, it is not stored in the EXIF's but in the IPTC fields.

from PIL import Image
import appex
fil = appex.get_attachments()[0]
with Image.open(fil) as im:
    for segment, content in im.applist:
        if content.startswith(b'Photoshop'):
            print(f"segment={segment}, content=Photoshop...")
            ls = content.split(b'\x00')
            for l in ls:
                print(l)

You will find in the output the caption you typed. I don't yet know how to identify the right field where it is stored, but it should be possible to do it.

JonB

That's pretty interesting...

Although I guess to set the caption would require figuring out which IPTC tag (maybe try 'Caption/Abstract', or just 'Caption'), and you'd be editing a copy, not the original...

Does iOS provide IPTC editing functions? Looks like as part of CGImageProperties:
tion/imageio/cgimageproperties?language=objc

https://stackoverflow.com/questions/44517834/modifing-metadata-from-existing-phasset-seems-not-working

Not clear if the approach here actually works to driectly modify the original.

cvp

@JonB I didn't think to update it, I was already happy to find a begin of way to get the caption.

I think these infos are not stored in the photo file.
If I import the same photo in Pythonista, these data are no more there.

cvp

@sgspecker here how to get the caption

from PIL import Image
import appex
fil = appex.get_attachments()[0]
with Image.open(fil) as im:
    for segment, content in im.applist:
        if content.startswith(b'Photoshop'):
            # caption field identified by x1c0278 then length in 2 bytes x'0005' = 5
            i = content.find(b'\x1c\x02\x78')
            l = int.from_bytes(content[i+3:i+5], "big")   
            caption = content[i+5:i+5+l].decode("utf-8") 
            print(caption)
            break
cvp

@sgspecker This allows to create a new photo with updated caption in camera roll if you share an existing photo which has already a caption

import appex
import console
import os
import photos

def main():
    if appex.is_running_extension():
        fil = appex.get_attachments()[0]
    else:
        fil = 'a.jpg' 
    with open(fil, mode='rb') as fin:
        b = fin.read()
        #b'\xff\xed\xllllPhotoshop 3.0\x00' = marker APP1 
        #           -----
        #b'8BIM\x04\x04\x00\x00\x00\x00\x00\x17\x1c\x01Z\x00\x03\x1b%G\x1c\x02\x0\x0\x02
        #                                   ---
        #b'\x00\x02\x1c\x02x\x00\x03Cap\x00
        ip = b.find(b'Photoshop')
        if ip >= 0:
            #print(b[ip-6:ip+100])
            lip = int.from_bytes(b[ip-2:ip], "big")   
            i8 = b.find(b'8BIM',ip)
            if i8 >= 0:
                    l8 = int.from_bytes(b[i8+10:i8+12], "big")   
                    i = b.find(b'\x1c\x02x', i8)                    
                    # caption field identified by x'1c0278' then length in 2 bytes x'0005' = 5
                    l = int.from_bytes(b[i+3:i+5], "big")   
                    caption = b[i+5:i+5+l].decode("utf-8") 
                    #print(caption)
                    #return # if no update
                    # 
                    caption = console.input_alert('new caption?','', caption, 'ok', hide_cancel_button=True)
                    lu = len(caption)
                    bl = lu.to_bytes(2,'big')   # x'000l'
                    # store new caption
                    b = b[:i+3] + bl + caption.encode('utf-8') + b[i+5+l:]
                    # change length of 8BIM marker
                    l8aft = l8 - l + lu
                    bl8aft = l8aft.to_bytes(2,'big')    # x'000l'
                    b = b[:i8+10] + bl8aft + b[i8+12:]
                    # change length of Photoshop marker at ip-2
                    lipaft = lip - l + lu
                    blaft = lipaft.to_bytes(2,'big')    # x'000l'
                    b = b[:ip-2] + blaft + b[ip:]
    tmp = '_temp.jpg'
    with open(tmp, mode='wb') as fout:
        fout.write(b)
    asset = photos.create_image_asset(tmp)
    os.remove(tmp)
    appex.finish()

if __name__ == '__main__':
    main()