文档管理中心
FAQ媒体开发拍照和图片图片处理(Image)获取媒体文件元数据信息及相关问题

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

问题现象

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

  • 获取媒体文件的存储路径与大小;
  • 读取图像的分辨率(宽高);
  • 提取EXIF信息,如拍摄时间、地理位置等;

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

背景知识

  1. PhotoViewPicker选择媒体资源后返回PhotoSelectResult,本文需要使用photoUris,返回图库选择后的媒体文件的uri数组。
  2. getImageInfoSync获取指定序号的图片信息,返回ImageInfo
  3. 使用getImageProperties批量获取图片中的指定属性键的值,指定属性值必须包含在EXIF信息中,如位置信息PropertyKey.GPS_LATITUDE。具体可获取EXIF图像信息的属性键参考PropertyKey

解决方案

  • 方案一:通过uri获取媒体文件信息。
    1. 通过PhotoPicker拉起图库选择界面,本篇以PhotoViewPicker为例,用户选择图片或视频后,可获取PhotoSelectResult结果集,从中提取所选资源的uri。
    2. 获得uri后,使用getAssets接口获得对应图片或视频资源的PhotoAsset,在FetchOptions的fetchColumns参数中设置检索条件。
    3. 上一步完成后,通过get接口获得对应的参数值。
    4. 通过getThumbnail获取缩略图,该接口需要申请ohos.permission.READ_IMAGEVIDEO权限,其申请方式请参考:申请使用受限权限
    // 通过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获取沙箱路径下媒体文件信息,可获取图片信息参考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获取EXIF信息,此处以获取经纬度为例。获取媒体文件地理信息需申请ohos.permission.MEDIA_LOCATION权限,申请权限请参考向用户申请授权。可获取图片属性值参考PropertyKey
    注意

    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);
  };
}

运行效果:

常见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

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

A:系统目前仅支持对部分EXIF信息的查看和修改。HarmonyOS出于对用户隐私安全保护,对图片EXIF中的信息做了去隐私化处理,例如图片拍摄时间、地址位置信息等。如果开发者需要获取被隐藏的EXIF信息(如地理位置信息等),需要单独申请ohos.permission.MEDIA_LOCATION等权限。申请方式请参考声明权限>向用户申请授权

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

A:image.createImageSource(uri)的参数uri当前仅支持应用沙箱路径。可参考接口文档image.createImageSource

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

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

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

A:使用phAccessHelper.getAssets根据指定uri获取资源时,该方式是获取媒体库图片和视频资源,uri必须为媒体文件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

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

A:通过文档如何获取图片的旋转角度信息判断图片是否存在旋转角度,若存在旋转角度属性图像查看器会根据旋转角度进行处理,因此屏幕图片展示宽高信息与图片实际宽高不一致。

在 FAQ 中进行搜索
请输入您想要搜索的关键词