获取媒体文件元数据信息及相关问题
================

#### 问题现象

在HarmonyOS应用开发中，存在大量需要获取媒体文件元数据信息的场景，例如：

*   获取媒体文件的存储路径与大小；
*   读取图像的分辨率（宽高）；
*   提取EXIF信息，如拍摄时间、地理位置等；

为满足不同场景需求，本文介绍三种高效获取媒体文件信息的方案。

#### 背景知识

1.  [PhotoViewPicker](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-photoaccesshelper-photoviewpicker)选择媒体资源后返回[PhotoSelectResult](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-photoaccesshelper-class#photoselectresult)，本文需要使用photoUris，返回图库选择后的媒体文件的uri数组。
2.  [getImageInfoSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-imagesource#getimageinfosync12)获取指定序号的图片信息，返回[ImageInfo](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-i#imageinfo)。
3.  使用[getImageProperties](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-imagesource#getimageproperties12)批量获取图片中的指定属性键的值，指定属性值必须包含在EXIF信息中，如位置信息PropertyKey.GPS\_LATITUDE。具体可获取EXIF图像信息的属性键参考[PropertyKey](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-e#propertykey7)。

#### 解决方案

*   方案一：通过uri获取媒体文件信息。
    
    1.  通过PhotoPicker拉起图库选择界面，本篇以PhotoViewPicker为例，用户选择图片或视频后，可获取[PhotoSelectResult](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-photoaccesshelper-class#photoselectresult)结果集，从中提取所选资源的uri。
    2.  获得uri后，使用[getAssets](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-sendablephotoaccesshelper#getassets)接口获得对应图片或视频资源的PhotoAsset，在[FetchOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-photoaccesshelper-i#fetchoptions)的fetchColumns参数中设置检索条件。
    3.  上一步完成后，通过[get](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-sendablephotoaccesshelper#get)接口获得对应的参数值。
    4.  通过[getThumbnail](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-photoaccesshelper-photoasset#getthumbnail)获取缩略图，该接口需要申请[ohos.permission.READ\_IMAGEVIDEO](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/restricted-permissions#ohospermissionread_imagevideo)权限，其申请方式请参考：[申请使用受限权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions-in-acl)。
    
    _// 通过uri获取媒体文件信息_
    async uriGetAssets(uri: string) {
      try {
        const context = this.getUIContext().getHostContext();
        let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
        let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
      _  // 配置查询条件，使用PhotoViewPicker选择图片返回的uri进行查询_
        predicates.equalTo('uri', uri);
        let fetchOption: photoAccessHelper.FetchOptions = {
          fetchColumns: \[photoAccessHelper.PhotoKeys.WIDTH, photoAccessHelper.PhotoKeys.HEIGHT,
            photoAccessHelper.PhotoKeys.TITLE, photoAccessHelper.PhotoKeys.DURATION\],
          predicates: predicates
        };
        let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
          await phAccessHelper.getAssets(fetchOption);
      _  // 得到uri对应的PhotoAsset对象，读取文件的部分信息_
        const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
        let infoAsset = \[asset.displayName, asset.uri, asset.photoType, asset.get(photoAccessHelper.PhotoKeys.WIDTH),
          asset.get(photoAccessHelper.PhotoKeys.HEIGHT), asset.get(photoAccessHelper.PhotoKeys.TITLE)\];
        let infoAll = '';
        for (let index = 0; index < this.ImageInfoalltitle.length; index++) {
          const element = this.ImageInfoalltitle\[index\] + ':' + infoAsset\[index\] + '\\n';
          infoAll = infoAll + element;
        }
        this.ImageInfoAll.push(infoAll);
       _ // 获取缩略图_
        asset.getThumbnail((err, pixelMap) => {
          if (err == undefined) {
            console.info(\`getThumbnail successful ${JSON.stringify(pixelMap)}\`);
          } else {
            console.error(\`getThumbnail fail ${err}\`);
          }
        });
      } catch (error) {
        console.error(\`uriGetAssets failed with err: ${JSON.stringify(error)}\`);
      }
    }
    
*   方案二：使用[getImageInfoSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-imagesource#getimageinfosync12)获取沙箱路径下媒体文件信息，可获取图片信息参考[ImageInfo](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-i#imageinfo)。
    
    _// 获取沙箱图片基本信息_
    async getImageInfo() {
      await this.photoPick();
      let filePath: string = this.filePath;
      console.info('filePath', filePath);
      let imageSource = image.createImageSource(filePath);
      let imageInfo = imageSource.getImageInfoSync(0);
      this.ImageInfo = JSON.stringify(imageInfo);
      if (imageInfo == undefined) {
        console.error('Failed to obtain the image information.');
      } else {
        console.info('Succeeded in obtaining the image information.', this.ImageInfo);
      }
    }
    
*   方案三：使用[getImageProperties](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-imagesource#getimageproperties12)获取EXIF信息，此处以获取经纬度为例。获取媒体文件地理信息需申请[ohos.permission.MEDIA\_LOCATION](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/permissions-for-all-user#ohospermissionmedia_location)权限，申请权限请参考[向用户申请授权](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/request-user-authorization)。可获取图片属性值参考[PropertyKey](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-e#propertykey7)。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/2d/v3/gQR2dK14Sm-C4JrtP6xSgw/notice_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134241Z&HW-CC-Expire=86400&HW-CC-Sign=33CF7FBA666FD17282BFE42EAD04ADD419A3C15EACADD7A193769DE89F887AA1)
    
    getImageProperties仅支持JPEG、PNG、HEIF、WEBP和DNG（不同硬件设备支持情况不同）文件，且需要包含Exif信息。WEBP和DNG在API23以上工程中支持。
    
    _// 获取沙箱图片Exif信息，此处以获取经纬度为例_
    async getImagePropertyKeyInfo() {
      await this.photoPick();
      let filePath: string = this.filePath;
      console.info('filePath', filePath);
      let imageSource = image.createImageSource(filePath);
      await imageSource.getImageProperties(\[image.PropertyKey.GPS\_LATITUDE, image.PropertyKey.GPS\_LONGITUDE\])
        .then((data) => {
          console.info('Succeeded in getting the value of the specified attribute key of the image.',
            JSON.stringify(data));
          this.ImageInfo2 = JSON.stringify(data);
        }).catch((error: BusinessError) => {
          this.ImageInfo2 = '';
          console.error('Failed to get the value of the specified attribute key of the image.', error);
        });
    }
    

完整示例代码如下：

import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import fs from '@ohos.file.fs';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { dataSharePredicates } from '@kit.ArkData';

let uris: Array<string> = \[\];
const CAMERA\_PERMISSION: Permissions = 'ohos.permission.MEDIA\_LOCATION';

async function requestPermissions(permissions: Array<Permissions>, context: common.UIAbilityContext): Promise<void> {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  try {
    await atManager.requestPermissionsFromUser(context, permissions);
  } catch (err) {
    console.error(\`failed to request permissions from user, error is ${err}\`);
  }
}

@Entry
@Component
struct GetImageInfo {
  @State ImageInfo: string = '点击后获取图片信息';
  @State ImageInfo2: string = '点击后获取图片信息';
  ImageInfoalltitle: Array<string> =
    \['displayName', 'uri', 'photoType', 'WIDTH', 'HEIGHT', 'TITLE'\];
  @State ImageInfoAll: Array<string | number | boolean> = \[\];
  @State filePath: string = '';

  aboutToAppear(): void {
   _ // 拉起弹窗请求用户授权_
    requestPermissions(\[CAMERA\_PERMISSION\], this.getUIContext().getHostContext() as common.UIAbilityContext);
  }

 _ // 从相册选择图片，将图片复制到沙箱_
  async photoPick() {
    let file1: fs.File | null = null;
    let file2: fs.File | null = null;
    try {
      let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
     _ // 从相册选择图片并复制到沙箱_
      if (canIUse('SystemCapability.FileManagement.PhotoAccessHelper.Core')) {
        let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
        PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE\_TYPE;
        PhotoSelectOptions.maxSelectNumber = 1;
        let photoPicker = new photoAccessHelper.PhotoViewPicker();
        await photoPicker.select(PhotoSelectOptions)
          .then(async (PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
            let file1 = fs.openSync(PhotoSelectResult.photoUris\[0\]);
            const dateStr = (new Date().getTime()).toString();
          _  // 临时文件目录_
            let newPath = context.cacheDir + \`/${dateStr + file1.name}\`;
            fs.copyFileSync(file1.fd, newPath);
            let file2 = fs.openSync(newPath, fs.OpenMode.READ\_WRITE);
            console.info(\`file fd ==> ${file2.fd} | file path ==> ${file2.path}\`);
            this.filePath = file2.path;
          })
          .catch((err: BusinessError) => {
            console.error('PhotoViewPicker.select failed with err: ', err);
          });
      } else {
      _  // Fallback for unsupported SystemCapability_
      }
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error('PhotoViewPicker failed with err: ', err);
    } finally {
      if (file2 && file1) {
        fs.closeSync(file1);
        fs.closeSync(file2);
      }
    }
  }

 _ // 获取沙箱图片基本信息_
  async getImageInfo() {
    await this.photoPick();
    let filePath: string = this.filePath;
    console.info('filePath', filePath);
    let imageSource = image.createImageSource(filePath);
    let imageInfo = imageSource.getImageInfoSync(0);
    this.ImageInfo = JSON.stringify(imageInfo);
    if (imageInfo == undefined) {
      console.error('Failed to obtain the image information.');
    } else {
      console.info('Succeeded in obtaining the image information.', this.ImageInfo);
    }
  }


 _ // 获取沙箱图片Exif信息，此处以获取经纬度为例_
  async getImagePropertyKeyInfo() {
    await this.photoPick();
    let filePath: string = this.filePath;
    console.info('filePath', filePath);
    let imageSource = image.createImageSource(filePath);
    await imageSource.getImageProperties(\[image.PropertyKey.GPS\_LATITUDE, image.PropertyKey.GPS\_LONGITUDE\])
      .then((data) => {
        console.info('Succeeded in getting the value of the specified attribute key of the image.',
          JSON.stringify(data));
        this.ImageInfo2 = JSON.stringify(data);
      }).catch((error: BusinessError) => {
        this.ImageInfo2 = '';
        console.error('Failed to get the value of the specified attribute key of the image.', error);
      });
  }


 _ // 拉起相册图片，获取图片PhotoSelectResult.photoUris_
  async photoPickerGetUri() {
    try {
      let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE\_TYPE;
      PhotoSelectOptions.maxSelectNumber = 5;
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
        console.info(\`PhotoViewPicker.select successfully, PhotoSelectResult uri: ${JSON.stringify(PhotoSelectResult)}\`);
        uris = PhotoSelectResult.photoUris;
        this.ImageInfoAll = \[\];
      _  // 通过uri获取媒体文件信息_
        for (let index = 0; index < uris.length; index++) {
          this.uriGetAssets(uris\[index\]);
        }
      }).catch((err: BusinessError) => {
        console.error(\`PhotoViewPicker.select failed with err: ${JSON.stringify(err)}\`);
      });
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error(\`PhotoViewPicker failed with err: ${JSON.stringify(err)}\`);
    }
  }


 _ // 通过uri获取媒体文件信息_
  async uriGetAssets(uri: string) {
    try {
      const context = this.getUIContext().getHostContext();
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
      let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
    _  // 配置查询条件，使用PhotoViewPicker选择图片返回的uri进行查询_
      predicates.equalTo('uri', uri);
      let fetchOption: photoAccessHelper.FetchOptions = {
        fetchColumns: \[photoAccessHelper.PhotoKeys.WIDTH, photoAccessHelper.PhotoKeys.HEIGHT,
          photoAccessHelper.PhotoKeys.TITLE, photoAccessHelper.PhotoKeys.DURATION\],
        predicates: predicates
      };
      let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
        await phAccessHelper.getAssets(fetchOption);
     _ // 得到uri对应的PhotoAsset对象，读取文件的部分信息_
      const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
      let infoAsset = \[asset.displayName, asset.uri, asset.photoType, asset.get(photoAccessHelper.PhotoKeys.WIDTH),
        asset.get(photoAccessHelper.PhotoKeys.HEIGHT), asset.get(photoAccessHelper.PhotoKeys.TITLE)\];
      let infoAll = '';
      for (let index = 0; index < this.ImageInfoalltitle.length; index++) {
        const element = this.ImageInfoalltitle\[index\] + ':' + infoAsset\[index\] + '\\n';
        infoAll = infoAll + element;
      }
      this.ImageInfoAll.push(infoAll);
    _  // 获取缩略图_
      asset.getThumbnail((err, pixelMap) => {
        if (err == undefined) {
          console.info(\`getThumbnail successful ${JSON.stringify(pixelMap)}\`);
        } else {
          console.error(\`getThumbnail fail ${err}\`);
        }
      });
    } catch (error) {
      console.error(\`uriGetAssets failed with err: ${JSON.stringify(error)}\`);
    }
  }



  build() {
    Column({ space: 10 }) {
      Column({ space: 10 }) {
        Text('获取沙箱路径图片基础信息')
          .fontSize(18)
          .fontWeight(600);
        Button('获取')
          .onClick(() => {
            this.getImageInfo();
          });
        Column() {
          Text(this.ImageInfo);
        }
        .width('90%')
        .padding(10)
        .backgroundColor('#f0f1f4')
        .borderRadius(8);
      };

      Column({ space: 10 }) {
        Text('获取沙箱路径图片exif信息')
          .fontSize(18)
          .fontWeight(600);
        Row() {
          Button('获取图片EXIF信息')
            .onClick(() => {
              this.getImagePropertyKeyInfo();
            });
        };

        Column() {
          Text(this.ImageInfo2);
        }
        .width('90%')
        .padding(10)
        .backgroundColor('#f0f1f4')
        .borderRadius(8);
      };

      Column({ space: 10 }) {
        Text('通过相册图片uri获取图片基础信息')
          .fontSize(18)
          .fontWeight(600);
        Button('获取')
          .onClick(() => {
            this.photoPickerGetUri();
          });

        if (this.ImageInfoAll\[0\]!) {
          Scroll() {
            Column() {
              ForEach(this.ImageInfoAll, (item: string | number | boolean) => {
                Text(\`${item}\`);
              }, (item: string, index: number) => index + item);
            }
            .width('90%')
            .padding(10)
            .backgroundColor('#f0f1f4')
            .borderRadius(8);
          }
          .constraintSize({ maxHeight: 160 });
        } else {
          Column() {
            Text('点击后获取图片信息');
          }
          .width('90%')
          .padding(10)
          .backgroundColor('#f0f1f4')
          .borderRadius(8);
        }
      };
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center);
  };
}

运行效果：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/58/v3/4MzMrVLKQWifBRkR3uyVwg/zh-cn_image_0000002658911827.png?HW-CC-KV=V1&HW-CC-Date=20260709T134241Z&HW-CC-Expire=86400&HW-CC-Sign=DC05DEF43A270D95894AF54DD53CA061830793615FC03E72A0EC6EE1F3D4D84F "点击放大")

#### 常见FAQ

Q：使用getPropertyKey获取照片拍照时间的格式是否可以自定义？

A：不可以，年月日之间的分隔符是使用冒号，如“YY:MM:DD HH:mm:ss”或“YY:MM:DD”。

Q：如何获取图片的位置信息？

A：可以通过getImageProperty接口选择属性参数image.PropertyKey.GPS\_LATITUDE和image.PropertyKey.GPS\_LONGITUDE来获取图片的经纬度信息。

Q：如何获得图库图片的创建时间与拍摄时间。

A：getImageProperty可以获得图片EXIF属性的值，但是图片本身必须包含EXIF信息。如果保存到本地图库的图片不包含EXIF属性，无法获得EXIF属性值。

参考正文中场景一，在FetchOptions的fetchColumns参数中设置检索条件，添加图片创建时间PhotoKeys.DATE\_ADDED\_MS和图片拍摄时间PhotoKeys.DATE\_TAKEN\_MS。设置检索条件并获得图片/视频资源PhotoAsset后，通过get接口获得对应的参数值。

Q：如何获取图片的ISO感光度和曝光时间。

A：使用接口getImageProperty，ISO感光度用ISO\_SPEED\_RATINGS获取。曝光时间用EXPOSURE\_TIME获取。具体可获取信息参考[PropertyKey](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-e#propertykey7)。

Q：用photoAccessHelper.getAssets获取相册文件uri之后，如何用imageSource获取文件全部EXIF信息。

A：系统目前仅支持对[部分EXIF信息](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-e#propertykey7)的查看和修改。HarmonyOS出于对用户隐私安全保护，对图片EXIF中的信息做了去隐私化处理，例如图片拍摄时间、地址位置信息等。如果开发者需要获取被隐藏的EXIF信息（如地理位置信息等），需要单独申请[ohos.permission.MEDIA\_LOCATION](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/permissions-for-all-user#ohospermissionmedia_location)等权限。申请方式请参考[声明权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/declare-permissions)\>[向用户申请授权](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/request-user-authorization)。

Q：关于图片处理的方法image.createImageSource(uri)的参数，目前只支持应用的沙箱路径吗？使用PhotoViewPicker获取的图片uri，image.createImageSource(uri)方法会返回undefined。

A：image.createImageSource(uri)的参数uri当前仅支持应用沙箱路径。可参考接口文档[image.createImageSource](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-f#imagecreateimagesource)。

Q：选择图片后获取的缩略图大小和原图相同，如何定义缩略图尺寸。

A：PhotoAsset.getThumbnail(size)方法中size参数可为空，此时默认大小为256\*256，如果需要自行调整缩略图大小，可手动设置size大小。

Q：调用phAccessHelper.getAssets方法根据指定获取资源时，抛出14000011错误码。

A：使用phAccessHelper.getAssets根据指定uri获取资源时，该方式是获取媒体库图片和视频资源，uri必须为[媒体文件uri](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/user-file-uri-intro#媒体文件uri)，不能传沙箱路径。

Q：如果获取EXIF信息时，获取的属性值不是图片存在的，是否会报错？

A：由于获取的属性图片不存在，会抛出异常：'Failed to get the value of the specified attribute key of the image.'。

Q：通过PhotoViewPicker选择图片后，读取已选择uri对应的图片宽高信息失败，抛出14000014错误码。

A：如果要获取宽高，需要指定FetchOptions。对于照片，如果该参数为空，默认查询'uri'、'media\_type'、'subtype'和'display\_name'，其他的参数并不包含，需要加上，参考文档：[FetchOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-photoaccesshelper-i#fetchoptions)。

Q：imageSource.getImageInfo()方法返回的宽高相反是什么原因。

A：通过文档[如何获取图片的旋转角度信息](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/image-rotate-faq)判断图片是否存在旋转角度，若存在旋转角度属性图像查看器会根据旋转角度进行处理，因此屏幕图片展示宽高信息与图片实际宽高不一致。