forked from 77media/video-flow
154 lines
4.7 KiB
TypeScript
154 lines
4.7 KiB
TypeScript
import { getUploadToken, uploadToQiniu } from "@/api/common";
|
||
import { useState, useCallback, useEffect } from "react";
|
||
import { ScriptEditKey } from "../usecase/ScriptEditUseCase";
|
||
/**
|
||
* 渲染数据转换器
|
||
* @param key
|
||
* @param headerName
|
||
* @param scriptText
|
||
* @returns
|
||
*/
|
||
export function parseScriptBlock(
|
||
key: ScriptEditKey,
|
||
headerName: string,
|
||
scriptText: string,
|
||
contentType?: "paragraph" | "bold" | "italic" | "heading" | "tag"
|
||
) {
|
||
return {
|
||
id: key,
|
||
title: headerName,
|
||
content: [
|
||
{
|
||
type: contentType || "paragraph",
|
||
text: scriptText,
|
||
},
|
||
],
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 用于上传文件到七牛云的自定义 Hook
|
||
* @returns {object} - 包含上传函数和加载状态
|
||
*/
|
||
export function useUploadFile() {
|
||
/** 加载状态 */
|
||
const [isUploading, setIsUploading] = useState(false);
|
||
|
||
/**
|
||
* 上传文件到七牛云
|
||
* @param {File} file - 要上传的文件
|
||
* @param {(progress: number) => void} [onProgress] - 上传进度回调
|
||
* @returns {Promise<string>} - 上传后文件的 URL
|
||
* @throws {Error} - 上传失败时抛出异常
|
||
*/
|
||
const uploadFile = useCallback(
|
||
async (
|
||
file: File,
|
||
onProgress?: (progress: number) => void
|
||
): Promise<string> => {
|
||
try {
|
||
setIsUploading(true);
|
||
const { token } = await getUploadToken();
|
||
const fileUrl = await uploadToQiniu(file, token, onProgress);
|
||
return fileUrl;
|
||
} catch (err) {
|
||
console.error("文件上传失败:", err);
|
||
throw err;
|
||
} finally {
|
||
setIsUploading(false);
|
||
}
|
||
},
|
||
[]
|
||
);
|
||
|
||
return { uploadFile, isUploading };
|
||
}
|
||
|
||
/**加载文案定时变 */
|
||
export function useLoadScriptText(loading: boolean): { loadingText: string } {
|
||
// 如果loading 为true 则每五秒切换一次文本,如果变false 则停止切换,且重置文本位置
|
||
const tests = [
|
||
"loading...",
|
||
"Brainstorming initial story concepts and themes.",
|
||
"Drafting the screenplay's first outline.",
|
||
"Refining character arcs and plot points.",
|
||
"Finalizing the script with dialogue polish.",
|
||
"Creating detailed storyboards for key scenes.",
|
||
"Scouting potential filming locations.",
|
||
"Designing mood boards for visual aesthetics.",
|
||
"Casting actors to bring characters to life.",
|
||
"Scheduling production timelines and shoots.",
|
||
"Securing permits for on-location filming.",
|
||
"Building sets to match the story’s vision.",
|
||
"Designing costumes for character authenticity.",
|
||
"Planning lighting setups for each scene.",
|
||
"Renting equipment for high-quality production.",
|
||
"Rehearsing actors for seamless performances.",
|
||
"Setting up cameras for the first shot.",
|
||
"Filming establishing shots for scene context.",
|
||
"Capturing key dialogue scenes with precision.",
|
||
"Recording action sequences with dynamic angles.",
|
||
"Filming close-ups to capture emotions.",
|
||
"Wrapping principal photography on set.",
|
||
"Reviewing dailies for quality assurance.",
|
||
"Organizing raw footage for editing.",
|
||
"Editing scenes for narrative flow.",
|
||
"Adding sound effects to enhance immersion.",
|
||
"Composing the film’s musical score.",
|
||
"Mixing audio for balanced sound design.",
|
||
"Applying color grading for visual consistency.",
|
||
"Rendering visual effects for final polish.",
|
||
"Exporting the final cut for distribution.",
|
||
];
|
||
const [currentIndex, setCurrentIndex] = useState(0);
|
||
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
|
||
useEffect(() => {
|
||
if (loading) {
|
||
const interval = setInterval(() => {
|
||
setCurrentIndex((prev) => (prev + 1) % tests.length);
|
||
}, 5000);
|
||
setIntervalId(interval);
|
||
} else {
|
||
if (intervalId) {
|
||
clearInterval(intervalId);
|
||
setIntervalId(null);
|
||
setCurrentIndex(0);
|
||
}
|
||
}
|
||
return () => {
|
||
if (intervalId) {
|
||
clearInterval(intervalId);
|
||
setIntervalId(null);
|
||
setCurrentIndex(0);
|
||
}
|
||
};
|
||
}, [loading, tests.length]);
|
||
return { loadingText: tests[currentIndex] };
|
||
}
|
||
|
||
|
||
/**
|
||
* 获取音频文件时长
|
||
* @param {File | Blob} audioFile - 音频文件或Blob
|
||
* @returns {Promise<number>} 音频时长(秒)
|
||
*/
|
||
export const getAudioDuration = (audioFile: File | Blob): Promise<number> => {
|
||
return new Promise((resolve, reject) => {
|
||
const audio = new Audio();
|
||
const url = URL.createObjectURL(audioFile);
|
||
|
||
audio.addEventListener('loadedmetadata', () => {
|
||
const duration = audio.duration;
|
||
URL.revokeObjectURL(url);
|
||
resolve(duration);
|
||
});
|
||
|
||
audio.addEventListener('error', (error) => {
|
||
URL.revokeObjectURL(url);
|
||
reject(new Error(`无法读取音频文件: ${error.message}`));
|
||
});
|
||
|
||
audio.src = url;
|
||
});
|
||
};
|