9

I can download the JPGs from my DCIM folder in my Pixel 2 and I know that the video is encoded inside the jpg, but I don't know what image viewer I can use to see the motion. I usually use Irfanview, but I haven't yet been able to find out how to make it show the motion. And I haven't found anyone talking about how to do it. Anyone know?

6

I did some experimenting based on the post by @jameshenstridge and came up with a simple PHP script that successfully split every google motion photo i threw at it.

<?php

  $src_arg = $argv[1];
  $src_dir = realpath(pathinfo($src_arg, PATHINFO_DIRNAME)); 

  echo "Scanning for files...\n";

  foreach (glob($src_arg) as $src) {

    $file = realpath($src);

    if (!is_dir($file) && in_array(strtoupper(pathinfo($file, PATHINFO_EXTENSION)), ["JPEG", "JPG"])) {

      echo "\tProcessing: " . $file . "\n";

      $filesize = filesize($file);
      echo "\t\tFile size: " . $filesize . "\n";

      $handle = fopen($file, "rb");
      $data = fread($handle, $filesize);
      fclose($handle);

      $eoi_pos = strpos($data, "\xFF\xD9\x00\x00\x00\x18");
      echo "\t\tEOI segment position: " . $eoi_pos . "\n";

      if ($eoi_pos !== FALSE) {
        $output_base = $src_dir . DIRECTORY_SEPARATOR . pathinfo($file, PATHINFO_FILENAME);
        echo "\t\tSaving photo...\n";
        file_put_contents($output_base . "_photo.jpg", substr($data, 0, $eoi_pos + 2));
        echo "\t\tSaving video...\n";
        file_put_contents($output_base . "_video.mp4", substr($data, $eoi_pos + 2));
      } else {
        echo "\t\tSKIPPING - File does not appear to be a Google motion photo.\n";
      }

    }

  }

  echo "Done.\n";

?>

It should work on Windows and Linux. You just pass a path as the first argument and it will split any files that it believes are motion photos. You can use wildcards. It's non-destructive - the source file(s) are not deleted.

Some example uses:

php google_motion_photo_splitter.php c:\test\file.jpg
php google_motion_photo_splitter.php c:\test\*.jpg
php google_motion_photo_splitter.php c:\test\*
1
  • Thanks for a good solution. Since my Pixel 4a 5g addes debug info between the photo and the video, I had to make some small adjustments to your solution (see my answer below) – j3App May 26 at 10:34
4

I haven't seen any software to view them other than on the Google Photos website. I was curious about this so started pulling apart one of the photos from my phone, and here's what I found:

  1. The image file appears to be a standard JPEG image, but continues on after the End of Image segment (0xFF 0xD9).

  2. exiftool reports unrecognised MakerNotes. I suspect this custom metadata identifies the file as a motion photo.

  3. If transfer all the data after the EOI segment to a separate file, you'll have a standard MPEG-4 container. I got a GStreamer crash when trying to play it, but ffmpeg seems to be able to handle it and displayed the following metadata:

    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'foo.mp4':
      Metadata:
        major_brand     : mp42
        minor_version   : 0
        compatible_brands: isommp42
        creation_time   : 2018-07-07T20:37:57.000000Z
        com.android.version: 8.1.0
      Duration: 00:00:01.87, start: 0.000000, bitrate: 20283 kb/s
        Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc, smpte170m/smpte170m/unknown), 1024x768, 20161 kb/s, SAR 1:1 DAR 4:3, 30.01 fps, 30 tbr, 90k tbn, 180k tbc (default)
        Metadata:
          creation_time   : 2018-07-07T20:37:57.000000Z
          handler_name    : VideoHandle
        Stream #0:1(eng): Data: none (mett / 0x7474656D), 108 kb/s (default)
        Metadata:
          creation_time   : 2018-07-07T20:37:57.000000Z
          handler_name    : MetadHandle
        Stream #0:2(eng): Data: none (mett / 0x7474656D), 0 kb/s (default)
        Metadata:
          creation_time   : 2018-07-07T20:37:57.000000Z
          handler_name    : MetadHandle
    Unsupported codec with id 0 for input stream 1
    Unsupported codec with id 0 for input stream 2
    

    So, that's a 1.87 second H.264 video with resolution 1024x768, which seems to roughly match up with what I see from Google's apps/website (a drop in resolution and change in aspect ratio).

