完善长图拼接,添加视频转文字(待完善)

This commit is contained in:
wangyu 2026-03-18 19:03:54 +08:00
parent ca75abba37
commit c6797fd97b
32 changed files with 788 additions and 114 deletions

Binary file not shown.

View File

@ -12,6 +12,7 @@
"cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har": "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har", "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har": "cmccssosdk@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/quick_login_hm_1.0.2.har",
"ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har": "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har", "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har": "ctaccount@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/ctaccount_v1.1.2.har",
"dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har": "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har", "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har": "dljson@../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har",
"qcloudfileflash@libs/qcloudfileflash.har": "qcloudfileflash@libs/qcloudfileflash.har",
"unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har" "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har"
}, },
"packages": { "packages": {
@ -62,6 +63,12 @@
"resolved": "../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har", "resolved": "../oh_modules/.ohpm/ctaccount@qccjk9bmoqtng+2vpbi+2wqjznsjx4thqhodhlvlvn0=/oh_modules/ctaccount/library/dlJson.har",
"registryType": "local" "registryType": "local"
}, },
"qcloudfileflash@libs/qcloudfileflash.har": {
"name": "qcloudfileflash",
"version": "1.0.0",
"resolved": "libs/qcloudfileflash.har",
"registryType": "local"
},
"unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": { "unicom_login_harmony@../oh_modules/.ohpm/@getui+gysdk@1.0.10/oh_modules/@getui/gysdk/libs/unicom_login_harmony_v1.0.4AR001B0214.har": {
"name": "unicom_login_harmony", "name": "unicom_login_harmony",
"version": "1.0.4", "version": "1.0.4",

View File

@ -7,7 +7,8 @@
"license": "", "license": "",
"dependencies": { "dependencies": {
"@ohos/axios": "^2.2.6", "@ohos/axios": "^2.2.6",
"@getui/gysdk": "1.0.10" "@getui/gysdk": "1.0.10",
"qcloudfileflash": "file:./libs/qcloudfileflash.har"
} }
} }

View File

@ -16,4 +16,9 @@ export class Constants {
// //
static readonly ENCRYPT = "wE8x4EnIHgyGOyjnoluzI2vk60wz5eNI" static readonly ENCRYPT = "wE8x4EnIHgyGOyjnoluzI2vk60wz5eNI"
static readonly SIGNATURE = "hfLLOtXRjd0e1Ac7O6sAXrECH2E828S9" static readonly SIGNATURE = "hfLLOtXRjd0e1Ac7O6sAXrECH2E828S9"
//腾讯云
static readonly QCLOUD_APP_ID = "1366199074"
static readonly QCLOUD_SECRET_ID = "AKIDYGvVCi06ycDk8ZprfFclgNpFer4D9sPi"
static readonly QCLOUD_SECRET_KEY = "72iBPPBj390d2PipqhMmyve9QSFpBKEu"
} }

View File

