Skip to content

素材添加

用户端可以通过 API 添加图片(jpeg, jpg, png, gif)、视频(mp4)、音频素材(mp3)。视频非 mp4 格式和音频非 mp3 格式均会自动转换为 mp4 和 mp3 格式。

视频素材添加

参考数据格式如下:

ts
const videoParams = {
  app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
  name: "test.mp4", // 视频名称
  urls: {
    url: "/uploads/test.mp4", // 视频链接
    thumb: "/uploads/test_thumb.png", // 封面图
  },
  attrs: {
    size: 1024, // 非必填,文件大小,单位kb
    type: "video/mp4", // 视频类型
    duration: 10, // 视频时长,单位秒
    videoWidth: 1280, // 视频宽度
    videoHeight: 720, // 视频高度
    noAudioTracks: false, // 非必填,判断是否存在音轨
    frames: "/uploads/frames.png", // 非必填,记录当前的帧图数据,如果不传,服务端会自行进行补充处理
    frameScale: 2, // 非必填,帧图的缩放比例,默认参数是2,
    wave: "/uploads/wave.json", // 非必填,当前视频中的音波数据
    rotation: false, // 非必填,记录当前视频是否是存在旋转90度拍摄
  },
};

音频素材添加

参考数据格式如下:

ts
const videoParams = {
  app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
  name: "test.mp3", // 音频名称
  urls: {
    url: "/uploads/test.mp3", // 音频链接
  },
  attrs: {
    size: 1024, // 非必填,文件大小,单位kb
    type: "audio/mp3", // 音频类型
    duration: 10, // 音频时长,单位秒
    wave: "/uploads/wave.json", // 非必填,当前音频中的音波数据
  },
};

图片素材添加

参考数据格式如下:

ts
const videoParams = {
  app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
  name: "test.png", // 图片名称
  urls: {
    url: "/uploads/test.png", // 图片链接
    thumb: "/uploads/test_thumb.png", // 封面图
  },
  attrs: {
    size: 1024, // 非必填,文件大小,单位kb
    type: "image/png", // 图片类型
    naturalWidth: 1000, // 图片宽度
    naturalHeight: 800, // 图片高度
  },
};

需要注意的是 gif 图片和普通图片不一样,gif 图片存在帧图,参考参数如下:

ts
const videoParams = {
  app_id: editor.appid, // 非必填,当前素材所属哪个作品,如果不传就是全局素材
  name: "test.gif", // 图片名称
  urls: {
    url: "/uploads/test.gif", // 图片链接
    thumb: "/uploads/test_thumb.png", // 封面图
  },
  attrs: {
    size: 1024, // 非必填,文件大小,单位kb
    type: "image/gif", // 图片类型
    naturalWidth: 1000, // 图片宽度
    naturalHeight: 800, // 图片高度
    frames: "/uploads/frames.png", // 非必填,记录当前的帧图数据,如果不传,服务端会自行进行补充处理
  },
};

参数获取说明

我们封装了一个 JS 包,可以帮助用户获取到这些常规文件的参数,需要在项目中引入相关的 JS 包(需要配合/assets/worker/assets/MediaInfoModule.wasm使用):

html
<script src="/assets/medianinfo/index.min.js"></script>

<script>

  // 获取上传文件的参数
  const info = await window.uploadInfo.getUploadBeforeData(
    v.file.url, // 文件URL,字符串
    'video', // 非必填、 文件类型:'audio' | 'image' | 'video' | 'image/svg' | 'image/gif'
    uploadBase64, // 非必填、base64文件上传的接口,用于上传封面图、帧图、音波数据等文件,如果不填写该参数,将返回base64文件,自行进行上传
  );
</script>

JS 示例代码

ts
async function main() {
  // 获取文件后缀名称
  const ext = util.getFileExtension(value);

  // 创建一个素材数据
  const obj: any = {
    app_id: editor.appid,
    name: util.getFileNameFromUrl(value),
    urls: {
      url: value,
      thumb: "",
    },
    attrs: {
      type: "video/mp4",
    },
  };
  switch (ext.toLocaleLowerCase()) {
    case "mp4":
      {
        // 资源获取
        try {
          const m = (await util.mediaLazy(value)) as any;
          obj.attrs.type = "video/mp4";
          obj.attrs.duration = m.duration;
          obj.attrs.videoWidth = m.videoWidth;
          obj.attrs.videoHeight = m.videoHeight;
          const base64 = await util.drawVideoFrame(m, 130, 1);
          const [res] = await editor.apiServer.uploadBase64({
            content: base64,
            name: util.randomID() + ".png",
          });
          obj.urls.thumb = res.storage_path;
        } catch (err) {
          Toast.error(err);
        }
      }
      break;
    case "jpeg":
    case "png":
    case "jpg":
    case "gif":
      {
        try {
          const m = await util.imgLazy(value);
          obj.attrs.type = "image/" + ext.toLocaleLowerCase();
          obj.attrs.naturalWidth = m.naturalWidth;
          obj.attrs.naturalHeight = m.naturalHeight;
        } catch (err) {
          Toast.error(err);
        }
      }
      break;
    case "mp3": {
      try {
        const m = (await util.mediaLazy(value)) as any;
        obj.attrs.type = "audio/mp3";
        obj.attrs.duration = m.duration;
      } catch (err) {
        Toast.error(err);
      }
    }
  }

  // 新增素材
  const [res2, err2] = await editor.apiServer.createUserMaterial(obj);
  if (!err2) {
    Toast.success("添加成功");
    // 更新编辑器的素材列表,非编辑器页面可不写
    pubsub.publish("addItemToCloudList", res2);
  }
}

