Trying to speak MTP in Ruby was giving me enough of a headache that I started looking for other options. (Again) I happened upon PyMTP, which is looking really good right now. It comes with a sample program that displays a bunch of info about the connected device, including the name and a folder listing! (Both of which caused segfaults in the ruby libraries)

There is also a python package for communicating with Firebase!

I just installed PyMTP with pip:

pip install pymtp

Since it appears that speaking MTP is no longer an issue, I’m going to use this post as a study for locating media files on the device.

MTP’s view of the world is pretty flat. When querying a device, you can get a list of all the files, or a list of all the folders. You have to reconstruct their hierarchical relationship using parent id numbers. I wrote a simple program to list all the files under the DCIM directory on my phone:

from sets import Set
import pymtp

def get_dcim_folder_id(device):
  for folder in device.get_parent_folders():
    if folder.name == "DCIM":
      return folder.folder_id

def get_child_folders(device, parent_folder_id):
  folder_ids = Set([parent_folder_id])

  all_folders = device.get_folder_list()

  current_length = len(folder_ids)
  new_length = None
  while current_length != new_length:
    current_length = len(folder_ids)

    for key in all_folders:
      f = all_folders[key]
      if f.parent_id in folder_ids:
        folder_ids.add(f.folder_id)

    new_length = len(folder_ids)

  return folder_ids

def get_picture_file_list(device, folder_ids):
  picture_files = []
  for f in device.get_filelisting():
    if f.parent_id in folder_ids:
      picture_files.append(f)

  return picture_files

# Connect to device
device = pymtp.MTP()
device.connect()

print "\nConnected to device: %s" % device.get_devicename()

dcim_folder_id = get_dcim_folder_id(device)
print "DCIM folder id: %s" % dcim_folder_id

folder_ids = get_child_folders(device, dcim_folder_id)
print "Folder Ids: %s" % folder_ids

picture_files = get_picture_file_list(device, folder_ids)
for f in picture_files:
  print "Picture: %s - %s" % (f.filename, f.filesize)

device.disconnect()

And here’s the output:

Device 0 (VID=22b8 and PID=2e82) is a Motorola Moto G (ID2).
PTP_ERROR_IO: failed to open session, trying again after resetting USB interface
LIBMTP libusb: Attempt to reset device
inep: usb_get_endpoint_status(): No such file or directory
outep: usb_get_endpoint_status(): No such file or directory
Android device detected, assigning default bug flags

Connected to device: None
DCIM folder id: 9
Folder Ids: Set([9L, 34L, 203L, 970L])
Picture: IMG_20150727_123625444.jpg - ID: 2395
Picture: IMG_20150727_123616712.jpg - ID: 2396

There are only a couple pictures on my phone right now, so there’s not much in the output. I’m just thrilled that it didn’t segfault! :)

Now, what if I want to transfer one of those pictures from my phone to my computer? How would I do that?

All I need are the file ids, which we got from the previous program:

file_ids = [2395, 2396]

for f in file_ids:
  filename = "/tmp/file_" + str(f) + ".jpg"
  device.get_file_to_file(f, filename, None)
  print "Downloaded %s" % filename

Output:

Device 0 (VID=22b8 and PID=2e82) is a Motorola Moto G (ID2).
libusb_get_active_config_descriptor(1) failed: No such file or directory
no active configuration, trying to set configuration
Android device detected, assigning default bug flags
Downloaded /tmp/file_2395.jpg
Downloaded /tmp/file_2396.jpg

Awesome! To delete the file when we’re done, we just have to add a call to delete_object:

device.delete_object(f)