添加视频转音频,md5修改, 视频加水印,视频打码,视频去原声,长图拼接(待完善)

This commit is contained in:
wangyu 2026-03-17 18:51:19 +08:00
parent c8589f4945
commit ca75abba37
81 changed files with 2022 additions and 808 deletions

View File

@ -7,6 +7,7 @@ export class EventConstants {
static readonly MineRefreshEvent = "MineRefreshEvent"
static readonly MediaActionEvent = "MediaActionEvent"
static readonly JumpToRecordEvent = "JumpToRecordEvent"
static readonly JumpToToolsEvent = "JumpToToolsEvent"
static readonly DownloadHistoryRefreshEvent = "DownloadHistoryRefreshEvent"
static readonly MaterialListRefreshEvent = "MaterialListRefreshEvent"

View File

@ -42,7 +42,12 @@ export class RouterUrls {
/**
* 添加水印页
*/
static readonly ADD_WATER_MARKER_PAGE = "pages/main/home/tools/AddWaterMarkerPage"
static readonly ADD_WATERMARK_PAGE = "pages/main/home/tools/AddWatermarkPage"
/**
* 视频去水印页
*/
static readonly REMOVE_WATERMARK_PAGE = "pages/main/home/tools/RemoveWatermarkPage"
/**
* MD5去重页
@ -77,7 +82,12 @@ export class RouterUrls {
/**
* 视频转音频页
*/
static readonly TAKE_AUDIO_PAGE = "pages/main/home/tools/TakeAudioPage"
static readonly VIDEO_TO_AUDIO_PAGE = "pages/main/home/tools/VideoToAudioPage"
/**
* 长图拼接页
*/
static readonly IMAGE_MERGE_PAGE = "pages/main/home/tools/ImageMergePage"
/**
* 素材详情页

View File

@ -1,5 +1,5 @@
import { ComponentContent } from '@kit.ArkUI';
import { AppUtil, DisplayUtil, FileUtil, FormatUtil, NumberUtil, StrUtil, WindowUtil } from '@pura/harmony-utils';
import { AppUtil, DisplayUtil, FileUtil, FormatUtil } from '@pura/harmony-utils';
import { DialogCallback } from '../callback/DialogCallback';
export enum DownloadStatus {
@ -29,7 +29,7 @@ function defaultBuilder(option: DownloadDialogOption) {
option.status === DownloadStatus.VIDEO_DOWNLOADING ? '视频下载中' :
option.status === DownloadStatus.AUDIO_DOWNLOADING ? '音频下载中' : '处理中') +
(option.totalCount > 1 ? ` ${option.index + 1}/${option.totalCount}` : ''))
.fontColor($r('app.color.color_90ffffff'))
.fontColor($r('app.color.color_212226'))
.fontSize(16)
.visibility(option.status === DownloadStatus.COMPLETED ? Visibility.None : Visibility.Visible)
@ -44,8 +44,8 @@ function defaultBuilder(option: DownloadDialogOption) {
Progress({ value: option.progress, total: option.totalSize, type: ProgressType.Linear })
.width('100%')
.style({ strokeWidth: 12, strokeRadius: 6 })
.color('#FC4F54')
.colorBlend($r('app.color.color_10ffffff'))
.color($r('app.color.color_466afd'))
// .colorBlend('#F1F2F6')
.borderRadius(6)
Text(FormatUtil.getFormatPercentage(option.progress / option.totalSize, 1))
@ -56,14 +56,14 @@ function defaultBuilder(option: DownloadDialogOption) {
.fontSize(10)
.borderRadius(10)
.borderWidth(1)
.borderColor($r('app.color.color_80ffffff'))
.borderColor(Color.White)
.backgroundColor($r("app.color.color_466afd"))
.translate({ x: (AppUtil.getUIContext().px2vp(DisplayUtil.getWidth()) * 0.8 - 80) * option.progress / option.totalSize })
}
Text(`${FileUtil.getFormatFileSize(option.progress)}/${option.totalSize !== 0 ?
FileUtil.getFormatFileSize(option.totalSize) : '获取中'}`)
.fontColor($r('app.color.color_999999'))
.fontColor($r('app.color.color_727686'))
.fontSize(12)
.margin({ top: 16 })
.visibility(option.status === DownloadStatus.PROCESSING ? Visibility.Hidden : Visibility.Visible)
@ -72,8 +72,8 @@ function defaultBuilder(option: DownloadDialogOption) {
Button('取消下载', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.backgroundColor('#F1F2F6')
.fontColor('#80859B')
.fontSize(15)
.onClick(() => {
if (option.callback?.cancel) {
@ -85,11 +85,8 @@ function defaultBuilder(option: DownloadDialogOption) {
Button('后台下载', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.backgroundColor($r('app.color.color_466afd'))
.fontColor(Color.White)
.fontSize(15)
.margin({ left: 10 })
.onClick(() => {
@ -106,10 +103,10 @@ function defaultBuilder(option: DownloadDialogOption) {
.visibility(option.status === DownloadStatus.COMPLETED ? Visibility.None : Visibility.Visible)
Column() {
Text(option.isAudio ? '已保存到本地' : '已保存到系统相册中').fontColor($r('app.color.color_90ffffff')).fontSize(16)
Text(option.isAudio ? '已保存到本地' : '已保存到系统相册中').fontColor($r('app.color.color_466afd')).fontSize(16)
Text(option.isAudio ? '文件管理/我的手机/Download/素材魔方' : '文件管理/我的手机/Download/图库')
.fontColor($r('app.color.color_999999'))
.fontColor($r('app.color.color_727686'))
.fontSize(12)
.margin({ top: 10 })
@ -117,8 +114,8 @@ function defaultBuilder(option: DownloadDialogOption) {
Button('取消', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.backgroundColor($r('app.color.color_333333'))
.fontColor($r('app.color.color_50ffffff'))
.backgroundColor('#F1F2F6')
.fontColor('#80859B')
.fontSize(15)
.onClick(() => {
if (option.callback?.cancel) {
@ -132,11 +129,8 @@ function defaultBuilder(option: DownloadDialogOption) {
Button('前往查看', { type: ButtonType.Capsule, stateEffect: true })
.width(110)
.height(36)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.fontColor($r('app.color.color_90ffffff'))
.backgroundColor($r('app.color.color_466afd'))
.fontColor(Color.White)
.fontSize(15)
.onClick(() => {
if (option.callback?.confirm) {
@ -153,7 +147,7 @@ function defaultBuilder(option: DownloadDialogOption) {
}
.width('80%')
.borderRadius(10)
.backgroundColor($r('app.color.color_222222'))
.backgroundColor(Color.White)
.padding(20)
}

View File

@ -5,6 +5,7 @@ import { SaveUtils } from '../utils/SaveUtils';
import { LoginManager } from '../manager/LoginGlobalManager';
import { Want } from '@kit.AbilityKit';
import { WXApi } from '../utils/wechat/WXApiEventHandlerImpl';
import { Constants } from '../common/Constants';
@CustomDialog
export struct JoinWxGroupCourseDialog {
@ -15,22 +16,22 @@ export struct JoinWxGroupCourseDialog {
isPlayback: boolean = true
images: Array<Resource> = [
$r('app.media.ic_wx_group_tip1'),
$r("app.media.ic_wx_group_tip2"),
$r("app.media.ic_wx_group_tip3"),
$r("app.media.ic_wx_group_tip4"),
$r('app.media.ic_wx_group_tip5')
$r('app.media.ic_join_wx_group_tip1'),
$r('app.media.ic_join_wx_group_tip2'),
$r("app.media.ic_join_wx_group_tip3"),
$r("app.media.ic_join_wx_group_tip4"),
$r("app.media.ic_join_wx_group_tip5"),
];
steps: Array<string> = ['第一步', '第二步', '第三步', '第四步', '第五步']
qrCodePath = 'https://cdn.batiao8.com/kct/mp/kcsp_qrcode.png'
qrCodePath = 'https://cdn.batiao8.com/kct/mp/scmf_qrcode.png'
@State currentIndex: number = 0
downloadImage() {
try {
const cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + 'kcsp_wx_group_qrcode.jpg';
const cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + 'scmf_wx_group_qrcode.jpg';
if (FileUtil.accessSync(cachePath)) {
FileUtil.unlink(cachePath)
}
@ -43,7 +44,7 @@ export struct JoinWxGroupCourseDialog {
SaveUtils.saveImageVideoToAlbumDialog([cachePath], false)
.then((saved) => {
if (saved) {
PasteboardUtil.setDataTextSync(LoginManager.getUserInfo()!!.user_id)
PasteboardUtil.setDataTextSync(`${LoginManager.getUserInfo()!!.user_id}|${Constants.APP_ID}`)
ToastUtils.show('ID复制成功')
this.jumpToWxScan()
this.controller.close()
@ -78,11 +79,11 @@ export struct JoinWxGroupCourseDialog {
build() {
RelativeContainer() {
Image($r('app.media.ic_wx_group_tip_bg')).width('100%').height(320)
Image($r('app.media.ic_join_wx_group_tip_bg')).width('100%').height(320)
Text(this.isPlayback ? '添加直播回放助手流程' : '添加视频助手流程')
.width('auto')
.fontColor(Color.White)
.fontColor($r('app.color.color_1a1a1a'))
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 16 })
@ -95,10 +96,17 @@ export struct JoinWxGroupCourseDialog {
List({space: 16}) {
ForEach(this.steps, (item: string, index) => {
ListItem() {
Text(item)
.fontColor(index == this.currentIndex ? Color.White : $r('app.color.color_bebebe'))
.fontSize(index == this.currentIndex ? 16 : 14)
.fontWeight(index == this.currentIndex ? FontWeight.Medium : FontWeight.Normal)
Stack() {
if (index === this.currentIndex) {
Image($r('app.media.ic_join_wx_group_tip_indicator')).width(44).height(10)
.margin({top: 10, left: 5})
}
Text(item)
.fontColor(index === this.currentIndex ? $r('app.color.color_466afd') : '#858D9F')
.fontSize(index === this.currentIndex ? 16 : 14)
.fontWeight(index === this.currentIndex ? FontWeight.Medium : FontWeight.Normal)
.fontFamily(index === this.currentIndex ? 'almmsht' : '')
}
}
.onClick(() => {
this.swiperController.changeIndex(index, true)
@ -137,9 +145,9 @@ export struct JoinWxGroupCourseDialog {
Row() {
Button('取消', { type: ButtonType.Capsule, stateEffect: false })
.fontColor($r('app.color.color_50ffffff'))
.fontColor($r('app.color.color_1a1a1a'))
.fontSize(15)
.backgroundColor($r('app.color.color_333333'))
.backgroundColor($r('app.color.color_eeeeee'))
.width(126)
.height(46)
.onClick(() => {
@ -149,11 +157,7 @@ export struct JoinWxGroupCourseDialog {
Button('前往微信扫码加群', { type: ButtonType.Capsule, stateEffect: false })
.fontColor(Color.White)
.fontSize(15)
.backgroundColor(Color.Transparent)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.backgroundColor($r('app.color.color_466afd'))
.layoutWeight(1)
.height(46)
.onClick(() => {
@ -164,7 +168,7 @@ export struct JoinWxGroupCourseDialog {
this.downloadImage()
})
}
.backgroundColor($r('app.color.color_222222'))
.backgroundColor(Color.White)
.alignRules({
top: {anchor: 'swiper', align: VerticalAlign.Bottom}
})

View File

@ -4,12 +4,12 @@ import { ComponentContent } from '@kit.ArkUI';
function defaultBuilder(text: string) {
Column() {
LoadingProgress()
.color(Color.White)
.color($r('app.color.color_466afd'))
.width(50)
.height(50)
Text(text)
.fontColor(Color.White)
.fontColor($r('app.color.color_466afd'))
.fontSize(12)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
@ -22,7 +22,7 @@ function defaultBuilder(text: string) {
.height(124)
.justifyContent(FlexAlign.Center)
.borderRadius(6)
.backgroundColor($r('app.color.color_222222'))
.backgroundColor(Color.White)
.padding({left: 10, right: 10})
}

View File

@ -52,11 +52,11 @@ export class MediaEntity {
initFileName(): string {
if (!this.name) {
if (this instanceof VideoMaterial) {
this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4`
this.name = `scmf_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4`
} else if (this instanceof AudioMaterial) {
this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp3`
this.name = `scmf_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp3`
} else if (this instanceof ImageMaterial) {
this.name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg`
this.name = `scmf_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg`
}
}
return this.name

View File

@ -1,5 +1,5 @@
import { ArrayList } from "@kit.ArkTS";
import { LoginManager } from "../manager/LoginGlobalManager";
import { ArrayList } from '@kit.ArkTS';
import { LoginManager } from '../manager/LoginGlobalManager';
export class MenuEntity {
icon: Resource | null = null;

View File

@ -0,0 +1,32 @@
import { ArrayList } from '@kit.ArkTS';
export class ToolMenuEntity {
icon: Resource | null = null;
title: string = "";
alias: string = "";
desc: string = "";
count: string = "";
colors: Array<string> = [];
constructor(icon: Resource, title: string, alias: string, desc: string, count: string, colors: Array<string>) {
this.icon = icon;
this.title = title;
this.alias = alias;
this.desc = desc;
this.count = count
this.colors = colors
}
}
export function toolsList(): ArrayList<ToolMenuEntity> {
let list = new ArrayList<ToolMenuEntity>()
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon4"), "MD5修改", "resetMD5", '修改视频MD5', '0.2', ['#BCFFDE', '#EEFFF7']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon5"), "视频转文字", "videoToText", '文案轻松提取', '0.3', ['#DFE8FF', '#F8FAFF']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon6"), "视频转音频", "videoToAudio", '提取背景音乐', '1.2', ['#E9E3FF', '#F9F7FF']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon7"), "语音转文字", "audioToText", '内容轻松记录', '1.1', ['#FFE1E1', '#FFF3F3']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon8"), "视频加水印", "addWatermark", '提高原创识别度', '0.5', ['#C2EFFF', '#E3F8FF']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon9"), "长图拼接", "longImageMerge", '多张图形成一张', '0.4', ['#FFF4C6', '#FDFAEF']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon10"), "视频打码", "removeWatermark", '敏感信息无处漏', '0.1', ['#DFE8FF', '#F8FAFF']))
list.add(new ToolMenuEntity($r("app.media.ic_tool_icon11"), "视频去原声", "removeAudio", '一键去背景音乐', '0.7', ['#F0DDFF', '#FAFAFE']))
return list;
}

View File

@ -44,6 +44,11 @@ export class Api {
*/
static readonly USER_DESTROY = '/api/user/destroy';
/**
* 退出登陆
*/
static readonly USER_LOGOUT = '/api/user/logout';
/**
* 用户账号列表
*/

View File

@ -147,6 +147,14 @@ class ApiService {
return AxiosRequest.post<HttpResult>(Api.USER_DESTROY)
}
/**
* 退出登陆
* @returns
*/
logout(): Promise<HttpResult> {
return AxiosRequest.post<HttpResult>(Api.USER_LOGOUT)
}
/**
* 解绑账号
* @returns

View File

@ -14,6 +14,7 @@ import { ConfigManager } from '../../manager/UserConfigManager';
import { EventReportGlobalManager } from '../../manager/EventReportGlobalManager';
import { PasteboardUtils } from '../../utils/PasteboardUtils';
import { MaterialPage } from './material/MaterialPage';
import { ToolsPage } from './mine/tool/ToolsPage';
@Entry
@ComponentV2
@ -51,6 +52,7 @@ struct MainPage {
aboutToDisappear(): void {
AppUtil.getContext().eventHub.off(EventConstants.LoginSuccessEvent);
AppUtil.getContext().eventHub.off(EventConstants.JumpToRecordEvent);
AppUtil.getContext().eventHub.off(EventConstants.JumpToToolsEvent);
}
onPageShow(): void {
@ -71,6 +73,9 @@ struct MainPage {
this.tabController.changeIndex(3)
this.currentIndex = 1
})
AppUtil.getContext().eventHub.on(EventConstants.JumpToToolsEvent, () => {
this.tabController.changeIndex(2)
})
}
checkPasteboard() {
@ -112,7 +117,7 @@ struct MainPage {
.tabBar(this.tabBuilder(this.titles[1], 1, $r('app.media.ic_material_select'), $r('app.media.ic_material_default')))
TabContent() {
ToolsPage()
}
.tabBar(this.tabBuilder(this.titles[2], 2, $r('app.media.ic_tool_select'), $r('app.media.ic_tool_default')))

View File

@ -340,20 +340,22 @@ export struct HomePage {
.onClick(() => {
switch (item.alias) {
case 'videoToAudio': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.TAKE_AUDIO_PAGE})
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_TO_AUDIO_PAGE})
break
}
case 'addWatermark': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.ADD_WATER_MARKER_PAGE})
this.getUIContext().getRouter().pushUrl({url: RouterUrls.ADD_WATERMARK_PAGE})
break
}
case 'videoToText': {
break
}
case 'longImageMerge': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.IMAGE_MERGE_PAGE})
break
}
case 'moreTools': {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToToolsEvent);
break
}
}

View File

@ -380,6 +380,7 @@ struct TakeMaterialPage {
parseUrl(url: string) {
if (StrUtil.isNotEmpty(this.inputText)) {
this.viewModel.getMaterialInfo(url);
KeyboardUtil.hide()
}
}
@ -499,7 +500,6 @@ struct TakeMaterialPage {
.backgroundColor($r('app.color.color_466afd'))
.onClick(() => {
if (StrUtil.isNotEmpty(this.inputText)) {
KeyboardUtil.hide()
this.parseUrl(this.inputText)
EventReportGlobalManager.eventReport(EventConstants.GET_MATERIAL, "material-button", this.inputText)
} else {

View File

@ -87,7 +87,7 @@ struct MaterialDetailPage {
download() {
LoadingDialog.show(this.getUIContext())
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.jpeg`
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.jpeg`
let config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: this.material!!.pic!!.url,

View File

@ -43,7 +43,7 @@ struct AddAudioPage {
// 复制音频文件到缓存目录下
FileUtil.copyFileSync(audioFile.fd, cacheAudioPath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${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}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {

View File

@ -21,7 +21,7 @@ import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct AddWaterMarkerPage {
struct AddWatermarkPage {
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@ -29,7 +29,7 @@ struct AddWaterMarkerPage {
@Local isSuccess: boolean = false
@Local playerSize: media.PixelMapParams = { width: 0, height: 0 }
@Local showWaterMaker: boolean = false
@Local showWatermark: boolean = false
@Local textContent: string = ''
@Local imagePath: string = ''
@ -37,10 +37,10 @@ struct AddWaterMarkerPage {
private videoSize: media.PixelMapParams = { width: 0, height: 0 }
private rect: RectPosition = { x: 0, y: 0, width: 0, height: 0 }
addWaterMarker() {
addWatermark() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
@ -58,7 +58,7 @@ struct AddWaterMarkerPage {
this.getUIContext().getComponentSnapshot().get(StrUtil.isNotEmpty(this.textContent) ? 'textWaterMarker' : 'imageWaterMarker')
.then(async (image: image.PixelMap) => {
let imagePath = await ImageUtil.savePixelMap(image, FileUtil.getCacheDirPath(), `cache_${systemDateTime.getTime()}.png`)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -i ${imagePath} -filter_complex [1:v]scale=${Math.round(imageWidth)}:${Math.round(imageHeight)}[wm];[0:v][wm]overlay=${imageX}:${imageY} -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
@ -93,7 +93,7 @@ struct AddWaterMarkerPage {
this.isSuccess = false
this.uri = uris[0]
this.showWaterMaker = false
this.showWatermark = false
this.textContent = ''
this.imagePath = ''
@ -101,7 +101,7 @@ struct AddWaterMarkerPage {
.then((size) => {
this.videoSize = size
if (size.width && size.height) {
const ratio = (DisplayUtil.getWidth() - 180) / size.width
const ratio = (DisplayUtil.getWidth() - 300) / size.width
this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)}
}
})
@ -168,37 +168,28 @@ struct AddWaterMarkerPage {
build() {
Column() {
TitleBar({ title: '加水印' })
TitleBar({ title: '视频加水印' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
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(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.width('100%')
.aspectRatio(1)
.borderRadius(20)
.backgroundColor(Color.White)
.shadow({radius: 10, color: '#1a9399a1'})
.onClick(() => {
this.selectVideo()
})
}
.width('100%')
.height('auto')
.padding({left: 32, right: 32})
.margin({top: 40})
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
@ -305,7 +296,7 @@ struct AddWaterMarkerPage {
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
if (this.showWaterMaker && this.uri && !this.isSuccess) {
if (this.showWatermark && this.uri && !this.isSuccess) {
WaterMarkerView({
content: this.textContent,
imagePath: this.imagePath,
@ -313,7 +304,7 @@ struct AddWaterMarkerPage {
this.rect = rect
},
onClose: () => {
this.showWaterMaker = false
this.showWatermark = false
this.textContent = ''
this.imagePath = ''
}
@ -332,25 +323,36 @@ struct AddWaterMarkerPage {
Row() {
Column(){
Image($r('app.media.ic_text_water_marker')).width(50).height(50)
Text('文字').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 })
Image($r('app.media.ic_watermark_icon1')).width(26).height(26)
Text('水印').fontColor($r('app.color.color_212226')).fontSize(12).margin({ top: 8 })
}
.layoutWeight(1)
.onClick(() => {
if (!this.showWaterMaker) {
})
Column(){
Image($r('app.media.ic_watermark_icon2')).width(26).height(26)
Text('文字').fontColor($r('app.color.color_212226')).fontSize(12).margin({ top: 8 })
}
.layoutWeight(1)
.onClick(() => {
if (!this.showWatermark) {
this.controller.stop()
EditTextDialog.show(this.getUIContext(), {title: '添加水印', hintText: '请输入文字', confirm: (text) => {
this.textContent = text
this.showWaterMaker = true
this.showWatermark = true
}})
}
})
Column(){
Image($r('app.media.ic_image_water_marker')).width(50).height(50)
Text('图片').fontColor($r('app.color.color_90ffffff')).fontSize(14).margin({ top: 8 })
Image($r('app.media.ic_watermark_icon3')).width(26).height(26)
Text('图片').fontColor($r('app.color.color_212226')).fontSize(12).margin({ top: 8 })
}
.margin({ left: 50 })
.layoutWeight(1)
.onClick(() => {
if (!this.showWaterMaker) {
if (!this.showWatermark) {
this.controller.stop()
PhotoHelper.selectEasy({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
@ -363,60 +365,88 @@ struct AddWaterMarkerPage {
if (uris.length != 0) {
this.isSuccess = false
this.imagePath = uris[0]
this.showWaterMaker = true
this.showWatermark = true
}
})
}
})
}
.margin({ top: 20 })
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff'))
.fontSize(17)
.margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定')
.fontColor($r("app.color.color_466afd"))
.fontSize(17)
.margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
} else {
if (this.showWaterMaker) {
this.addWaterMarker()
} else {
ToastUtils.show('请添加水印')
}
}
})
}
.margin({ top: 20, bottom: 30 })
.width('90%')
.height(82)
.margin({ top: 20, bottom: 20 })
.borderRadius(10)
.backgroundColor(Color.White)
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
Blank().layoutWeight(1).visibility(this.uri ? 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.uri) {
this.addWatermark()
} 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_download3')).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.uri!!])
.then((saved) => {
if (saved) {
this.uri = 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%')

View File

@ -35,7 +35,7 @@ struct ClipVideoPage {
clipVideo() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
@ -68,7 +68,7 @@ struct ClipVideoPage {
clipY = clipHeight === originPlayerSize.height ? 0 : (originPlayerSize.height!! - this.playerSize.height!!) / 2 * (this.videoSize.height!!) / originPlayerSize.height!!
}
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -vf \"crop=${Math.ceil(clipWidth)}:${Math.ceil(clipHeight)}:${Math.ceil(clipX)}:${Math.ceil(clipY)}\" -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {

View File

@ -0,0 +1,251 @@
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'
@Entry
@ComponentV2
struct ImageMergePage {
@Local uri?: string = undefined
@Local selectedImage?: string = undefined
@Local imageUris: Array<string> = []
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
private selectedImages: Array<string> = []
mergeImage() {
}
selectPhotos() {
PhotoHelper.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 9,
preselectedUris: this.selectedImages,
isOriginalSupported: true,
})
.then((result: photoAccessHelper.PhotoSelectResult) => {
if (result.photoUris.length != 0) {
this.isSuccess = false
this.selectedImages = result.photoUris
this.imageUris = result.photoUris
this.selectedImage = result.photoUris[0]
}
})
.catch((e: BusinessError) => {
ToastUtils.show(e.message)
})
}
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()
}
} })
}
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_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%')
.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})
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() {
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})
.onClick(() => {
this.selectPhotos()
})
}
}
}
.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})
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.imageUris) {
this.mergeImage()
} 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.selectPhotos()
})
Blank().width(9)
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Row() {
Image($r('app.media.ic_download3')).width(20).height(20)
Text('保存').fontColor(Color.White).fontSize(15).fontWeight(FontWeight.Medium)
}
}
.height(46)
.layoutWeight(1)
.backgroundColor($r('app.color.color_466afd'))
.onClick(() => {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.imageUris = []
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

@ -25,7 +25,7 @@ struct MD5ResetPage {
modifyMD5() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(outputPath)) {
FileUtil.unlinkSync(outputPath)
}
@ -115,172 +115,201 @@ struct MD5ResetPage {
Column() {
TitleBar({ title: 'MD5去重' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Stack() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
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(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.width('100%')
.height('100%')
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.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()
.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
}
})
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处
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
.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 }
})
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.width('100%')
.height('100%')
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.layoutWeight(1)
.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)
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.uri) {
this.modifyMD5()
} else {
ToastUtils.show('请上传视频')
}
})
.visibility(!this.isSuccess ? Visibility.Visible : Visibility.None)
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
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()
})
} else {
this.modifyMD5()
}
})
Blank().width(9)
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Row() {
Image($r('app.media.ic_download3')).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.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
})
}
.margin({ top: 140, bottom: 30 })
.visibility(this.isSuccess ? Visibility.Visible : Visibility.None)
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
.padding({left: 16, top: 9, right: 16, bottom: 30 })
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')

View File

@ -23,7 +23,7 @@ struct RemoveAudioPage {
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
mirrorVideo() {
removeAudio() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cachePath = FileUtil.getCacheDirPath() + FileUtil.separator + `cache_${systemDateTime.getTime()}.mp4`
@ -34,7 +34,7 @@ struct RemoveAudioPage {
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cachePath} -an -c:v copy ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
@ -119,174 +119,203 @@ struct RemoveAudioPage {
build() {
Column() {
TitleBar({ title: '去音乐' })
TitleBar({ title: '视频去原声' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Stack() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
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(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: '__container__', align: VerticalAlign.Top },
end: { anchor: '__container__', align: HorizontalAlign.End },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.width('100%')
.height('100%')
.onClick(() => {
this.selectVideo()
})
}
.height(220)
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
}.margin({ left: 16, top: 16, right: 16 })
.visibility(this.uri ? Visibility.None : Visibility.Visible)
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Contain) // 设置视频填充模式
.onPrepared((event) => {
if (event) {
this.durationTime = event.duration
}
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.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()
.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
}
})
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处
.onUpdate((event) => {
if (event) {
this.currentTime = event.time
}
})
.layoutWeight(1)
Text(this.formatTime(this.durationTime)).width(35).fontColor(Color.White).fontSize(12)
.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 }
})
}
.opacity(0.8)
.width("100%")
.padding({ left: 30, right: 30 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.width('100%')
.height('100%')
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.layoutWeight(1)
.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)
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.uri) {
this.removeAudio()
} else {
ToastUtils.show('请上传视频')
}
})
.visibility(!this.isSuccess ? Visibility.Visible : Visibility.None)
Row() {
Text(this.isSuccess ? '重新上传' : '取消').fontColor($r('app.color.color_90ffffff')).fontSize(17).margin({ left: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
this.selectVideo()
} else {
this.getUIContext().getRouter().back()
}
})
Blank().layoutWeight(1)
Text(this.isSuccess ? '保存' : '确定').fontColor($r("app.color.color_466afd")).fontSize(17).margin({ right: 16 })
.onClick(() => {
this.controller.stop()
if (this.isSuccess) {
SaveUtils.saveImageVideoToAlbumDialog([this.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
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()
})
} else {
this.mirrorVideo()
}
})
Blank().width(9)
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Row() {
Image($r('app.media.ic_download3')).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.uri!!])
.then((saved) => {
if (saved) {
this.uri = undefined
this.showDownloadDialog()
} else {
ToastUtils.show('保存失败')
}
})
.catch((e: BusinessError) => {
ToastUtils.show('保存失败:' + e.message)
})
})
}
.margin({ top: 140, bottom: 30 })
.visibility(this.isSuccess ? Visibility.Visible : Visibility.None)
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
.padding({left: 16, top: 9, right: 16, bottom: 30 })
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')

View File

@ -0,0 +1,400 @@
import { PhotoHelper } from '@pura/picker_utils'
import { TitleBar } from '../../../../view/TitleBar'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit'
import { AppUtil, DisplayUtil, FileUtil, ImageUtil, StrUtil } 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 { RectPosition } from '../../../../view/RectCropView'
import { media } from '@kit.MediaKit'
import { MediaUtils } from '../../../../utils/MediaUtils'
import { MP4Parser } from '@ohos/mp4parser'
import { TipDialog } from '../../../../dialog/TipDialog'
import { WaterMarkerView } from '../../../../view/WaterMarkerView'
import { EditTextDialog } from '../../../../dialog/EditTextDialog'
import { image } from '@kit.ImageKit'
import { avSessionManager } from '../../../../manager/AVSessionManager'
import { SelectBoundsView } from '../../../../view/SelectBoundsView'
@Entry
@ComponentV2
struct RemoveWatermarkPage {
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
@Local playerSize: media.PixelMapParams = { width: 0, height: 0 }
@Local showBound: boolean = false
private controller: VideoController = new VideoController()
private videoSize: media.PixelMapParams = { width: 0, height: 0 }
private rect: RectPosition = { x: 0, y: 0, width: 0, height: 0 }
addWatermark() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
let file = FileUtil.openSync(this.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
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 rectWidth = (vp2px(this.rect.width * this.videoSize.width!!) / this.playerSize.width!!)
let rectHeight = (vp2px(this.rect.height * this.videoSize.height!!) / this.playerSize.height!!)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
let cmd = `ffmpeg -i ${cacheVideoPath} -filter_complex "[0:v]crop=${Math.round(rectWidth)}:${Math.round(rectHeight)}:${rectX}:${rectY},avgblur=15:15[fg]; [0:v][fg]overlay=${rectX}:${rectY}" -c:v h264 -pix_fmt yuv420p -y ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
this.uri = FileUtil.getUriFromPath(outputPath)
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
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.uri = uris[0]
this.showBound = false
MediaUtils.getVideoSize(this.uri)
.then((size) => {
this.videoSize = size
if (size.width && size.height) {
const ratio = (DisplayUtil.getWidth() - 300) / size.width
this.playerSize = {width: Math.ceil(size.width * ratio), height: Math.ceil(size.height * ratio)}
}
})
}
})
}
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() {
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%')
.aspectRatio(1)
.borderRadius(20)
.backgroundColor(Color.White)
.shadow({radius: 10, color: '#1a9399a1'})
.onClick(() => {
this.selectVideo()
})
}
.width('100%')
.height('auto')
.padding({left: 32, right: 32})
.margin({top: 40})
.visibility(this.uri ? Visibility.None : Visibility.Visible)
Column() {
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.id('video')
.width(this.playerSize ? px2vp(this.playerSize.width) : '100%')
.height(this.playerSize ? px2vp(this.playerSize.height) : '100%')
.backgroundColor($r('app.color.window_background'))
.controls(false) // 设置是否显示默认控制条
.autoPlay(false) // 设置是否自动播放
.loop(false) // 设置是否循环播放
.objectFit(ImageFit.Cover) // 设置视频填充模式
.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 }
})
.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: 'video', align: HorizontalAlign.Start },
top: { anchor: 'video', align: VerticalAlign.Top },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', 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(this.playerSize ? px2vp(this.playerSize.width) : "100%")
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
if (this.showBound && this.uri && !this.isSuccess) {
SelectBoundsView({
onRectChange: (rect) => {
this.rect = rect
},
onClose: () => {
this.showBound = false
}
})
.width(this.playerSize ? px2vp(this.playerSize.width) : '100%')
.height(this.playerSize ? px2vp(this.playerSize.height) : '100%')
.alignRules({
left: { anchor: 'video', align: HorizontalAlign.Start },
top: { anchor: 'video', align: VerticalAlign.Top },
right: { anchor: 'video', align: HorizontalAlign.End },
bottom: { anchor: 'video', align: VerticalAlign.Bottom }
})
}
}
.layoutWeight(1)
Stack() {
Column(){
Image($r('app.media.ic_remove_watermark')).width(26).height(26)
Text('马赛克').fontColor($r('app.color.color_212226')).fontSize(12).margin({ top: 8 })
}
.onClick(() => {
if (!this.showBound) {
this.controller.stop()
this.showBound = true
}
})
}
.width(82)
.height(82)
.margin({ top: 20, bottom: 20 })
.borderRadius(10)
.backgroundColor(Color.White)
}
.layoutWeight(1)
.visibility(this.uri ? Visibility.Visible : Visibility.None)
Blank().layoutWeight(1).visibility(this.uri ? 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.uri) {
this.addWatermark()
} 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_download3')).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.uri!!])
.then((saved) => {
if (saved) {
this.uri = 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

@ -1,287 +0,0 @@
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 { MP4Parser } from '@ohos/mp4parser'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct TakeAudioPage {
private controller: VideoController = new VideoController()
@Local uri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
takeAudio() {
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.uri!!, fileIo.OpenMode.READ_ONLY)
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cachePath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp3`
let cmd = `ffmpeg -i ${cachePath} -vn -c:a mp3 ${outputPath}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {
if (code === 0) {
SaveUtils.saveAudioToMusic([outputPath])
.then(() => {
this.uri = undefined
this.isSuccess = true
this.isPlaying = false
this.showDownloadDialog()
})
.catch((e: BusinessError) => {
ToastUtils.show('提取失败:' + e.message)
})
} else {
ToastUtils.show('提取失败')
}
LoadingDialog.dismiss()
}
})
}
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.uri = uris[0]
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, isAudio: true, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 2)
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}`
}
}
}
build() {
Column() {
TitleBar({ title: '视频转音频' })
Column() {
Row() {
Text('上传视频').fontColor($r('app.color.color_90ffffff')).fontSize(16).fontWeight(FontWeight.Medium)
Text('仅支持mp4格式').fontColor($r('app.color.color_50ffffff')).fontSize(12)
}.alignSelf(ItemAlign.Start)
RelativeContainer() {
Stack() {
Stack() {
Image($r('app.media.ic_add_video')).width(44).height(44)
}
.width(140)
.height(140)
.borderRadius(10)
.backgroundColor($r('app.color.color_333333'))
.margin({top: 40})
.onClick(() => {
this.selectVideo()
})
.visibility(this.uri ? Visibility.None : Visibility.Visible)
RelativeContainer() {
Video({
src: this.uri, // 设置视频源
controller: this.controller, //设置视频控制器,可以控制视频的播放状态
posterOptions: { showFirstFrame: true }
})
.width('100%')
.height('100%')
.borderRadius(12)
.backgroundColor($r('app.color.window_background'))
.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%")
.padding({ left: 20, right: 20 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.height(350)
.margin({top: 50})
.visibility(this.uri ? Visibility.Visible : Visibility.None)
}
.alignRules({
start: { anchor: '__container__', align: HorizontalAlign.Start },
end: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('layout_content')
Image($r('app.media.ic_reupload_video')).width(20).height(20)
.alignRules({
right: {anchor: '__container__', align: HorizontalAlign.End}
})
.margin({top: 20})
.onClick(() => {
this.controller.stop()
this.selectVideo()
})
.visibility(this.uri ? Visibility.Visible : Visibility.None)
Button('确认提取', {type: ButtonType.Capsule ,stateEffect:true})
.width('100%')
.height(42)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(16)
.linearGradient({
colors: [['#F62C6C', 0.0], ['#FC4F54', 1.0]],
direction: GradientDirection.Right
})
.alignRules({
top: {anchor: 'layout_content', align: VerticalAlign.Bottom}
})
.margin({top: 40})
.onClick(() => {
if (this.uri) {
this.takeAudio()
} else {
ToastUtils.show('请先上传视频')
}
})
}
.height('auto')
.margin({ top: 12 })
.borderRadius(8)
.backgroundColor($r('app.color.color_222222'))
.padding({left: 20, right: 20, bottom: 30})
}.margin({ left: 16, top: 16, right: 16 })
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -28,7 +28,7 @@ struct VideoMirrorPage {
mirrorVideo() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
@ -37,7 +37,7 @@ struct VideoMirrorPage {
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${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}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {

View File

@ -26,7 +26,7 @@ struct VideoReversePage {
videoReverse() {
LoadingDialog.show(this.getUIContext())
this.isSuccess = false
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${systemDateTime.getTime()}.mp4`
let cacheVideoPath = FileUtil.getCacheDirPath() + FileUtil.separator + `scmf_${systemDateTime.getTime()}.mp4`
if (FileUtil.accessSync(cacheVideoPath)) {
FileUtil.unlinkSync(cacheVideoPath)
}
@ -35,7 +35,7 @@ struct VideoReversePage {
// 复制文件到缓存目录下
FileUtil.copyFileSync(file.fd, cacheVideoPath)
let outputPath = FileUtil.getCacheDirPath() + FileUtil.separator + `kcsp_${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}`
MP4Parser.ffmpegCmd(cmd, {
callBackResult: (code: number) => {

View File

@ -0,0 +1,313 @@
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 { MP4Parser } from '@ohos/mp4parser'
import { LoadingDialog } from '../../../../dialog/LoadingDialog'
import { DownloadDialog, DownloadStatus } from '../../../../dialog/DownloadDialog'
import { EventConstants } from '../../../../common/EventConstants'
import { avSessionManager } from '../../../../manager/AVSessionManager'
@Entry
@ComponentV2
struct VideoToAudioPage {
private controller: VideoController = new VideoController()
@Local videoUri?: string
@Local currentTime: number = 0
@Local durationTime: number = 0
@Local isPlaying: boolean = false
@Local isSuccess: boolean = false
@Local audioUri?: string
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)
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.audioUri = outputPath
this.isSuccess = true
this.isPlaying = false
ToastUtils.show('处理成功')
} else {
ToastUtils.show('处理失败')
}
LoadingDialog.dismiss()
}
})
}
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]
this.audioUri = undefined
}
})
}
showDownloadDialog() {
DownloadDialog.show(this.getUIContext(), { status: DownloadStatus.COMPLETED, isAudio: true, totalSize: 0, progress: 0, totalCount: 1, index: 0, callback: {
confirm: () => {
AppUtil.getContext().eventHub.emit(EventConstants.JumpToRecordEvent, 2)
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}`
}
}
}
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})
Blank().layoutWeight(1)
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_download3')).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.saveAudioToMusic([this.audioUri!!])
.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

@ -9,6 +9,7 @@ import { TipDialog } from '../../../../dialog/TipDialog';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { LocalMediaManager } from '../../../../manager/LocalMediaManager';
import { fileIo } from '@kit.CoreFileKit';
import { ShareManager } from '../../../../manager/ShareManager';
@ComponentV2
export struct AudioRecordPage {
@ -68,6 +69,11 @@ export struct AudioRecordPage {
ListItem() {
AudioRecordItemView({ media: item })
}
.swipeAction({
end: this.itemEnd(item)
})
.borderRadius(8)
.backgroundColor(Color.White)
})
}
.width('auto')
@ -83,4 +89,51 @@ export struct AudioRecordPage {
})
}
}
@Builder
itemEnd(media: MediaRecordEntity) {
Row() {
Button({ type: ButtonType.Normal, stateEffect: true }) {
Column() {
Image($r('app.media.ic_share_material')).width(18).height(18)
Text('分享').fontColor(Color.White).fontSize(10).margin({top: 4})
}
}
.width(56)
.height('100%')
.backgroundColor('#FF9E43')
.onClick(() => {
ShareManager.shareFile(media.uri!!)
})
Button({ type: ButtonType.Normal, stateEffect: true }) {
Column() {
Image($r('app.media.ic_delete_material')).width(18).height(18)
Text('删除').fontColor(Color.White).fontSize(10).margin({top: 4})
}
}
.width(56)
.height('100%')
.backgroundColor('#FF3E3E')
.borderRadius({topRight: 8, bottomRight: 8})
.onClick(() => {
TipDialog.show(this.getUIContext(), {
title: '提示', content: '确定删除该音频?', callback: {
confirm: () => {
fileIo.unlink(media.uri!!)
.then(() => {
ToastUtils.show('删除成功')
LocalMediaManager.delete(media.name!!)
AppUtil.getContext().eventHub.emit(EventConstants.MediaActionEvent, MediaType.AUDIO, MediaAction.DELETE)
})
.catch(() => {
ToastUtils.show('删除失败, 请到文件管理中手动删除')
})
}
}
})
})
}
.height(74)
}
}

View File

@ -29,6 +29,12 @@ struct SettingsPage {
ToastUtils.show('账户已注销');
}
@Monitor('viewModel.logout')
onLogout(monitor: IMonitor) {
EventReportGlobalManager.eventReport(EventConstants.EXIT_LOGIN)
this.logout();
}
aboutToAppear(): void {
this.getCache()
}
@ -141,8 +147,7 @@ struct SettingsPage {
.onClick(() => {
TipDialog.show(this.getUIContext(), {title: '温馨提示', content: '确定退出登录?', callback: {
confirm: () => {
EventReportGlobalManager.eventReport(EventConstants.EXIT_LOGIN)
this.logout();
this.viewModel.userLogout()
}
}})
})

View File

@ -0,0 +1,67 @@
import { RouterUrls } from '../../../../common/RouterUrls'
import { ToolMenuEntity, toolsList } from '../../../../entity/ToolMenuEntity'
import { ToolItemView } from '../../../../view/ToolItemView'
@ComponentV2
export struct ToolsPage {
build() {
Scroll() {
Column() {
Image($r('app.media.ic_tools_top_bg')).width('100%').aspectRatio(2.925)
Grid() {
ForEach(toolsList().convertToArray(), (item: ToolMenuEntity) => {
GridItem() {
ToolItemView({ menu: item })
}
.onClick(() => {
switch (item.alias) {
case 'resetMD5': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.MD5_RESET_PAGE})
break
}
case 'videoToText': {
break
}
case 'videoToAudio': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.VIDEO_TO_AUDIO_PAGE})
break
}
case 'audioToText': {
break
}
case 'addWatermark': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.ADD_WATERMARK_PAGE})
break
}
case 'longImageMerge': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.IMAGE_MERGE_PAGE})
break
}
case 'removeWatermark': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.REMOVE_WATERMARK_PAGE})
break
}
case 'removeAudio': {
this.getUIContext().getRouter().pushUrl({url: RouterUrls.REMOVE_AUDIO_PAGE})
break
}
}
})
})
}
.scrollBar(BarState.Off)
.columnsTemplate('1fr 1fr')
.rowsGap(9)
.columnsGap(9)
.margin({top: 20})
}
.width('100%')
.height('auto')
.padding({ left: 12, top: 70, right: 12, bottom: 10 })
}
.height('100%')
.scrollBar(BarState.Off)
.backgroundColor($r('app.color.window_background'))
}
}

View File

@ -78,7 +78,7 @@ export class SaveUtils {
*/
static async saveVideoToAlbum(path: string, name: string): Promise<boolean> {
try {
let name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4`
let name = `scmf_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.mp4`
const uri = await PhotoHelper.save(photoAccessHelper.PhotoType.VIDEO, 'mp4', { title: name.replace('.mp4', '') });
let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await FileUtil.copyFile(path, file.fd)
@ -101,7 +101,7 @@ export class SaveUtils {
*/
static async saveImageToAlbum(path: string, name: string): Promise<boolean> {
try {
if (FileUtil.accessSync(path)) name = `kcsp_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg`
if (FileUtil.accessSync(path)) name = `scmf_${systemDateTime.getTime() + RandomUtil.getRandomInt(1000, 2000)}.jpeg`
const uri = await PhotoHelper.save(photoAccessHelper.PhotoType.IMAGE, 'jpeg', { title: name.replace('.jpeg', '') });
let file = FileUtil.openSync(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await FileUtil.copyFile(path, file.fd)

View File

@ -138,61 +138,6 @@ export struct ImageRecordItemView {
export struct AudioRecordItemView {
@Param media?: MediaRecordEntity = undefined;
build() {
RelativeContainer() {
Text(this.media?.name)
.layoutWeight(1)
.fontColor($r('app.color.color_90ffffff'))
.fontSize(15)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Center }
})
.margin({ bottom: 2 })
Text(this.formatTime(Math.trunc(this.media!!.duration / 1000)))
.fontColor($r('app.color.color_60ffffff'))
.fontSize(14)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Center }
})
.margin({ top: 2 })
.id('tv_duration')
Text(DateUtil.getFormatDateStr(this.media!!.createTime, 'yyyy年MM月dd日 HH:mm:ss'))
.fontColor($r('app.color.color_30ffffff'))
.fontSize(12)
.alignRules({
left: { anchor: 'tv_duration', align: HorizontalAlign.End },
top: { anchor: 'tv_duration', align: VerticalAlign.Top },
bottom: { anchor: 'tv_duration', align: VerticalAlign.Bottom }
})
.margin({ left: 12 })
Image($r('app.media.ic_arrow_dp22')).width(24).height(24)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
Divider().color($r('app.color.color_10ffffff')).width('100%').strokeWidth(1)
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.height(74)
.onClick(() => {
this.getUIContext()
.getRouter()
.pushUrl({
url: RouterUrls.AUDIO_PLAYER_PAGE,
params: { title: this.media?.name, uri: this.media?.uri, showActions: true }
}, router.RouterMode.Single)
})
}
formatTime(time: number): string {
let minute: number = 0
let second: number = 0
@ -221,4 +166,60 @@ export struct AudioRecordItemView {
}
}
}
build() {
RelativeContainer() {
Text(this.media?.name)
.layoutWeight(1)
.fontColor($r('app.color.color_212226'))
.fontSize(15)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Center }
})
.margin({ bottom: 2 })
Text(this.formatTime(Math.trunc(this.media!!.duration / 1000)))
.fontColor($r('app.color.color_666666'))
.fontSize(12)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Center }
})
.margin({ top: 2 })
.id('tv_duration')
Text(DateUtil.getFormatDateStr(this.media!!.createTime, 'yyyy年MM月dd日 HH:mm:ss'))
.fontColor($r('app.color.color_30ffffff'))
.fontSize(12)
.alignRules({
left: { anchor: 'tv_duration', align: HorizontalAlign.End },
top: { anchor: 'tv_duration', align: VerticalAlign.Top },
bottom: { anchor: 'tv_duration', align: VerticalAlign.Bottom }
})
.margin({ left: 12 })
Image($r('app.media.ic_arrow_dp16')).width(16).height(16)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
Divider().color($r('app.color.color_10ffffff')).width('100%').strokeWidth(1)
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
}
.height(74)
.padding({left: 14, right: 14})
.onClick(() => {
this.getUIContext()
.getRouter()
.pushUrl({
url: RouterUrls.AUDIO_PLAYER_PAGE,
params: { title: this.media?.name, uri: this.media?.uri, showActions: true }
}, router.RouterMode.Single)
})
}
}

View File

@ -0,0 +1,201 @@
import { ActionType, Position, RectPosition } from './RectCropView';
@ComponentV2
export struct SelectBoundsView {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private actionType: ActionType = ActionType.move;
private touchPosition: Position = { x: 0, y: 0 };
private sw: number = 0; //图片展示框固定宽度
private sh: number = 0; //图片展示框固定高度
@Param onRectChange?: (rect: RectPosition) => void = undefined
@Param onClose?: () => void = undefined
@Local clipRect: RectPosition = {
x: 0,
y: 0,
width: 80,
height: 40
};
@Local initPosition: Position = {
x: 0,
y: 0
}
@Local fontSize: number = 15
build() {
Stack() {
Stack()
.position({
x: this.clipRect.x,
y: this.clipRect.y
})
.width(this.clipRect.width)
.height(this.clipRect.height)
.blur(20)
.id('mosaic')
Image($r('app.media.ic_right_bottom_rect'))
.position({
x: this.clipRect.x + this.clipRect.width - 9,
y: this.clipRect.y + this.clipRect.height - 9
})
.width(15)
.height(15)
// 裁剪框
Canvas(this.canvasContext)
.position({
x: this.clipRect.x,
y: this.clipRect.y
})
.width(this.clipRect.width)
.height(this.clipRect.height)
.onReady(() => {
this.drawClipImage()
})
.onTouch(event => {
if (event.type === TouchType.Down) {
this.isMove(event.target.area, event.touches[0]);
this.touchPosition = {
x: event.touches[0].screenX,
y: event.touches[0].screenY
}
} else if (event.type === TouchType.Move) {
let moveX = event.changedTouches[0].screenX - this.touchPosition.x;
let moveY = event.changedTouches[0].screenY - this.touchPosition.y;
this.touchPosition = {
x: event.changedTouches[0].screenX,
y: event.changedTouches[0].screenY
}
this.moveClipCanvas(moveX, moveY);
}
})
Image($r('app.media.ic_left_top_rect'))
.position({
x: this.clipRect.x - 7,
y: this.clipRect.y - 7
})
.width(16)
.height(16)
.onClick(() => {
if (this.onClose) {
this.onClose()
}
})
}
.width('100%')
.height('100%')
.onAreaChange((_oldArea, newArea) => {
this.sw = newArea.width as number
this.sh = newArea.height as number
if (this.onRectChange) {
this.onRectChange(this.clipRect)
}
})
}
// 绘制裁剪框
drawClipImage() {
this.canvasContext.clearRect(0, 0, this.clipRect.width, this.clipRect.height);
this.canvasContext.lineWidth = 2
this.canvasContext.strokeStyle = Color.White
this.canvasContext.beginPath()
this.canvasContext.rect(0, 0, this.clipRect.width, this.clipRect.height)
this.canvasContext.stroke()
}
// 裁剪框位置和大小变化 初始位置为图片的初始坐标 移动的坐标
moveClipCanvas(moveX: number, moveY: number) {
let clipRect: RectPosition = {
x: this.clipRect.x,
y: this.clipRect.y,
width: this.clipRect.width,
height: this.clipRect.height
}
switch (this.actionType) {
case ActionType.move:
clipRect.x += moveX;
clipRect.y += moveY;
break;
case ActionType.topLeft:
clipRect.x += moveX;
clipRect.y += moveY;
clipRect.width += -moveX;
clipRect.height += -moveY;
break;
case ActionType.topRight:
clipRect.y += moveY;
clipRect.width += moveX;
clipRect.height += -moveY;
break;
case ActionType.bottomLeft:
clipRect.x += moveX;
clipRect.width += -moveX;
clipRect.height += moveY;
break;
case ActionType.bottomRight:
clipRect.width += moveX;
clipRect.height += moveY;
break;
default:
break;
}
// 偏移坐标小于初始位置
if (clipRect.x < this.initPosition.x) {
clipRect.x = this.initPosition.x;
}
if (clipRect.y < this.initPosition.y) {
clipRect.y = this.initPosition.y;
}
// 横坐标限制位置
if (clipRect.width + clipRect.x > this.sw + this.initPosition.x) {
if (this.actionType === ActionType.move) {
clipRect.x = this.sw + this.initPosition.x - clipRect.width;
} else {
clipRect.width = this.sw + this.initPosition.x - clipRect.x;
}
}
// 纵坐标限制
if (clipRect.height + clipRect.y > this.sh + this.initPosition.y) {
if (this.actionType === ActionType.move) {
clipRect.y = this.sh + this.initPosition.y - clipRect.height;
} else {
clipRect.height = this.sh + this.initPosition.y - clipRect.y;
}
}
//裁剪框位置大小
this.clipRect = {
x: Math.round(clipRect.x),
y: Math.round(clipRect.y),
width: Math.max(Math.round(clipRect.width), 80),
height: Math.max(Math.round(clipRect.height), 40)
};
if (this.onRectChange) {
this.onRectChange(this.clipRect)
}
}
// 判断操作类型
isMove(area: Area, touch: TouchObject) {
if (touch.x < 30 && touch.y < 30) { // 左上角
this.actionType = ActionType.topLeft
} else if (touch.x < 30 && touch.y > (Number(area.height) - 30)) { // 左下
this.actionType = ActionType.bottomLeft
} else if (touch.x > Number(area.width) - 30 && touch.y < 30) { // 右上
this.actionType = ActionType.topRight
} else if (touch.x > Number(area.width) - 30 && touch.y > (Number(area.height) - 30)) { // 右下
this.actionType = ActionType.bottomRight
} else {
this.actionType = ActionType.move
}
}
}

View File

@ -0,0 +1,37 @@
import { ToolMenuEntity } from '../entity/ToolMenuEntity'
@ComponentV2
export struct ToolItemView {
@Param menu?: ToolMenuEntity = undefined
build() {
Column() {
Row() {
Image(this.menu?.icon).width(44).height(44).margin({left: 12, top: 16, bottom: 16})
Column() {
Text(this.menu?.title).fontColor($r('app.color.color_212226')).fontSize(16).fontWeight(FontWeight.Medium)
Text(this.menu?.desc).fontColor($r('app.color.color_666666')).fontSize(12).margin({top: 4})
}
.margin({left: 12})
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.borderRadius({topLeft: 8, topRight: 8})
.linearGradient({
direction: GradientDirection.Bottom,
colors: [[this.menu?.colors[0], 0.0], [this.menu?.colors[1], 1.0]]
})
Row() {
Text(this.menu?.count + 'w人使用').fontColor($r('app.color.color_999999')).fontSize(12).layoutWeight(1)
Image($r('app.media.ic_tool_arrow')).width(20).height(20)
}
.padding({left:12, top: 8, right: 12, bottom: 8 })
}
.width('100%')
.borderRadius(8)
.backgroundColor(Color.White)
.padding(1)
.alignItems(HorizontalAlign.Start)
}
}

View File

@ -5,6 +5,7 @@ import { BaseViewModel } from './BaseViewModel';
@ObservedV2
export class SettingsViewModel extends BaseViewModel {
@Trace destroy?: object;
@Trace logout?: object;
async userDestroy() {
this.showLoading();
@ -22,4 +23,21 @@ export class SettingsViewModel extends BaseViewModel {
this.dismissLoading();
}
}
async userLogout() {
this.showLoading();
try {
const result = await apiService.logout();
if (result.isSuccess()) {
this.logout = new Object();
} else {
ToastUtils.show(result.message, true);
}
this.dismissLoading();
} catch (e) {
console.log(e);
ToastUtils.show(e);
this.dismissLoading();
}
}
}

View File

@ -52,6 +52,10 @@
"name": "color_cccccc",
"value": "#CCCCCC"
},
{
"name": "color_eeeeee",
"value": "#EEEEEE"
},
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -9,14 +9,16 @@
"pages/main/home/link/TakeMaterialPage",
"pages/main/home/course/CoursePage",
"pages/main/home/wx/WxVideoPage",
"pages/main/home/tools/AddWaterMarkerPage",
"pages/main/home/tools/AddWatermarkPage",
"pages/main/home/tools/RemoveWatermarkPage",
"pages/main/home/tools/MD5ResetPage",
"pages/main/home/tools/VideoReversePage",
"pages/main/home/tools/VideoMirrorPage",
"pages/main/home/tools/ClipVideoPage",
"pages/main/home/tools/RemoveAudioPage",
"pages/main/home/tools/AddAudioPage",
"pages/main/home/tools/TakeAudioPage",
"pages/main/home/tools/VideoToAudioPage",
"pages/main/home/tools/ImageMergePage",
"pages/main/home/material/MaterialDetailPage",
"pages/main/mine/user/UserSettingsPage",
"pages/main/mine/vip/VipPage",