/**
 * 获取文件后缀
 */
export function getFileExtension(url: string) {
  url = url.split("?")[0];
  const pathArray = url.split(".");
  if (pathArray.length > 1) {
    return pathArray.pop().toLowerCase();
  } else {
    return null; // 没有后缀名
  }
}

/**
 * 获取文件名称
 */
export function getFileNameFromUrl(url) {
  try {
    // 解析URL
    const parsedUrl = new URL(url);
    let path = parsedUrl.pathname;

    // 移除路径末尾的斜杠
    if (path.endsWith("/")) {
      path = path.slice(0, -1);
    }

    // 获取路径最后一段(可能包含文件名)
    let fileName = path.split("/").pop() || "";

    // 如果没有获取到文件名,检查查询参数
    if (!fileName) {
      // 尝试从查询参数中寻找类似文件名的参数
      const params = parsedUrl.searchParams;
      const fileParams = ["file", "filename", "name", "download"];

      for (const param of fileParams) {
        if (params.has(param)) {
          fileName = params.get(param);
          break;
        }
      }
    }

    // 处理可能包含的URL编码(如中文)
    fileName = decodeURIComponent(fileName);

    // 移除可能的查询参数或锚点残留
    const queryIndex = fileName.indexOf("?");
    if (queryIndex !== -1) {
      fileName = fileName.substring(0, queryIndex);
    }

    const hashIndex = fileName.indexOf("#");
    if (hashIndex !== -1) {
      fileName = fileName.substring(0, hashIndex);
    }

    return fileName || null; // 如果最终没有找到文件名,返回null
  } catch (e) {
    console.error("解析URL失败:", e);
    return null;
  }
}

/**
 * 异步加载媒体资源
 * @param src
 * @param type
 * @returns
 */
export function mediaLazy(
  src: string,
  time?: number,
  type?: "video" | "audio"
): Promise<HTMLVideoElement | HTMLAudioElement> {
  return new Promise((resolve, reject) => {
    const media = document.createElement(type || "video");
    media.preload = "auto";
    media.crossOrigin = "Anonymous";
    media.autoplay = false;
    media.src = src;
    media.setAttribute("playsinline", "");
    media.setAttribute("webkit-playsinline", "");
    media.setAttribute("x5-video-player-type", "h5"); // 针对微信的额外属性
    media.setAttribute("x5-video-player-fullscreen", "false");
    try {
      media.load();
    } catch (e) {
      console.warn("iOS微信中load()可能受限:", e);
    }

    if (time !== undefined) {
      media.currentTime = time;
    }

    // 如果在微信IOS浏览器中,直接延迟3秒返回对象,因为无法自动触发调用 loadedmetadata 和 canplay
    if (isIOSWechatBrowser()) {
      setTimeout(() => {
        resolve(media);
      }, 3000);
    } else {
      // 监听多个事件以确保兼容性
      const handleLoad = () => {
        console.log("媒体加载成功");
        cleanup();
        resolve(media);
      };

      const handleError = (err: Event | string) => {
        console.error("媒体加载失败", src, err);
        cleanup();
        reject(err);
      };

      const cleanup = () => {
        media.removeEventListener("loadedmetadata", handleLoad);
        media.removeEventListener("canplay", handleLoad);
        media.removeEventListener("error", handleError);
      };
      media.addEventListener("loadedmetadata", handleLoad);
      media.addEventListener("canplay", handleLoad);
      media.addEventListener("error", handleError);
    }
  });
}

/**
 * 获取视频1s的封面图,返回base64
 * @param video
 * @param limitWidth
 */
export async function drawVideoFrame(
  video: HTMLVideoElement,
  limitWidth: number,
  time?: number
) {
  video = video.cloneNode() as any;
  video.muted = true;
  video.currentTime = time || 1;
  await video.play();
  video.pause();
  const canvas = document.createElement("canvas"),
    width = video.videoWidth, //canvas的尺寸和图片一样
    height = video.videoHeight;
  const scale = limitWidth / width;
  canvas.width = limitWidth;
  canvas.height = Math.floor(height * scale);
  canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height); //绘制canvas
  const thumb = canvas.toDataURL("image/jpeg");
  return thumb;
}

export const imgLazy = (src: string): Promise<HTMLImageElement> => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.src = src;
    img.onload = function () {
      resolve(img);
    };
    img.onerror = function () {
      console.error("图片加载失败", src, img);
      reject(img);
    };
  });
};

Powered by 四川爱趣五科技有限公司.蜀ICP备18034069号.