Replies: 4 comments 13 replies
-
Hello @Yorian Roughly... Live Photos on iOS are a pair/combination of an .HEIC image file and corresponding .MOV video file. As far as I know the .HEIC file has a tag which indicates there should be a corresponding video file and, such file existing, apps and Photos can then understand them as a Live Photo. They can be loaded directly to Photos and osxphotos can then export the pair (with appropriate options!). Osxphotos can also import into Photos pics and photos and I trust also Live Photos -- did not test it though ;). Now to your question: When you say Live Photos from Android don't know if Android follows the same structure and approach as iOS iPhones and if Photos will recognize it.
|
Beta Was this translation helpful? Give feedback.
-
Making some progress. The following prototype python code will write the correct MakerNote to the image file using Core Graphics. I'll need to write separate code using AVAsset to write the metadata to the movie file. You'll need to
"""Write Maker Note asset id to image file to associate it with a live image"""
from __future__ import annotations
import os
import sys
import uuid
from typing import Any
import objc
import Quartz
from Foundation import (
NSURL,
CFDictionaryRef,
NSData,
NSMutableData,
NSMutableDictionary,
)
from wurlitzer import pipes
# what exiftool tool displays as "Content Identifier" is actually a note with value "17"
# this is the value that is used to associate the live image video file
kFigAppleMakerNote_AssetIdentifier = "17"
def image_from_path(
image_path: str | os.PathLike,
) -> Quartz.CGImageSourceRef:
"""Create a CGImageSourceRef from an image file.
Args:
image_path: Path to the image file.
Returns: CGImageSourceRef
"""
with objc.autorelease_pool():
image_url = NSURL.fileURLWithPath_(str(image_path))
image_source = Quartz.CGImageSourceCreateWithURL(image_url, None)
if not image_source:
raise ValueError(f"Could not create image source for {image_path}")
return image_source
def add_asset_id_to_image(
image_path: str | os.PathLike,
asset_id: str,
destination_path: str | os.PathLike,
) -> None:
"""Write the asset id to file at image_path and save to destination path"""
image_data = image_from_path(image_path)
metadata = Quartz.CGImageSourceCopyPropertiesAtIndex(image_data, 0, None)
metadata_as_mutable = metadata.mutableCopy()
maker_apple = metadata_as_mutable.objectForKey_(
Quartz.kCGImagePropertyMakerAppleDictionary
)
if not maker_apple:
maker_apple = NSMutableDictionary.alloc().init()
maker_apple.setObject_forKey_(asset_id, kFigAppleMakerNote_AssetIdentifier)
metadata_as_mutable.setObject_forKey_(
maker_apple, Quartz.kCGImagePropertyMakerAppleDictionary
)
write_image_with_metadata(image_data, metadata_as_mutable, destination_path)
def write_image_with_metadata(
image_data: Quartz.CGImageSourceRef,
metadata: CFDictionaryRef,
destination_path: str | os.PathLike,
) -> None:
"""Write image with metadata to destination path"""
with objc.autorelease_pool():
image_type = Quartz.CGImageSourceGetType(image_data)
dest_data = NSMutableData.data()
destination = Quartz.CGImageDestinationCreateWithData(
dest_data, image_type, 1, None
)
if not destination:
raise ValueError(
f"Could not create image destination for {destination_path}"
)
with pipes() as (_out, _err):
# use pipes to catch error messages from CGImageDestinationAddImageFromSource
# there's a bug in Core Graphics that causes an error similar to
# AVEBridge Info: AVEEncoder_CreateInstance: Received CreateInstance (from VT)
# ... AVEBridge Error: AVEEncoder_CreateInstance: returning err = -12908
# to output to stderr/console but the image is still written correctly
# reference: https://github.com/biodranik/HEIF/issues/5 and
# https://forums.developer.apple.com/forums/thread/722204
Quartz.CGImageDestinationAddImageFromSource(
destination, image_data, 0, metadata
)
Quartz.CGImageDestinationFinalize(destination)
new_image_data = NSData.dataWithData_(dest_data)
new_image_data.writeToFile_atomically_(destination_path, True)
def main():
filename = sys.argv[1]
asset_id = str(uuid.uuid4()).upper()
print(f"Adding asset id {asset_id} to {filename}")
add_asset_id_to_image(filename, asset_id, filename)
if __name__ == "__main__":
main() |
Beta Was this translation helpful? Give feedback.
-
@Yorian I've got an initial working script here. I tested it on the samples you sent and it works (on Ventura 13.5.1). The script is very rudimentary and must be passed the name of the image and the name of the video to be paired. It will overwrite the originals so ensure you have a backup. I've done very little testing. When I get some more time I'll add a test suite, better UI for the CLI, etc. Also happy to accept PRs. I'd like to eventually package this with PyApp and get it onto PyPI so it can be pip installed. But I wanted to get a working MVP out quickly. I think the script should really scan a list of input files to find matching pairs then apply the tag but currently the photo and video files must be passed as separate arguments. |
Beta Was this translation helpful? Give feedback.
-
@RhetTbull You are amazing. Thanks for the quick deep dive you did. Will look into it this evening! |
Beta Was this translation helpful? Give feedback.
-
Hi,
I have some old live photos which used to be on Android. Would it be possible to create "live photos" in the Photos app using osxphotos?
Beta Was this translation helpful? Give feedback.
All reactions