@ttt-productions/file-input 0.11.14 → 0.11.16
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/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/image-utils.d.ts +5 -2
- package/dist/lib/image-utils.d.ts.map +1 -1
- package/dist/lib/image-utils.js +16 -9
- package/dist/lib/image-utils.js.map +1 -1
- package/dist/lib/infer-content-type.d.ts +1 -1
- package/dist/lib/infer-content-type.js +29 -29
- package/dist/lib/infer-content-type.js.map +1 -1
- package/dist/lib/read-media-meta.d.ts +1 -1
- package/dist/lib/read-media-meta.d.ts.map +1 -1
- package/dist/lib/read-media-meta.js +20 -13
- package/dist/lib/read-media-meta.js.map +1 -1
- package/dist/lib/validate-media-duration.js +3 -3
- package/dist/lib/validate-media-duration.js.map +1 -1
- package/dist/lib/validate-upload-kind.d.ts.map +1 -1
- package/dist/lib/validate-upload-kind.js.map +1 -1
- package/dist/progress-bar.d.ts +2 -0
- package/dist/progress-bar.d.ts.map +1 -0
- package/dist/progress-bar.js +2 -0
- package/dist/progress-bar.js.map +1 -0
- package/dist/react/components/audio-input.d.ts +2 -2
- package/dist/react/components/audio-input.js +2 -2
- package/dist/react/components/audio-input.js.map +1 -1
- package/dist/react/components/file-input.d.ts +1 -1
- package/dist/react/components/file-input.d.ts.map +1 -1
- package/dist/react/components/file-input.js +59 -42
- package/dist/react/components/file-input.js.map +1 -1
- package/dist/react/components/image-cropper-modal.d.ts +4 -2
- package/dist/react/components/image-cropper-modal.d.ts.map +1 -1
- package/dist/react/components/image-cropper-modal.js +7 -7
- package/dist/react/components/image-cropper-modal.js.map +1 -1
- package/dist/react/components/image-input.d.ts +2 -2
- package/dist/react/components/image-input.js +2 -2
- package/dist/react/components/image-input.js.map +1 -1
- package/dist/react/components/media-constraints-hint.d.ts +1 -1
- package/dist/react/components/media-constraints-hint.d.ts.map +1 -1
- package/dist/react/components/media-constraints-hint.js +26 -24
- package/dist/react/components/media-constraints-hint.js.map +1 -1
- package/dist/react/components/media-input.d.ts +2 -2
- package/dist/react/components/media-input.d.ts.map +1 -1
- package/dist/react/components/media-input.js +107 -89
- package/dist/react/components/media-input.js.map +1 -1
- package/dist/react/components/photo-capture-modal.d.ts +1 -1
- package/dist/react/components/photo-capture-modal.d.ts.map +1 -1
- package/dist/react/components/photo-capture-modal.js +29 -16
- package/dist/react/components/photo-capture-modal.js.map +1 -1
- package/dist/react/components/record-dialog.d.ts +3 -3
- package/dist/react/components/record-dialog.d.ts.map +1 -1
- package/dist/react/components/record-dialog.js +49 -50
- package/dist/react/components/record-dialog.js.map +1 -1
- package/dist/react/components/video-input.d.ts +2 -2
- package/dist/react/components/video-input.js +2 -2
- package/dist/react/components/video-input.js.map +1 -1
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +8 -8
- package/dist/react/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './lib/infer-content-type.js';
|
|
3
|
+
export * from './lib/validate-upload-kind.js';
|
|
4
|
+
export { DEFAULT_PROGRESS_BAR_MIN_BYTES } from './progress-bar.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,OAAO,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './lib/infer-content-type.js';
|
|
3
|
+
export * from './lib/validate-upload-kind.js';
|
|
4
|
+
export { DEFAULT_PROGRESS_BAR_MIN_BYTES } from './progress-bar.js';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,OAAO,EAAE,8BAA8B,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export * from './types.js';\nexport * from './lib/infer-content-type.js';\nexport * from './lib/validate-upload-kind.js';\nexport { DEFAULT_PROGRESS_BAR_MIN_BYTES } from './progress-bar.js';\n"]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { Area } from
|
|
1
|
+
import type { Area } from 'react-easy-crop';
|
|
2
|
+
import { type MediaCropSpec } from '@ttt-productions/media-schemas';
|
|
3
|
+
/** How a crop is encoded; the fields an origin's crop spec declares. */
|
|
4
|
+
export type CropEncoding = Pick<MediaCropSpec, 'format' | 'quality'>;
|
|
2
5
|
export declare function getCroppedImg(imageSrc: string, pixelCrop: Area, rotation?: number, flip?: {
|
|
3
6
|
horizontal: boolean;
|
|
4
7
|
vertical: boolean;
|
|
5
|
-
}, outputWidth?: number, outputHeight?: number): Promise<Blob | null>;
|
|
8
|
+
}, outputWidth?: number, outputHeight?: number, encoding?: CropEncoding): Promise<Blob | null>;
|
|
6
9
|
//# sourceMappingURL=image-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-utils.d.ts","sourceRoot":"","sources":["../../src/lib/image-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"image-utils.d.ts","sourceRoot":"","sources":["../../src/lib/image-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEnF,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC;AA0BrE,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,IAAI,EACf,QAAQ,SAAI,EACZ,IAAI;;;CAAyC,EAC7C,WAAW,CAAC,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,EACrB,QAAQ,GAAE,YAAiB,GAC1B,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAiDtB"}
|
package/dist/lib/image-utils.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { MEDIA_FORMATS } from '@ttt-productions/media-schemas';
|
|
2
|
+
const DEFAULT_CROP_FORMAT = 'jpeg';
|
|
3
|
+
const DEFAULT_CROP_QUALITY = 80;
|
|
1
4
|
const createImage = (url) => new Promise((resolve, reject) => {
|
|
2
5
|
const image = new Image();
|
|
3
|
-
image.addEventListener(
|
|
4
|
-
image.addEventListener(
|
|
5
|
-
image.setAttribute(
|
|
6
|
+
image.addEventListener('load', () => resolve(image));
|
|
7
|
+
image.addEventListener('error', (error) => reject(error));
|
|
8
|
+
image.setAttribute('crossOrigin', 'anonymous');
|
|
6
9
|
image.src = url;
|
|
7
10
|
});
|
|
8
11
|
function getRadianAngle(deg) {
|
|
@@ -15,10 +18,10 @@ function rotateSize(width, height, rotation) {
|
|
|
15
18
|
height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
|
-
export async function getCroppedImg(imageSrc, pixelCrop, rotation = 0, flip = { horizontal: false, vertical: false }, outputWidth, outputHeight) {
|
|
21
|
+
export async function getCroppedImg(imageSrc, pixelCrop, rotation = 0, flip = { horizontal: false, vertical: false }, outputWidth, outputHeight, encoding = {}) {
|
|
19
22
|
const image = await createImage(imageSrc);
|
|
20
|
-
const canvas = document.createElement(
|
|
21
|
-
const ctx = canvas.getContext(
|
|
23
|
+
const canvas = document.createElement('canvas');
|
|
24
|
+
const ctx = canvas.getContext('2d');
|
|
22
25
|
if (!ctx)
|
|
23
26
|
return null;
|
|
24
27
|
const rotRad = getRadianAngle(rotation);
|
|
@@ -30,15 +33,19 @@ export async function getCroppedImg(imageSrc, pixelCrop, rotation = 0, flip = {
|
|
|
30
33
|
ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);
|
|
31
34
|
ctx.translate(-image.width / 2, -image.height / 2);
|
|
32
35
|
ctx.drawImage(image, 0, 0);
|
|
33
|
-
const croppedCanvas = document.createElement(
|
|
34
|
-
const croppedCtx = croppedCanvas.getContext(
|
|
36
|
+
const croppedCanvas = document.createElement('canvas');
|
|
37
|
+
const croppedCtx = croppedCanvas.getContext('2d');
|
|
35
38
|
if (!croppedCtx)
|
|
36
39
|
return null;
|
|
37
40
|
croppedCanvas.width = outputWidth || pixelCrop.width;
|
|
38
41
|
croppedCanvas.height = outputHeight || pixelCrop.height;
|
|
39
42
|
croppedCtx.drawImage(canvas, pixelCrop.x, pixelCrop.y, pixelCrop.width, pixelCrop.height, 0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
43
|
+
// This is the first lossy encode the image gets, so it uses the origin's declared format and
|
|
44
|
+
// quality; a browser that cannot encode the format returns PNG, and the blob's type says so.
|
|
45
|
+
const mime = MEDIA_FORMATS[encoding.format ?? DEFAULT_CROP_FORMAT].mimes.all[0];
|
|
46
|
+
const quality = (encoding.quality ?? DEFAULT_CROP_QUALITY) / 100;
|
|
40
47
|
return new Promise((resolve, reject) => {
|
|
41
|
-
croppedCanvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error(
|
|
48
|
+
croppedCanvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('Canvas to Blob conversion failed'))), mime, quality);
|
|
42
49
|
});
|
|
43
50
|
}
|
|
44
51
|
//# sourceMappingURL=image-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-utils.js","sourceRoot":"","sources":["../../src/lib/image-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image-utils.js","sourceRoot":"","sources":["../../src/lib/image-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Area } from 'react-easy-crop';\nimport { MEDIA_FORMATS, type MediaCropSpec } from '@ttt-productions/media-schemas';\n\n/** How a crop is encoded; the fields an origin's crop spec declares. */\nexport type CropEncoding = Pick<MediaCropSpec, 'format' | 'quality'>;\n\nconst DEFAULT_CROP_FORMAT: NonNullable<MediaCropSpec['format']> = 'jpeg';\nconst DEFAULT_CROP_QUALITY = 80;\n\nconst createImage = (url: string): Promise<HTMLImageElement> =>\n new Promise((resolve, reject) => {\n const image = new Image();\n image.addEventListener('load', () => resolve(image));\n image.addEventListener('error', (error) => reject(error));\n image.setAttribute('crossOrigin', 'anonymous');\n image.src = url;\n });\n\nfunction getRadianAngle(deg: number) {\n return (deg * Math.PI) / 180;\n}\n\nfunction rotateSize(width: number, height: number, rotation: number) {\n const rotRad = getRadianAngle(rotation);\n return {\n width: Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),\n height: Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),\n };\n}\n\nexport async function getCroppedImg(\n imageSrc: string,\n pixelCrop: Area,\n rotation = 0,\n flip = { horizontal: false, vertical: false },\n outputWidth?: number,\n outputHeight?: number,\n encoding: CropEncoding = {},\n): Promise<Blob | null> {\n const image = await createImage(imageSrc);\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n\n const rotRad = getRadianAngle(rotation);\n const { width: bBoxWidth, height: bBoxHeight } = rotateSize(image.width, image.height, rotation);\n\n canvas.width = bBoxWidth;\n canvas.height = bBoxHeight;\n\n ctx.translate(bBoxWidth / 2, bBoxHeight / 2);\n ctx.rotate(rotRad);\n ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);\n ctx.translate(-image.width / 2, -image.height / 2);\n ctx.drawImage(image, 0, 0);\n\n const croppedCanvas = document.createElement('canvas');\n const croppedCtx = croppedCanvas.getContext('2d');\n if (!croppedCtx) return null;\n\n croppedCanvas.width = outputWidth || pixelCrop.width;\n croppedCanvas.height = outputHeight || pixelCrop.height;\n\n croppedCtx.drawImage(\n canvas,\n pixelCrop.x,\n pixelCrop.y,\n pixelCrop.width,\n pixelCrop.height,\n 0,\n 0,\n croppedCanvas.width,\n croppedCanvas.height,\n );\n\n // This is the first lossy encode the image gets, so it uses the origin's declared format and\n // quality; a browser that cannot encode the format returns PNG, and the blob's type says so.\n const mime = MEDIA_FORMATS[encoding.format ?? DEFAULT_CROP_FORMAT].mimes.all![0];\n const quality = (encoding.quality ?? DEFAULT_CROP_QUALITY) / 100;\n\n return new Promise((resolve, reject) => {\n croppedCanvas.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error('Canvas to Blob conversion failed'))),\n mime,\n quality,\n );\n });\n}\n"]}
|
|
@@ -16,44 +16,44 @@
|
|
|
16
16
|
// behavior defaulted such files to `image/jpeg`, which invented a false media
|
|
17
17
|
// fact the whole pipeline then trusted. upload-core accepts the neutral type
|
|
18
18
|
// only via its explicit opt-in (`allowNeutralContentType`).
|
|
19
|
-
import { NEUTRAL_CONTENT_TYPE } from
|
|
19
|
+
import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
|
|
20
20
|
const EXT_TO_MIME = {
|
|
21
21
|
// image
|
|
22
|
-
jpg:
|
|
23
|
-
jpeg:
|
|
24
|
-
png:
|
|
25
|
-
gif:
|
|
26
|
-
webp:
|
|
27
|
-
avif:
|
|
28
|
-
heic:
|
|
29
|
-
heif:
|
|
22
|
+
jpg: 'image/jpeg',
|
|
23
|
+
jpeg: 'image/jpeg',
|
|
24
|
+
png: 'image/png',
|
|
25
|
+
gif: 'image/gif',
|
|
26
|
+
webp: 'image/webp',
|
|
27
|
+
avif: 'image/avif',
|
|
28
|
+
heic: 'image/heic',
|
|
29
|
+
heif: 'image/heif',
|
|
30
30
|
// video
|
|
31
|
-
mp4:
|
|
32
|
-
m4v:
|
|
33
|
-
mov:
|
|
34
|
-
webm:
|
|
35
|
-
mkv:
|
|
31
|
+
mp4: 'video/mp4',
|
|
32
|
+
m4v: 'video/mp4',
|
|
33
|
+
mov: 'video/quicktime',
|
|
34
|
+
webm: 'video/webm',
|
|
35
|
+
mkv: 'video/x-matroska',
|
|
36
36
|
// audio
|
|
37
|
-
mp3:
|
|
38
|
-
m4a:
|
|
39
|
-
aac:
|
|
40
|
-
wav:
|
|
41
|
-
ogg:
|
|
42
|
-
oga:
|
|
43
|
-
flac:
|
|
37
|
+
mp3: 'audio/mpeg',
|
|
38
|
+
m4a: 'audio/mp4',
|
|
39
|
+
aac: 'audio/aac',
|
|
40
|
+
wav: 'audio/wav',
|
|
41
|
+
ogg: 'audio/ogg',
|
|
42
|
+
oga: 'audio/ogg',
|
|
43
|
+
flac: 'audio/flac',
|
|
44
44
|
};
|
|
45
45
|
// Containers that legitimately hold either audio or video. When the caller
|
|
46
46
|
// declares a kind (e.g. the record dialog's chosen recording kind), that kind
|
|
47
47
|
// picks the matching side; without a kind we keep the historical single-kind
|
|
48
48
|
// guess in EXT_TO_MIME (webm → video, ogg → audio).
|
|
49
49
|
const AMBIGUOUS_EXT_TO_MIME = {
|
|
50
|
-
webm: { audio:
|
|
51
|
-
ogg: { audio:
|
|
50
|
+
webm: { audio: 'audio/webm', video: 'video/webm' },
|
|
51
|
+
ogg: { audio: 'audio/ogg', video: 'video/ogg' },
|
|
52
52
|
};
|
|
53
53
|
const KIND_DEFAULT = {
|
|
54
|
-
image:
|
|
55
|
-
video:
|
|
56
|
-
audio:
|
|
54
|
+
image: 'image/jpeg',
|
|
55
|
+
video: 'video/webm',
|
|
56
|
+
audio: 'audio/webm',
|
|
57
57
|
};
|
|
58
58
|
const BASE_MEDIA_MIME_RE = /^(image|video|audio)\/[a-z0-9.+-]+$/;
|
|
59
59
|
/**
|
|
@@ -65,16 +65,16 @@ const BASE_MEDIA_MIME_RE = /^(image|video|audio)\/[a-z0-9.+-]+$/;
|
|
|
65
65
|
function parseBaseMime(mime) {
|
|
66
66
|
if (!mime)
|
|
67
67
|
return null;
|
|
68
|
-
const base = mime.split(
|
|
68
|
+
const base = mime.split(';')[0].trim().toLowerCase();
|
|
69
69
|
if (base === NEUTRAL_CONTENT_TYPE)
|
|
70
70
|
return null;
|
|
71
71
|
return BASE_MEDIA_MIME_RE.test(base) ? base : null;
|
|
72
72
|
}
|
|
73
73
|
function kindOfMime(base) {
|
|
74
|
-
return base.slice(0, base.indexOf(
|
|
74
|
+
return base.slice(0, base.indexOf('/'));
|
|
75
75
|
}
|
|
76
76
|
function extOf(name) {
|
|
77
|
-
const dot = name.lastIndexOf(
|
|
77
|
+
const dot = name.lastIndexOf('.');
|
|
78
78
|
if (dot < 0 || dot === name.length - 1)
|
|
79
79
|
return null;
|
|
80
80
|
return name.slice(dot + 1).toLowerCase();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infer-content-type.js","sourceRoot":"","sources":["../../src/lib/infer-content-type.ts"],"names":[],"mappings":"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","sourcesContent":["// Defense-in-depth: guarantee every File we emit has a NORMALIZED, honest\n// content type. If file.type is missing or unusable, infer from the extension;\n// with a caller-declared kind (recorder/capture — the caller KNOWS what it\n// produced) fall back to that kind's default container.\n//\n// MediaRecorder (and some pickers) report parameterized types such as\n// \"audio/webm;codecs=opus\". Downstream storage rules validate a\n// parameter-less base type, so we always emit the base (\"audio/webm\"),\n// never the parameterized form.\n//\n// UNKNOWN PICKER METADATA IS NOT FABRICATED (canonical-upload-content-\n// classification, 2026-07-31): when there is no declared kind, no usable MIME,\n// and no known extension, the file passes through as the NEUTRAL\n// `application/octet-stream` so the server's canonical byte inspector — the\n// only classification authority — sees \"unknown\" as unknown. The previous\n// behavior defaulted such files to `image/jpeg`, which invented a false media\n// fact the whole pipeline then trusted. upload-core accepts the neutral type\n// only via its explicit opt-in (`allowNeutralContentType`).\n\nimport { NEUTRAL_CONTENT_TYPE } from
|
|
1
|
+
{"version":3,"file":"infer-content-type.js","sourceRoot":"","sources":["../../src/lib/infer-content-type.ts"],"names":[],"mappings":"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","sourcesContent":["// Defense-in-depth: guarantee every File we emit has a NORMALIZED, honest\n// content type. If file.type is missing or unusable, infer from the extension;\n// with a caller-declared kind (recorder/capture — the caller KNOWS what it\n// produced) fall back to that kind's default container.\n//\n// MediaRecorder (and some pickers) report parameterized types such as\n// \"audio/webm;codecs=opus\". Downstream storage rules validate a\n// parameter-less base type, so we always emit the base (\"audio/webm\"),\n// never the parameterized form.\n//\n// UNKNOWN PICKER METADATA IS NOT FABRICATED (canonical-upload-content-\n// classification, 2026-07-31): when there is no declared kind, no usable MIME,\n// and no known extension, the file passes through as the NEUTRAL\n// `application/octet-stream` so the server's canonical byte inspector — the\n// only classification authority — sees \"unknown\" as unknown. The previous\n// behavior defaulted such files to `image/jpeg`, which invented a false media\n// fact the whole pipeline then trusted. upload-core accepts the neutral type\n// only via its explicit opt-in (`allowNeutralContentType`).\n\nimport { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\n\nexport type InferKind = 'image' | 'video' | 'audio';\n\nconst EXT_TO_MIME: Record<string, string> = {\n // image\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n heic: 'image/heic',\n heif: 'image/heif',\n // video\n mp4: 'video/mp4',\n m4v: 'video/mp4',\n mov: 'video/quicktime',\n webm: 'video/webm',\n mkv: 'video/x-matroska',\n // audio\n mp3: 'audio/mpeg',\n m4a: 'audio/mp4',\n aac: 'audio/aac',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n oga: 'audio/ogg',\n flac: 'audio/flac',\n};\n\n// Containers that legitimately hold either audio or video. When the caller\n// declares a kind (e.g. the record dialog's chosen recording kind), that kind\n// picks the matching side; without a kind we keep the historical single-kind\n// guess in EXT_TO_MIME (webm → video, ogg → audio).\nconst AMBIGUOUS_EXT_TO_MIME: Record<string, Partial<Record<InferKind, string>>> = {\n webm: { audio: 'audio/webm', video: 'video/webm' },\n ogg: { audio: 'audio/ogg', video: 'video/ogg' },\n};\n\nconst KIND_DEFAULT: Record<InferKind, string> = {\n image: 'image/jpeg',\n video: 'video/webm',\n audio: 'audio/webm',\n};\n\nconst BASE_MEDIA_MIME_RE = /^(image|video|audio)\\/[a-z0-9.+-]+$/;\n\n/**\n * Extracts the parameter-less base MIME (\"audio/webm\" from\n * \"audio/webm;codecs=opus\"), lowercased, when it is a usable media type.\n * Returns null for empty values, application/octet-stream, and anything\n * whose base is not image/*, video/*, or audio/*.\n */\nfunction parseBaseMime(mime: string | undefined | null): string | null {\n if (!mime) return null;\n const base = mime.split(';')[0].trim().toLowerCase();\n if (base === NEUTRAL_CONTENT_TYPE) return null;\n return BASE_MEDIA_MIME_RE.test(base) ? base : null;\n}\n\nfunction kindOfMime(base: string): InferKind {\n return base.slice(0, base.indexOf('/')) as InferKind;\n}\n\nfunction extOf(name: string): string | null {\n const dot = name.lastIndexOf('.');\n if (dot < 0 || dot === name.length - 1) return null;\n return name.slice(dot + 1).toLowerCase();\n}\n\n/**\n * Returns a valid, parameter-less media MIME for the given File.\n *\n * Precedence:\n * 1. The base of file.type (parameters stripped, lowercased) when valid —\n * unless `fallbackKind` is supplied and disagrees with the declared\n * top-level kind. A caller-declared kind (e.g. the record dialog's chosen\n * recording kind) is authoritative context, so a conflicting declared type\n * is treated as untrustworthy and resolution falls through to the\n * extension.\n * 2. Extension lookup. For ambiguous containers (webm/ogg exist as both audio\n * and video) a supplied `fallbackKind` picks the matching side; unambiguous\n * extensions keep their single mapping.\n * 3. The kind default for `fallbackKind`; without one, the NEUTRAL type — an\n * unknown picker file is never rewritten into a false media fact.\n */\nexport function inferContentType(file: File, fallbackKind?: InferKind): string {\n const base = parseBaseMime(file.type);\n if (base && (!fallbackKind || kindOfMime(base) === fallbackKind)) return base;\n\n const ext = extOf(file.name);\n if (ext) {\n const byKind = fallbackKind ? AMBIGUOUS_EXT_TO_MIME[ext]?.[fallbackKind] : undefined;\n if (byKind) return byKind;\n if (EXT_TO_MIME[ext]) return EXT_TO_MIME[ext];\n }\n\n if (fallbackKind) return KIND_DEFAULT[fallbackKind];\n\n // No declared kind, no usable MIME, no known extension: NEUTRAL, never a\n // fabricated media type. The server inspector owns classification.\n return NEUTRAL_CONTENT_TYPE;\n}\n\n/**\n * Returns a File whose .type is exactly the valid, parameter-less media MIME\n * from inferContentType. If the original file already carries that exact\n * type, it is returned unchanged; otherwise (missing type, parameterized\n * type, wrong case, or a type overridden by fallbackKind precedence) the\n * bytes are wrapped in a new File with the normalized type.\n */\nexport function ensureFileWithContentType(file: File, fallbackKind?: InferKind): File {\n const type = inferContentType(file, fallbackKind);\n if (file.type === type) return file;\n return new File([file], file.name, { type, lastModified: file.lastModified });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-media-meta.d.ts","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"read-media-meta.d.ts","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoFD,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6B5E"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { getSimplifiedMediaType } from
|
|
1
|
+
import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';
|
|
2
2
|
function simplifiedToKind(s) {
|
|
3
|
-
if (s ===
|
|
3
|
+
if (s === 'image' || s === 'video' || s === 'audio')
|
|
4
4
|
return s;
|
|
5
|
-
return
|
|
5
|
+
return 'file';
|
|
6
6
|
}
|
|
7
7
|
function computeOrientation(width, height) {
|
|
8
8
|
if (!width || !height)
|
|
9
9
|
return undefined;
|
|
10
10
|
if (width === height)
|
|
11
|
-
return
|
|
12
|
-
return height > width ?
|
|
11
|
+
return 'any';
|
|
12
|
+
return height > width ? 'vertical' : 'horizontal';
|
|
13
13
|
}
|
|
14
14
|
async function readImage(file) {
|
|
15
15
|
return new Promise((resolve) => {
|
|
@@ -37,18 +37,25 @@ async function readImage(file) {
|
|
|
37
37
|
}
|
|
38
38
|
async function readAV(file) {
|
|
39
39
|
return new Promise((resolve) => {
|
|
40
|
-
const isVideo = file.type.startsWith(
|
|
41
|
-
const el = document.createElement(isVideo ?
|
|
42
|
-
el.preload =
|
|
40
|
+
const isVideo = file.type.startsWith('video/');
|
|
41
|
+
const el = document.createElement(isVideo ? 'video' : 'audio');
|
|
42
|
+
el.preload = 'metadata';
|
|
43
43
|
const url = URL.createObjectURL(file);
|
|
44
44
|
el.onloadedmetadata = () => {
|
|
45
45
|
try {
|
|
46
|
-
const durationSec = Number.isFinite(el.duration)
|
|
46
|
+
const durationSec = Number.isFinite(el.duration)
|
|
47
|
+
? el.duration
|
|
48
|
+
: undefined;
|
|
47
49
|
if (isVideo) {
|
|
48
50
|
const v = el;
|
|
49
51
|
const width = v.videoWidth || undefined;
|
|
50
52
|
const height = v.videoHeight || undefined;
|
|
51
|
-
resolve({
|
|
53
|
+
resolve({
|
|
54
|
+
width,
|
|
55
|
+
height,
|
|
56
|
+
durationSec,
|
|
57
|
+
aspectRatio: height ? width / height : undefined,
|
|
58
|
+
});
|
|
52
59
|
}
|
|
53
60
|
else {
|
|
54
61
|
resolve({ durationSec });
|
|
@@ -79,7 +86,7 @@ export async function readMediaMeta(file) {
|
|
|
79
86
|
mime: file.type || undefined,
|
|
80
87
|
sizeBytes: file.size,
|
|
81
88
|
};
|
|
82
|
-
if (kind ===
|
|
89
|
+
if (kind === 'image') {
|
|
83
90
|
const img = await readImage(file);
|
|
84
91
|
return {
|
|
85
92
|
...base,
|
|
@@ -87,12 +94,12 @@ export async function readMediaMeta(file) {
|
|
|
87
94
|
orientation: computeOrientation(img.width, img.height),
|
|
88
95
|
};
|
|
89
96
|
}
|
|
90
|
-
if (kind ===
|
|
97
|
+
if (kind === 'video' || kind === 'audio') {
|
|
91
98
|
const av = await readAV(file);
|
|
92
99
|
return {
|
|
93
100
|
...base,
|
|
94
101
|
...av,
|
|
95
|
-
orientation: kind ===
|
|
102
|
+
orientation: kind === 'video' ? computeOrientation(av.width, av.height) : undefined,
|
|
96
103
|
};
|
|
97
104
|
}
|
|
98
105
|
return base;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-media-meta.js","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAcxE,SAAS,gBAAgB,CAAC,CAA4C;IACpE,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,OAAO;QAAE,OAAO,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc,EAAE,MAAe;IACzD,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;AACpD,CAAC;AAED,KAAK,UAAU,SAAS,
|
|
1
|
+
{"version":3,"file":"read-media-meta.js","sourceRoot":"","sources":["../../src/lib/read-media-meta.ts"],"names":[],"mappings":"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","sourcesContent":["import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport type { MediaKind, VideoOrientation } from '@ttt-productions/media-schemas';\n\nexport interface ReadMediaMetaResult {\n kind: MediaKind;\n mime?: string;\n sizeBytes: number;\n width?: number;\n height?: number;\n durationSec?: number;\n orientation?: VideoOrientation;\n aspectRatio?: number;\n}\n\nfunction simplifiedToKind(s: ReturnType<typeof getSimplifiedMediaType>): MediaKind {\n if (s === 'image' || s === 'video' || s === 'audio') return s;\n return 'file';\n}\n\nfunction computeOrientation(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return 'any';\n return height > width ? 'vertical' : 'horizontal';\n}\n\nasync function readImage(\n file: File,\n): Promise<Pick<ReadMediaMetaResult, 'width' | 'height' | 'aspectRatio'>> {\n return new Promise((resolve) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n try {\n const width = img.naturalWidth;\n const height = img.naturalHeight;\n resolve({ width, height, aspectRatio: height ? width / height : undefined });\n } finally {\n URL.revokeObjectURL(url);\n }\n };\n img.onerror = () => {\n try {\n URL.revokeObjectURL(url);\n } catch {}\n resolve({});\n };\n img.src = url;\n });\n}\n\nasync function readAV(\n file: File,\n): Promise<Pick<ReadMediaMetaResult, 'width' | 'height' | 'durationSec' | 'aspectRatio'>> {\n return new Promise((resolve) => {\n const isVideo = file.type.startsWith('video/');\n const el = document.createElement(isVideo ? 'video' : 'audio');\n el.preload = 'metadata';\n\n const url = URL.createObjectURL(file);\n\n el.onloadedmetadata = () => {\n try {\n const durationSec = Number.isFinite((el as any).duration)\n ? (el as any).duration\n : undefined;\n if (isVideo) {\n const v = el as HTMLVideoElement;\n const width = v.videoWidth || undefined;\n const height = v.videoHeight || undefined;\n resolve({\n width,\n height,\n durationSec,\n aspectRatio: height ? width! / height : undefined,\n });\n } else {\n resolve({ durationSec });\n }\n } finally {\n try {\n URL.revokeObjectURL(url);\n } catch {}\n }\n };\n\n el.onerror = () => {\n try {\n URL.revokeObjectURL(url);\n } catch {}\n resolve({});\n };\n\n el.src = url;\n });\n}\n\nexport async function readMediaMeta(file: File): Promise<ReadMediaMetaResult> {\n const simplified = getSimplifiedMediaType(file);\n const kind = simplifiedToKind(simplified);\n\n const base: ReadMediaMetaResult = {\n kind,\n mime: file.type || undefined,\n sizeBytes: file.size,\n };\n\n if (kind === 'image') {\n const img = await readImage(file);\n return {\n ...base,\n ...img,\n orientation: computeOrientation(img.width, img.height),\n };\n }\n\n if (kind === 'video' || kind === 'audio') {\n const av = await readAV(file);\n return {\n ...base,\n ...av,\n orientation: kind === 'video' ? computeOrientation(av.width, av.height) : undefined,\n };\n }\n\n return base;\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { resolveInfiniteDuration } from
|
|
1
|
+
import { resolveInfiniteDuration } from '@ttt-productions/media-viewer';
|
|
2
2
|
export async function validateMediaDuration(file, maxDurationSec) {
|
|
3
3
|
return new Promise((resolve) => {
|
|
4
|
-
const el = document.createElement(file.type.startsWith(
|
|
5
|
-
el.preload =
|
|
4
|
+
const el = document.createElement(file.type.startsWith('video/') ? 'video' : 'audio');
|
|
5
|
+
el.preload = 'metadata';
|
|
6
6
|
let settled = false;
|
|
7
7
|
const finish = (ok) => {
|
|
8
8
|
if (settled)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-media-duration.js","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,cAAsB;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,GAAG,UAAU,CAAC;QAExB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC;gBACH,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,GAAG,GAAG,EAAE;YACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,sEAAsE;YACtE,sEAAsE;YACtE,uEAAuE;YACvE,sEAAsE;YACtE,sEAAsE;YACtE,oBAAoB;YACpB,KAAK,uBAAuB,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,mDAAmD;YACnD,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { resolveInfiniteDuration } from
|
|
1
|
+
{"version":3,"file":"validate-media-duration.js","sourceRoot":"","sources":["../../src/lib/validate-media-duration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU,EAAE,cAAsB;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,EAAE,CAAC,OAAO,GAAG,UAAU,CAAC;QAExB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,EAAW,EAAE,EAAE;YAC7B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC;gBACH,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,GAAG,GAAG,EAAE;YACzB,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,sEAAsE;YACtE,sEAAsE;YACtE,uEAAuE;YACvE,sEAAsE;YACtE,sEAAsE;YACtE,oBAAoB;YACpB,KAAK,uBAAuB,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,mDAAmD;YACnD,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { resolveInfiniteDuration } from '@ttt-productions/media-viewer';\n\nexport async function validateMediaDuration(file: File, maxDurationSec: number): Promise<boolean> {\n return new Promise((resolve) => {\n const el = document.createElement(file.type.startsWith('video/') ? 'video' : 'audio');\n el.preload = 'metadata';\n\n let settled = false;\n const finish = (ok: boolean) => {\n if (settled) return;\n settled = true;\n try {\n URL.revokeObjectURL(el.src);\n } catch {}\n resolve(ok);\n };\n\n el.onloadedmetadata = () => {\n if (Number.isFinite(el.duration)) {\n finish(el.duration <= maxDurationSec);\n return;\n }\n // Chromium MediaRecorder blobs (recorded audio/video) report duration\n // Infinity at loadedmetadata, so `Infinity <= max` would reject every\n // recording. Resolve the real duration via the shared seek workaround;\n // if it still cannot be determined, let the file through — the server\n // measures every file's length and refuses one over the limit or with\n // no length at all.\n void resolveInfiniteDuration(el).then((duration) => {\n finish(duration === null ? true : duration <= maxDurationSec);\n });\n };\n\n el.onerror = () => {\n // Fail open on client (backend can enforce again).\n finish(true);\n };\n\n el.src = URL.createObjectURL(file);\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-upload-kind.d.ts","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC9C,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,UAAU,EAAE,CAAC;AAEtE;;;GAGG;AACH,MAAM,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"validate-upload-kind.d.ts","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC9C,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,UAAU,EAAE,CAAC;AAEtE;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GAAG,2BAA2B,GAAG,sBAAsB,CAAC;AAE/F,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,2BAA2B,EACjC,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,KACE,IAAI,CAAC;IACV;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,OAAO,CAAC,EAAE,yBAAyB,GAClC,IAAI,CAqCN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-upload-kind.js","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-upload-kind.js","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAyCpE,SAAS,gBAAgB,CAAC,QAAsB;IAC9C,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,wBAAwB,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,QAAkB,CAAC;AAC5B,CAAC;AAED,SAAS,eAAe,CAAC,QAAoB,EAAE,QAAsB;IACnE,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAQ,QAAkC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3F,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,8BAA8B,CAC5C,IAAU,EACV,UAAkB,EAClB,YAA0B,EAC1B,OAAmC;IAEnC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAE9C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,OAAO,EAAE,kBAAkB,KAAK,IAAI,EAAE,CAAC;YACzC,qEAAqE;YACrE,+CAA+C;YAC/C,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,2BAA2B,EAAE;YAC9C,UAAU;YACV,YAAY,EAAE,gBAAgB,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,sBAAsB,EAAE;YACzC,UAAU;YACV,YAAY,EAAE,gBAAgB,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,KAAK,CACb,0BAA0B,gBAAgB,CAAC,YAAY,CAAC,gCAAgC,QAAQ,GAAG,CACpG,CAAC;IACJ,CAAC;IAED,OAAO,yBAAyB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport { ensureFileWithContentType } from './infer-content-type.js';\n\ntype SingleKind = 'image' | 'video' | 'audio';\nexport type ExpectedKind = SingleKind | 'any' | readonly SingleKind[];\n\n/**\n * Diagnostic codes emitted by validateAndNormalizeUploadFile when validation\n * fails. Consumers can route these to monitoring/Sentry via the onError option.\n */\nexport type ValidateUploadFileErrorCode = 'upload_kind_indeterminate' | 'upload_kind_mismatch';\n\nexport interface ValidateUploadFileOptions {\n /**\n * Optional diagnostic callback fired before the function throws.\n * Useful for routing to Sentry / monitoring-core. The thrown Error\n * carries the user-facing message; this callback gets the machine\n * code + structured extras.\n */\n onError?: (\n code: ValidateUploadFileErrorCode,\n extra: {\n fileOrigin: string;\n expectedKind: string;\n detectedKind?: SingleKind | 'other';\n fileName: string;\n fileSize: number;\n fileType: string;\n },\n ) => void;\n /**\n * FAIL-OPEN for indeterminate picker metadata: when the file's kind cannot\n * be determined from browser MIME/extension, pass it through with the\n * NEUTRAL `application/octet-stream` type (never a fabricated media type)\n * instead of throwing, so the server's canonical byte inspector — the only\n * classification authority — decides. The kind-mismatch check still throws\n * when the client metadata IS determinate. Requires the upload path to opt\n * into `allowNeutralContentType`.\n */\n allowIndeterminate?: boolean;\n}\n\nfunction describeExpected(expected: ExpectedKind): string {\n if (expected === 'any') return 'image, video, or audio';\n if (Array.isArray(expected)) return expected.join(' or ');\n return expected as string;\n}\n\nfunction matchesExpected(detected: SingleKind, expected: ExpectedKind): boolean {\n if (expected === 'any') return true;\n if (Array.isArray(expected)) return (expected as readonly SingleKind[]).includes(detected);\n return detected === expected;\n}\n\n/**\n * Validates and normalizes a file for upload.\n *\n * - Throws a user-friendly Error if the file's kind cannot be determined\n * (unless `allowIndeterminate` fails open to the neutral content type).\n * - Throws a user-friendly Error if `expectedKind` is not 'any' and doesn't\n * match the detected kind.\n * - Otherwise returns a File with a guaranteed valid media contentType.\n *\n * Optional `onError` callback fires with a machine-readable code + structured\n * extras before the throw, so consumers can route to monitoring without this\n * package depending on any monitoring library.\n *\n * Use this at the top of every upload mutation, before uploadFileResumable.\n */\nexport function validateAndNormalizeUploadFile(\n file: File,\n fileOrigin: string,\n expectedKind: ExpectedKind,\n options?: ValidateUploadFileOptions,\n): File {\n const detected = getSimplifiedMediaType(file);\n\n if (detected === 'other') {\n if (options?.allowIndeterminate === true) {\n // No fallback kind: inferContentType resolves an unknown file to the\n // NEUTRAL type, never a fabricated media fact.\n return ensureFileWithContentType(file);\n }\n options?.onError?.('upload_kind_indeterminate', {\n fileOrigin,\n expectedKind: describeExpected(expectedKind),\n detectedKind: detected,\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type,\n });\n throw new Error(\n 'Could not determine the file type. Please try a different file or re-export it in a common format.',\n );\n }\n\n if (!matchesExpected(detected, expectedKind)) {\n options?.onError?.('upload_kind_mismatch', {\n fileOrigin,\n expectedKind: describeExpected(expectedKind),\n detectedKind: detected,\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type,\n });\n throw new Error(\n `This slot only accepts ${describeExpected(expectedKind)} files. The selected file is ${detected}.`,\n );\n }\n\n return ensureFileWithContentType(file, detected);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-bar.d.ts","sourceRoot":"","sources":["../src/progress-bar.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,8BAA8B,QAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-bar.js","sourceRoot":"","sources":["../src/progress-bar.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,GAAG,IAAI,CAAC","sourcesContent":["export const DEFAULT_PROGRESS_BAR_MIN_BYTES = 512 * 1024;\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { FileInputProps } from
|
|
2
|
-
export declare function AudioInput(props: Omit<FileInputProps,
|
|
1
|
+
import type { FileInputProps } from '../../types.js';
|
|
2
|
+
export declare function AudioInput(props: Omit<FileInputProps, 'acceptTypes'>): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=audio-input.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { FileInput } from
|
|
2
|
+
import { FileInput } from './file-input.js';
|
|
3
3
|
export function AudioInput(props) {
|
|
4
|
-
return _jsx(FileInput, { ...props, acceptTypes: [
|
|
4
|
+
return _jsx(FileInput, { ...props, acceptTypes: ['audio'] });
|
|
5
5
|
}
|
|
6
6
|
//# sourceMappingURL=audio-input.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-input.js","sourceRoot":"","sources":["../../../src/react/components/audio-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from
|
|
1
|
+
{"version":3,"file":"audio-input.js","sourceRoot":"","sources":["../../../src/react/components/audio-input.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAA0C;IACnE,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,GAAI,CAAC;AAC1D,CAAC","sourcesContent":["import type { FileInputProps } from '../../types.js';\nimport { FileInput } from './file-input.js';\n\nexport function AudioInput(props: Omit<FileInputProps, 'acceptTypes'>) {\n return <FileInput {...props} acceptTypes={['audio']} />;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/react/components/file-input.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgCrE,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BA0V9C"}
|