You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python脚本调用Google Photos API无法获取图片GPS元数据求助

无法通过Google Photos API获取图片GPS位置数据的解决方案

问题描述

我编写了一个Python脚本,用于通过Google Photos API下载图片及其元数据(核心需求为获取GPS位置数据)。尝试多种方案后仍无法成功获取GPS数据:使用photo_url = photo['baseUrl'] + "=d"参数下载图片时,可获取除地理位置外的所有元数据;若不添加该参数,则无法导出任何元数据。

原脚本代码:

import os
import json
import requests
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import discovery
from PIL import Image, ExifTags
import exifread
import piexif
import time
import io
import exif 
from PIL.ExifTags import TAGS, GPSTAGS

# Define the scopes
SCOPES = ['https://www.googleapis.com/auth/photoslibrary.readonly']
DEBUG = 1
SHORT_EXECUTION = 1
PATH_TEMP_FOLDER= '/tmp/scripts/my-venv/photos/'

def authenticate_google_photos():
    creds = None
    # The file token.json stores the user's access and refresh tokens
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file('client_secret.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.json', 'w') as token:
            token.write(creds.to_json())
    return creds

def get_photos_list_from_album(creds, albumID, page_size=10):
    service = discovery.build('photoslibrary', 'v1', credentials = creds, static_discovery = False)
    hasNextPageToken = True
    nextPageToken = ""
    i=0
    
    while(hasNextPageToken):
        results = service.mediaItems().search(body={"albumId": albumID, "pageSize": 100, "pageToken": nextPageToken}).execute()

        if(i==0):
            photos = results.get('mediaItems', [])
        else:
            photos = photos + results.get('mediaItems', [])

        #print(f"{photos[0]}")
        result = service.mediaItems().get(mediaItemId="test").execute()
        
        metadata = result.get('mediaMetadata', {})
        gps_info = metadata.get('location', {})
        print(f"{metadata}")
        print(f"{gps_info}")
            
        #if (DEBUG):
                #print(f"{results}")
            
        if 'nextPageToken' in results:
            hasNextPageToken = True
            nextPageToken = results['nextPageToken']
        else:
            hasNextPageToken = False
            nextPageToken = ""
        
        i=i+1
    
    return photos
    
def get_albums_list(creds, page_size=10):
    service = discovery.build('photoslibrary', 'v1', credentials = creds, static_discovery = False)
    hasNextPageToken = True
    nextPageToken = ""
    i=0
    
    while(hasNextPageToken):
        results = service.albums().list(pageSize=page_size, pageToken =nextPageToken, fields="nextPageToken,albums(id,title)").execute()
            
        if(i==0):
            albums = results.get('albums', [])
        else:
            albums = albums + results.get('albums', [])
            
        if (DEBUG):
                #print(f"{results}")
                print(f"Reading albums: {len(albums)} identified")
            
        if 'nextPageToken' in results:
            hasNextPageToken = True
            nextPageToken = results['nextPageToken']
        else:
            hasNextPageToken = False
            nextPageToken = ""
        
        #todo remove it
        if(SHORT_EXECUTION):
            hasNextPageToken = False
        i=i+1
            
    if not albums:
        print('No albums found.')
    else:
        if (DEBUG):
            print('Albums:')
        for item in albums:
            if (DEBUG):
                print(f"{item['title'].encode('utf8')} ({item['id']})")
    return albums

def download_photo(url, filename):
    print("in download " +filename)
    if (not(os.path.isfile(PATH_TEMP_FOLDER + filename))):
        response = requests.get(url)
       
        with open(PATH_TEMP_FOLDER + filename, 'wb') as file:
            file.write(response.content)
    #elif (DEBUG):
    #    print("File exist, skip download")

    #if (filename.count(".heic")>0 or filename.count(".HEIC")>0):
    #    print("File heic convertion done")
    

def get_exif_data(photo_data):
    
    fp = open(photo_data, "rb")
    exif_image = exif.Image(fp)
    result = {}
    
    for field in exif_image.list_all():
        try:
            result[field] = exif_image[field]
        except:
            pass
    
    exif_data = {}
    gps_data = {}

    image_exif = Image.open(photo_data)._getexif()
    if not image_exif:
        return None

    # Iterate over all EXIF data
    for tag, value in image_exif.items():
        tag_name = TAGS.get(tag, tag)
        exif_data[tag_name] = value
        
        #if (DEBUG):
        #    print(f"Field: {tag_name}={value}")

        # Extract GPS info if present
        if tag_name == 'GPSInfo':
            for gps_tag in value:
                sub_tag_name = GPSTAGS.get(gps_tag, gps_tag)
                gps_data[sub_tag_name] = value[gps_tag]
        #        print(f"gps_data: {sub_tag_name}={value[gps_tag]}")

    #return gps_data if gps_data else None



    return result
    
def extract_gps_from_image(image_path):
    with open(image_path, 'rb') as f:
        tags = exifread.process_file(f, details=False)
       
        if (DEBUG):
            for t in tags:
                if ("GPS" in t):
                    print(f"tag: {t}={tags[t]}")

        gps_info = get_gps_location(tags)
    
    
    fields = get_exif_data(image_path)
    if (DEBUG):
        for f in fields:
            if (("gps" or "GPS") in f):
                print(f"Field: {f}={fields[f]}")
                
    
    #todo remove
    #time.sleep(5)
    return gps_info

