@utilix-tech/sdk 0.27.0 → 0.28.0
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.
- package/CHANGELOG.md +3 -0
- package/README.md +12 -5
- package/dist/{chunk-Q3M23WNJ.js → chunk-47DTCCQJ.js} +250 -1
- package/dist/index.cjs +249 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{media-CN17a3MM.d.cts → media-cuxfzXxw.d.cts} +24 -2
- package/dist/{media-CN17a3MM.d.ts → media-cuxfzXxw.d.ts} +24 -2
- package/dist/tools/media.cjs +249 -0
- package/dist/tools/media.d.cts +1 -1
- package/dist/tools/media.d.ts +1 -1
- package/dist/tools/media.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
|
|
4
4
|
|
|
5
|
+
## 0.28.0
|
|
6
|
+
- Add `readVideoInfo` (mp4-webm-video-info): duration, resolution, and video/audio codec identifiers from an MP4 (ISO BMFF moov box) or WebM (EBML/Matroska Segment) container header, with no frame decoding or transcoding.
|
|
7
|
+
|
|
5
8
|
## 0.27.0
|
|
6
9
|
- Add `calculateRuleOf72` (rule-of-72-calculator): estimate years to double an investment from a rate, or the rate needed to double in a given number of years, alongside the exact logarithmic result.
|
|
7
10
|
- Add `readFlacInfo` (flac-metadata-reader): sample rate, channels, bit depth, total samples, and duration from a FLAC file's STREAMINFO block, plus Vorbis comment tags when present.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/sdk
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**579 developer utility functions for Node.js: runs locally, no API key required.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@utilix-tech/sdk)
|
|
6
6
|
[](https://www.npmjs.com/package/@utilix-tech/sdk)
|
|
@@ -561,10 +561,10 @@ analyzeString("café"); // { length: 4, codePoints: [...], ... }
|
|
|
561
561
|
|
|
562
562
|
---
|
|
563
563
|
|
|
564
|
-
### `/media`: Image Header Parsing, EXIF, PDF, WAV, ID3, ICO &
|
|
564
|
+
### `/media`: Image Header Parsing, EXIF, PDF, WAV, ID3, ICO, FLAC & Video Metadata
|
|
565
565
|
|
|
566
566
|
```ts
|
|
567
|
-
import { readImageInfo, readExifData, readPdfMetadata, readWavInfo, readId3Tags, readIcoInfo, readFlacInfo } from "@utilix-tech/sdk/media";
|
|
567
|
+
import { readImageInfo, readExifData, readPdfMetadata, readWavInfo, readId3Tags, readIcoInfo, readFlacInfo, readVideoInfo } from "@utilix-tech/sdk/media";
|
|
568
568
|
|
|
569
569
|
// Reads format, dimensions, bit depth, and alpha channel straight from
|
|
570
570
|
// file bytes: no decoding, no canvas, no image library.
|
|
@@ -609,9 +609,16 @@ readIcoInfo(new Uint8Array(icoBytes));
|
|
|
609
609
|
const flacBytes = await fs.promises.readFile("track.flac");
|
|
610
610
|
readFlacInfo(new Uint8Array(flacBytes));
|
|
611
611
|
// { sampleRate: 44100, channels: 2, bitsPerSample: 16, durationSeconds: 214.7, artist: "Artist Name", ... }
|
|
612
|
+
|
|
613
|
+
// Reads duration, resolution, and video/audio codec identifiers from an
|
|
614
|
+
// MP4 (ISO BMFF moov box) or WebM (EBML/Matroska Segment) container
|
|
615
|
+
// header: no frame decoding, demuxing, or transcoding.
|
|
616
|
+
const videoBytes = await fs.promises.readFile("clip.mp4");
|
|
617
|
+
readVideoInfo(new Uint8Array(videoBytes));
|
|
618
|
+
// { format: "mp4", durationSeconds: 12.5, width: 1920, height: 1080, videoCodec: "avc1", audioCodec: "mp4a", ... }
|
|
612
619
|
```
|
|
613
620
|
|
|
614
|
-
`readImageInfo` supports PNG, JPEG, GIF, WebP, and BMP. `readExifData` supports JPEG only. `readWavInfo` supports the canonical RIFF/WAVE container only. `readId3Tags` decodes ID3v2.3/2.4 and ID3v1/1.1 only (ID3v2.2 is detected but not decoded). `readIcoInfo` reads .ico icon files only and rejects .cur cursor files. `readFlacInfo` reads the native FLAC container only, not FLAC-in-Ogg.
|
|
621
|
+
`readImageInfo` supports PNG, JPEG, GIF, WebP, and BMP. `readExifData` supports JPEG only. `readWavInfo` supports the canonical RIFF/WAVE container only. `readId3Tags` decodes ID3v2.3/2.4 and ID3v1/1.1 only (ID3v2.2 is detected but not decoded). `readIcoInfo` reads .ico icon files only and rejects .cur cursor files. `readFlacInfo` reads the native FLAC container only, not FLAC-in-Ogg. `readVideoInfo` supports MP4 (ISO BMFF) and WebM (EBML/Matroska) containers only.
|
|
615
622
|
|
|
616
623
|
---
|
|
617
624
|
|
|
@@ -634,7 +641,7 @@ readFlacInfo(new Uint8Array(flacBytes));
|
|
|
634
641
|
| `@utilix-tech/sdk/css` | CSS gradient, box-shadow, border-radius, keyframe animation generators |
|
|
635
642
|
| `@utilix-tech/sdk/misc` | SVG optimize/sanitize, file size format, Unicode inspector |
|
|
636
643
|
| `@utilix-tech/sdk/ai_agent` | Token estimate/trim, chunk text, extract URLs/JSON/keywords, sanitize HTML, flatten/merge JSON, dedupe lines, validate schema, PII/secret/injection detect, vector similarity |
|
|
637
|
-
| `@utilix-tech/sdk/media` | Image format & dimension reading from raw bytes (PNG, JPEG, GIF, WebP, BMP); PDF metadata reading; WAV audio info; ID3 tags; ICO favicon inspection; FLAC metadata reading |
|
|
644
|
+
| `@utilix-tech/sdk/media` | Image format & dimension reading from raw bytes (PNG, JPEG, GIF, WebP, BMP); PDF metadata reading; WAV audio info; ID3 tags; ICO favicon inspection; FLAC metadata reading; MP4/WebM video info |
|
|
638
645
|
|
|
639
646
|
---
|
|
640
647
|
|
|
@@ -9,6 +9,7 @@ __export(media_exports, {
|
|
|
9
9
|
readId3Tags: () => readId3Tags,
|
|
10
10
|
readImageInfo: () => readImageInfo,
|
|
11
11
|
readPdfMetadata: () => readPdfMetadata,
|
|
12
|
+
readVideoInfo: () => readVideoInfo,
|
|
12
13
|
readWavInfo: () => readWavInfo
|
|
13
14
|
});
|
|
14
15
|
function readU32BE(bytes, offset) {
|
|
@@ -860,5 +861,253 @@ function readFlacInfo(bytes) {
|
|
|
860
861
|
}
|
|
861
862
|
return result;
|
|
862
863
|
}
|
|
864
|
+
function readU64BE(bytes, offset) {
|
|
865
|
+
return readU32BE(bytes, offset) * 4294967296 + readU32BE(bytes, offset + 4);
|
|
866
|
+
}
|
|
867
|
+
function readMp4Boxes(bytes, start, end) {
|
|
868
|
+
const boxes = [];
|
|
869
|
+
let offset = start;
|
|
870
|
+
while (offset + 8 <= end) {
|
|
871
|
+
let size = readU32BE(bytes, offset);
|
|
872
|
+
const type = readAscii4(bytes, offset + 4);
|
|
873
|
+
let headerSize = 8;
|
|
874
|
+
if (size === 1) {
|
|
875
|
+
if (offset + 16 > end) break;
|
|
876
|
+
size = readU64BE(bytes, offset + 8);
|
|
877
|
+
headerSize = 16;
|
|
878
|
+
} else if (size === 0) {
|
|
879
|
+
size = end - offset;
|
|
880
|
+
}
|
|
881
|
+
if (size < headerSize || offset + size > end) break;
|
|
882
|
+
boxes.push({ type, start: offset + headerSize, end: offset + size });
|
|
883
|
+
offset += size;
|
|
884
|
+
}
|
|
885
|
+
return boxes;
|
|
886
|
+
}
|
|
887
|
+
function findMp4Box(boxes, type) {
|
|
888
|
+
return boxes.find((b) => b.type === type);
|
|
889
|
+
}
|
|
890
|
+
function parseMp4Mvhd(bytes, box) {
|
|
891
|
+
const o = box.start;
|
|
892
|
+
if (o >= box.end) return null;
|
|
893
|
+
const version = bytes[o];
|
|
894
|
+
if (version === 1) {
|
|
895
|
+
if (o + 32 > box.end) return null;
|
|
896
|
+
return { timescale: readU32BE(bytes, o + 20), duration: readU64BE(bytes, o + 24) };
|
|
897
|
+
}
|
|
898
|
+
if (o + 20 > box.end) return null;
|
|
899
|
+
return { timescale: readU32BE(bytes, o + 12), duration: readU32BE(bytes, o + 16) };
|
|
900
|
+
}
|
|
901
|
+
function parseMp4Tkhd(bytes, box) {
|
|
902
|
+
const o = box.start;
|
|
903
|
+
if (o >= box.end) return null;
|
|
904
|
+
const version = bytes[o];
|
|
905
|
+
const fixedFieldsSize = version === 1 ? 4 + 8 + 8 + 4 + 4 + 8 : 4 + 4 + 4 + 4 + 4 + 4;
|
|
906
|
+
const afterDuration = o + fixedFieldsSize;
|
|
907
|
+
const matrixStart = afterDuration + 8 + 2 + 2 + 2 + 2;
|
|
908
|
+
const widthOffset = matrixStart + 36;
|
|
909
|
+
if (widthOffset + 8 > box.end) return null;
|
|
910
|
+
return { width: readU32BE(bytes, widthOffset) / 65536, height: readU32BE(bytes, widthOffset + 4) / 65536 };
|
|
911
|
+
}
|
|
912
|
+
function parseMp4Hdlr(bytes, box) {
|
|
913
|
+
const o = box.start;
|
|
914
|
+
if (o + 12 > box.end) return null;
|
|
915
|
+
return readAscii4(bytes, o + 8);
|
|
916
|
+
}
|
|
917
|
+
function parseMp4StsdCodec(bytes, box) {
|
|
918
|
+
const o = box.start;
|
|
919
|
+
if (o + 8 > box.end) return null;
|
|
920
|
+
const entryCount = readU32BE(bytes, o + 4);
|
|
921
|
+
if (entryCount < 1) return null;
|
|
922
|
+
const entryStart = o + 8;
|
|
923
|
+
if (entryStart + 8 > box.end) return null;
|
|
924
|
+
return readAscii4(bytes, entryStart + 4);
|
|
925
|
+
}
|
|
926
|
+
function parseMp4StsdVideoDims(bytes, box) {
|
|
927
|
+
const o = box.start;
|
|
928
|
+
if (o + 8 > box.end) return null;
|
|
929
|
+
const entryCount = readU32BE(bytes, o + 4);
|
|
930
|
+
if (entryCount < 1) return null;
|
|
931
|
+
const entryStart = o + 8;
|
|
932
|
+
if (entryStart + 36 > box.end) return null;
|
|
933
|
+
return {
|
|
934
|
+
width: bytes[entryStart + 32] << 8 | bytes[entryStart + 33],
|
|
935
|
+
height: bytes[entryStart + 34] << 8 | bytes[entryStart + 35]
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function parseMp4(bytes) {
|
|
939
|
+
const topBoxes = readMp4Boxes(bytes, 0, bytes.length);
|
|
940
|
+
const ftyp = findMp4Box(topBoxes, "ftyp");
|
|
941
|
+
const moov = findMp4Box(topBoxes, "moov");
|
|
942
|
+
if (!moov) return { error: "No moov box found (not a valid or fully-downloaded MP4 file)" };
|
|
943
|
+
const result = { format: "mp4" };
|
|
944
|
+
if (ftyp && ftyp.start + 4 <= ftyp.end) result.majorBrand = readAscii4(bytes, ftyp.start);
|
|
945
|
+
const moovBoxes = readMp4Boxes(bytes, moov.start, moov.end);
|
|
946
|
+
const mvhd = findMp4Box(moovBoxes, "mvhd");
|
|
947
|
+
if (mvhd) {
|
|
948
|
+
const info = parseMp4Mvhd(bytes, mvhd);
|
|
949
|
+
if (info && info.timescale > 0) result.durationSeconds = Math.round(info.duration / info.timescale * 1e3) / 1e3;
|
|
950
|
+
}
|
|
951
|
+
for (const trak of moovBoxes.filter((b) => b.type === "trak")) {
|
|
952
|
+
const trakBoxes = readMp4Boxes(bytes, trak.start, trak.end);
|
|
953
|
+
const mdia = findMp4Box(trakBoxes, "mdia");
|
|
954
|
+
if (!mdia) continue;
|
|
955
|
+
const mdiaBoxes = readMp4Boxes(bytes, mdia.start, mdia.end);
|
|
956
|
+
const hdlr = findMp4Box(mdiaBoxes, "hdlr");
|
|
957
|
+
const handlerType = hdlr ? parseMp4Hdlr(bytes, hdlr) : null;
|
|
958
|
+
const minf = findMp4Box(mdiaBoxes, "minf");
|
|
959
|
+
if (!minf) continue;
|
|
960
|
+
const stbl = findMp4Box(readMp4Boxes(bytes, minf.start, minf.end), "stbl");
|
|
961
|
+
if (!stbl) continue;
|
|
962
|
+
const stsd = findMp4Box(readMp4Boxes(bytes, stbl.start, stbl.end), "stsd");
|
|
963
|
+
const codec = stsd ? parseMp4StsdCodec(bytes, stsd) : null;
|
|
964
|
+
if (handlerType === "vide") {
|
|
965
|
+
if (codec) result.videoCodec = codec;
|
|
966
|
+
const tkhd = findMp4Box(trakBoxes, "tkhd");
|
|
967
|
+
const dims = tkhd ? parseMp4Tkhd(bytes, tkhd) : null;
|
|
968
|
+
if (dims && dims.width > 0 && dims.height > 0) {
|
|
969
|
+
result.width = Math.round(dims.width);
|
|
970
|
+
result.height = Math.round(dims.height);
|
|
971
|
+
} else if (stsd) {
|
|
972
|
+
const pixelDims = parseMp4StsdVideoDims(bytes, stsd);
|
|
973
|
+
if (pixelDims && pixelDims.width > 0 && pixelDims.height > 0) {
|
|
974
|
+
result.width = pixelDims.width;
|
|
975
|
+
result.height = pixelDims.height;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
} else if (handlerType === "soun") {
|
|
979
|
+
if (codec) result.audioCodec = codec;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (result.durationSeconds === void 0 && !result.videoCodec && !result.audioCodec) {
|
|
983
|
+
return { error: "Could not find any usable track metadata in this MP4 file's moov box" };
|
|
984
|
+
}
|
|
985
|
+
return result;
|
|
986
|
+
}
|
|
987
|
+
function ebmlVintLength(firstByte) {
|
|
988
|
+
if (firstByte === 0) return 0;
|
|
989
|
+
let mask = 128;
|
|
990
|
+
let length = 1;
|
|
991
|
+
while (length <= 8 && (firstByte & mask) === 0) {
|
|
992
|
+
mask >>= 1;
|
|
993
|
+
length++;
|
|
994
|
+
}
|
|
995
|
+
return length;
|
|
996
|
+
}
|
|
997
|
+
function readEbmlId(bytes, offset) {
|
|
998
|
+
if (offset >= bytes.length) return null;
|
|
999
|
+
const length = ebmlVintLength(bytes[offset]);
|
|
1000
|
+
if (length === 0 || offset + length > bytes.length) return null;
|
|
1001
|
+
let id = 0;
|
|
1002
|
+
for (let i = 0; i < length; i++) id = id * 256 + bytes[offset + i];
|
|
1003
|
+
return { id, length };
|
|
1004
|
+
}
|
|
1005
|
+
function readEbmlSize(bytes, offset) {
|
|
1006
|
+
if (offset >= bytes.length) return null;
|
|
1007
|
+
const length = ebmlVintLength(bytes[offset]);
|
|
1008
|
+
if (length === 0 || offset + length > bytes.length) return null;
|
|
1009
|
+
const mask = 255 >> length;
|
|
1010
|
+
let value = bytes[offset] & mask;
|
|
1011
|
+
let allOnes = value === mask;
|
|
1012
|
+
for (let i = 1; i < length; i++) {
|
|
1013
|
+
value = value * 256 + bytes[offset + i];
|
|
1014
|
+
if (bytes[offset + i] !== 255) allOnes = false;
|
|
1015
|
+
}
|
|
1016
|
+
return { value, length, unknown: allOnes };
|
|
1017
|
+
}
|
|
1018
|
+
function readEbmlUint(bytes, offset, length) {
|
|
1019
|
+
let value = 0;
|
|
1020
|
+
for (let i = 0; i < length; i++) value = value * 256 + bytes[offset + i];
|
|
1021
|
+
return value;
|
|
1022
|
+
}
|
|
1023
|
+
function readEbmlFloat(bytes, offset, length) {
|
|
1024
|
+
if (offset + length > bytes.length) return 0;
|
|
1025
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, length);
|
|
1026
|
+
if (length === 4) return view.getFloat32(0, false);
|
|
1027
|
+
if (length === 8) return view.getFloat64(0, false);
|
|
1028
|
+
return 0;
|
|
1029
|
+
}
|
|
1030
|
+
function ebmlChildren(bytes, start, end) {
|
|
1031
|
+
const children = [];
|
|
1032
|
+
let offset = start;
|
|
1033
|
+
while (offset < end) {
|
|
1034
|
+
const idInfo = readEbmlId(bytes, offset);
|
|
1035
|
+
if (!idInfo) break;
|
|
1036
|
+
const sizeInfo = readEbmlSize(bytes, offset + idInfo.length);
|
|
1037
|
+
if (!sizeInfo) break;
|
|
1038
|
+
const dataStart = offset + idInfo.length + sizeInfo.length;
|
|
1039
|
+
const dataEnd = sizeInfo.unknown ? end : Math.min(dataStart + sizeInfo.value, end);
|
|
1040
|
+
children.push({ id: idInfo.id, dataStart, dataEnd });
|
|
1041
|
+
offset = dataEnd;
|
|
1042
|
+
}
|
|
1043
|
+
return children;
|
|
1044
|
+
}
|
|
1045
|
+
var EBML_ID_SEGMENT = 408125543;
|
|
1046
|
+
var EBML_ID_INFO = 357149030;
|
|
1047
|
+
var EBML_ID_TIMECODE_SCALE = 2807729;
|
|
1048
|
+
var EBML_ID_DURATION = 17545;
|
|
1049
|
+
var EBML_ID_TRACKS = 374648427;
|
|
1050
|
+
var EBML_ID_TRACK_ENTRY = 174;
|
|
1051
|
+
var EBML_ID_TRACK_TYPE = 131;
|
|
1052
|
+
var EBML_ID_CODEC_ID = 134;
|
|
1053
|
+
var EBML_ID_VIDEO = 224;
|
|
1054
|
+
var EBML_ID_PIXEL_WIDTH = 176;
|
|
1055
|
+
var EBML_ID_PIXEL_HEIGHT = 186;
|
|
1056
|
+
var EBML_ID_CLUSTER = 524531317;
|
|
1057
|
+
function readVideoInfo(bytes) {
|
|
1058
|
+
if (bytes.length < 12) return { error: "File is too small to be a valid video file" };
|
|
1059
|
+
if (readAscii4(bytes, 4) === "ftyp") return parseMp4(bytes);
|
|
1060
|
+
if (bytes[0] === 26 && bytes[1] === 69 && bytes[2] === 223 && bytes[3] === 163) return parseWebm(bytes);
|
|
1061
|
+
return { error: 'Not a recognized MP4 or WebM file (missing an "ftyp" box or an EBML header signature)' };
|
|
1062
|
+
}
|
|
1063
|
+
function parseWebm(bytes) {
|
|
1064
|
+
const top = ebmlChildren(bytes, 0, bytes.length);
|
|
1065
|
+
const segment = top.find((c) => c.id === EBML_ID_SEGMENT);
|
|
1066
|
+
if (!segment) return { error: "No Segment element found (not a valid WebM/Matroska file)" };
|
|
1067
|
+
const result = { format: "webm" };
|
|
1068
|
+
let timecodeScale = 1e6;
|
|
1069
|
+
let rawDuration;
|
|
1070
|
+
for (const child of ebmlChildren(bytes, segment.dataStart, segment.dataEnd)) {
|
|
1071
|
+
if (child.id === EBML_ID_CLUSTER) break;
|
|
1072
|
+
if (child.id === EBML_ID_INFO) {
|
|
1073
|
+
for (const f of ebmlChildren(bytes, child.dataStart, child.dataEnd)) {
|
|
1074
|
+
if (f.id === EBML_ID_TIMECODE_SCALE) timecodeScale = readEbmlUint(bytes, f.dataStart, f.dataEnd - f.dataStart) || 1e6;
|
|
1075
|
+
else if (f.id === EBML_ID_DURATION) rawDuration = readEbmlFloat(bytes, f.dataStart, f.dataEnd - f.dataStart);
|
|
1076
|
+
}
|
|
1077
|
+
} else if (child.id === EBML_ID_TRACKS) {
|
|
1078
|
+
for (const entry of ebmlChildren(bytes, child.dataStart, child.dataEnd)) {
|
|
1079
|
+
if (entry.id !== EBML_ID_TRACK_ENTRY) continue;
|
|
1080
|
+
let trackType;
|
|
1081
|
+
let codecId;
|
|
1082
|
+
let pixelWidth;
|
|
1083
|
+
let pixelHeight;
|
|
1084
|
+
for (const f of ebmlChildren(bytes, entry.dataStart, entry.dataEnd)) {
|
|
1085
|
+
if (f.id === EBML_ID_TRACK_TYPE) trackType = readEbmlUint(bytes, f.dataStart, f.dataEnd - f.dataStart);
|
|
1086
|
+
else if (f.id === EBML_ID_CODEC_ID) codecId = Buffer.from(bytes.subarray(f.dataStart, f.dataEnd)).toString("ascii").replace(/\0+$/, "");
|
|
1087
|
+
else if (f.id === EBML_ID_VIDEO) {
|
|
1088
|
+
for (const v of ebmlChildren(bytes, f.dataStart, f.dataEnd)) {
|
|
1089
|
+
if (v.id === EBML_ID_PIXEL_WIDTH) pixelWidth = readEbmlUint(bytes, v.dataStart, v.dataEnd - v.dataStart);
|
|
1090
|
+
else if (v.id === EBML_ID_PIXEL_HEIGHT) pixelHeight = readEbmlUint(bytes, v.dataStart, v.dataEnd - v.dataStart);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (trackType === 1) {
|
|
1095
|
+
if (codecId) result.videoCodec = codecId;
|
|
1096
|
+
if (pixelWidth) result.width = pixelWidth;
|
|
1097
|
+
if (pixelHeight) result.height = pixelHeight;
|
|
1098
|
+
} else if (trackType === 2) {
|
|
1099
|
+
if (codecId) result.audioCodec = codecId;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (rawDuration !== void 0 && timecodeScale > 0) {
|
|
1105
|
+
result.durationSeconds = Math.round(rawDuration * timecodeScale / 1e9 * 1e3) / 1e3;
|
|
1106
|
+
}
|
|
1107
|
+
if (result.durationSeconds === void 0 && !result.videoCodec && !result.audioCodec) {
|
|
1108
|
+
return { error: "Could not find any usable metadata in this WebM file's Segment element" };
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
}
|
|
863
1112
|
|
|
864
|
-
export { media_exports, readExifData, readFlacInfo, readIcoInfo, readId3Tags, readImageInfo, readPdfMetadata, readWavInfo };
|
|
1113
|
+
export { media_exports, readExifData, readFlacInfo, readIcoInfo, readId3Tags, readImageInfo, readPdfMetadata, readVideoInfo, readWavInfo };
|
package/dist/index.cjs
CHANGED
|
@@ -19237,6 +19237,7 @@ __export(media_exports, {
|
|
|
19237
19237
|
readId3Tags: () => readId3Tags,
|
|
19238
19238
|
readImageInfo: () => readImageInfo,
|
|
19239
19239
|
readPdfMetadata: () => readPdfMetadata,
|
|
19240
|
+
readVideoInfo: () => readVideoInfo,
|
|
19240
19241
|
readWavInfo: () => readWavInfo
|
|
19241
19242
|
});
|
|
19242
19243
|
function readU32BE(bytes, offset) {
|
|
@@ -20088,6 +20089,254 @@ function readFlacInfo(bytes) {
|
|
|
20088
20089
|
}
|
|
20089
20090
|
return result;
|
|
20090
20091
|
}
|
|
20092
|
+
function readU64BE(bytes, offset) {
|
|
20093
|
+
return readU32BE(bytes, offset) * 4294967296 + readU32BE(bytes, offset + 4);
|
|
20094
|
+
}
|
|
20095
|
+
function readMp4Boxes(bytes, start, end) {
|
|
20096
|
+
const boxes = [];
|
|
20097
|
+
let offset = start;
|
|
20098
|
+
while (offset + 8 <= end) {
|
|
20099
|
+
let size = readU32BE(bytes, offset);
|
|
20100
|
+
const type = readAscii4(bytes, offset + 4);
|
|
20101
|
+
let headerSize = 8;
|
|
20102
|
+
if (size === 1) {
|
|
20103
|
+
if (offset + 16 > end) break;
|
|
20104
|
+
size = readU64BE(bytes, offset + 8);
|
|
20105
|
+
headerSize = 16;
|
|
20106
|
+
} else if (size === 0) {
|
|
20107
|
+
size = end - offset;
|
|
20108
|
+
}
|
|
20109
|
+
if (size < headerSize || offset + size > end) break;
|
|
20110
|
+
boxes.push({ type, start: offset + headerSize, end: offset + size });
|
|
20111
|
+
offset += size;
|
|
20112
|
+
}
|
|
20113
|
+
return boxes;
|
|
20114
|
+
}
|
|
20115
|
+
function findMp4Box(boxes, type) {
|
|
20116
|
+
return boxes.find((b) => b.type === type);
|
|
20117
|
+
}
|
|
20118
|
+
function parseMp4Mvhd(bytes, box) {
|
|
20119
|
+
const o = box.start;
|
|
20120
|
+
if (o >= box.end) return null;
|
|
20121
|
+
const version2 = bytes[o];
|
|
20122
|
+
if (version2 === 1) {
|
|
20123
|
+
if (o + 32 > box.end) return null;
|
|
20124
|
+
return { timescale: readU32BE(bytes, o + 20), duration: readU64BE(bytes, o + 24) };
|
|
20125
|
+
}
|
|
20126
|
+
if (o + 20 > box.end) return null;
|
|
20127
|
+
return { timescale: readU32BE(bytes, o + 12), duration: readU32BE(bytes, o + 16) };
|
|
20128
|
+
}
|
|
20129
|
+
function parseMp4Tkhd(bytes, box) {
|
|
20130
|
+
const o = box.start;
|
|
20131
|
+
if (o >= box.end) return null;
|
|
20132
|
+
const version2 = bytes[o];
|
|
20133
|
+
const fixedFieldsSize = version2 === 1 ? 4 + 8 + 8 + 4 + 4 + 8 : 4 + 4 + 4 + 4 + 4 + 4;
|
|
20134
|
+
const afterDuration = o + fixedFieldsSize;
|
|
20135
|
+
const matrixStart = afterDuration + 8 + 2 + 2 + 2 + 2;
|
|
20136
|
+
const widthOffset = matrixStart + 36;
|
|
20137
|
+
if (widthOffset + 8 > box.end) return null;
|
|
20138
|
+
return { width: readU32BE(bytes, widthOffset) / 65536, height: readU32BE(bytes, widthOffset + 4) / 65536 };
|
|
20139
|
+
}
|
|
20140
|
+
function parseMp4Hdlr(bytes, box) {
|
|
20141
|
+
const o = box.start;
|
|
20142
|
+
if (o + 12 > box.end) return null;
|
|
20143
|
+
return readAscii4(bytes, o + 8);
|
|
20144
|
+
}
|
|
20145
|
+
function parseMp4StsdCodec(bytes, box) {
|
|
20146
|
+
const o = box.start;
|
|
20147
|
+
if (o + 8 > box.end) return null;
|
|
20148
|
+
const entryCount = readU32BE(bytes, o + 4);
|
|
20149
|
+
if (entryCount < 1) return null;
|
|
20150
|
+
const entryStart = o + 8;
|
|
20151
|
+
if (entryStart + 8 > box.end) return null;
|
|
20152
|
+
return readAscii4(bytes, entryStart + 4);
|
|
20153
|
+
}
|
|
20154
|
+
function parseMp4StsdVideoDims(bytes, box) {
|
|
20155
|
+
const o = box.start;
|
|
20156
|
+
if (o + 8 > box.end) return null;
|
|
20157
|
+
const entryCount = readU32BE(bytes, o + 4);
|
|
20158
|
+
if (entryCount < 1) return null;
|
|
20159
|
+
const entryStart = o + 8;
|
|
20160
|
+
if (entryStart + 36 > box.end) return null;
|
|
20161
|
+
return {
|
|
20162
|
+
width: bytes[entryStart + 32] << 8 | bytes[entryStart + 33],
|
|
20163
|
+
height: bytes[entryStart + 34] << 8 | bytes[entryStart + 35]
|
|
20164
|
+
};
|
|
20165
|
+
}
|
|
20166
|
+
function parseMp4(bytes) {
|
|
20167
|
+
const topBoxes = readMp4Boxes(bytes, 0, bytes.length);
|
|
20168
|
+
const ftyp = findMp4Box(topBoxes, "ftyp");
|
|
20169
|
+
const moov = findMp4Box(topBoxes, "moov");
|
|
20170
|
+
if (!moov) return { error: "No moov box found (not a valid or fully-downloaded MP4 file)" };
|
|
20171
|
+
const result = { format: "mp4" };
|
|
20172
|
+
if (ftyp && ftyp.start + 4 <= ftyp.end) result.majorBrand = readAscii4(bytes, ftyp.start);
|
|
20173
|
+
const moovBoxes = readMp4Boxes(bytes, moov.start, moov.end);
|
|
20174
|
+
const mvhd = findMp4Box(moovBoxes, "mvhd");
|
|
20175
|
+
if (mvhd) {
|
|
20176
|
+
const info = parseMp4Mvhd(bytes, mvhd);
|
|
20177
|
+
if (info && info.timescale > 0) result.durationSeconds = Math.round(info.duration / info.timescale * 1e3) / 1e3;
|
|
20178
|
+
}
|
|
20179
|
+
for (const trak of moovBoxes.filter((b) => b.type === "trak")) {
|
|
20180
|
+
const trakBoxes = readMp4Boxes(bytes, trak.start, trak.end);
|
|
20181
|
+
const mdia = findMp4Box(trakBoxes, "mdia");
|
|
20182
|
+
if (!mdia) continue;
|
|
20183
|
+
const mdiaBoxes = readMp4Boxes(bytes, mdia.start, mdia.end);
|
|
20184
|
+
const hdlr = findMp4Box(mdiaBoxes, "hdlr");
|
|
20185
|
+
const handlerType = hdlr ? parseMp4Hdlr(bytes, hdlr) : null;
|
|
20186
|
+
const minf = findMp4Box(mdiaBoxes, "minf");
|
|
20187
|
+
if (!minf) continue;
|
|
20188
|
+
const stbl = findMp4Box(readMp4Boxes(bytes, minf.start, minf.end), "stbl");
|
|
20189
|
+
if (!stbl) continue;
|
|
20190
|
+
const stsd = findMp4Box(readMp4Boxes(bytes, stbl.start, stbl.end), "stsd");
|
|
20191
|
+
const codec = stsd ? parseMp4StsdCodec(bytes, stsd) : null;
|
|
20192
|
+
if (handlerType === "vide") {
|
|
20193
|
+
if (codec) result.videoCodec = codec;
|
|
20194
|
+
const tkhd = findMp4Box(trakBoxes, "tkhd");
|
|
20195
|
+
const dims = tkhd ? parseMp4Tkhd(bytes, tkhd) : null;
|
|
20196
|
+
if (dims && dims.width > 0 && dims.height > 0) {
|
|
20197
|
+
result.width = Math.round(dims.width);
|
|
20198
|
+
result.height = Math.round(dims.height);
|
|
20199
|
+
} else if (stsd) {
|
|
20200
|
+
const pixelDims = parseMp4StsdVideoDims(bytes, stsd);
|
|
20201
|
+
if (pixelDims && pixelDims.width > 0 && pixelDims.height > 0) {
|
|
20202
|
+
result.width = pixelDims.width;
|
|
20203
|
+
result.height = pixelDims.height;
|
|
20204
|
+
}
|
|
20205
|
+
}
|
|
20206
|
+
} else if (handlerType === "soun") {
|
|
20207
|
+
if (codec) result.audioCodec = codec;
|
|
20208
|
+
}
|
|
20209
|
+
}
|
|
20210
|
+
if (result.durationSeconds === void 0 && !result.videoCodec && !result.audioCodec) {
|
|
20211
|
+
return { error: "Could not find any usable track metadata in this MP4 file's moov box" };
|
|
20212
|
+
}
|
|
20213
|
+
return result;
|
|
20214
|
+
}
|
|
20215
|
+
function ebmlVintLength(firstByte) {
|
|
20216
|
+
if (firstByte === 0) return 0;
|
|
20217
|
+
let mask = 128;
|
|
20218
|
+
let length = 1;
|
|
20219
|
+
while (length <= 8 && (firstByte & mask) === 0) {
|
|
20220
|
+
mask >>= 1;
|
|
20221
|
+
length++;
|
|
20222
|
+
}
|
|
20223
|
+
return length;
|
|
20224
|
+
}
|
|
20225
|
+
function readEbmlId(bytes, offset) {
|
|
20226
|
+
if (offset >= bytes.length) return null;
|
|
20227
|
+
const length = ebmlVintLength(bytes[offset]);
|
|
20228
|
+
if (length === 0 || offset + length > bytes.length) return null;
|
|
20229
|
+
let id = 0;
|
|
20230
|
+
for (let i = 0; i < length; i++) id = id * 256 + bytes[offset + i];
|
|
20231
|
+
return { id, length };
|
|
20232
|
+
}
|
|
20233
|
+
function readEbmlSize(bytes, offset) {
|
|
20234
|
+
if (offset >= bytes.length) return null;
|
|
20235
|
+
const length = ebmlVintLength(bytes[offset]);
|
|
20236
|
+
if (length === 0 || offset + length > bytes.length) return null;
|
|
20237
|
+
const mask = 255 >> length;
|
|
20238
|
+
let value = bytes[offset] & mask;
|
|
20239
|
+
let allOnes = value === mask;
|
|
20240
|
+
for (let i = 1; i < length; i++) {
|
|
20241
|
+
value = value * 256 + bytes[offset + i];
|
|
20242
|
+
if (bytes[offset + i] !== 255) allOnes = false;
|
|
20243
|
+
}
|
|
20244
|
+
return { value, length, unknown: allOnes };
|
|
20245
|
+
}
|
|
20246
|
+
function readEbmlUint(bytes, offset, length) {
|
|
20247
|
+
let value = 0;
|
|
20248
|
+
for (let i = 0; i < length; i++) value = value * 256 + bytes[offset + i];
|
|
20249
|
+
return value;
|
|
20250
|
+
}
|
|
20251
|
+
function readEbmlFloat(bytes, offset, length) {
|
|
20252
|
+
if (offset + length > bytes.length) return 0;
|
|
20253
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, length);
|
|
20254
|
+
if (length === 4) return view.getFloat32(0, false);
|
|
20255
|
+
if (length === 8) return view.getFloat64(0, false);
|
|
20256
|
+
return 0;
|
|
20257
|
+
}
|
|
20258
|
+
function ebmlChildren(bytes, start, end) {
|
|
20259
|
+
const children = [];
|
|
20260
|
+
let offset = start;
|
|
20261
|
+
while (offset < end) {
|
|
20262
|
+
const idInfo = readEbmlId(bytes, offset);
|
|
20263
|
+
if (!idInfo) break;
|
|
20264
|
+
const sizeInfo = readEbmlSize(bytes, offset + idInfo.length);
|
|
20265
|
+
if (!sizeInfo) break;
|
|
20266
|
+
const dataStart = offset + idInfo.length + sizeInfo.length;
|
|
20267
|
+
const dataEnd = sizeInfo.unknown ? end : Math.min(dataStart + sizeInfo.value, end);
|
|
20268
|
+
children.push({ id: idInfo.id, dataStart, dataEnd });
|
|
20269
|
+
offset = dataEnd;
|
|
20270
|
+
}
|
|
20271
|
+
return children;
|
|
20272
|
+
}
|
|
20273
|
+
var EBML_ID_SEGMENT = 408125543;
|
|
20274
|
+
var EBML_ID_INFO = 357149030;
|
|
20275
|
+
var EBML_ID_TIMECODE_SCALE = 2807729;
|
|
20276
|
+
var EBML_ID_DURATION = 17545;
|
|
20277
|
+
var EBML_ID_TRACKS = 374648427;
|
|
20278
|
+
var EBML_ID_TRACK_ENTRY = 174;
|
|
20279
|
+
var EBML_ID_TRACK_TYPE = 131;
|
|
20280
|
+
var EBML_ID_CODEC_ID = 134;
|
|
20281
|
+
var EBML_ID_VIDEO = 224;
|
|
20282
|
+
var EBML_ID_PIXEL_WIDTH = 176;
|
|
20283
|
+
var EBML_ID_PIXEL_HEIGHT = 186;
|
|
20284
|
+
var EBML_ID_CLUSTER = 524531317;
|
|
20285
|
+
function readVideoInfo(bytes) {
|
|
20286
|
+
if (bytes.length < 12) return { error: "File is too small to be a valid video file" };
|
|
20287
|
+
if (readAscii4(bytes, 4) === "ftyp") return parseMp4(bytes);
|
|
20288
|
+
if (bytes[0] === 26 && bytes[1] === 69 && bytes[2] === 223 && bytes[3] === 163) return parseWebm(bytes);
|
|
20289
|
+
return { error: 'Not a recognized MP4 or WebM file (missing an "ftyp" box or an EBML header signature)' };
|
|
20290
|
+
}
|
|
20291
|
+
function parseWebm(bytes) {
|
|
20292
|
+
const top = ebmlChildren(bytes, 0, bytes.length);
|
|
20293
|
+
const segment = top.find((c) => c.id === EBML_ID_SEGMENT);
|
|
20294
|
+
if (!segment) return { error: "No Segment element found (not a valid WebM/Matroska file)" };
|
|
20295
|
+
const result = { format: "webm" };
|
|
20296
|
+
let timecodeScale = 1e6;
|
|
20297
|
+
let rawDuration;
|
|
20298
|
+
for (const child of ebmlChildren(bytes, segment.dataStart, segment.dataEnd)) {
|
|
20299
|
+
if (child.id === EBML_ID_CLUSTER) break;
|
|
20300
|
+
if (child.id === EBML_ID_INFO) {
|
|
20301
|
+
for (const f of ebmlChildren(bytes, child.dataStart, child.dataEnd)) {
|
|
20302
|
+
if (f.id === EBML_ID_TIMECODE_SCALE) timecodeScale = readEbmlUint(bytes, f.dataStart, f.dataEnd - f.dataStart) || 1e6;
|
|
20303
|
+
else if (f.id === EBML_ID_DURATION) rawDuration = readEbmlFloat(bytes, f.dataStart, f.dataEnd - f.dataStart);
|
|
20304
|
+
}
|
|
20305
|
+
} else if (child.id === EBML_ID_TRACKS) {
|
|
20306
|
+
for (const entry of ebmlChildren(bytes, child.dataStart, child.dataEnd)) {
|
|
20307
|
+
if (entry.id !== EBML_ID_TRACK_ENTRY) continue;
|
|
20308
|
+
let trackType;
|
|
20309
|
+
let codecId;
|
|
20310
|
+
let pixelWidth;
|
|
20311
|
+
let pixelHeight;
|
|
20312
|
+
for (const f of ebmlChildren(bytes, entry.dataStart, entry.dataEnd)) {
|
|
20313
|
+
if (f.id === EBML_ID_TRACK_TYPE) trackType = readEbmlUint(bytes, f.dataStart, f.dataEnd - f.dataStart);
|
|
20314
|
+
else if (f.id === EBML_ID_CODEC_ID) codecId = Buffer.from(bytes.subarray(f.dataStart, f.dataEnd)).toString("ascii").replace(/\0+$/, "");
|
|
20315
|
+
else if (f.id === EBML_ID_VIDEO) {
|
|
20316
|
+
for (const v of ebmlChildren(bytes, f.dataStart, f.dataEnd)) {
|
|
20317
|
+
if (v.id === EBML_ID_PIXEL_WIDTH) pixelWidth = readEbmlUint(bytes, v.dataStart, v.dataEnd - v.dataStart);
|
|
20318
|
+
else if (v.id === EBML_ID_PIXEL_HEIGHT) pixelHeight = readEbmlUint(bytes, v.dataStart, v.dataEnd - v.dataStart);
|
|
20319
|
+
}
|
|
20320
|
+
}
|
|
20321
|
+
}
|
|
20322
|
+
if (trackType === 1) {
|
|
20323
|
+
if (codecId) result.videoCodec = codecId;
|
|
20324
|
+
if (pixelWidth) result.width = pixelWidth;
|
|
20325
|
+
if (pixelHeight) result.height = pixelHeight;
|
|
20326
|
+
} else if (trackType === 2) {
|
|
20327
|
+
if (codecId) result.audioCodec = codecId;
|
|
20328
|
+
}
|
|
20329
|
+
}
|
|
20330
|
+
}
|
|
20331
|
+
}
|
|
20332
|
+
if (rawDuration !== void 0 && timecodeScale > 0) {
|
|
20333
|
+
result.durationSeconds = Math.round(rawDuration * timecodeScale / 1e9 * 1e3) / 1e3;
|
|
20334
|
+
}
|
|
20335
|
+
if (result.durationSeconds === void 0 && !result.videoCodec && !result.audioCodec) {
|
|
20336
|
+
return { error: "Could not find any usable metadata in this WebM file's Segment element" };
|
|
20337
|
+
}
|
|
20338
|
+
return result;
|
|
20339
|
+
}
|
|
20091
20340
|
|
|
20092
20341
|
// src/index.ts
|
|
20093
20342
|
var version = "0.1.0";
|
package/dist/index.d.cts
CHANGED
|
@@ -13,7 +13,7 @@ export { c as color } from './color-tPwZCr9H.cjs';
|
|
|
13
13
|
export { c as css } from './css-Cf7AMGM-.cjs';
|
|
14
14
|
export { m as misc } from './misc-CA3N198T.cjs';
|
|
15
15
|
export { a as ai_agent } from './ai_agent-B-c59NXD.cjs';
|
|
16
|
-
export { m as media } from './media-
|
|
16
|
+
export { m as media } from './media-cuxfzXxw.cjs';
|
|
17
17
|
|
|
18
18
|
declare const version = "0.1.0";
|
|
19
19
|
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { c as color } from './color-tPwZCr9H.js';
|
|
|
13
13
|
export { c as css } from './css-Cf7AMGM-.js';
|
|
14
14
|
export { m as misc } from './misc-CA3N198T.js';
|
|
15
15
|
export { a as ai_agent } from './ai_agent-B-c59NXD.js';
|
|
16
|
-
export { m as media } from './media-
|
|
16
|
+
export { m as media } from './media-cuxfzXxw.js';
|
|
17
17
|
|
|
18
18
|
declare const version = "0.1.0";
|
|
19
19
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { media_exports as media } from './chunk-
|
|
1
|
+
export { media_exports as media } from './chunk-47DTCCQJ.js';
|
|
2
2
|
export { units_exports as units } from './chunk-I63BCOAG.js';
|
|
3
3
|
export { network_exports as network } from './chunk-Y5OOP6WN.js';
|
|
4
4
|
export { api_exports as api } from './chunk-52ZDKSIX.js';
|
|
@@ -160,6 +160,25 @@ interface FlacInfoError {
|
|
|
160
160
|
* metadata-block headers. No audio frame decoding is performed.
|
|
161
161
|
*/
|
|
162
162
|
declare function readFlacInfo(bytes: Uint8Array): FlacInfo | FlacInfoError;
|
|
163
|
+
interface VideoInfo {
|
|
164
|
+
format: 'mp4' | 'webm';
|
|
165
|
+
durationSeconds?: number;
|
|
166
|
+
width?: number;
|
|
167
|
+
height?: number;
|
|
168
|
+
videoCodec?: string;
|
|
169
|
+
audioCodec?: string;
|
|
170
|
+
majorBrand?: string;
|
|
171
|
+
}
|
|
172
|
+
interface VideoInfoError {
|
|
173
|
+
error: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Read an MP4 (ISO BMFF) or WebM (EBML/Matroska) container's header boxes
|
|
177
|
+
* to report duration, resolution, and video/audio codec identifiers.
|
|
178
|
+
* No frame decoding, demuxing of sample data, or transcoding is performed;
|
|
179
|
+
* only the moov box (MP4) or Segment/Info/Tracks elements (WebM) are read.
|
|
180
|
+
*/
|
|
181
|
+
declare function readVideoInfo(bytes: Uint8Array): VideoInfo | VideoInfoError;
|
|
163
182
|
|
|
164
183
|
type media_ExifData = ExifData;
|
|
165
184
|
type media_ExifError = ExifError;
|
|
@@ -175,6 +194,8 @@ type media_ImageInfo = ImageInfo;
|
|
|
175
194
|
type media_ImageInfoError = ImageInfoError;
|
|
176
195
|
type media_PdfMetadata = PdfMetadata;
|
|
177
196
|
type media_PdfMetadataError = PdfMetadataError;
|
|
197
|
+
type media_VideoInfo = VideoInfo;
|
|
198
|
+
type media_VideoInfoError = VideoInfoError;
|
|
178
199
|
type media_WavInfo = WavInfo;
|
|
179
200
|
type media_WavInfoError = WavInfoError;
|
|
180
201
|
declare const media_readExifData: typeof readExifData;
|
|
@@ -183,9 +204,10 @@ declare const media_readIcoInfo: typeof readIcoInfo;
|
|
|
183
204
|
declare const media_readId3Tags: typeof readId3Tags;
|
|
184
205
|
declare const media_readImageInfo: typeof readImageInfo;
|
|
185
206
|
declare const media_readPdfMetadata: typeof readPdfMetadata;
|
|
207
|
+
declare const media_readVideoInfo: typeof readVideoInfo;
|
|
186
208
|
declare const media_readWavInfo: typeof readWavInfo;
|
|
187
209
|
declare namespace media {
|
|
188
|
-
export { type media_ExifData as ExifData, type media_ExifError as ExifError, type media_FlacInfo as FlacInfo, type media_FlacInfoError as FlacInfoError, type media_IcoImageEntry as IcoImageEntry, type media_IcoInfo as IcoInfo, type media_IcoInfoError as IcoInfoError, type media_Id3Tags as Id3Tags, type media_Id3TagsError as Id3TagsError, type media_ImageFormat as ImageFormat, type media_ImageInfo as ImageInfo, type media_ImageInfoError as ImageInfoError, type media_PdfMetadata as PdfMetadata, type media_PdfMetadataError as PdfMetadataError, type media_WavInfo as WavInfo, type media_WavInfoError as WavInfoError, media_readExifData as readExifData, media_readFlacInfo as readFlacInfo, media_readIcoInfo as readIcoInfo, media_readId3Tags as readId3Tags, media_readImageInfo as readImageInfo, media_readPdfMetadata as readPdfMetadata, media_readWavInfo as readWavInfo };
|
|
210
|
+
export { type media_ExifData as ExifData, type media_ExifError as ExifError, type media_FlacInfo as FlacInfo, type media_FlacInfoError as FlacInfoError, type media_IcoImageEntry as IcoImageEntry, type media_IcoInfo as IcoInfo, type media_IcoInfoError as IcoInfoError, type media_Id3Tags as Id3Tags, type media_Id3TagsError as Id3TagsError, type media_ImageFormat as ImageFormat, type media_ImageInfo as ImageInfo, type media_ImageInfoError as ImageInfoError, type media_PdfMetadata as PdfMetadata, type media_PdfMetadataError as PdfMetadataError, type media_VideoInfo as VideoInfo, type media_VideoInfoError as VideoInfoError, type media_WavInfo as WavInfo, type media_WavInfoError as WavInfoError, media_readExifData as readExifData, media_readFlacInfo as readFlacInfo, media_readIcoInfo as readIcoInfo, media_readId3Tags as readId3Tags, media_readImageInfo as readImageInfo, media_readPdfMetadata as readPdfMetadata, media_readVideoInfo as readVideoInfo, media_readWavInfo as readWavInfo };
|
|
189
211
|
}
|
|
190
212
|
|
|
191
|
-
export { type ExifData as E, type FlacInfo as F, type IcoImageEntry as I, type PdfMetadata as P, type WavInfo as W, type ExifError as a, type FlacInfoError as b, type IcoInfo as c, type IcoInfoError as d, type Id3Tags as e, type Id3TagsError as f, type ImageFormat as g, type ImageInfo as h, type ImageInfoError as i, type PdfMetadataError as j, type
|
|
213
|
+
export { type ExifData as E, type FlacInfo as F, type IcoImageEntry as I, type PdfMetadata as P, type VideoInfo as V, type WavInfo as W, type ExifError as a, type FlacInfoError as b, type IcoInfo as c, type IcoInfoError as d, type Id3Tags as e, type Id3TagsError as f, type ImageFormat as g, type ImageInfo as h, type ImageInfoError as i, type PdfMetadataError as j, type VideoInfoError as k, type WavInfoError as l, media as m, readFlacInfo as n, readIcoInfo as o, readId3Tags as p, readImageInfo as q, readExifData as r, readPdfMetadata as s, readVideoInfo as t, readWavInfo as u };
|
|
@@ -160,6 +160,25 @@ interface FlacInfoError {
|
|
|
160
160
|
* metadata-block headers. No audio frame decoding is performed.
|
|
161
161
|
*/
|
|
162
162
|
declare function readFlacInfo(bytes: Uint8Array): FlacInfo | FlacInfoError;
|
|
163
|
+
interface VideoInfo {
|
|
164
|
+
format: 'mp4' | 'webm';
|
|
165
|
+
durationSeconds?: number;
|
|
166
|
+
width?: number;
|
|
167
|
+
height?: number;
|
|
168
|
+
videoCodec?: string;
|
|
169
|
+
audioCodec?: string;
|
|
170
|
+
majorBrand?: string;
|
|
171
|
+
}
|
|
172
|
+
interface VideoInfoError {
|
|
173
|
+
error: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Read an MP4 (ISO BMFF) or WebM (EBML/Matroska) container's header boxes
|
|
177
|
+
* to report duration, resolution, and video/audio codec identifiers.
|
|
178
|
+
* No frame decoding, demuxing of sample data, or transcoding is performed;
|
|
179
|
+
* only the moov box (MP4) or Segment/Info/Tracks elements (WebM) are read.
|
|
180
|
+
*/
|
|
181
|
+
declare function readVideoInfo(bytes: Uint8Array): VideoInfo | VideoInfoError;
|
|
163
182
|
|
|
164
183
|
type media_ExifData = ExifData;
|
|
165
184
|
type media_ExifError = ExifError;
|
|
@@ -175,6 +194,8 @@ type media_ImageInfo = ImageInfo;
|
|
|
175
194
|
type media_ImageInfoError = ImageInfoError;
|
|
176
195
|
type media_PdfMetadata = PdfMetadata;
|
|
177
196
|
type media_PdfMetadataError = PdfMetadataError;
|
|
197
|
+
type media_VideoInfo = VideoInfo;
|
|
198
|
+
type media_VideoInfoError = VideoInfoError;
|
|
178
199
|
type media_WavInfo = WavInfo;
|
|
179
200
|
type media_WavInfoError = WavInfoError;
|
|
180
201
|
declare const media_readExifData: typeof readExifData;
|
|
@@ -183,9 +204,10 @@ declare const media_readIcoInfo: typeof readIcoInfo;
|
|
|
183
204
|
declare const media_readId3Tags: typeof readId3Tags;
|
|
184
205
|
declare const media_readImageInfo: typeof readImageInfo;
|
|
185
206
|
declare const media_readPdfMetadata: typeof readPdfMetadata;
|
|
207
|
+
declare const media_readVideoInfo: typeof readVideoInfo;
|
|
186
208
|
declare const media_readWavInfo: typeof readWavInfo;
|
|
187
209
|
declare namespace media {
|
|
188
|
-
export { type media_ExifData as ExifData, type media_ExifError as ExifError, type media_FlacInfo as FlacInfo, type media_FlacInfoError as FlacInfoError, type media_IcoImageEntry as IcoImageEntry, type media_IcoInfo as IcoInfo, type media_IcoInfoError as IcoInfoError, type media_Id3Tags as Id3Tags, type media_Id3TagsError as Id3TagsError, type media_ImageFormat as ImageFormat, type media_ImageInfo as ImageInfo, type media_ImageInfoError as ImageInfoError, type media_PdfMetadata as PdfMetadata, type media_PdfMetadataError as PdfMetadataError, type media_WavInfo as WavInfo, type media_WavInfoError as WavInfoError, media_readExifData as readExifData, media_readFlacInfo as readFlacInfo, media_readIcoInfo as readIcoInfo, media_readId3Tags as readId3Tags, media_readImageInfo as readImageInfo, media_readPdfMetadata as readPdfMetadata, media_readWavInfo as readWavInfo };
|
|
210
|
+
export { type media_ExifData as ExifData, type media_ExifError as ExifError, type media_FlacInfo as FlacInfo, type media_FlacInfoError as FlacInfoError, type media_IcoImageEntry as IcoImageEntry, type media_IcoInfo as IcoInfo, type media_IcoInfoError as IcoInfoError, type media_Id3Tags as Id3Tags, type media_Id3TagsError as Id3TagsError, type media_ImageFormat as ImageFormat, type media_ImageInfo as ImageInfo, type media_ImageInfoError as ImageInfoError, type media_PdfMetadata as PdfMetadata, type media_PdfMetadataError as PdfMetadataError, type media_VideoInfo as VideoInfo, type media_VideoInfoError as VideoInfoError, type media_WavInfo as WavInfo, type media_WavInfoError as WavInfoError, media_readExifData as readExifData, media_readFlacInfo as readFlacInfo, media_readIcoInfo as readIcoInfo, media_readId3Tags as readId3Tags, media_readImageInfo as readImageInfo, media_readPdfMetadata as readPdfMetadata, media_readVideoInfo as readVideoInfo, media_readWavInfo as readWavInfo };
|
|
189
211
|
}
|
|
190
212
|
|
|
191
|
-
export { type ExifData as E, type FlacInfo as F, type IcoImageEntry as I, type PdfMetadata as P, type WavInfo as W, type ExifError as a, type FlacInfoError as b, type IcoInfo as c, type IcoInfoError as d, type Id3Tags as e, type Id3TagsError as f, type ImageFormat as g, type ImageInfo as h, type ImageInfoError as i, type PdfMetadataError as j, type
|
|
213
|
+
export { type ExifData as E, type FlacInfo as F, type IcoImageEntry as I, type PdfMetadata as P, type VideoInfo as V, type WavInfo as W, type ExifError as a, type FlacInfoError as b, type IcoInfo as c, type IcoInfoError as d, type Id3Tags as e, type Id3TagsError as f, type ImageFormat as g, type ImageInfo as h, type ImageInfoError as i, type PdfMetadataError as j, type VideoInfoError as k, type WavInfoError as l, media as m, readFlacInfo as n, readIcoInfo as o, readId3Tags as p, readImageInfo as q, readExifData as r, readPdfMetadata as s, readVideoInfo as t, readWavInfo as u };
|
package/dist/tools/media.cjs
CHANGED
|
@@ -850,6 +850,254 @@ function readFlacInfo(bytes) {
|
|
|
850
850
|
}
|
|
851
851
|
return result;
|
|
852
852
|
}
|
|
853
|
+
function readU64BE(bytes, offset) {
|
|
854
|
+
return readU32BE(bytes, offset) * 4294967296 + readU32BE(bytes, offset + 4);
|
|
855
|
+
}
|
|
856
|
+
function readMp4Boxes(bytes, start, end) {
|
|
857
|
+
const boxes = [];
|
|
858
|
+
let offset = start;
|
|
859
|
+
while (offset + 8 <= end) {
|
|
860
|
+
let size = readU32BE(bytes, offset);
|
|
861
|
+
const type = readAscii4(bytes, offset + 4);
|
|
862
|
+
let headerSize = 8;
|
|
863
|
+
if (size === 1) {
|
|
864
|
+
if (offset + 16 > end) break;
|
|
865
|
+
size = readU64BE(bytes, offset + 8);
|
|
866
|
+
headerSize = 16;
|
|
867
|
+
} else if (size === 0) {
|
|
868
|
+
size = end - offset;
|
|
869
|
+
}
|
|
870
|
+
if (size < headerSize || offset + size > end) break;
|
|
871
|
+
boxes.push({ type, start: offset + headerSize, end: offset + size });
|
|
872
|
+
offset += size;
|
|
873
|
+
}
|
|
874
|
+
return boxes;
|
|
875
|
+
}
|
|
876
|
+
function findMp4Box(boxes, type) {
|
|
877
|
+
return boxes.find((b) => b.type === type);
|
|
878
|
+
}
|
|
879
|
+
function parseMp4Mvhd(bytes, box) {
|
|
880
|
+
const o = box.start;
|
|
881
|
+
if (o >= box.end) return null;
|
|
882
|
+
const version = bytes[o];
|
|
883
|
+
if (version === 1) {
|
|
884
|
+
if (o + 32 > box.end) return null;
|
|
885
|
+
return { timescale: readU32BE(bytes, o + 20), duration: readU64BE(bytes, o + 24) };
|
|
886
|
+
}
|
|
887
|
+
if (o + 20 > box.end) return null;
|
|
888
|
+
return { timescale: readU32BE(bytes, o + 12), duration: readU32BE(bytes, o + 16) };
|
|
889
|
+
}
|
|
890
|
+
function parseMp4Tkhd(bytes, box) {
|
|
891
|
+
const o = box.start;
|
|
892
|
+
if (o >= box.end) return null;
|
|
893
|
+
const version = bytes[o];
|
|
894
|
+
const fixedFieldsSize = version === 1 ? 4 + 8 + 8 + 4 + 4 + 8 : 4 + 4 + 4 + 4 + 4 + 4;
|
|
895
|
+
const afterDuration = o + fixedFieldsSize;
|
|
896
|
+
const matrixStart = afterDuration + 8 + 2 + 2 + 2 + 2;
|
|
897
|
+
const widthOffset = matrixStart + 36;
|
|
898
|
+
if (widthOffset + 8 > box.end) return null;
|
|
899
|
+
return { width: readU32BE(bytes, widthOffset) / 65536, height: readU32BE(bytes, widthOffset + 4) / 65536 };
|
|
900
|
+
}
|
|
901
|
+
function parseMp4Hdlr(bytes, box) {
|
|
902
|
+
const o = box.start;
|
|
903
|
+
if (o + 12 > box.end) return null;
|
|
904
|
+
return readAscii4(bytes, o + 8);
|
|
905
|
+
}
|
|
906
|
+
function parseMp4StsdCodec(bytes, box) {
|
|
907
|
+
const o = box.start;
|
|
908
|
+
if (o + 8 > box.end) return null;
|
|
909
|
+
const entryCount = readU32BE(bytes, o + 4);
|
|
910
|
+
if (entryCount < 1) return null;
|
|
911
|
+
const entryStart = o + 8;
|
|
912
|
+
if (entryStart + 8 > box.end) return null;
|
|
913
|
+
return readAscii4(bytes, entryStart + 4);
|
|
914
|
+
}
|
|
915
|
+
function parseMp4StsdVideoDims(bytes, box) {
|
|
916
|
+
const o = box.start;
|
|
917
|
+
if (o + 8 > box.end) return null;
|
|
918
|
+
const entryCount = readU32BE(bytes, o + 4);
|
|
919
|
+
if (entryCount < 1) return null;
|
|
920
|
+
const entryStart = o + 8;
|
|
921
|
+
if (entryStart + 36 > box.end) return null;
|
|
922
|
+
return {
|
|
923
|
+
width: bytes[entryStart + 32] << 8 | bytes[entryStart + 33],
|
|
924
|
+
height: bytes[entryStart + 34] << 8 | bytes[entryStart + 35]
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function parseMp4(bytes) {
|
|
928
|
+
const topBoxes = readMp4Boxes(bytes, 0, bytes.length);
|
|
929
|
+
const ftyp = findMp4Box(topBoxes, "ftyp");
|
|
930
|
+
const moov = findMp4Box(topBoxes, "moov");
|
|
931
|
+
if (!moov) return { error: "No moov box found (not a valid or fully-downloaded MP4 file)" };
|
|
932
|
+
const result = { format: "mp4" };
|
|
933
|
+
if (ftyp && ftyp.start + 4 <= ftyp.end) result.majorBrand = readAscii4(bytes, ftyp.start);
|
|
934
|
+
const moovBoxes = readMp4Boxes(bytes, moov.start, moov.end);
|
|
935
|
+
const mvhd = findMp4Box(moovBoxes, "mvhd");
|
|
936
|
+
if (mvhd) {
|
|
937
|
+
const info = parseMp4Mvhd(bytes, mvhd);
|
|
938
|
+
if (info && info.timescale > 0) result.durationSeconds = Math.round(info.duration / info.timescale * 1e3) / 1e3;
|
|
939
|
+
}
|
|
940
|
+
for (const trak of moovBoxes.filter((b) => b.type === "trak")) {
|
|
941
|
+
const trakBoxes = readMp4Boxes(bytes, trak.start, trak.end);
|
|
942
|
+
const mdia = findMp4Box(trakBoxes, "mdia");
|
|
943
|
+
if (!mdia) continue;
|
|
944
|
+
const mdiaBoxes = readMp4Boxes(bytes, mdia.start, mdia.end);
|
|
945
|
+
const hdlr = findMp4Box(mdiaBoxes, "hdlr");
|
|
946
|
+
const handlerType = hdlr ? parseMp4Hdlr(bytes, hdlr) : null;
|
|
947
|
+
const minf = findMp4Box(mdiaBoxes, "minf");
|
|
948
|
+
if (!minf) continue;
|
|
949
|
+
const stbl = findMp4Box(readMp4Boxes(bytes, minf.start, minf.end), "stbl");
|
|
950
|
+
if (!stbl) continue;
|
|
951
|
+
const stsd = findMp4Box(readMp4Boxes(bytes, stbl.start, stbl.end), "stsd");
|
|
952
|
+
const codec = stsd ? parseMp4StsdCodec(bytes, stsd) : null;
|
|
953
|
+
if (handlerType === "vide") {
|
|
954
|
+
if (codec) result.videoCodec = codec;
|
|
955
|
+
const tkhd = findMp4Box(trakBoxes, "tkhd");
|
|
956
|
+
const dims = tkhd ? parseMp4Tkhd(bytes, tkhd) : null;
|
|
957
|
+
if (dims && dims.width > 0 && dims.height > 0) {
|
|
958
|
+
result.width = Math.round(dims.width);
|
|
959
|
+
result.height = Math.round(dims.height);
|
|
960
|
+
} else if (stsd) {
|
|
961
|
+
const pixelDims = parseMp4StsdVideoDims(bytes, stsd);
|
|
962
|
+
if (pixelDims && pixelDims.width > 0 && pixelDims.height > 0) {
|
|
963
|
+
result.width = pixelDims.width;
|
|
964
|
+
result.height = pixelDims.height;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
} else if (handlerType === "soun") {
|
|
968
|
+
if (codec) result.audioCodec = codec;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (result.durationSeconds === void 0 && !result.videoCodec && !result.audioCodec) {
|
|
972
|
+
return { error: "Could not find any usable track metadata in this MP4 file's moov box" };
|
|
973
|
+
}
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
function ebmlVintLength(firstByte) {
|
|
977
|
+
if (firstByte === 0) return 0;
|
|
978
|
+
let mask = 128;
|
|
979
|
+
let length = 1;
|
|
980
|
+
while (length <= 8 && (firstByte & mask) === 0) {
|
|
981
|
+
mask >>= 1;
|
|
982
|
+
length++;
|
|
983
|
+
}
|
|
984
|
+
return length;
|
|
985
|
+
}
|
|
986
|
+
function readEbmlId(bytes, offset) {
|
|
987
|
+
if (offset >= bytes.length) return null;
|
|
988
|
+
const length = ebmlVintLength(bytes[offset]);
|
|
989
|
+
if (length === 0 || offset + length > bytes.length) return null;
|
|
990
|
+
let id = 0;
|
|
991
|
+
for (let i = 0; i < length; i++) id = id * 256 + bytes[offset + i];
|
|
992
|
+
return { id, length };
|
|
993
|
+
}
|
|
994
|
+
function readEbmlSize(bytes, offset) {
|
|
995
|
+
if (offset >= bytes.length) return null;
|
|
996
|
+
const length = ebmlVintLength(bytes[offset]);
|
|
997
|
+
if (length === 0 || offset + length > bytes.length) return null;
|
|
998
|
+
const mask = 255 >> length;
|
|
999
|
+
let value = bytes[offset] & mask;
|
|
1000
|
+
let allOnes = value === mask;
|
|
1001
|
+
for (let i = 1; i < length; i++) {
|
|
1002
|
+
value = value * 256 + bytes[offset + i];
|
|
1003
|
+
if (bytes[offset + i] !== 255) allOnes = false;
|
|
1004
|
+
}
|
|
1005
|
+
return { value, length, unknown: allOnes };
|
|
1006
|
+
}
|
|
1007
|
+
function readEbmlUint(bytes, offset, length) {
|
|
1008
|
+
let value = 0;
|
|
1009
|
+
for (let i = 0; i < length; i++) value = value * 256 + bytes[offset + i];
|
|
1010
|
+
return value;
|
|
1011
|
+
}
|
|
1012
|
+
function readEbmlFloat(bytes, offset, length) {
|
|
1013
|
+
if (offset + length > bytes.length) return 0;
|
|
1014
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, length);
|
|
1015
|
+
if (length === 4) return view.getFloat32(0, false);
|
|
1016
|
+
if (length === 8) return view.getFloat64(0, false);
|
|
1017
|
+
return 0;
|
|
1018
|
+
}
|
|
1019
|
+
function ebmlChildren(bytes, start, end) {
|
|
1020
|
+
const children = [];
|
|
1021
|
+
let offset = start;
|
|
1022
|
+
while (offset < end) {
|
|
1023
|
+
const idInfo = readEbmlId(bytes, offset);
|
|
1024
|
+
if (!idInfo) break;
|
|
1025
|
+
const sizeInfo = readEbmlSize(bytes, offset + idInfo.length);
|
|
1026
|
+
if (!sizeInfo) break;
|
|
1027
|
+
const dataStart = offset + idInfo.length + sizeInfo.length;
|
|
1028
|
+
const dataEnd = sizeInfo.unknown ? end : Math.min(dataStart + sizeInfo.value, end);
|
|
1029
|
+
children.push({ id: idInfo.id, dataStart, dataEnd });
|
|
1030
|
+
offset = dataEnd;
|
|
1031
|
+
}
|
|
1032
|
+
return children;
|
|
1033
|
+
}
|
|
1034
|
+
var EBML_ID_SEGMENT = 408125543;
|
|
1035
|
+
var EBML_ID_INFO = 357149030;
|
|
1036
|
+
var EBML_ID_TIMECODE_SCALE = 2807729;
|
|
1037
|
+
var EBML_ID_DURATION = 17545;
|
|
1038
|
+
var EBML_ID_TRACKS = 374648427;
|
|
1039
|
+
var EBML_ID_TRACK_ENTRY = 174;
|
|
1040
|
+
var EBML_ID_TRACK_TYPE = 131;
|
|
1041
|
+
var EBML_ID_CODEC_ID = 134;
|
|
1042
|
+
var EBML_ID_VIDEO = 224;
|
|
1043
|
+
var EBML_ID_PIXEL_WIDTH = 176;
|
|
1044
|
+
var EBML_ID_PIXEL_HEIGHT = 186;
|
|
1045
|
+
var EBML_ID_CLUSTER = 524531317;
|
|
1046
|
+
function readVideoInfo(bytes) {
|
|
1047
|
+
if (bytes.length < 12) return { error: "File is too small to be a valid video file" };
|
|
1048
|
+
if (readAscii4(bytes, 4) === "ftyp") return parseMp4(bytes);
|
|
1049
|
+
if (bytes[0] === 26 && bytes[1] === 69 && bytes[2] === 223 && bytes[3] === 163) return parseWebm(bytes);
|
|
1050
|
+
return { error: 'Not a recognized MP4 or WebM file (missing an "ftyp" box or an EBML header signature)' };
|
|
1051
|
+
}
|
|
1052
|
+
function parseWebm(bytes) {
|
|
1053
|
+
const top = ebmlChildren(bytes, 0, bytes.length);
|
|
1054
|
+
const segment = top.find((c) => c.id === EBML_ID_SEGMENT);
|
|
1055
|
+
if (!segment) return { error: "No Segment element found (not a valid WebM/Matroska file)" };
|
|
1056
|
+
const result = { format: "webm" };
|
|
1057
|
+
let timecodeScale = 1e6;
|
|
1058
|
+
let rawDuration;
|
|
1059
|
+
for (const child of ebmlChildren(bytes, segment.dataStart, segment.dataEnd)) {
|
|
1060
|
+
if (child.id === EBML_ID_CLUSTER) break;
|
|
1061
|
+
if (child.id === EBML_ID_INFO) {
|
|
1062
|
+
for (const f of ebmlChildren(bytes, child.dataStart, child.dataEnd)) {
|
|
1063
|
+
if (f.id === EBML_ID_TIMECODE_SCALE) timecodeScale = readEbmlUint(bytes, f.dataStart, f.dataEnd - f.dataStart) || 1e6;
|
|
1064
|
+
else if (f.id === EBML_ID_DURATION) rawDuration = readEbmlFloat(bytes, f.dataStart, f.dataEnd - f.dataStart);
|
|
1065
|
+
}
|
|
1066
|
+
} else if (child.id === EBML_ID_TRACKS) {
|
|
1067
|
+
for (const entry of ebmlChildren(bytes, child.dataStart, child.dataEnd)) {
|
|
1068
|
+
if (entry.id !== EBML_ID_TRACK_ENTRY) continue;
|
|
1069
|
+
let trackType;
|
|
1070
|
+
let codecId;
|
|
1071
|
+
let pixelWidth;
|
|
1072
|
+
let pixelHeight;
|
|
1073
|
+
for (const f of ebmlChildren(bytes, entry.dataStart, entry.dataEnd)) {
|
|
1074
|
+
if (f.id === EBML_ID_TRACK_TYPE) trackType = readEbmlUint(bytes, f.dataStart, f.dataEnd - f.dataStart);
|
|
1075
|
+
else if (f.id === EBML_ID_CODEC_ID) codecId = Buffer.from(bytes.subarray(f.dataStart, f.dataEnd)).toString("ascii").replace(/\0+$/, "");
|
|
1076
|
+
else if (f.id === EBML_ID_VIDEO) {
|
|
1077
|
+
for (const v of ebmlChildren(bytes, f.dataStart, f.dataEnd)) {
|
|
1078
|
+
if (v.id === EBML_ID_PIXEL_WIDTH) pixelWidth = readEbmlUint(bytes, v.dataStart, v.dataEnd - v.dataStart);
|
|
1079
|
+
else if (v.id === EBML_ID_PIXEL_HEIGHT) pixelHeight = readEbmlUint(bytes, v.dataStart, v.dataEnd - v.dataStart);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (trackType === 1) {
|
|
1084
|
+
if (codecId) result.videoCodec = codecId;
|
|
1085
|
+
if (pixelWidth) result.width = pixelWidth;
|
|
1086
|
+
if (pixelHeight) result.height = pixelHeight;
|
|
1087
|
+
} else if (trackType === 2) {
|
|
1088
|
+
if (codecId) result.audioCodec = codecId;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (rawDuration !== void 0 && timecodeScale > 0) {
|
|
1094
|
+
result.durationSeconds = Math.round(rawDuration * timecodeScale / 1e9 * 1e3) / 1e3;
|
|
1095
|
+
}
|
|
1096
|
+
if (result.durationSeconds === void 0 && !result.videoCodec && !result.audioCodec) {
|
|
1097
|
+
return { error: "Could not find any usable metadata in this WebM file's Segment element" };
|
|
1098
|
+
}
|
|
1099
|
+
return result;
|
|
1100
|
+
}
|
|
853
1101
|
|
|
854
1102
|
exports.readExifData = readExifData;
|
|
855
1103
|
exports.readFlacInfo = readFlacInfo;
|
|
@@ -857,4 +1105,5 @@ exports.readIcoInfo = readIcoInfo;
|
|
|
857
1105
|
exports.readId3Tags = readId3Tags;
|
|
858
1106
|
exports.readImageInfo = readImageInfo;
|
|
859
1107
|
exports.readPdfMetadata = readPdfMetadata;
|
|
1108
|
+
exports.readVideoInfo = readVideoInfo;
|
|
860
1109
|
exports.readWavInfo = readWavInfo;
|
package/dist/tools/media.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { E as ExifData, a as ExifError, F as FlacInfo, b as FlacInfoError, I as IcoImageEntry, c as IcoInfo, d as IcoInfoError, e as Id3Tags, f as Id3TagsError, g as ImageFormat, h as ImageInfo, i as ImageInfoError, P as PdfMetadata, j as PdfMetadataError, W as WavInfo,
|
|
1
|
+
export { E as ExifData, a as ExifError, F as FlacInfo, b as FlacInfoError, I as IcoImageEntry, c as IcoInfo, d as IcoInfoError, e as Id3Tags, f as Id3TagsError, g as ImageFormat, h as ImageInfo, i as ImageInfoError, P as PdfMetadata, j as PdfMetadataError, V as VideoInfo, k as VideoInfoError, W as WavInfo, l as WavInfoError, r as readExifData, n as readFlacInfo, o as readIcoInfo, p as readId3Tags, q as readImageInfo, s as readPdfMetadata, t as readVideoInfo, u as readWavInfo } from '../media-cuxfzXxw.cjs';
|
package/dist/tools/media.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { E as ExifData, a as ExifError, F as FlacInfo, b as FlacInfoError, I as IcoImageEntry, c as IcoInfo, d as IcoInfoError, e as Id3Tags, f as Id3TagsError, g as ImageFormat, h as ImageInfo, i as ImageInfoError, P as PdfMetadata, j as PdfMetadataError, W as WavInfo,
|
|
1
|
+
export { E as ExifData, a as ExifError, F as FlacInfo, b as FlacInfoError, I as IcoImageEntry, c as IcoInfo, d as IcoInfoError, e as Id3Tags, f as Id3TagsError, g as ImageFormat, h as ImageInfo, i as ImageInfoError, P as PdfMetadata, j as PdfMetadataError, V as VideoInfo, k as VideoInfoError, W as WavInfo, l as WavInfoError, r as readExifData, n as readFlacInfo, o as readIcoInfo, p as readId3Tags, q as readImageInfo, s as readPdfMetadata, t as readVideoInfo, u as readWavInfo } from '../media-cuxfzXxw.js';
|
package/dist/tools/media.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { readExifData, readFlacInfo, readIcoInfo, readId3Tags, readImageInfo, readPdfMetadata, readWavInfo } from '../chunk-
|
|
1
|
+
export { readExifData, readFlacInfo, readIcoInfo, readId3Tags, readImageInfo, readPdfMetadata, readVideoInfo, readWavInfo } from '../chunk-47DTCCQJ.js';
|
|
2
2
|
import '../chunk-MLKGABMK.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilix-tech/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "154+ developer utility tools for Node.js: JSON, encoding, hashing, color, CSS, network and more. Runs locally, no API key required.",
|
|
5
5
|
"author": "Utilix <hello@utilix.tech>",
|
|
6
6
|
"license": "MIT",
|