页面
视频识别代码:
<script lang="ts">
import Button from 'primevue/button';
import MediaImport from '@/components/MediaImport';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import WebSocketService from '@/utils/WebSocketService';
//import { ws } from '@/main';
export default {
name: "VideoRecognition",
emits: ['upload-media'],
setup(_, { emit }) {
const hasFiles = ref<Boolean>(false);
const hasUploaded = ref<Boolean>(false);
const mediaObjectUrl = ref<String>("");
const handleHasFiles = (value: boolean) => {
hasFiles.value = value;
};
const handleHasUploaded = (value: boolean) => {
hasUploaded.value = value;
};
const handleSendMediaObjectUrl = (path: string) => {
mediaObjectUrl.value = path;
};
const ws = new WebSocketService();
const router = useRouter();
const goToRecognitionView = () => {
emit('upload-media', true);
if (hasFiles.value && hasUploaded.value) {
console.log(hasUploaded.value)
//ws.send("video_recognition_media_object_url", mediaObjectUrl.value);
//ws.close();
//router.push('/recognition');
} else {
console.log('Focus')
}
};
return { hasFiles, handleHasFiles, handleHasUploaded, handleSendMediaObjectUrl, goToRecognitionView };
},
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Button,
MediaImport,
},
};
</script>
<template>
<div class="container">
<div class="flex flex-col gap-4">
<div class="flex flex-col justify-center w-full">
<div class="w-full flex-auto flex flex-col justify-center items-center border-2 border-solid border-surface-200 dark:border-surface-700 rounded-md bg-[#1e2d4b] font-medium p-4">
<p class="pb-4">Select video to recognize</p>
<MediaImport ref="MediaImport" name="media[]" url="/api/upload" mediaType="video" accept="video/*" @has-files="handleHasFiles" @media-object-url="handleSendMediaObjectUrl" @has-uploaded="handleHasUploaded" :maxFileSize="512000000" :auto="true" chooseLabel="Browse" />
</div>
<div class="flex pt-4 justify-end">
<Button label="Next" icon="pi pi-arrow-right" iconPos="right" @click="goToRecognitionView" />
</div>
</div>
</div>
</div>
</template>
组件MediaImport代码:
<script lang="ts">
import Message from 'primevue/message';
import BaseMediaImport from './BaseMediaImport.vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { Comment, ref } from 'vue';
import { randomNumber } from '@/utils/randomNumber';
export interface MediaFile extends File {
name: string;
type: string;
size: number;
objectURL?: string;
path?: string;
}
export default {
name: 'MediaImport',
extends: BaseMediaImport,
emits: ['select', 'before-upload', 'progress', 'error', 'clear', 'remove', 'has-files', 'media-object-url', 'upload', 'uploader', 'has-uploaded', 'before-send'],
setup() {
const mediaUpload = ref<Boolean>(false);
const handleMediaUpload = (state: boolean) => {
mediaUpload.value = state;
}
return { handleMediaUpload };
},
data() {
return {
files: [] as MediaFile[],
uploadedFiles: [] as MediaFile[],
messages: [] as String[],
focused: false,
progress: null,
acceptedTypes: '',
showDragOverIcon: false,
uploadedFileCount: 0
};
},
methods: {
upload() {
if (this.customUpload) {
if (this.fileLimit) {
this.uploadedFileCount += this.files.length;
}
this.$emit('uploader', { files: this.files });
this.clear();
} else {
let xhr = new XMLHttpRequest();
let formData = new FormData();
this.$emit('before-upload', {
xhr: xhr,
formData: formData
});
for (let file of this.files) {
const minFileSizeToCreatePart = 80 * 1024 * 1024 // 80 MB
const fileName = this.generateUniqueFileName(file.name);
if (file.size >= minFileSizeToCreatePart) {
let start = 0;
let partSize = 60 * 1024 * 1024; // 60 MB
while (start < file.size) {
let chunk = file.slice(start, start + partSize);
formData.append(`${this.name}[]`, chunk, `${fileName}.part${Math.ceil((start + 1) / partSize)}`);
start += partSize;
}
} else {
formData.append(this.name, file, fileName);
}
}
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
// @ts-ignore
this.progress = Math.round((event.loaded * 100) / event.total);
}
this.$emit('progress', {
originalEvent: event,
progress: this.progress
});
});
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
// @ts-ignore
this.progress = 0;
if (xhr.status >= 200 && xhr.status < 300) {
if (this.fileLimit) {
this.uploadedFileCount += this.files.length;
}
this.$emit('upload', {
xhr: xhr,
files: this.files
});
} else {
this.$emit('error', {
xhr: xhr,
files: this.files
});
}
this.uploadedFiles.push(...this.files);
this.clear();
}
};
xhr.open('POST', this.url, true);
this.$emit('before-send', {
xhr: xhr,
formData: formData
});
xhr.withCredentials = this.withCredentials;
xhr.send(formData);
}
this.$emit('has-uploaded', true);
},
choose() {
this.$refs.fileInput.click();
},
clear() {
this.files = [];
this.messages = [];
this.$emit('clear');
this.$emit('has-files', false);
},
onFocus() {
this.focused = true;
},
onBlur() {
this.focused = false;
},
getFileExtension(file: MediaFile) {
return '.' + file.name.split('.').pop();
},
isImage(file: MediaFile) {
return /^image\//.test(file.type);
},
isVideo(file: MediaFile) {
return /^video\//.test(file.type);
},
generateUniqueFileName(file: any) {
let prefix = this.isImage(file) ? 'image' : 'video';
let fileUniqueId = randomNumber(18);
let fileExtension = file.split('.').pop();
return `${prefix}-${fileUniqueId}.${fileExtension}`;
},
getMediaTypeSupported() {
const mediaType = this.mediaType.toLowerCase();
if (mediaType === 'image') {
return this.acceptedTypes = this.accept ? this.accept.split(',').filter(type => !type.includes('video')).join(', ') : 'image/jpeg, image/jpg, image/png';
} else if (mediaType === 'video') {
return this.acceptedTypes = this.accept ? this.accept.split(',').filter(type => !type.includes('image')).join(', ') : 'video/mp4, video/webm, video/avi';
} else {
return this.acceptedTypes = (this.accept || 'image/jpeg, image/jpg, image/png, video/mp4, video/webm, video/avi');
}
},
getAllSupportedMediaTypes() {
const imageTypes = ['jpeg', 'jpg', 'png', 'gif', 'svg'];
const videoTypes = ['mp4', 'webm', 'avi', 'mov', 'mkv'];
const supportedTypes = this.acceptedTypes.includes('image/*') ? imageTypes : this.acceptedTypes.includes('video/*') ? videoTypes : [...imageTypes, ...videoTypes];
return supportedTypes.map(type => type.toUpperCase());
}
},
computed: {
hasFiles() {
return Boolean(this.files && this.files.length > 0);
},
hasUploadedFiles() {
return Boolean(this.uploadedFiles && this.uploadedFiles.length > 0);
},
chooseDisabled() {
return this.disabled || Boolean(this.fileLimit && this.fileLimit <= this.files.length);
},
uploadDisabled() {
return this.disabled || !this.hasFiles || Boolean(this.fileLimit && this.fileLimit < this.files.length);
},
cancelDisabled() {
return this.disabled || !this.hasFiles;
},
hasContentInsideTemplateOrEmpty() {
if (this.$slots.default && this.$slots.default().length > 0) {
return this.$slots.default().every(slot => {
return slot.type === 'template' || slot.type === Comment;
});
} else {
return true;
}
},
supportedFileTypes() {
const specialCases: Record<string, string> = {
'svg+xml': 'svg',
'x-matroska': 'mkv',
'quicktime': 'mov',
};
return [...new Set(this.acceptedTypes.split(',').map(type => {
const fileType = type.trim().split('/')[1];
return fileType.includes('*') ? this.getAllSupportedMediaTypes().join(', ') : (specialCases[fileType] || fileType).toUpperCase();
}))].join(', ');
}
},
components: {
ErrorMessage: Message,
FontAwesomeIcon
},
}
</script>
<template>...</template>