def get_gps_location(exif_data):
    gps_info = {}
    if 'GPS GPSLatitude' in exif_data and 'GPS GPSLongitude' in exif_data:
        gps_latitude = exif_data['GPS GPSLatitude']
        gps_latitude_ref = exif_data['GPS GPSLatitudeRef']
        gps_longitude = exif_data['GPS GPSLongitude']
        gps_longitude_ref = exif_data['GPS GPSLongitudeRef']
        
        lat = convert_to_degrees(gps_latitude)
        lon = convert_to_degrees(gps_longitude)
        
        if gps_latitude_ref.values[0] != 'N':
            lat = -lat
        if gps_longitude_ref.values[0] != 'E':
            lon = -lon
            
        gps_info['Latitude'] = lat
        gps_info['Longitude'] = lon
    return gps_info

def convert_to_degrees(value):
    d = float(value.values[0].num) / float(value.values[0].den)
    m = float(value.values[1].num) / float(value.values[1].den)
    s = float(value.values[2].num) / float(value.values[2].den)
    
    return d + (m / 60.0) + (s / 3600.0)


def extract_photo_from_album(creds,album):
    
    if (DEBUG):
        print(f"Album: {album}")
    
    #todo remove it    
    if (SHORT_EXECUTION):
        photos = get_photos_list_from_album(creds,"test_Album_code")
    else:
        photos = get_photos_list_from_album(creds,album["id"])
    
    if (DEBUG):
        print(f"Photos identified: {len(photos)}")
    
    extract_photo_metadata(photos)
    
def extract_photo_metadata(photos):
    gps_data = {}
    for photo in photos:
        print(f"Photo: {photo}")
        
        #TODO check
        #photo_url = photo['baseUrl'] + "=d"
        photo_url = photo['baseUrl']
        photo_filename = photo['filename']
        #photo_filename =  photo_filename.replace('.HEIC', '.jpg')
        if (not (photo_filename.count(".MOV")>0 
            or photo_filename.count(".mov")>0 
            or photo_filename.count(".mp4")>0 
            or photo_filename.count(".MP4")>0 )):
            #print(f"Photo: {photo_filename}, URL: {photo_url}")
            download_photo(photo_url, photo_filename)
            
            if (DEBUG):
                print(f"Photo name: {photo['filename']}")
            
            gps_info = extract_gps_from_image(PATH_TEMP_FOLDER + photo_filename)
            if gps_info:
                gps_data[photo_filename] = gps_info
            #os.remove(PATH_TEMP_FOLDER+photo_filename)  # Remove the downloaded photo

    # Print the GPS locations
    for photo, location in gps_data.items():
        if (DEBUG):
            print(f"Photo: {photo}, Location: {location}")

def main():
    creds = authenticate_google_photos()
    
    albums = get_albums_list(creds)
    
    for album in albums:
        extract_photo_from_album(creds,album)
    
    exit(0)

if __name__ == '__main__':
    main()

问题根源

Google Photos API返回的baseUrl默认提供压缩处理后的图片,会丢失EXIF中的GPS数据;添加=d参数可获取原始分辨率图片,但Google会剥离敏感元数据(包括GPS),这就是两种方式都无法获取GPS的原因。

解决方案:直接从API提取GPS数据

Google Photos API的mediaItem对象本身包含GPS位置数据,无需从下载图片中解析EXIF,直接从接口返回的元数据提取即可,修改关键函数如下:

1. 修改get_photos_list_from_album函数

移除硬编码测试逻辑,直接遍历返回的媒体项提取GPS:

def get_photos_list_from_album(creds, albumID, page_size=10):
    service = discovery.build('photoslibrary', 'v1', credentials=creds, static_discovery=False)
    hasNextPageToken = True
    nextPageToken = ""
    photos = []
    
    while hasNextPageToken:
        results = service.mediaItems().search(
            body={"albumId": albumID, "pageSize": 100, "pageToken": nextPageToken}
        ).execute()
        
        current_items = results.get('mediaItems', [])
        photos.extend(current_items)
        
        # 直接从API元数据提取GPS
        for item in current_items:
            metadata = item.get('mediaMetadata', {})
            location = metadata.get('location', {})
            if location:
                print(f"图片 {item['filename']} 的GPS位置: 纬度 {location.get('latitude')}, 经度 {location.get('longitude')}")
        
        nextPageToken = results.get('nextPageToken')
        hasNextPageToken = nextPageToken is not None
    
    return photos

2. 简化extract_photo_metadata函数

直接使用API返回的GPS数据,无需从图片解析:

def extract_photo_metadata(photos):
    gps_data = {}
    for photo in photos:
        filename = photo['filename']
        # 从API元数据获取GPS
        metadata = photo.get('mediaMetadata', {})
        location = metadata.get('location', {})
        if location and 'latitude' in location and 'longitude' in location:
            gps_data[filename] = {
                'Latitude': location['latitude'],
                'Longitude': location['longitude']
            }
            if DEBUG:
                print(f"Photo: {filename}, Location: {gps_data[filename]}")
        
        # 如需下载图片,保留= d参数获取高清版本
        if not (filename.endswith(('.MOV', '.mov', '.mp4', '.MP4'))):
            photo_url = photo['baseUrl'] + "=d"
            download_photo(photo_url, filename)
    
    return gps_data

关键说明

  • mediaMetadata.location字段是Google Photos API官方提供的GPS数据入口,比解析图片EXIF更可靠。
  • 下载图片时GPS丢失是Google的图片处理策略导致,直接从API拿元数据是最优解。

内容的提问来源于stack exchange,提问作者Luca Costante

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 04:49:52