I know it isn't a complete solution, but it might be enough to get started on a tool to extract the videos.

2

Although the question is for Windows, I think it's appropriate to post my script to play it from command line on Linux here:

https://gist.github.com/vi/5de17bb8d4ea91b8c28e79e0bac6c3cb

#!/bin/bash

if [[ -z "$1"  || "$1" == --help || "$1" == "-?" ]]; then
    echo "Usage: mvimg_play MVIMG_20190806_183324.jpg [other files]"
    echo "Plays Google's Motion Photo using mpv. Depends on exiftool, mktemp, bash and mpv."
    exit 0
fi

FOUND=0
ARGS=()

TORM=()
TOKILL=()

function cleanup() {
    for i in "${TORM[@]}"; do
        rm -f "$i"
    done
    for p in ${TOKILL[@]}; do
        wait $p
    done
}

trap "cleanup" EXIT

for i in "$@"; do
    O=$(exiftool -t $i  | grep -F 'Micro Video Offset' | cut -f 2-2)
    if [[ -z "$O" ]]; then
        # wrong file? Just appending to playlist as is
        ARGS+=($i)
    else
        FOUND=1
        S=$(find $i -printf '%s')
        T=`mktemp`

        ARGS+=("$T")
        dd if="$i" skip=$((S-O)) iflag=skip_bytes of="$T" 2> /dev/null &
        TOKILL+=($!)
        TORM+=("$T")
    fi
done

if [[ $FOUND == 0 ]]; then
    echo "EXIF tag wasn't detected in specified files. Maybe exiftool does not work?" >&2
fi

mpv "${ARGS[@]}"

Maybe it can be used on Windows as well, with enough Mingw/Cygwin hackery.

2

I tried out the answer from Kory above - thanks Kory. Since my Pixel 4a 5G adds a significant amount of debug info between the jpg and the mp4, my extracted mp4's didn't play.

So I had to add a little bit of extra functionality to Kory's solution

<?php

/** execute like > php motionPhotoSplitter.php "Camera Roll/*.MP.jpg" < */
const MP4_TYPES = ["avc1", "iso2", "isom", "mmp4", "mp41", "mp42", "mp71", "msnv", "ndas", "ndsc", "ndsh", "ndsm", "ndsp", "ndss", "ndxc", "ndxh", "ndxm", "ndxp", "ndxs"];
$src_arg = $argv[1];
$src_dir = realpath(pathinfo($src_arg, PATHINFO_DIRNAME));

echo "Scanning for files...\n";

foreach (glob($src_arg) as $src) {

    $file = realpath($src);

    if (!is_dir($file) && in_array(strtoupper(pathinfo($file, PATHINFO_EXTENSION)), ["JPEG", "JPG"])) {

        echo "\tProcessing: " . $file . "\n";

        $filesize = filesize($file);
        echo "\t\tFile size: " . $filesize . "\n";

        $handle = fopen($file, "rb"); // binary read
        $data = fread($handle, $filesize);
        fclose($handle);

        $eoi_pos = strpos($data, "\xFF\xD9"); // end of image in a jpeg file: #FFD9
        echo "\t\tEOI segment position: " . $eoi_pos . "\n";

        if ($eoi_pos !== FALSE) {
            $output_base = $src_dir . DIRECTORY_SEPARATOR . pathinfo($file, PATHINFO_FILENAME);
            echo "\t\tSaving photo...\n";
            file_put_contents($output_base . "_photo.jpg", substr($data, 0, $eoi_pos + 2));

/** an mp4 block starts with 4 bytes for length, then "ftyp", then a valid mp4-type as defined in the array MP4_TYPES */

            $mp4Pos = strpos($data, "ftyp", $eoi_pos);
            if ($mp4Pos && in_array(substr($data, $mp4Pos + 4, 4), MP4_TYPES)) {
                echo "\t\tSaving video...\n";
                file_put_contents($output_base . "_video.mp4", substr($data, $mp4Pos - 4));
            }
        } else {
            echo "\t\tSKIPPING - File does not appear to be a Google motion photo.\n";
        }
    }
}
echo "Done.\n";
?>
0

There's also Motion-Photo-Viewer. Can either be self hosted or used online to play a single *.MP.jpg (and extract the mp4 through right-click-save-as.

You do not need a Web Server, so cloning the project and opening index.html will suffice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.