Python library for creating Google and Samsung compatible Motion Photos.
Most notably this can be used to convert Live Photos downloaded from iCloud (where each photo comes as a separate image and video file) into Motion Photos that users with Android can experience.
This library has a dependency on the exiftool
executable by Phil Harvey.
Instructions for installing exiftool
can be found here, and it must be installed
on PATH
. Alternatively, it can be installed using a package manager.
Linux/MacOS:
$ python3 -m pip install motionphoto
Windows:
$ py -m pip install motionphoto
If you run either command with the system installation of Python rather than in a virtual environment, you may need to
append --break-system-packages
to the end of the command.
The motionphoto
executable takes the following options:
-i/--image <imagePath>
: (required) an existing readable JPEG file which will become the key-frame-v/--video <videoPath>
: (required) an existing readable file, no bigger than INT32_MAX
bytes-m/--motion <outputPath>
: (required) a readable and writable path, file must not exist unless --overwrite
-t_us/--timestamp_us <keyFrameOffset>
: (optional) key-frame time offset in microseconds from the start of the video--(no-)overwrite/
: (optional) permit <outputPath>
to be an existing file and overwrite it instead of returning an errorNote: <outputPath>
's file name must start with MV
.
Putting that together we get this:
$ motionphoto -i <imagePath> -v <videoPath> -m <outputPath> [-t_us <keyFrameOffset> --(no-)overwrite]
Which with dummy parameters, a minimal example might look like this:
$ motionphoto -i "data/IMG_1234.JPEG" -v "data/IMG_1234.MOV" -m "data/MVIMG_1234.JPEG"
For the version run motionphoto --version
, and for more information run motionphoto --help
.
There is a single core function available as:
from motionphoto import create_motion_photo
Documentation for how to use it can be found in the function's docstring, but the API is fairly self-explanatory:
from pathlib import Path
from typing import Optional
def create_motion_photo(*, image: Path, video: Path, motion: Path,
timestamp_us: Optional[int] = None, overwrite: bool = False) -> None:
pass
This section will cover the reverse engineered binary specification of Motion Photos.
This section is solely for information, and is NOT required to use the library.
Since each application/vendor has their own implementation, they also have separate specifications. The Motion Photos created by this library will work transparently across vendors without any input needed from the user.
The only common requirement is that the image format should be JPEG, not HEIC. HEIC occasionally works but is not reliable. This is already enforced by the library.
Google requires that the video file be embedded inside the image file, however no requirements are placed on how this should be done. The video file is commonly appended to the end of the image file.
The Google Photos app, at a minimum, requires the following metadata tags to be set on the image:
MicroVideo
: set to 1
MicroVideoVersion
: this library sets it to 1
MicroVideoOffset
: offset in bytes from the end of the Motion Photo file to the start of the embedded video fileThe offset is encoded as an XML field, so it does not impose any limit on the video size.
The additional metadata tag may be optionally set if known, may be set to -1
if unknown, or may
be omitted entirely:
MicroVideoPresentationTimestampUs
: key-frame time offset in microsecondsThe Google Gallery app additionally requires that the Motion Photo filename start with MV
.
Samsung requires that the video file be embedded inside the image file by constructing a trailer in a custom
format and appending it to the end of the image file.
It MUST be the last trailer in the image file.
Trailer fields are written sequentially in the following format:
[\x00\x00][marker_value(ule16)][name_size(ule32)][name][data]
In our case we want:
name size raw video file
∨∨ ∨∨∨∨∨
[\x00\x00][\x30\x0A][16]["MotionPhoto_Data"][<bin>]
∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧ ∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧
marker name
Additional fields can be found here but are not necessary.
The trailer also has an additional SEF section that contains the sizes of fields and is used for validation. It is appended directly after the fields, and has the following format:
["SEFH"][version(ule32)][field_count(ule32)]
for each field: [\x00\x00][field_marker_value(ule16)][field_offset_from_sef(ule32)][field_size(ule32)]
[sef_size(ule32)]["SEFT"]
The sef_size
is the size of that whole section not including "SEFH"
and "SEFT"
(head and tail markers).
In our case (assuming tsbsef
is the trailer size before the start of the SEF section), we want:
head count marker field size tail
∨∨∨∨ ∨ ∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨ ∨∨∨∨∨∨∨ ∨∨∨∨
["SEFH"][106][1][\x00\x00][\x30\x0A][<tsbsef>][<tsbef>][24]["SEFT"]
∧∧∧ ∧∧∧∧∧∧∧∧ ∧∧
version field offset sef size
The field_size
and field_offset
are both encoded as little-endian 32bit integers. This imposes a limit on the size
of the video file, namely that its size (plus the start of the SEF section) cannot exceed 2 ^ 32 - 1
.
The following metadata tags may optionally be set:
MotionPhoto
: set to 1
MotionPhotoVersion
: this library sets it to 1
MotionPhotoPresentationTimestampUs
: key-frame time offset in microsecondsSamsung does not place any requirements on the file name.