|
|
@@ -405,7 +405,7 @@ export default () => {
|
|
|
/**
|
|
|
* 将 base64 字符串或 Blob 转换为 File 对象
|
|
|
*/
|
|
|
-
|
|
|
+
|
|
|
const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): File => {
|
|
|
if (typeof data === 'string') {
|
|
|
// 1. 通过 fetch 获取 Blob 数据
|
|
|
@@ -425,61 +425,61 @@ export default () => {
|
|
|
}
|
|
|
throw new Error('Unsupported data type')
|
|
|
}
|
|
|
+
|
|
|
+ /*
|
|
|
+ // 你原有的 dataToFile 函数保持不变
|
|
|
+ const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): Promise<File> => {
|
|
|
+ if (typeof data === 'string') {
|
|
|
+ const response = await fetch(data);
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`Failed to fetch blob: ${response.statusText}`);
|
|
|
+ }
|
|
|
+ const blob = await response.blob();
|
|
|
+ const mime = videoMimeType || blob.type;
|
|
|
+ return new File([blob], filename, { type: mime });
|
|
|
+ } else if (data instanceof Blob) {
|
|
|
+ return new File([data], filename, { type: data.type });
|
|
|
+ }
|
|
|
+ throw new Error('Unsupported data type');
|
|
|
+ };
|
|
|
|
|
|
-/*
|
|
|
- // 你原有的 dataToFile 函数保持不变
|
|
|
- const dataToFile = async (data: string | Blob, filename: string, videoMimeType: string): Promise<File> => {
|
|
|
- if (typeof data === 'string') {
|
|
|
- const response = await fetch(data);
|
|
|
- if (!response.ok) {
|
|
|
- throw new Error(`Failed to fetch blob: ${response.statusText}`);
|
|
|
+
|
|
|
+ const convertVideoToMP4 = async (
|
|
|
+ videoSource: string | Blob,
|
|
|
+ outputFilename: string = `video_${Date.now()}.mp4`
|
|
|
+ ): Promise<File> => {
|
|
|
+ // 1. 检查浏览器支持
|
|
|
+ const supported = await canEncode();
|
|
|
+ if (!supported) {
|
|
|
+ throw new Error('当前浏览器不支持 WebCodecs,请使用最新 Chrome/Edge 并确保 HTTPS');
|
|
|
}
|
|
|
- const blob = await response.blob();
|
|
|
- const mime = videoMimeType || blob.type;
|
|
|
- return new File([blob], filename, { type: mime });
|
|
|
- } else if (data instanceof Blob) {
|
|
|
- return new File([data], filename, { type: data.type });
|
|
|
- }
|
|
|
- throw new Error('Unsupported data type');
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- const convertVideoToMP4 = async (
|
|
|
- videoSource: string | Blob,
|
|
|
- outputFilename: string = `video_${Date.now()}.mp4`
|
|
|
- ): Promise<File> => {
|
|
|
- // 1. 检查浏览器支持
|
|
|
- const supported = await canEncode();
|
|
|
- if (!supported) {
|
|
|
- throw new Error('当前浏览器不支持 WebCodecs,请使用最新 Chrome/Edge 并确保 HTTPS');
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 转为 File
|
|
|
- const inputFile = await dataToFile(videoSource, 'input.mp4', 'video/mp4');
|
|
|
-
|
|
|
- // 3. 创建 VideoFile 对象(webcodecs-encoder 的输入包装)
|
|
|
- //const videoFile = new VideoFile(inputFile);
|
|
|
- const videoFile = {
|
|
|
- file: inputFile,
|
|
|
- type: 'video/mp4'
|
|
|
+
|
|
|
+ // 2. 转为 File
|
|
|
+ const inputFile = await dataToFile(videoSource, 'input.mp4', 'video/mp4');
|
|
|
+
|
|
|
+ // 3. 创建 VideoFile 对象(webcodecs-encoder 的输入包装)
|
|
|
+ //const videoFile = new VideoFile(inputFile);
|
|
|
+ const videoFile = {
|
|
|
+ file: inputFile,
|
|
|
+ type: 'video/mp4'
|
|
|
+ };
|
|
|
+ // 4. 执行编码
|
|
|
+ const encodedData = await encode(videoFile, {
|
|
|
+ quality: 'high',
|
|
|
+ video: {
|
|
|
+ codec: 'av1',
|
|
|
+ bitrate: 2_000_000,
|
|
|
+ hardwareAcceleration: 'prefer-hardware'
|
|
|
+ },
|
|
|
+ audio: false, // 显式禁用音频编码
|
|
|
+ container: 'mp4',
|
|
|
+ onProgress: (progress) => console.log(progress)
|
|
|
+ });
|
|
|
+
|
|
|
+ // 5. 返回 File
|
|
|
+ return new File([encodedData], outputFilename, { type: 'video/mp4' });
|
|
|
};
|
|
|
- // 4. 执行编码
|
|
|
- const encodedData = await encode(videoFile, {
|
|
|
- quality: 'high',
|
|
|
- video: {
|
|
|
- codec: 'av1',
|
|
|
- bitrate: 2_000_000,
|
|
|
- hardwareAcceleration: 'prefer-hardware'
|
|
|
- },
|
|
|
- audio: false, // 显式禁用音频编码
|
|
|
- container: 'mp4',
|
|
|
- onProgress: (progress) => console.log(progress)
|
|
|
- });
|
|
|
-
|
|
|
- // 5. 返回 File
|
|
|
- return new File([encodedData], outputFilename, { type: 'video/mp4' });
|
|
|
- };
|
|
|
-*/
|
|
|
+ */
|
|
|
|
|
|
|
|
|
/*
|
|
|
@@ -587,7 +587,7 @@ export default () => {
|
|
|
const response = await fetch(input)
|
|
|
const blob = await response.blob()
|
|
|
return { blob, mime: blob.type }
|
|
|
- }
|
|
|
+ }
|
|
|
// 纯 base64 字符串 → 按原逻辑默认当作 PNG
|
|
|
const binary = atob(input)
|
|
|
const bytes = new Uint8Array(binary.length)
|
|
|
@@ -597,7 +597,7 @@ export default () => {
|
|
|
// 默认 MIME 为 image/png(与原函数行为一致)
|
|
|
const blob = new Blob([bytes], { type: 'image/png' })
|
|
|
return { blob, mime: 'image/png' }
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// 获取统一的 blob 和实际 MIME 类型
|
|
|
@@ -612,7 +612,7 @@ export default () => {
|
|
|
// 1. 创建对象 URL 用于加载图片
|
|
|
const imageUrl = URL.createObjectURL(blob)
|
|
|
const needRevoke = true
|
|
|
-
|
|
|
+
|
|
|
// 2. 加载图像
|
|
|
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
|
const image = new Image()
|
|
|
@@ -1467,12 +1467,47 @@ export default () => {
|
|
|
range: [[0, 0], [100, 100]],
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 如果 src 是 base64,触发上传
|
|
|
+ if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
|
|
|
+ const uploadTask = (async () => {
|
|
|
+ try {
|
|
|
+ const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
|
|
|
+ if (file) {
|
|
|
+ const url = await uploadFileToS3(file)
|
|
|
+ element.src = url // 替换为远程 URL
|
|
|
+ const slidesStore = useSlidesStore()
|
|
|
+ slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('Image upload failed:', error)
|
|
|
+ // 失败时保留原 base64(或可置空)
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ uploadTasks.push(uploadTask)
|
|
|
+ }
|
|
|
+
|
|
|
+ slide.elements.push(element)
|
|
|
+ }
|
|
|
+ else if (el.type === 'math') {
|
|
|
+ const element: PPTImageElement = {
|
|
|
+ type: 'image',
|
|
|
+ id: nanoid(10),
|
|
|
+ src: el.picBase64,
|
|
|
+ width: el.width,
|
|
|
+ height: el.height,
|
|
|
+ left: el.left,
|
|
|
+ top: el.top,
|
|
|
+ fixedRatio: true,
|
|
|
+ rotate: 0,
|
|
|
+ }
|
|
|
/*
|
|
|
// 如果 src 是 base64,触发上传
|
|
|
- if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
|
|
|
+ if (el.picBase64 && typeof el.picBase64 === 'string' && el.picBase64.startsWith('data:')) {
|
|
|
const uploadTask = (async () => {
|
|
|
try {
|
|
|
- const file = await makeWhiteTransparent(el.src, `image_${Date.now()}.png`)
|
|
|
+ const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`)
|
|
|
if (file) {
|
|
|
const url = await uploadFileToS3(file)
|
|
|
element.src = url // 替换为远程 URL
|
|
|
@@ -1488,41 +1523,6 @@ export default () => {
|
|
|
uploadTasks.push(uploadTask)
|
|
|
}
|
|
|
*/
|
|
|
- slide.elements.push(element)
|
|
|
- }
|
|
|
- else if (el.type === 'math') {
|
|
|
- const element: PPTImageElement = {
|
|
|
- type: 'image',
|
|
|
- id: nanoid(10),
|
|
|
- src: el.picBase64,
|
|
|
- width: el.width,
|
|
|
- height: el.height,
|
|
|
- left: el.left,
|
|
|
- top: el.top,
|
|
|
- fixedRatio: true,
|
|
|
- rotate: 0,
|
|
|
- }
|
|
|
-/*
|
|
|
- // 如果 src 是 base64,触发上传
|
|
|
- if (el.picBase64 && typeof el.picBase64 === 'string' && el.picBase64.startsWith('data:')) {
|
|
|
- const uploadTask = (async () => {
|
|
|
- try {
|
|
|
- const file = makeWhiteTransparent(el.picBase64, `image_${Date.now()}.png`)
|
|
|
- if (file) {
|
|
|
- const url = await uploadFileToS3(file)
|
|
|
- element.src = url // 替换为远程 URL
|
|
|
- const slidesStore = useSlidesStore()
|
|
|
- slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
- }
|
|
|
- }
|
|
|
- catch (error) {
|
|
|
- console.error('Image upload failed:', error)
|
|
|
- // 失败时保留原 base64(或可置空)
|
|
|
- }
|
|
|
- })()
|
|
|
- uploadTasks.push(uploadTask)
|
|
|
- }
|
|
|
-*/
|
|
|
|
|
|
slide.elements.push(element)
|
|
|
|
|
|
@@ -1579,26 +1579,26 @@ export default () => {
|
|
|
rotate: 0,
|
|
|
autoplay: false,
|
|
|
}
|
|
|
-/*
|
|
|
- const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)
|
|
|
- if (localData) {
|
|
|
- const uploadTask = (async () => {
|
|
|
- try {
|
|
|
- const file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
|
|
|
- if (file) {
|
|
|
- const url = await uploadFileToS3(file)
|
|
|
- element.src = url
|
|
|
- const slidesStore = useSlidesStore()
|
|
|
- slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
- }
|
|
|
- }
|
|
|
- catch (error) {
|
|
|
- console.error('Video upload failed:', error)
|
|
|
- }
|
|
|
- })()
|
|
|
- uploadTasks.push(uploadTask)
|
|
|
- }
|
|
|
-*/
|
|
|
+ /*
|
|
|
+ const localData = el.blob || (el.src && typeof el.src === 'string' && el.src.startsWith('data:') ? el.src : null)
|
|
|
+ if (localData) {
|
|
|
+ const uploadTask = (async () => {
|
|
|
+ try {
|
|
|
+ const file = await dataToFile(localData, `video_${Date.now()}.mp4`, 'video/mp4')
|
|
|
+ if (file) {
|
|
|
+ const url = await uploadFileToS3(file)
|
|
|
+ element.src = url
|
|
|
+ const slidesStore = useSlidesStore()
|
|
|
+ slidesStore.updateElement({ id: element.id, props: { src: url } })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('Video upload failed:', error)
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ uploadTasks.push(uploadTask)
|
|
|
+ }
|
|
|
+ */
|
|
|
slide.elements.push(element)
|
|
|
}
|
|
|
|