@geekapps/silo-elements-nextjs 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { useState, useCallback, useRef } from 'react';
2
2
  import { useMultipartUpload, useBatchUpload } from '@geekapps/silo-nextjs';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import MediaInfoFactory from 'mediainfo.js';
4
5
 
5
6
  // src/utils/theme.ts
6
7
  var defaultTheme = {
@@ -158,7 +159,7 @@ var FORMATS = [
158
159
  { value: "png", label: "PNG", hint: "Sem perda" }
159
160
  ];
160
161
  function ImageOptions({ value, onChange, style }) {
161
- const fmt = value.format ?? "webp";
162
+ const fmt2 = value.format ?? "webp";
162
163
  const optimize = value.optimize ?? true;
163
164
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5", style, children: [
164
165
  /* @__PURE__ */ jsxs("div", { children: [
@@ -169,7 +170,7 @@ function ImageOptions({ value, onChange, style }) {
169
170
  type: "button",
170
171
  onClick: () => onChange({ ...value, format: f.value }),
171
172
  title: f.hint,
172
- style: fmt === f.value ? { border: "1px solid #6366f1", background: "#6366f1", color: "#fff", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" } : { border: "1px solid rgba(255,255,255,0.2)", background: "rgba(255,255,255,0.06)", color: "rgba(255,255,255,0.8)", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" },
173
+ style: fmt2 === f.value ? { border: "1px solid #6366f1", background: "#6366f1", color: "#fff", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" } : { border: "1px solid rgba(255,255,255,0.2)", background: "rgba(255,255,255,0.06)", color: "rgba(255,255,255,0.8)", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" },
173
174
  children: f.label
174
175
  },
175
176
  f.value
@@ -693,6 +694,125 @@ function VideoOptions({ value, onChange, style }) {
693
694
  )
694
695
  ] });
695
696
  }
697
+ var CODEC_MESSAGES = {
698
+ AV1: { title: "AV1 n\xE3o \xE9 suportado como entrada", detail: "O pipeline de transcodifica\xE7\xE3o n\xE3o aceita arquivos AV1. Converta para H.264 ou H.265 antes de enviar." },
699
+ VP9: { title: "VP9 n\xE3o \xE9 suportado como entrada", detail: "Arquivos WebM/VP9 n\xE3o s\xE3o aceitos. Converta para H.264 ou H.265 antes de enviar." },
700
+ VP8: { title: "VP8 n\xE3o \xE9 suportado como entrada", detail: "Arquivos WebM/VP8 n\xE3o s\xE3o aceitos. Converta para H.264 antes de enviar." },
701
+ "MPEG-1 Video": { title: "MPEG-1 n\xE3o \xE9 suportado", detail: "Formato muito antigo. Converta para H.264 antes de enviar." },
702
+ "MPEG-2 Video": { title: "MPEG-2 n\xE3o \xE9 suportado", detail: "Converta para H.264 antes de enviar." }
703
+ };
704
+ var CONTAINER_MESSAGES = {
705
+ FLV: { title: "Formato FLV n\xE3o suportado", detail: "Arquivos Flash Video n\xE3o s\xE3o aceitos. Converta para MP4 (H.264) antes de enviar." },
706
+ "Flash Video": { title: "Formato FLV n\xE3o suportado", detail: "Arquivos Flash Video n\xE3o s\xE3o aceitos. Converta para MP4 (H.264) antes de enviar." },
707
+ RealMedia: { title: "Formato RealMedia n\xE3o suportado", detail: "Converta para MP4 (H.264) antes de enviar." },
708
+ ASF: { title: "Formato ASF/WMV n\xE3o suportado", detail: "Converta para MP4 (H.264) antes de enviar." }
709
+ };
710
+ function issueMessage(issue) {
711
+ if (issue.code === "unsupported_codec") {
712
+ return CODEC_MESSAGES[issue.codec] ?? { title: `Codec ${issue.codec} n\xE3o suportado`, detail: "Converta para H.264 ou H.265 antes de enviar." };
713
+ }
714
+ const key = Object.keys(CONTAINER_MESSAGES).find((k) => issue.container.includes(k));
715
+ return (key ? CONTAINER_MESSAGES[key] : null) ?? { title: `Container ${issue.container} n\xE3o suportado`, detail: "Converta para MP4 (H.264) antes de enviar." };
716
+ }
717
+ function fmt(n, unit) {
718
+ return `${n.toLocaleString("pt-BR")}${unit}`;
719
+ }
720
+ function fmtDuration(secs) {
721
+ const h = Math.floor(secs / 3600);
722
+ const m = Math.floor(secs % 3600 / 60);
723
+ const s = Math.floor(secs % 60);
724
+ if (h > 0) return `${h}h ${m}m ${s}s`;
725
+ if (m > 0) return `${m}m ${s}s`;
726
+ return `${s}s`;
727
+ }
728
+ function VideoCompatAlert({ info, issues, analyzing }) {
729
+ if (analyzing) {
730
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", background: "rgba(99,102,241,0.06)", border: "1px solid rgba(99,102,241,0.18)", borderRadius: 10 }, children: [
731
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14 }, children: "\u{1F50D}" }),
732
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "rgba(165,180,252,0.9)" }, children: "Analisando arquivo\u2026" })
733
+ ] });
734
+ }
735
+ const v = info.video;
736
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
737
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: [
738
+ v && /* @__PURE__ */ jsxs(Fragment, { children: [
739
+ /* @__PURE__ */ jsx(Chip, { label: v.codec || "?", color: "neutral" }),
740
+ v.width > 0 && v.height > 0 && /* @__PURE__ */ jsx(Chip, { label: `${v.width}\xD7${v.height}`, color: "neutral" }),
741
+ v.duration > 0 && /* @__PURE__ */ jsx(Chip, { label: fmtDuration(v.duration), color: "neutral" }),
742
+ v.frameRate > 0 && /* @__PURE__ */ jsx(Chip, { label: fmt(Math.round(v.frameRate), " fps"), color: "neutral" }),
743
+ v.hdr && /* @__PURE__ */ jsx(Chip, { label: "HDR", color: "purple" })
744
+ ] }),
745
+ info.audio && /* @__PURE__ */ jsx(Chip, { label: info.audio.codec || "sem \xE1udio", color: "neutral" }),
746
+ /* @__PURE__ */ jsx(Chip, { label: formatBytes(info.fileSize), color: "neutral" })
747
+ ] }),
748
+ issues.map((issue, i) => {
749
+ const msg = issueMessage(issue);
750
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, padding: "10px 14px", background: "rgba(239,68,68,0.07)", border: "1px solid rgba(239,68,68,0.22)", borderRadius: 10 }, children: [
751
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14, flexShrink: 0 }, children: "\u{1F6AB}" }),
752
+ /* @__PURE__ */ jsxs("div", { children: [
753
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 700, color: "#f87171", marginBottom: 2 }, children: msg.title }),
754
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "rgba(255,255,255,0.5)", lineHeight: 1.55 }, children: msg.detail })
755
+ ] })
756
+ ] }, i);
757
+ })
758
+ ] });
759
+ }
760
+ function Chip({ label, color }) {
761
+ const s = color === "purple" ? { background: "rgba(168,85,247,0.12)", border: "1px solid rgba(168,85,247,0.25)", color: "#c084fc" } : { background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "rgba(255,255,255,0.6)" };
762
+ return /* @__PURE__ */ jsx("span", { style: { ...s, fontSize: 11, fontWeight: 600, borderRadius: 6, padding: "2px 8px", display: "inline-block" }, children: label });
763
+ }
764
+ var UNSUPPORTED_CODECS = ["AV1", "VP8", "VP9", "MPEG-1 Video", "MPEG-2 Video", "Theora", "RealVideo"];
765
+ var UNSUPPORTED_CONTAINERS = ["FLV", "Flash Video", "RealMedia", "ASF"];
766
+ function getCompatIssues(info) {
767
+ const issues = [];
768
+ if (info.video && UNSUPPORTED_CODECS.includes(info.video.codec)) {
769
+ issues.push({ code: "unsupported_codec", codec: info.video.codec });
770
+ }
771
+ if (UNSUPPORTED_CONTAINERS.some((c) => info.container.includes(c))) {
772
+ issues.push({ code: "unsupported_container", container: info.container });
773
+ }
774
+ return issues;
775
+ }
776
+ async function analyzeVideoFile(file) {
777
+ const wasmUrl = `https://cdn.jsdelivr.net/npm/mediainfo.js@0.3.7/dist/MediaInfoModule.wasm`;
778
+ const mediainfo = await MediaInfoFactory({ format: "object", locateFile: () => wasmUrl });
779
+ const getSize = () => file.size;
780
+ const readChunk = (chunkSize, offset) => new Promise((resolve, reject) => {
781
+ const reader = new FileReader();
782
+ reader.onload = (e) => resolve(new Uint8Array(e.target.result));
783
+ reader.onerror = reject;
784
+ reader.readAsArrayBuffer(file.slice(offset, offset + chunkSize));
785
+ });
786
+ const result = await mediainfo.analyzeData(getSize, readChunk);
787
+ mediainfo.close();
788
+ const tracks = result.media?.track ?? [];
789
+ const general = tracks.find((t) => t["@type"] === "General");
790
+ const video = tracks.find((t) => t["@type"] === "Video");
791
+ const audio = tracks.find((t) => t["@type"] === "Audio");
792
+ const str = (v) => typeof v === "string" ? v : "";
793
+ const num = (v) => parseFloat(str(v)) || 0;
794
+ const hdrFormats = str(video?.["HDR_Format"] ?? video?.["transfer_characteristics"]);
795
+ const isHdr = hdrFormats.length > 0 && !hdrFormats.toLowerCase().includes("bt.709") && !hdrFormats.toLowerCase().includes("sdr");
796
+ return {
797
+ container: str(general?.["Format"]) || file.type || "Unknown",
798
+ fileSize: file.size,
799
+ video: video ? {
800
+ codec: str(video["Format"]),
801
+ width: num(video["Width"]),
802
+ height: num(video["Height"]),
803
+ duration: num(video["Duration"]) / 1e3,
804
+ bitrate: num(video["BitRate"]),
805
+ frameRate: num(video["FrameRate"]),
806
+ hdr: isHdr,
807
+ colorSpace: str(video["colour_primaries"]) || str(video["ColorSpace"])
808
+ } : null,
809
+ audio: audio ? {
810
+ codec: str(audio["Format"]),
811
+ channels: num(audio["Channels"]),
812
+ sampleRate: num(audio["SamplingRate"])
813
+ } : null
814
+ };
815
+ }
696
816
  var DEFAULT_VIDEO_OPTS = {
697
817
  thumbnails: true,
698
818
  storyboard: false,
@@ -737,6 +857,9 @@ function VideoUploader({
737
857
  const [preview, setPreview] = useState(null);
738
858
  const [videoOpts, setVideoOpts] = useState(() => createInitialVideoOpts(video));
739
859
  const [stagedFile, setStagedFile] = useState(null);
860
+ const [videoInfo, setVideoInfo] = useState(null);
861
+ const [videoIssues, setVideoIssues] = useState([]);
862
+ const [analyzing, setAnalyzing] = useState(false);
740
863
  const t = resolveTheme(theme);
741
864
  const vars = themeToVars(t);
742
865
  const doUpload = useCallback(async (file, opts) => {
@@ -753,8 +876,19 @@ function VideoUploader({
753
876
  if (!file) return;
754
877
  if (showVideoOptions) {
755
878
  setVideoOpts(createInitialVideoOpts(video));
879
+ setVideoInfo(null);
880
+ setVideoIssues([]);
756
881
  setStagedFile(file);
757
882
  if (showPreview) setPreview(URL.createObjectURL(file));
883
+ setAnalyzing(true);
884
+ try {
885
+ const info = await analyzeVideoFile(file);
886
+ setVideoInfo(info);
887
+ setVideoIssues(getCompatIssues(info));
888
+ } catch {
889
+ } finally {
890
+ setAnalyzing(false);
891
+ }
758
892
  } else {
759
893
  await doUpload(file, video ?? videoOpts);
760
894
  }
@@ -797,7 +931,11 @@ function VideoUploader({
797
931
  ),
798
932
  showVideoOptions && stagedFile && !isUploading && state.status !== "done" && /* @__PURE__ */ jsxs(Fragment, { children: [
799
933
  preview && /* @__PURE__ */ jsx("div", { className: "border border-slate-200 rounded-xl overflow-hidden bg-black", children: /* @__PURE__ */ jsx("video", { src: preview, className: "w-full max-h-[200px] block", muted: true, playsInline: true, controls: true }) }),
800
- /* @__PURE__ */ jsxs("div", { className: "border border-slate-200 rounded-xl overflow-hidden", children: [
934
+ (analyzing || videoInfo) && /* @__PURE__ */ jsxs("div", { className: "border border-slate-200 rounded-xl overflow-hidden", children: [
935
+ /* @__PURE__ */ jsx("div", { className: "px-3.5 py-2 bg-slate-100 text-[12px] font-bold text-slate-500 tracking-[0.04em]", children: "\u{1F4CB} Informa\xE7\xF5es do arquivo" }),
936
+ /* @__PURE__ */ jsx("div", { className: "p-3.5", children: /* @__PURE__ */ jsx(VideoCompatAlert, { info: videoInfo, issues: videoIssues, analyzing }) })
937
+ ] }),
938
+ videoIssues.length === 0 && /* @__PURE__ */ jsxs("div", { className: "border border-slate-200 rounded-xl overflow-hidden", children: [
801
939
  /* @__PURE__ */ jsx("div", { className: "px-3.5 py-2 bg-slate-100 text-[12px] font-bold text-slate-500 tracking-[0.04em]", children: "\u{1F3AC} Configura\xE7\xF5es de v\xEDdeo" }),
802
940
  /* @__PURE__ */ jsx(VideoOptions, { value: videoOpts, onChange: setVideoOpts, style: { padding: "12px 14px" } })
803
941
  ] }),
@@ -809,6 +947,8 @@ function VideoUploader({
809
947
  onClick: () => {
810
948
  setStagedFile(null);
811
949
  setPreview(null);
950
+ setVideoInfo(null);
951
+ setVideoIssues([]);
812
952
  setVideoOpts(createInitialVideoOpts(video));
813
953
  },
814
954
  children: "Cancelar"
@@ -817,10 +957,13 @@ function VideoUploader({
817
957
  /* @__PURE__ */ jsx(
818
958
  "button",
819
959
  {
820
- className: "flex-1 inline-flex items-center justify-center gap-1.5 text-sm font-bold py-2.5 px-4 rounded-xl border-transparent bg-indigo-500 text-white cursor-pointer hover:opacity-90",
960
+ disabled: videoIssues.length > 0 || analyzing,
961
+ className: "flex-1 inline-flex items-center justify-center gap-1.5 text-sm font-bold py-2.5 px-4 rounded-xl border-transparent bg-indigo-500 text-white cursor-pointer hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed",
821
962
  onClick: () => {
822
963
  const f = stagedFile;
823
964
  setStagedFile(null);
965
+ setVideoInfo(null);
966
+ setVideoIssues([]);
824
967
  void doUpload(f, videoOpts);
825
968
  setVideoOpts(createInitialVideoOpts(video));
826
969
  },