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
相关产品推荐
相关产品推荐