@ -89,6 +89,16 @@ export class RouterUrls {
*/ */
static readonly IMAGE_MERGE_PAGE = "pages/main/home/tools/ImageMergePage" static readonly IMAGE_MERGE_PAGE = "pages/main/home/tools/ImageMergePage"
/**
* 视频转文字页
*/
static readonly VIDEO_TO_TEXT_PAGE = "pages/main/home/tools/VideoToTextPage"
/**
* 语音转文字页
*/
static readonly AUDIO_TO_TEXT_PAGE = "pages/main/home/tools/AudioToTextPage"
/** /**
* 素材详情页 * 素材详情页
*/ */

View File

@ -0,0 +1,27 @@
import { Type } from "class-transformer";
import "reflect-metadata";
export class VoiceRecognizeResultEntity {
code: number = 0;
message: string = '';
request_id: string = '';
@Type(() => FlashResult)
flash_result: Array<FlashResult> = [];
audio_duration: number = 0;
}
export class FlashResult {
channel_id: number = 0;
@Type(() => Sentence)
sentence_list: Array<Sentence> = [];
text: string = '';
}
export class Sentence {
emotional_energy: number = 0;
end_time: number = 0;
speaker_id: number = 0;
speech_speed: number = 0;
start_time: number = 0;
text: string = '';
}

View File

@ -47,7 +47,7 @@ export class LocalMediaManager {
static getAllImages(): Array<string> { static getAllImages(): Array<string> {
let array = PrefUtils.getStringArray('local_record') let array = PrefUtils.getStringArray('local_record')
return array.filter(item => item.endsWith('.jpeg')) return array.filter(item => item.endsWith('.jpeg') || item.endsWith('.jpg') || item.endsWith('.png'))
} }
static deleteAllImages() { static deleteAllImages() {

View File

@ -46,6 +46,7 @@ export class MediaManager {
} else { } else {
console.error('Create AVImageGenerator failed!'); console.error('Create AVImageGenerator failed!');
} }
FileUtil.closeSync(file)
mediaList.push(record) mediaList.push(record)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -69,9 +70,10 @@ export class MediaManager {
for (let i = 0; i < imageUri.length; i++) { for (let i = 0; i < imageUri.length; i++) {
try { try {
let uri = imageUri[i] let uri = imageUri[i]
let file = FileUtil.openSync(uri) let file = FileUtil.openSync(uri) //判断图片是否存在 FileUtil.access()无效
let record = new MediaRecordEntity(uri) let record = new MediaRecordEntity(uri)
record.name = FileUtil.getFileName(uri) record.name = FileUtil.getFileName(uri)
FileUtil.closeSync(file)
mediaList.push(record) mediaList.push(record)
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -144,13 +144,15 @@ export struct HomePage {
break; break;
} }
case 'check_Task': { case 'check_Task': {
break
} }
case 'course': { case 'course': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.COURSE_PAGE}) this.getUIContext().getRouter().pushUrl({url: RouterUrls.COURSE_PAGE})
break
} }
case 'web_link': { case 'web_link': {
WantUtil.toWebBrowser(Constants.WEB_URL) WantUtil.toWebBrowser(Constants.WEB_URL)
break
} }
} }
}) })
@ -348,6 +350,7 @@ export struct HomePage {
break break
} }
case 'videoToText': { case 'videoToText': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_TO_TEXT_PAGE})
break break
} }
case 'longImageMerge': { case 'longImageMerge': {

View File

@ -34,6 +34,7 @@ struct AddAudioPage {
let videoFile = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY) let videoFile = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY)
// 复制视频文件到缓存目录下 // 复制视频文件到缓存目录下
FileUtil.copyFileSync(videoFile.fd, cacheVideoPath) FileUtil.copyFileSync(videoFile.fd, cacheVideoPath)
FileUtil.closeSync(videoFile)
let cacheAudioPath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp3` let cacheAudioPath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp3`
if (FileUtil.accessSync(cacheAudioPath)) { if (FileUtil.accessSync(cacheAudioPath)) {
@ -42,6 +43,7 @@ struct AddAudioPage {
let audioFile = FileUtil.openSync(this.audioUri!!, fileIo.OpenMode.READ_ONLY) let audioFile = FileUtil.openSync(this.audioUri!!, fileIo.OpenMode.READ_ONLY)
// 复制音频文件到缓存目录下 // 复制音频文件到缓存目录下
FileUtil.copyFileSync(audioFile.fd, cacheAudioPath) FileUtil.copyFileSync(audioFile.fd, cacheAudioPath)
FileUtil.closeSync(audioFile)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4` let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -stream_loop -1 -i ${cacheAudioPath} -c:v copy -c:a aac -shortest -map 0:v -map 1:a ${outputPath}` let cmd = `ffmpeg -i ${cacheVideoPath} -stream_loop -1 -i ${cacheAudioPath} -c:v copy -c:a aac -shortest -map 0:v -map 1:a ${outputPath}`

View File

@ -48,6 +48,7 @@ struct AddWatermarkPage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath) FileUtil.copyFileSync(file.fd, cacheVideoPath)
FileUtil.closeSync(file)
let imageX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!! let imageX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!!
let imageY = (vp2px(this.rect.y) * this.videoSize.height!!) / this.playerSize.height!! let imageY = (vp2px(this.rect.y) * this.videoSize.height!!) / this.playerSize.height!!

View File

@ -0,0 +1,6 @@
@ComponentV2
@Entry
struct AudioToTextPage {
build() {
}
}

View File

@ -43,6 +43,7 @@ struct ClipVideoPage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath) FileUtil.copyFileSync(file.fd, cacheVideoPath)
FileUtil.closeSync(file)
let clipWidth: number = 0 let clipWidth: number = 0
let clipHeight: number = 0 let clipHeight: number = 0

View File

@ -1,8 +1,8 @@
import { PhotoHelper } from '@pura/picker_utils' import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar' import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit' import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit' import { BusinessError } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils' import { AppUtil, DisplayUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils' import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit' import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils' import { SaveUtils } from '../../../../utils/SaveUtils'
@ -10,23 +10,74 @@ import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog' import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants' import { EventConstants } from '../../../../common/EventConstants'
import { TipDialog } from '../../../../dialog/TipDialog' import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager' import { image } from '@kit.ImageKit'
import { ImageUtils } from '../../../../utils/ImageUtils'
import { Luban } from '@ark/luban'
@Entry @Entry
@ComponentV2 @ComponentV2
struct ImageMergePage { struct ImageMergePage {
@Local uri?: string = undefined @Local pixelMap?: image.PixelMap = undefined
@Local selectedImage?: string = undefined @Local selectedImage?: string = undefined
@Local imageUris: Array<string> = [] @Local imageUris: Array<string> = []
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false @Local isSuccess: boolean = false
private selectedImages: Array<string> = [] private selectedImages: Array<string> = []
mergeImage() { async mergeImage() {
LoadingDialog.show(this.getUIContext())
try {
let pixelArray: Array<image.PixelMap> = []
let maxWidth = DisplayUtil.getWidth()
for (let i = 0;i < this.imageUris.length;i++) {
let compressedUri = await Luban.with(this.imageUris[i]).ignoreBy(100).get()
const imageSource: image.ImageSource = image.createImageSource(compressedUri[0])
let decodingOptions: image.DecodingOptions = {
editable: true,
desiredPixelFormat: image.PixelMapFormat.RGB_565,
}
let pixelMap = imageSource.createPixelMapSync(decodingOptions)
if (pixelMap) {
let imageInfo = pixelMap.getImageInfoSync()
maxWidth = Math.max(imageInfo.size.width)
pixelArray.push(pixelMap)
} else {
ToastUtils.show('处理失败')
return
}
}
let totalHeight = 0
let newPixelArray: Array<image.PixelMap> = []
for (let i = 0;i < pixelArray.length;i++) {
let pixelMap = pixelArray[i]
let imageInfo = pixelMap.getImageInfoSync()
let height = Math.trunc(imageInfo.size.height * maxWidth / imageInfo.size.width)
let newPixel = await ImageUtils.resizeImage(pixelMap, maxWidth, height)
newPixelArray.push(newPixel)
totalHeight += height
}
let offScreenCanvas = new OffscreenCanvas(maxWidth, totalHeight)
let OffScreenContext = offScreenCanvas.getContext('2d')
let top = 0
newPixelArray.forEach((pixelMap) => {
let imageInfo = pixelMap.getImageInfoSync()
OffScreenContext.drawImage(pixelMap, 0, top, imageInfo.size.width, imageInfo.size.height)
top += imageInfo.size.height
})
this.pixelMap = OffScreenContext.getPixelMap(0, 0, maxWidth, totalHeight)
this.selectedImage = undefined
this.imageUris = []
this.selectedImages = []
this.isSuccess = true
ToastUtils.show('处理成功')
} catch (e) {
console.error(e)
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
} }
selectPhotos() { selectPhotos() {
@ -34,11 +85,12 @@ struct ImageMergePage {
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 9, maxSelectNumber: 9,
preselectedUris: this.selectedImages, preselectedUris: this.selectedImages,
isOriginalSupported: true, isOriginalSupported: false,
}) })
.then((result: photoAccessHelper.PhotoSelectResult) => { .then((result: photoAccessHelper.PhotoSelectResult) => {
if (result.photoUris.length != 0) { if (result.photoUris.length != 0) {
this.isSuccess = false this.isSuccess = false
this.pixelMap = undefined
this.selectedImages = result.photoUris this.selectedImages = result.photoUris
this.imageUris = result.photoUris this.imageUris = result.photoUris
this.selectedImage = result.photoUris[0] this.selectedImage = result.photoUris[0]
@ -75,110 +127,126 @@ struct ImageMergePage {
TitleBar({ title: '长图拼接' }) TitleBar({ title: '长图拼接' })
Stack() { Stack() {
Stack() { Scroll() {
Stack() { Image(this.pixelMap).width('100%').height('auto')
Column() {
Image($r('app.media.ic_add_image')).width(40).height(40)
Text('请上传图片').fontColor($r('app.color.color_466afd')).fontSize(15).fontWeight(FontWeight.Medium).margin({ top: 8})
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.selectPhotos()
})
.visibility(this.imageUris.length !== 0 ? Visibility.None : Visibility.Visible)
Image(this.selectedImage).width('100%').height('100%')
.borderRadius(20)
.visibility(this.imageUris.length === 0 ? Visibility.None : Visibility.Visible)
} }
.width('100%') .height('100%')
.aspectRatio(1) .scrollBar(BarState.Off)
.borderRadius(20) .visibility(this.pixelMap ? Visibility.Visible : Visibility.None)
.backgroundColor(Color.White)
.shadow({radius: 10, color: '#1a9399a1'})
}
.width('100%')
.height('auto')
.padding({left: 32, right: 32})
.margin({top: 40})
Blank().layoutWeight(1) Column() {
Stack() {
Scroll() { Stack() {
List({space: 8}) {
ForEach(this.imageUris, (item: string, index) => {
ListItem() {
RelativeContainer() {
Stack() {
Image(item).width('100%').height('100%').borderRadius(6)
Text(`${index + 1}`)
.width(20)
.height(20)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(14)
.borderRadius(10)
.backgroundColor('#99000000')
}
.width(80)
.height(80)
.margin({top: 11, right: 11})
Image($r('app.media.ic_delete_image')).width(22).height(22)
.alignRules({
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.onClick(() => {
this.imageUris.splice(index, 1)
if (this.imageUris.length === 0) {
this.selectedImage = undefined
} else {
if (item !== this.selectedImage) {
this.selectedImage = this.imageUris[0]
}
}
})
}
.height('100%')
.aspectRatio(1)
}
.onClick(() => {
this.selectedImage = item
})
})
if (this.imageUris.length > 0 && this.imageUris.length < 9) {
ListItem() {
Stack() { Stack() {
Column() { Column() {
Image($r('app.media.ic_add_image')).width(24).height(24) Image($r('app.media.ic_add_image')).width(40).height(40)
Text('请上传图片').fontColor($r('app.color.color_466afd')).fontSize(10).margin({top: 4}) Text('请上传图片').fontColor($r('app.color.color_466afd')).fontSize(15).fontWeight(FontWeight.Medium).margin({ top: 8})
} }
} }
.width(80) .width('100%')
.height(80) .height('100%')
.borderRadius(6)
.backgroundColor(Color.White)
.margin({top: 11, right: 11})
.onClick(() => { .onClick(() => {
this.selectPhotos() this.selectPhotos()
}) })
} .visibility(this.imageUris.length !== 0 ? Visibility.None : Visibility.Visible)
}
}
.width('100%')
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
.padding({left: 32, right: 32})
}
.width('100%')
.height(92)
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Horizontal)
.margin({bottom: 20})
Image(this.selectedImage).width('100%').height('100%')
.borderRadius(20)
.visibility(this.imageUris.length === 0 ? Visibility.None : Visibility.Visible)
}
.width('100%')
.aspectRatio(1)
.borderRadius(20)
.backgroundColor(Color.White)
.shadow({radius: 10, color: '#1a9399a1'})
}
.width('100%')
.height('auto')
.padding({left: 32, right: 32})
.margin({top: 40})
Blank().layoutWeight(1)
Scroll() {
List({space: 8}) {
ForEach(this.imageUris, (item: string, index) => {
ListItem() {
RelativeContainer() {
Stack() {
Image(item).width('100%').height('100%').borderRadius(6)
Text(`${index + 1}`)
.width(20)
.height(20)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.fontSize(14)
.borderRadius(10)
.backgroundColor('#99000000')
}
.width(80)
.height(80)
.margin({top: 11, right: 11})
.borderWidth(1)
.borderRadius(6)
.borderColor(item === this.selectedImage ? $r('app.color.color_466afd') : Color.Transparent)
Image($r('app.media.ic_delete_image')).width(22).height(22)
.alignRules({
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.onClick(() => {
this.imageUris.splice(index, 1)
if (this.imageUris.length === 0) {
this.selectedImage = undefined
} else {
if (item !== this.selectedImage) {
this.selectedImage = this.imageUris[0]
}
}
})
}
.height('100%')
.aspectRatio(1)
}
.margin({left: index === 0 ? 32 : 0, right: index === 8 ? 32 : 0})
.onClick(() => {
this.selectedImage = item
})
})
if (this.imageUris.length > 0 && this.imageUris.length < 9) {
ListItem() {
Stack() {
Column() {
Image($r('app.media.ic_add_image')).width(24).height(24)
Text('请上传图片').fontColor($r('app.color.color_466afd')).fontSize(10).margin({top: 4})
}
}
.width(80)
.height(80)
.borderRadius(6)
.backgroundColor(Color.White)
.margin({top: 11, right: 11})
}
.margin({right: 32})
.onClick(() => {
this.selectPhotos()
})
}
}
.width('100%')
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
}
.width('100%')
.height(92)
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Horizontal)
.margin({bottom: 20})
}
.visibility(this.pixelMap ? Visibility.None : Visibility.Visible)
}
.layoutWeight(1)
Stack() { Stack() {
Button('确认处理', { type: ButtonType.Capsule, stateEffect: true }) Button('确认处理', { type: ButtonType.Capsule, stateEffect: true })
@ -225,10 +293,13 @@ struct ImageMergePage {
.layoutWeight(1) .layoutWeight(1)
.backgroundColor($r('app.color.color_466afd')) .backgroundColor($r('app.color.color_466afd'))
.onClick(() => { .onClick(() => {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!]) SaveUtils.savePixelMapToAlbum(this.pixelMap!!)
.then((saved) => { .then((saved) => {
if (saved) { if (saved) {
this.pixelMap = undefined
this.selectedImage = undefined
this.imageUris = [] this.imageUris = []
this.selectedImages = []
this.showDownloadDialog() this.showDownloadDialog()
} else { } else {
ToastUtils.show('保存失败') ToastUtils.show('保存失败')

View File

@ -33,6 +33,7 @@ struct MD5ResetPage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, outputPath) FileUtil.copyFileSync(file.fd, outputPath)
FileUtil.closeSync(file)
if (FileUtil.accessSync(outputPath)) { if (FileUtil.accessSync(outputPath)) {
this.uri = FileUtil.getUriFromPath(outputPath) this.uri = FileUtil.getUriFromPath(outputPath)

View File

@ -33,6 +33,7 @@ struct RemoveAudioPage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath) FileUtil.copyFileSync(file.fd, cachePath)
FileUtil.closeSync(file)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4` let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cachePath} -an -c:v copy ${outputPath}` let cmd = `ffmpeg -i ${cachePath} -an -c:v copy ${outputPath}`

View File

@ -47,6 +47,7 @@ struct RemoveWatermarkPage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath) FileUtil.copyFileSync(file.fd, cacheVideoPath)
FileUtil.closeSync(file)
let rectX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!! let rectX = (vp2px(this.rect.x) * this.videoSize.width!!) / this.playerSize.width!!
let rectY = (vp2px(this.rect.y) * this.videoSize.height!!) / this.playerSize.height!! let rectY = (vp2px(this.rect.y) * this.videoSize.height!!) / this.playerSize.height!!

View File

@ -36,6 +36,7 @@ struct VideoMirrorPage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath) FileUtil.copyFileSync(file.fd, cacheVideoPath)
FileUtil.closeSync(file)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4` let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -vf ${this.orientation === 1 ? "hflip" : "vflip"} -c:v h264 -pix_fmt yuv420p -y ${outputPath}` let cmd = `ffmpeg -i ${cacheVideoPath} -vf ${this.orientation === 1 ? "hflip" : "vflip"} -c:v h264 -pix_fmt yuv420p -y ${outputPath}`

View File

@ -34,6 +34,7 @@ struct VideoReversePage {
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath) FileUtil.copyFileSync(file.fd, cacheVideoPath)
FileUtil.closeSync(file)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4` let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -vf reverse -af areverse -c:v h264 -pix_fmt yuv420p -y ${outputPath}` let cmd = `ffmpeg -i ${cacheVideoPath} -vf reverse -af areverse -c:v h264 -pix_fmt yuv420p -y ${outputPath}`

View File

@ -17,11 +17,11 @@ import { avSessionManager } from '../../../../manager/AVSessionManager'
struct VideoToAudioPage { struct VideoToAudioPage {
private controller: VideoController = new VideoController() private controller: VideoController = new VideoController()
@Local videoUri?: string @Local videoUri?: string
@Local audioUri?: string
@Local currentTime: number = 0 @Local currentTime: number = 0
@Local durationTime: number = 0 @Local durationTime: number = 0
@Local isPlaying: boolean = false @Local isPlaying: boolean = false
@Local isSuccess: boolean = false @Local isSuccess: boolean = false
@Local audioUri?: string
videoToAudio() { videoToAudio() {
LoadingDialog.show(this.getUIContext()) LoadingDialog.show(this.getUIContext())
@ -33,6 +33,7 @@ struct VideoToAudioPage {
let file = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY) let file = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath) FileUtil.copyFileSync(file.fd, cachePath)
FileUtil.closeSync(file)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp3` let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp3`
let cmd = `ffmpeg -i ${cachePath} -vn -c:a mp3 ${outputPath}` let cmd = `ffmpeg -i ${cachePath} -vn -c:a mp3 ${outputPath}`

View File

@ -0,0 +1,386 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, FileUtil } from '@pura/harmony-utils'
import { ToastUtils } from '../../../../utils/ToastUtils'
import { fileIo } from '@kit.CoreFileKit'
import { SaveUtils } from '../../../../utils/SaveUtils'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { TipDialog } from '../../../../dialog/TipDialog'
import { avSessionManager } from '../../../../manager/AVSessionManager'
import { MP4Parser } from '@ohos/mp4parser'
import { QCloud } from 'qcloudfileflash'
import { Constants } from '../../../../common/Constants'
import { VoiceRecognizeResultEntity } from '../../../../entity/VoiceRecognizeResultEntity'
import { plainToInstance } from 'class-transformer'
@Entry
@ComponentV2
struct VideoToTextPage {
private controller: VideoController = new VideoController()
@Local resultText?: string
@Local videoUri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
videoToAudio() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cachePath)) {
FileUtil.unlinkSync(cachePath)
}
let file = FileUtil.openSync(this.videoUri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath)
FileUtil.closeSync(file)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp3`
let cmd = `ffmpeg -i ${cachePath} -vn -c:a mp3 ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.recognizeAudio(outputPath)
.then((result: VoiceRecognizeResultEntity) => {
this.resultText = result.flash_result[0].text
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
LoadingDialog.dismiss()
})
.catch((e: BusinessError) => {
console.log(e.message)
ToastUtils.show('处理失败')
LoadingDialog.dismiss()
})
} else {
ToastUtils.show('处理失败')
LoadingDialog.dismiss()
}
}
})
}
async recognizeAudio(uri: string): Promise<VoiceRecognizeResultEntity> {
let builder = new QCloud.FileFlash.Builder()
builder.appID = Constants.QCLOUD_APP_ID
builder.secretID = Constants.QCLOUD_SECRET_ID
builder.secretKey = Constants.QCLOUD_SECRET_KEY
// builder.token = this._token
builder.setApiParam(QCloud.FileFlash.kEngineType, '16k_zh')
builder.setApiParam(QCloud.FileFlash.kVoiceFormat, 'mp3')
builder.setApiParam(QCloud.FileFlash.kFilterDirty, 0)
builder.setApiParam(QCloud.FileFlash.kFilterModal, 0)
builder.setApiParam(QCloud.FileFlash.kFilterPunc, 0)
builder.setApiParam(QCloud.FileFlash.kConvertNumMode, 1)
builder.setApiParam(QCloud.FileFlash.kWordInfo, 0)
builder.setApiParam(QCloud.FileFlash.kSpeakerDiarization, 1)
try {
let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_ONLY)
const stat = FileUtil.lstatSync(uri)
const buffer = new ArrayBuffer(stat.size)
FileUtil.readSync(file.fd, buffer)
FileUtil.closeSync(file)
let result = await builder.build(buffer).task
const voiceResult = plainToInstance(VoiceRecognizeResultEntity, result)
return Promise.resolve(voiceResult)
} catch (e) {
console.error(e)
return Promise.reject(e)
}
}
selectVideo() {
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
maxSelectNumber: 1,
isPhotoTakingSupported: false,
isEditSupported: false,
isOriginalSupported: false
})
.then((uris) => {
if (uris.length != 0) {
this.isSuccess = false
this.videoUri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 0)
this.getUIContext().getRouter().back()
}
} })
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
if (time > 60) {
minute = Math.trunc(time / 60)
second = time % 60
if (minute < 10) {
if (second < 10) {
return `0${minute}:0${second}`
} else {
return `0${minute}:${second}`
}
} else {
if (second < 10) {
return `${minute}:0${second}`
} else {
return `${minute}:${second}`
}
}
} else {
second = time
if (second < 10) {
return `00:0${second}`
} else {
return `00:${second}`
}
}
}
onBackPress(): boolean | void {
if (this.isSuccess) {
TipDialog.show(this.getUIContext(), {title:'温馨提示', content:'文本尚未保存,是否确定退出?', callback: {
confirm: () => {
this.getUIContext().getRouter().back()
}
}})
return true
}
return false
}
build() {
Column() {
TitleBar({ title: '视频转文字' })
Stack() {
Stack() {
Stack() {
Column() {
Image($r('app.media.ic_add_video')).width(40).height(40)
Text('请上传视频').fontColor($r('app.color.color_466afd')).fontSize(15).fontWeight(FontWeight.Medium).margin({ top: 8})
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.selectVideo()
})
.visibility(this.videoUri ? Visibility.None : Visibility.Visible)
RelativeContainer() {
Video({
src: this.videoUri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.borderRadius(20)
.backgroundColor(Color.White)
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
})
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.onStart(() => {
this.isPlaying = true
})
.onPause(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onStop(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onFinish(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onError(() => {
this.isPlaying = false
avSessionManager.deactivate()
})
.onDisAppear(() => {
avSessionManager.deactivate()
})
Image($r('app.media.ic_play_video'))
.width(50)
.height(50)
.visibility(this.isPlaying ? Visibility.None : Visibility.Visible)
.onClick(async () => {
await avSessionManager.activate()
this.controller.start()
})
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
right: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
Row() {
Image(this.isPlaying ? $r('app.media.ic_player_controls_pause') : $r('app.media.ic_player_controls_play'))
.width(20)
.height(20)
.margin({ right: 20 })
.onClick(async () => {
if (this.isPlaying) {
this.controller.pause()
} else {
await avSessionManager.activate()
this.controller.start()
}
})
Text(this.formatTime(this.currentTime)).width(35).fontColor(Color.White).fontSize(12)
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime
})
.blockColor(Color.White)
.trackColor($r('app.color.color_60ffffff'))
.onChange((value: number, mode: SliderChangeMode) => {
this.controller.setCurrentTime(value); // 设置视频播放的进度跳转到value处
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
}
.opacity(0.8)
.width("100%")
.borderRadius({bottomLeft: 20, bottomRight: 20})
.backgroundColor('#1A000000')
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.width('100%')
.height('100%')
.visibility(this.videoUri ? Visibility.Visible : Visibility.None)
}
.width('100%')
.aspectRatio(1)
.borderRadius(20)
.backgroundColor(Color.White)
.shadow({radius: 10, color: '#1a9399a1'})
}
.width('100%')
.height('auto')
.padding({left: 32, right: 32})
.margin({top: 40})
Column() {
Row() {
Image($r('app.media.ic_star')).width(22).height(22)
Text('文本结果').fontColor($r('app.color.color_212226')).fontSize(15).fontWeight(FontWeight.Medium).margin({left: 4})
}
Divider().strokeWidth(1).color($r('app.color.color_eeeeee')).margin({top: 12})
Text(this.resultText).width('100%').height('auto').fontColor($r('app.color.color_212226')).fontSize(14).margin({top: 14})
}
.width('90%')
.layoutWeight(1)
.borderRadius(10)
.borderWidth(1)
.borderColor('#DADEE5')
.backgroundColor(Color.White)
.margin({top: 30, bottom: 20})
.padding(12)
.visibility(this.resultText ? Visibility.Visible : Visibility.None)
Blank().layoutWeight(1).visibility(this.resultText ? Visibility.None : Visibility.Visible)
Stack() {
Button('确认处理', { type: ButtonType.Capsule, stateEffect: true })
.width('100%')
.height(46)
.fontColor(Color.White)
.fontSize(15)
.fontWeight(FontWeight.Medium)
.backgroundColor($r('app.color.color_466afd'))
.onClick(() => {
if (this.videoUri) {
this.videoToAudio()
} else {
ToastUtils.show('请上传视频')
}
})
.visibility(!this.isSuccess ? Visibility.Visible : Visibility.None)
Row() {
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Row() {
Image($r('app.media.ic_reupload')).width(20).height(20)
Text('重新上传').fontColor($r('app.color.color_466afd')).fontSize(15).fontWeight(FontWeight.Medium)
}
}
.height(46)
.layoutWeight(1)
.borderWidth(1)
.borderColor($r('app.color.color_466afd'))
.backgroundColor(Color.Transparent)
.onClick(() => {
this.controller.stop()
this.selectVideo()
})
Blank().width(9)
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Row() {
Image($r('app.media.ic_copy_text')).width(20).height(20)
Text('复制文本').fontColor(Color.White).fontSize(15).fontWeight(FontWeight.Medium)
}
}
.height(46)
.layoutWeight(1)
.backgroundColor($r('app.color.color_466afd'))
.onClick(() => {
this.controller.stop()
SaveUtils.saveImageVideoToAlbumDialog([this.videoUri!!])
.then((saved) => {
if (saved) {
this.videoUri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
})
}
.visibility(this.isSuccess ? Visibility.Visible : Visibility.None)
}
.padding({left: 16, top: 9, right: 16, bottom: 30 })
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -21,6 +21,7 @@ export struct ToolsPage {
break break
} }
case 'videoToText': { case 'videoToText': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_TO_TEXT_PAGE})
break break
} }
case 'videoToAudio': { case 'videoToAudio': {
@ -28,6 +29,7 @@ export struct ToolsPage {
break break
} }
case 'audioToText': { case 'audioToText': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.AUDIO_TO_TEXT_PAGE})
break break
} }
case 'addWatermark': { case 'addWatermark': {

View File

@ -1,4 +1,9 @@
import { DisplayUtil } from '@pura/harmony-utils';
import { ImagePreview } from '@rv/image-preview';
import { TitleBar } from '../../view/TitleBar' import { TitleBar } from '../../view/TitleBar'
import { media } from '@kit.MediaKit';
import image from '@ohos.multimedia.image';
import { fileIo } from '@kit.CoreFileKit';
@Entry @Entry
@ComponentV2 @ComponentV2
@ -6,8 +11,11 @@ struct PhotoViewPage {
@Local title: string = '' @Local title: string = ''
@Local uri?: string = '' @Local uri?: string = ''
private imageSize: media.PixelMapParams = {}
aboutToAppear(): void { aboutToAppear(): void {
this.initParams() this.initParams()
this.initImageSize()
} }
initParams() { initParams() {
@ -18,12 +26,31 @@ struct PhotoViewPage {
} }
} }
initImageSize() {
let file = fileIo.openSync(this.uri, fileIo.OpenMode.READ_ONLY)
let imageSource = image.createImageSource(file.fd)
let imageInfo = imageSource.getImageInfoSync()
this.imageSize.width = imageInfo.size.width
this.imageSize.height = imageInfo.size.height
}
build() { build() {
Column() { Column() {
TitleBar({title: this.title}).width('100%') TitleBar({title: this.title}).width('100%')
Image(this.uri).width('100%').layoutWeight(1) Stack() {
.margin({bottom: 50}) ImagePreview() {
.objectFit(ImageFit.Contain) Image(this.uri)
.width('100%')
.objectFit(ImageFit.Contain)
.draggable(false)
.sourceSize({
width: px2vp(DisplayUtil.getWidth()),
height: px2vp(Math.round(this.imageSize.height!! * DisplayUtil.getWidth() / this.imageSize.width!!))
})
}
}
.layoutWeight(1)
.margin({bottom: 50})
} }
.width('100%') .width('100%')
.height('100%') .height('100%')

View File

@ -0,0 +1,53 @@
import { common2D, drawing } from "@kit.ArkGraphics2D";
import { image } from "@kit.ImageKit";
export class ImageUtils {
/**
* 调整图片分辨率
* @param bitmap
* @param w
* @param h
* @returns
*/
static async resizeImage(bitmap: PixelMap, w: number, h: number): Promise<image.PixelMap> {
// 获取图像像素信息
const imageInfo = await bitmap.getImageInfo();
const width: number = imageInfo.size.width;
const height: number = imageInfo.size.height;
const scaleWidth: number = w / width;
const scaleHeight: number = h / height;
const pixelMapColor: ArrayBuffer = new ArrayBuffer(w * h * 2);
const options: image.InitializationOptions = {
editable: true,
pixelFormat: image.PixelMapFormat.RGB_565,
size: { height: h, width: w }
};
// 采用RGB_565格式创建画布PixelMap
const canvasPixelMap = await image.createPixelMap(pixelMapColor, options);
// 创建一个以PixelMap作为绘制目标的Canvas对象
const canvas = new drawing.Canvas(canvasPixelMap);
// 构造矩阵对象
const matrix = new drawing.Matrix();
// 构造画笔对象
let pen = new drawing.Pen();
// 绑定画笔到画布上,在画布上进行绘制时,将使用画笔的样式去绘制图形形状的轮廓
canvas.attachPen(pen);
// 矩形区域
let rect: common2D.Rect = {
left: 0,
top: 0,
right: w,
bottom: h
};
// 将图片绘制到画布的指定区域上
canvas.drawImageRect(bitmap, rect);
// 将矩阵设置为矩阵右乘围绕轴心点按一定缩放系数缩放后的单位矩阵后得到的矩阵
matrix.postScale(scaleWidth, scaleHeight, 0, 0);
// 设置矩阵对象参数
canvas.setMatrix(matrix);
// 将画笔与画布解绑
canvas.detachPen();
return Promise.resolve(canvasPixelMap)
}
}

View File

@ -13,6 +13,7 @@ export class MediaUtils {
let metadata = await avMetaDataExtractor.fetchMetadata() let metadata = await avMetaDataExtractor.fetchMetadata()
videoSize.width = parseInt(metadata.videoWidth as string); videoSize.width = parseInt(metadata.videoWidth as string);
videoSize.height = parseInt(metadata.videoHeight as string); videoSize.height = parseInt(metadata.videoHeight as string);
FileUtil.closeSync(file)
return Promise.resolve(videoSize) return Promise.resolve(videoSize)
} catch (e) { } catch (e) {
let cacheFilePath = FileUtil.getCacheDirPath() + '/' + FileUtil.getFileName(uri) let cacheFilePath = FileUtil.getCacheDirPath() + '/' + FileUtil.getFileName(uri)
@ -20,6 +21,7 @@ export class MediaUtils {
let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_ONLY); let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_ONLY);
// 复制文件到缓存目录下 // 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheFilePath) FileUtil.copyFileSync(file.fd, cacheFilePath)
FileUtil.closeSync(file)
avMetaDataExtractor.fdSrc = FileUtil.openSync(cacheFilePath); avMetaDataExtractor.fdSrc = FileUtil.openSync(cacheFilePath);
let metadata = await avMetaDataExtractor.fetchMetadata() let metadata = await avMetaDataExtractor.fetchMetadata()

View File

@ -6,9 +6,39 @@ import { EventConstants } from '../common/EventConstants';
import { MediaAction, MediaType } from '../manager/MediaManager'; import { MediaAction, MediaType } from '../manager/MediaManager';
import { LocalMediaManager } from '../manager/LocalMediaManager'; import { LocalMediaManager } from '../manager/LocalMediaManager';
import { systemDateTime } from '@kit.BasicServicesKit'; import { systemDateTime } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
export class SaveUtils { export class SaveUtils {
/**
* 保存pixelMap到相册
* @param pixelMap
* @returns
*/
static async savePixelMapToAlbum(pixelMap: image.PixelMap): Promise<boolean> {
try {
const packOptions: image.PackingOption = {
format: 'image/jpeg',
quality: 80
}
let buffer = await image.createImagePacker().packToData(pixelMap, packOptions)
// 应用沙箱路径
let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.jpeg`
// 在沙箱新建并打开文件
let file = fileIo.openSync(cachePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
// 写入pixelMap图片内容
fileIo.writeSync(file.fd, buffer)
// 关闭文件
fileIo.closeSync(file.fd)
// 使用showAssetsCreationDialog保存沙箱中的图片
let saved = await SaveUtils.saveImageVideoToAlbumDialog([cachePath])
return Promise.resolve(saved)
} catch (e) {
console.error(e)
return Promise.resolve(false)
}
}
/** /**
* 保存视频和图片到相册, 弹窗授权 * 保存视频和图片到相册, 弹窗授权
* @param path * @param path

View File

@ -1,12 +1,15 @@
import { RouterUrls } from '../common/RouterUrls'; import { RouterUrls } from '../common/RouterUrls';
import { MediaRecordEntity } from '../entity/MediaRecordEntity'; import { MediaRecordEntity } from '../entity/MediaRecordEntity';
import { router } from '@kit.ArkUI'; import { router } from '@kit.ArkUI';
import { AppUtil, DateUtil, StrUtil } from '@pura/harmony-utils'; import { AppUtil, DateUtil, DisplayUtil, StrUtil } from '@pura/harmony-utils';
import { ShareManager } from '../manager/ShareManager'; import { ShareManager } from '../manager/ShareManager';
import { Want } from '@kit.AbilityKit'; import { Want } from '@kit.AbilityKit';
import { SimpleTipDialog } from '../dialog/SimpleTipDialog'; import { SimpleTipDialog } from '../dialog/SimpleTipDialog';
import { PrefUtils } from '../utils/PrefUtils'; import { PrefUtils } from '../utils/PrefUtils';
import { WantUtils } from '../utils/WantUtils'; import { WantUtils } from '../utils/WantUtils';
import { media } from '@kit.MediaKit';
import { fileIo } from '@kit.CoreFileKit';
import { image } from '@kit.ImageKit';
@ComponentV2 @ComponentV2
export struct VideoRecordItemView { export struct VideoRecordItemView {
@ -85,6 +88,20 @@ export struct ImageRecordItemView {
@Param media?: MediaRecordEntity = undefined; @Param media?: MediaRecordEntity = undefined;
@Param rowCount: number = 1; @Param rowCount: number = 1;
private imageSize: media.PixelMapParams = {}
aboutToAppear(): void {
this.initImageSize()
}
initImageSize() {
let file = fileIo.openSync(this.media?.uri, fileIo.OpenMode.READ_ONLY)
let imageSource = image.createImageSource(file.fd)
let imageInfo = imageSource.getImageInfoSync()
this.imageSize.width = imageInfo.size.width
this.imageSize.height = imageInfo.size.height
}
build() { build() {
RelativeContainer() { RelativeContainer() {
Image(this.media?.uri) Image(this.media?.uri)
@ -92,6 +109,10 @@ export struct ImageRecordItemView {
.height('100%') .height('100%')
.borderRadius(6) .borderRadius(6)
.backgroundColor($r('app.color.color_222222')) .backgroundColor($r('app.color.color_222222'))
.sourceSize({
width: px2vp(DisplayUtil.getWidth() / 2),
height: px2vp(Math.round(this.imageSize.height!! * DisplayUtil.getWidth() / 2 / this.imageSize.width!!))
})
.onClick(() => { .onClick(() => {
this.getUIContext().getRouter().pushUrl({ url: RouterUrls.PHOTO_VIEW_PAGE, params: { uri : this.media?.uri } }) this.getUIContext().getRouter().pushUrl({ url: RouterUrls.PHOTO_VIEW_PAGE, params: { uri : this.media?.uri } })
}) })

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -19,6 +19,8 @@
"pages/main/home/tools/AddAudioPage", "pages/main/home/tools/AddAudioPage",
"pages/main/home/tools/VideoToAudioPage", "pages/main/home/tools/VideoToAudioPage",
"pages/main/home/tools/ImageMergePage", "pages/main/home/tools/ImageMergePage",
"pages/main/home/tools/VideoToTextPage",
"pages/main/home/tools/AudioToTextPage",
"pages/main/home/material/MaterialDetailPage", "pages/main/home/material/MaterialDetailPage",
"pages/main/mine/user/UserSettingsPage", "pages/main/mine/user/UserSettingsPage",
"pages/main/mine/vip/VipPage", "pages/main/mine/vip/VipPage",

View File

@ -7,6 +7,7 @@
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": { "specifiers": {
"@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har": "@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har", "@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har": "@alipay/blueshieldsdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.36/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har",
"@ark/luban@^1.0.2": "@ark/luban@1.0.2",
"@cashier_alipay/cashiersdk@^15.8.36": "@cashier_alipay/cashiersdk@15.8.36", "@cashier_alipay/cashiersdk@^15.8.36": "@cashier_alipay/cashiersdk@15.8.36",
"@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4", "@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4",
"@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
@ -38,6 +39,13 @@
"libblueshield.so": "file:./src/main/cpp/types/libblueshield" "libblueshield.so": "file:./src/main/cpp/types/libblueshield"
} }
}, },
"@ark/luban@1.0.2": {
"name": "@ark/luban",
"version": "1.0.2",
"integrity": "sha512-p5YLHNVi8sSE2b3GxeXXg88bmP16qtGv8QRrNfxDSt9aKgt9PZ6xuPKY6+ORrXIbih0n9t97qHRfMlinqv6UdA==",
"resolved": "https://repo.harmonyos.com/ohpm/@ark/luban/-/luban-1.0.2.har",
"registryType": "ohpm"
},
"@cashier_alipay/cashiersdk@15.8.36": { "@cashier_alipay/cashiersdk@15.8.36": {
"name": "@cashier_alipay/cashiersdk", "name": "@cashier_alipay/cashiersdk",
"version": "15.8.36", "version": "15.8.36",

View File

@ -11,7 +11,8 @@
"@tencent/libpag": "^4.4.31", "@tencent/libpag": "^4.4.31",
"@ohos/mp4parser": "^2.0.7", "@ohos/mp4parser": "^2.0.7",
"@ohos/imageknifepro": "^1.0.12", "@ohos/imageknifepro": "^1.0.12",
"@rv/image-preview": "^2.1.2" "@rv/image-preview": "^2.1.2",
"@ark/luban": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@ohos/hypium": "1.0.21", "@ohos/hypium": "1.0.21",