Easily Convert iPhone Live Photos on Windows and Mac! (feat. Python)
(Updated January 24) I worked hard on the content below, but found an even better program!
While browsing Clien, I found that someone had created and uploaded a much better program on GitHub. It includes everything I struggled with using GPT, and it provides a sleek UI. Please refer to the information below, and check out
Motionphoto2 on this GitHub page
. The usage is the same. If you put the image and video files with matching file names for a Live Photo in one folder and specify it, the conversion to Motion Photo will proceed. By the way, if you want to extract the photo and video with the same file name from your iPhone, try using 3uTools.
The Appeal and Inconvenience of iPhone Live Photos
Many of you capture vivid moments with your iPhones, making them even more special with the Live Photos feature, right? However, you may have noticed that managing these Live Photos in Windows or Mac environments or backing them up to other services can be quite cumbersome. Especially when backing up to Google Photos, you may have had frustrating experiences where the Live Photos are not uploaded properly.
I, too, felt the inconvenience when backing up iPhone Live Photos to Google Photos, so I decided to solve the problem myself. After much trial and error, I developed a Python script that combines the .jpg images and .mov video files taken on the iPhone and converts them into Live Photo format. Now, I can easily convert Live Photos and back them up to Google Photos in both Windows and Mac environments.
In this blog post, I will introduce the usage method and principle of the Python script I developed, as well as detailed instructions on how to install it. If you have had trouble with iPhone Live Photos, I am confident that this article will be helpful.
Why Is it Difficult to Convert Live Photos on Windows and Mac?
Live Photos taken on iPhones are not just a combination of photos and videos; they have a complex structure internally. There is a .jpg photo file and a short .mov video file, and additional information called
XMP metadata
connects these two.
The problem is that both Windows and Mac environments do not properly recognize this structure of Live Photos. The operating systems treat .jpg files and .mov files as separate files. Therefore, if you directly back up Live Photos to Google Photos, they will not be displayed as moving photos, but rather as separate still photos and videos.
Python Script, the Solution for Live Photo Conversion!
To solve these problems, the Python script I developed provides the following key functions:
- Merge .jpg Photo Files and .mov Video Files: Finds .jpg photo files and .mov video files that have the same file name and combines them into one file. During the merging process, it combines the binary data of the files as is, without any data loss.
- Insert XMP Metadata: Uses an external program called exiftool to insert the necessary XMP metadata into the merged file so that it can be recognized as a Live Photo.
-
Manage Converted Live Photo Files:
Saves the converted files in the output folder, and moves the original files to the livephoto folder to keep the original folder clean.
How to Use the Script: Easy for Everyone!
This script is made with a GUI, so even users who don't know coding can use it easily.
- Click the "Select Folder" button: Select the folder where the Live Photo files are located.
- Check File Information: Check the number of convertible Live Photos within the selected folder and the output path. If there are no files to convert, a warning message will appear.
- Click the "Start Conversion" button: Start the conversion process. The conversion process can be visually confirmed using the progress bar.
- Conversion Completion and File Moving Decision: Once the conversion is complete, a window will appear asking whether you would like to move the Motion Photos to the original location.
How the Script Works
This script was created using Python's powerful features. The main modules used are os, shutil, and subprocess, and tkinter was used to create the GUI. Also, the concurrent.futures module was applied to support multi-threading.
Installation and Execution Method
First, download and install the latest version of Python from the official Python website ( https://www.python.org/ ). After that, install the necessary libraries in the terminal (macOS/Linux) or command prompt (Windows):
pip install tqdm pip install Pillow
Here are the instructions on how to install dependencies by OS:
- macOS: Execute the commands brew install exiftool and xcode-select --install.
- Linux: Execute the commands sudo apt update && sudo apt install exiftool python3-tk.
- Windows: Download and install from the exiftool official website .
In Conclusion...
With this Python script, you can easily convert iPhone Live Photos in Windows and Mac environments. I hope you can more conveniently manage and share your precious memories. If you have any questions or problems during use, please ask in the comments!
Python Script for Live Photo Conversion
Below is the main part of the VBA code I wrote. This code extracts the subject, recipients, sent date, and the first 400 bytes of the body from the sent mail folder into Excel.
Python Script for Live Photo Conversion() import os import shutil import subprocess import mmap from tkinter import Tk, Label, filedialog, StringVar, DISABLED, NORMAL, messagebox from tkinter.ttk import Progressbar, Style, Button from concurrent.futures import ThreadPoolExecutor import platform def merge_files(photo_path, video_path, output_path): out_path = os.path.join(output_path, os.path.basename(photo_path)) os.makedirs(os.path.dirname(out_path), exist_ok=True) try: with open(out_path, "wb") as outfile: for file in [photo_path, video_path]: with open(file, "rb") as f: with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as mm: outfile.write(mm[:]) except Exception as e: print(f"Error merging {photo_path} and {video_path}: {e}") return None return out_path def add_xmp_metadata(merged_file, offset, exiftool_path): command = [ exiftool_path, '-XMP-GCamera:MicroVideo=1', '-XMP-GCamera:MicroVideoVersion=1', f'-XMP-GCamera:MicroVideoOffset={offset}', '-XMP-GCamera:MicroVideoPresentationTimestampUs=1500000', '-overwrite_original', merged_file ] try: subprocess.run(command, check=True) except subprocess.CalledProcessError as e: print(f"Error adding metadata to {merged_file}: {e}") except FileNotFoundError: messagebox.showerror("Error", "exiftool not found. Please install exiftool and add to system environment variable.") def update_file_timestamps(original_file, new_file): if platform.system() != "Windows": original_stat = os.stat(original_file) os.utime(new_file, (original_stat.st_atime, original_stat.st_mtime)) else: print("File timestamp cannot be accurately copied in Windows.") #File timestamp cannot be accurately copied in Windows, so the feature is disabled. def convert(photo_path, video_path, output_path, exiftool_path): merged = merge_files(photo_path, video_path, output_path) if merged is None: return try: offset = os.path.getsize(merged) - os.path.getsize(photo_path) add_xmp_metadata(merged, offset, exiftool_path) update_file_timestamps(photo_path, merged) except Exception as e: print(f"Error processing {photo_path} and {video_path}: {e}") def find_pairs(directory): pairs = [] image_extensions = ('.jpg', '.jpeg', '.png') for file in os.listdir(directory): if file.lower().endswith(image_extensions): base = os.path.splitext(file)[0] photo_path = os.path.join(directory, file) video_path = os.path.join(directory, base + ".mov") if os.path.exists(video_path): pairs.append((photo_path, video_path)) return pairs def count_files_and_pairs(directory): total_files = len(os.listdir(directory)) conversion_count = len(find_pairs(directory)) return total_files, conversion_count def move_livephotos(src_directory, dest_directory): os.makedirs(dest_directory, exist_ok=True) pairs = find_pairs(src_directory) for photo_path, video_path in pairs: shutil.move(photo_path, dest_directory) shutil.move(video_path, dest_directory) return pairs def process_directory(directory, pairs, progress_var, exiftool_path): output_dir = os.path.join(directory, "output") os.makedirs(output_dir, exist_ok=True) with ThreadPoolExecutor() as executor: futures = [executor.submit(convert, photo_path, video_path, output_dir, exiftool_path) for photo_path, video_path in pairs] total_pairs = len(pairs) for i, future in enumerate(futures): future.result() progress_var.set((i + 1) / total_pairs * 100) progress_bar.update() def select_folder(): folder_selected = filedialog.askdirectory() if folder_selected: folder_var.set(folder_selected) total_files, conversion_count = count_files_and_pairs(folder_selected) livephoto_dir = os.path.join(folder_selected, 'livephoto') info_var.set(f"Total: {total_files} files, {conversion_count} can be converted.") output_var.set(f"{livephoto_dir}/output") if conversion_count > 0: convert_button.config(state=NORMAL) move_livephotos(folder_selected, livephoto_dir) messagebox.showinfo("Information", f"{conversion_count} files are ready to convert.") else: convert_button.config(state=DISABLED) messagebox.showwarning("Warning", "No convertible files found.") def start_conversion(): directory = folder_var.get() livephoto_dir = os.path.join(directory, 'livephoto') pairs = find_pairs(livephoto_dir) progress_var.set(0) progress_bar.update() # Set exiftool path (Change to actual path in Windows) if platform.system() == "Windows": exiftool_path = r"C:\exiftool\exiftool.exe" # Please specify the path where exiftool is installed in Windows environment. else: exiftool_path = "/usr/local/bin/exiftool" if os.path.exists(exiftool_path): # Check if the executable file exists process_directory(livephoto_dir, pairs, progress_var, exiftool_path) else: messagebox.showerror("Error", "exiftool not found. Check the path.") result = messagebox.askyesno("Complete", "All conversions have been completed. Would you like to move the motion photos to the image storage location?") if result: move_output_to_storage(livephoto_dir) def move_output_to_storage(livephoto_dir): output_dir = os.path.join(livephoto_dir, 'output') storage_dir = folder_var.get() for file in os.listdir(output_dir): src_path = os.path.join(output_dir, file) dest_path = os.path.join(storage_dir, file) if not os.path.exists(dest_path): try: shutil.move(src_path, storage_dir) except Exception as e: print(f"Error moving {src_path} to {storage_dir}: {e}") else: print(f"File {dest_path} already exists. Skipping.") messagebox.showinfo("Complete", "All tasks have been completed.") # GUI Setting root = Tk() root.title("LivePhoto Converter") # Set theme for macOS style = Style(root) style.theme_use('aqua') if platform.system() == 'Darwin' else style.theme_use('default') # Auto settings for macOS theme style.configure('TButton', font=('Helvetica', 12), padding=10) style.configure('TLabel', font=('Helvetica', 14)) style.configure('TProgressbar', thickness=20) folder_var = StringVar() info_var = StringVar() output_var = StringVar() progress_var = StringVar() Label(root, text="Select the photo storage location:").pack(pady=5) Button(root, text="Select Folder", command=select_folder).pack(pady=5) Label(root, textvariable=folder_var).pack(pady=5) Label(root, textvariable=info_var).pack(pady=5) Label(root, text="Output location:").pack(pady=5) Label(root, textvariable=output_var).pack(pady=5) convert_button = Button(root, text="Start Conversion", command=start_conversion, state=DISABLED, style='TButton') convert_button.pack(pady=10) progress_bar = Progressbar(root, orient='horizontal', length=300, mode='determinate', variable=progress_var, style="TProgressbar", maximum=100) progress_bar.pack(pady=10) root.geometry("400x350+300+200") root.mainloop()