Last active
December 18, 2025 14:16
-
-
Save mohsenasm/d7e9e31521add91029428c5e28d24b3e to your computer and use it in GitHub Desktop.
Samsung Motion Photo extractor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Samsung Motion Photo extractor | |
| This code is the minimal version of the work in https://github.com/joemck/ExtractMotionPhotos | |
| Usage: python3 samsung_motion_photo_extractor.py <motion_photo_file> | |
| """ | |
| def init_search(magic): | |
| """ | |
| Initialize Boyer-Moore search tables d1 and d2. | |
| """ | |
| magic_len = len(magic) | |
| d1 = [magic_len] * 256 | |
| d2 = [0] * magic_len | |
| # Build d1 table | |
| for i in range(magic_len - 1): | |
| d1[magic[i]] = magic_len - 1 - i | |
| # Build d2 table | |
| last_prefix = magic_len - 1 | |
| for i in range(magic_len - 1, -1, -1): | |
| suffix_len = magic_len - i - 1 | |
| j = 0 | |
| while j < suffix_len and magic[j] == magic[i + 1 + j]: | |
| j += 1 | |
| if j == suffix_len: | |
| last_prefix = i + 1 | |
| d2[i] = last_prefix + (magic_len - 1 - i) | |
| # Update d2 for proper suffix matching | |
| for i in range(magic_len - 1): | |
| suffix_len = 0 | |
| while (suffix_len < i and | |
| magic[i - suffix_len] == magic[magic_len - 1 - suffix_len]): | |
| suffix_len += 1 | |
| if magic[i - suffix_len] != magic[magic_len - 1 - suffix_len]: | |
| d2[magic_len - 1 - suffix_len] = magic_len - 1 - i + suffix_len | |
| return d1, d2 | |
| def find_magic(data): | |
| """ | |
| Boyer-Moore string search to find the MotionPhoto_Data magic bytes. | |
| Returns the position where the magic bytes were found, or -1 if not found. | |
| """ | |
| magic = bytes([ | |
| 0x4D, 0x6F, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x68, | |
| 0x6F, 0x74, 0x6F, 0x5F, 0x44, 0x61, 0x74, 0x61 | |
| ]) | |
| d1, d2 = init_search(magic) | |
| magic_len = len(magic) | |
| size = len(data) | |
| i = magic_len - 1 | |
| while i < size: | |
| j = magic_len - 1 | |
| while j >= 0 and data[i] == magic[j]: | |
| i -= 1 | |
| j -= 1 | |
| if j < 0: | |
| return i + 1 | |
| i += max(d1[data[i]], d2[j]) | |
| return -1 | |
| def split_motion_photo(input_file): | |
| """ | |
| Split a Samsung motion photo into photo and video files. | |
| """ | |
| # Read the input file | |
| with open(input_file, 'rb') as f: | |
| file_data = f.read() | |
| # Find the magic bytes | |
| split_pos = find_magic(file_data) | |
| if split_pos < 0: | |
| print(f"Error: {input_file} is not a motion photo (magic bytes not found)") | |
| return False | |
| # Generate output filenames | |
| base_name = input_file.rsplit('.', 1)[0] | |
| ext = input_file.rsplit('.', 1)[1] | |
| photo_file = f"{base_name}_photo.{ext}" | |
| video_file = f"{base_name}_video.mp4" | |
| # Write the photo portion (before split) | |
| with open(photo_file, 'wb') as f: | |
| f.write(file_data[:split_pos]) | |
| print(f"Extracted: {photo_file}") | |
| # Write the video portion (after split, skipping 16 magic bytes) | |
| with open(video_file, 'wb') as f: | |
| f.write(file_data[split_pos + 16:]) | |
| print(f"Extracted: {video_file}") | |
| return True | |
| if __name__ == "__main__": | |
| import sys | |
| if len(sys.argv) < 2: | |
| print("Usage: python3 samsung_motion_photo_extractor.py <motion_photo_file>") | |
| print("Example: python3 samsung_motion_photo_extractor.py file.jpg") | |
| sys.exit(1) | |
| input_file = sys.argv[1] | |
| split_motion_photo(input_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment