convex-cms 0.0.1
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/cli/commands/admin.d.ts +16 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +88 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/detectConvexUrl.d.ts +13 -0
- package/dist/cli/utils/detectConvexUrl.d.ts.map +1 -0
- package/dist/cli/utils/detectConvexUrl.js +48 -0
- package/dist/cli/utils/detectConvexUrl.js.map +1 -0
- package/dist/cli/utils/openBrowser.d.ts +7 -0
- package/dist/cli/utils/openBrowser.d.ts.map +1 -0
- package/dist/cli/utils/openBrowser.js +17 -0
- package/dist/cli/utils/openBrowser.js.map +1 -0
- package/dist/client/admin-config.d.ts +126 -0
- package/dist/client/admin-config.d.ts.map +1 -0
- package/dist/client/admin-config.js +117 -0
- package/dist/client/admin-config.js.map +1 -0
- package/dist/client/adminApi.d.ts +2273 -0
- package/dist/client/adminApi.d.ts.map +1 -0
- package/dist/client/adminApi.js +716 -0
- package/dist/client/adminApi.js.map +1 -0
- package/dist/client/agentTools.d.ts +933 -0
- package/dist/client/agentTools.d.ts.map +1 -0
- package/dist/client/agentTools.js +1004 -0
- package/dist/client/agentTools.js.map +1 -0
- package/dist/client/argTypes.d.ts +212 -0
- package/dist/client/argTypes.d.ts.map +1 -0
- package/dist/client/argTypes.js +5 -0
- package/dist/client/argTypes.js.map +1 -0
- package/dist/client/field-types.d.ts +55 -0
- package/dist/client/field-types.d.ts.map +1 -0
- package/dist/client/field-types.js +152 -0
- package/dist/client/field-types.js.map +1 -0
- package/dist/client/index.d.ts +189 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +668 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/queryBuilder.d.ts +765 -0
- package/dist/client/queryBuilder.d.ts.map +1 -0
- package/dist/client/queryBuilder.js +970 -0
- package/dist/client/queryBuilder.js.map +1 -0
- package/dist/client/schema/codegen.d.ts +128 -0
- package/dist/client/schema/codegen.d.ts.map +1 -0
- package/dist/client/schema/codegen.js +318 -0
- package/dist/client/schema/codegen.js.map +1 -0
- package/dist/client/schema/defineContentType.d.ts +221 -0
- package/dist/client/schema/defineContentType.d.ts.map +1 -0
- package/dist/client/schema/defineContentType.js +380 -0
- package/dist/client/schema/defineContentType.js.map +1 -0
- package/dist/client/schema/index.d.ts +85 -0
- package/dist/client/schema/index.d.ts.map +1 -0
- package/dist/client/schema/index.js +92 -0
- package/dist/client/schema/index.js.map +1 -0
- package/dist/client/schema/schemaDrift.d.ts +199 -0
- package/dist/client/schema/schemaDrift.d.ts.map +1 -0
- package/dist/client/schema/schemaDrift.js +340 -0
- package/dist/client/schema/schemaDrift.js.map +1 -0
- package/dist/client/schema/typedClient.d.ts +401 -0
- package/dist/client/schema/typedClient.d.ts.map +1 -0
- package/dist/client/schema/typedClient.js +269 -0
- package/dist/client/schema/typedClient.js.map +1 -0
- package/dist/client/schema/types.d.ts +477 -0
- package/dist/client/schema/types.d.ts.map +1 -0
- package/dist/client/schema/types.js +39 -0
- package/dist/client/schema/types.js.map +1 -0
- package/dist/client/types.d.ts +449 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +149 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/workflows.d.ts +51 -0
- package/dist/client/workflows.d.ts.map +1 -0
- package/dist/client/workflows.js +103 -0
- package/dist/client/workflows.js.map +1 -0
- package/dist/client/wrapper.d.ts +2198 -0
- package/dist/client/wrapper.d.ts.map +1 -0
- package/dist/client/wrapper.js +2651 -0
- package/dist/client/wrapper.js.map +1 -0
- package/dist/component/_generated/api.d.ts +124 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +4321 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/auditLog.d.ts +410 -0
- package/dist/component/auditLog.d.ts.map +1 -0
- package/dist/component/auditLog.js +607 -0
- package/dist/component/auditLog.js.map +1 -0
- package/dist/component/authorization.d.ts +323 -0
- package/dist/component/authorization.d.ts.map +1 -0
- package/dist/component/authorization.js +464 -0
- package/dist/component/authorization.js.map +1 -0
- package/dist/component/authorizationHooks.d.ts +184 -0
- package/dist/component/authorizationHooks.d.ts.map +1 -0
- package/dist/component/authorizationHooks.js +521 -0
- package/dist/component/authorizationHooks.js.map +1 -0
- package/dist/component/bulkOperations.d.ts +200 -0
- package/dist/component/bulkOperations.d.ts.map +1 -0
- package/dist/component/bulkOperations.js +568 -0
- package/dist/component/bulkOperations.js.map +1 -0
- package/dist/component/contentEntries.d.ts +719 -0
- package/dist/component/contentEntries.d.ts.map +1 -0
- package/dist/component/contentEntries.js +1617 -0
- package/dist/component/contentEntries.js.map +1 -0
- package/dist/component/contentEntryMutations.d.ts +505 -0
- package/dist/component/contentEntryMutations.d.ts.map +1 -0
- package/dist/component/contentEntryMutations.js +1009 -0
- package/dist/component/contentEntryMutations.js.map +1 -0
- package/dist/component/contentEntryValidation.d.ts +115 -0
- package/dist/component/contentEntryValidation.d.ts.map +1 -0
- package/dist/component/contentEntryValidation.js +546 -0
- package/dist/component/contentEntryValidation.js.map +1 -0
- package/dist/component/contentLock.d.ts +328 -0
- package/dist/component/contentLock.d.ts.map +1 -0
- package/dist/component/contentLock.js +471 -0
- package/dist/component/contentLock.js.map +1 -0
- package/dist/component/contentTypeMigration.d.ts +411 -0
- package/dist/component/contentTypeMigration.d.ts.map +1 -0
- package/dist/component/contentTypeMigration.js +805 -0
- package/dist/component/contentTypeMigration.js.map +1 -0
- package/dist/component/contentTypeMutations.d.ts +975 -0
- package/dist/component/contentTypeMutations.d.ts.map +1 -0
- package/dist/component/contentTypeMutations.js +768 -0
- package/dist/component/contentTypeMutations.js.map +1 -0
- package/dist/component/contentTypes.d.ts +538 -0
- package/dist/component/contentTypes.d.ts.map +1 -0
- package/dist/component/contentTypes.js +304 -0
- package/dist/component/contentTypes.js.map +1 -0
- package/dist/component/convex.config.d.ts +42 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +43 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/documentTypes.d.ts +186 -0
- package/dist/component/documentTypes.d.ts.map +1 -0
- package/dist/component/documentTypes.js +23 -0
- package/dist/component/documentTypes.js.map +1 -0
- package/dist/component/eventEmitter.d.ts +281 -0
- package/dist/component/eventEmitter.d.ts.map +1 -0
- package/dist/component/eventEmitter.js +300 -0
- package/dist/component/eventEmitter.js.map +1 -0
- package/dist/component/exportImport.d.ts +1120 -0
- package/dist/component/exportImport.d.ts.map +1 -0
- package/dist/component/exportImport.js +931 -0
- package/dist/component/exportImport.js.map +1 -0
- package/dist/component/index.d.ts +28 -0
- package/dist/component/index.d.ts.map +1 -0
- package/dist/component/index.js +142 -0
- package/dist/component/index.js.map +1 -0
- package/dist/component/lib/deepReferenceResolver.d.ts +252 -0
- package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -0
- package/dist/component/lib/deepReferenceResolver.js +601 -0
- package/dist/component/lib/deepReferenceResolver.js.map +1 -0
- package/dist/component/lib/errors.d.ts +306 -0
- package/dist/component/lib/errors.d.ts.map +1 -0
- package/dist/component/lib/errors.js +407 -0
- package/dist/component/lib/errors.js.map +1 -0
- package/dist/component/lib/index.d.ts +10 -0
- package/dist/component/lib/index.d.ts.map +1 -0
- package/dist/component/lib/index.js +33 -0
- package/dist/component/lib/index.js.map +1 -0
- package/dist/component/lib/mediaReferenceResolver.d.ts +217 -0
- package/dist/component/lib/mediaReferenceResolver.d.ts.map +1 -0
- package/dist/component/lib/mediaReferenceResolver.js +326 -0
- package/dist/component/lib/mediaReferenceResolver.js.map +1 -0
- package/dist/component/lib/metadataExtractor.d.ts +245 -0
- package/dist/component/lib/metadataExtractor.d.ts.map +1 -0
- package/dist/component/lib/metadataExtractor.js +548 -0
- package/dist/component/lib/metadataExtractor.js.map +1 -0
- package/dist/component/lib/mutationAuth.d.ts +95 -0
- package/dist/component/lib/mutationAuth.d.ts.map +1 -0
- package/dist/component/lib/mutationAuth.js +146 -0
- package/dist/component/lib/mutationAuth.js.map +1 -0
- package/dist/component/lib/queries.d.ts +17 -0
- package/dist/component/lib/queries.d.ts.map +1 -0
- package/dist/component/lib/queries.js +49 -0
- package/dist/component/lib/queries.js.map +1 -0
- package/dist/component/lib/ragContentChunker.d.ts +423 -0
- package/dist/component/lib/ragContentChunker.d.ts.map +1 -0
- package/dist/component/lib/ragContentChunker.js +897 -0
- package/dist/component/lib/ragContentChunker.js.map +1 -0
- package/dist/component/lib/referenceResolver.d.ts +175 -0
- package/dist/component/lib/referenceResolver.d.ts.map +1 -0
- package/dist/component/lib/referenceResolver.js +293 -0
- package/dist/component/lib/referenceResolver.js.map +1 -0
- package/dist/component/lib/slugGenerator.d.ts +71 -0
- package/dist/component/lib/slugGenerator.d.ts.map +1 -0
- package/dist/component/lib/slugGenerator.js +207 -0
- package/dist/component/lib/slugGenerator.js.map +1 -0
- package/dist/component/lib/slugUniqueness.d.ts +131 -0
- package/dist/component/lib/slugUniqueness.d.ts.map +1 -0
- package/dist/component/lib/slugUniqueness.js +229 -0
- package/dist/component/lib/slugUniqueness.js.map +1 -0
- package/dist/component/lib/softDelete.d.ts +18 -0
- package/dist/component/lib/softDelete.d.ts.map +1 -0
- package/dist/component/lib/softDelete.js +29 -0
- package/dist/component/lib/softDelete.js.map +1 -0
- package/dist/component/localeFallbackChain.d.ts +410 -0
- package/dist/component/localeFallbackChain.d.ts.map +1 -0
- package/dist/component/localeFallbackChain.js +467 -0
- package/dist/component/localeFallbackChain.js.map +1 -0
- package/dist/component/localeFields.d.ts +508 -0
- package/dist/component/localeFields.d.ts.map +1 -0
- package/dist/component/localeFields.js +592 -0
- package/dist/component/localeFields.js.map +1 -0
- package/dist/component/mediaAssetMutations.d.ts +235 -0
- package/dist/component/mediaAssetMutations.d.ts.map +1 -0
- package/dist/component/mediaAssetMutations.js +558 -0
- package/dist/component/mediaAssetMutations.js.map +1 -0
- package/dist/component/mediaAssets.d.ts +168 -0
- package/dist/component/mediaAssets.d.ts.map +1 -0
- package/dist/component/mediaAssets.js +618 -0
- package/dist/component/mediaAssets.js.map +1 -0
- package/dist/component/mediaFolderMutations.d.ts +642 -0
- package/dist/component/mediaFolderMutations.d.ts.map +1 -0
- package/dist/component/mediaFolderMutations.js +849 -0
- package/dist/component/mediaFolderMutations.js.map +1 -0
- package/dist/component/mediaUploadMutations.d.ts +136 -0
- package/dist/component/mediaUploadMutations.d.ts.map +1 -0
- package/dist/component/mediaUploadMutations.js +205 -0
- package/dist/component/mediaUploadMutations.js.map +1 -0
- package/dist/component/mediaVariantMutations.d.ts +468 -0
- package/dist/component/mediaVariantMutations.d.ts.map +1 -0
- package/dist/component/mediaVariantMutations.js +737 -0
- package/dist/component/mediaVariantMutations.js.map +1 -0
- package/dist/component/mediaVariants.d.ts +525 -0
- package/dist/component/mediaVariants.d.ts.map +1 -0
- package/dist/component/mediaVariants.js +661 -0
- package/dist/component/mediaVariants.js.map +1 -0
- package/dist/component/ragContentIndexer.d.ts +595 -0
- package/dist/component/ragContentIndexer.d.ts.map +1 -0
- package/dist/component/ragContentIndexer.js +794 -0
- package/dist/component/ragContentIndexer.js.map +1 -0
- package/dist/component/rateLimitHooks.d.ts +266 -0
- package/dist/component/rateLimitHooks.d.ts.map +1 -0
- package/dist/component/rateLimitHooks.js +412 -0
- package/dist/component/rateLimitHooks.js.map +1 -0
- package/dist/component/roles.d.ts +649 -0
- package/dist/component/roles.d.ts.map +1 -0
- package/dist/component/roles.js +884 -0
- package/dist/component/roles.js.map +1 -0
- package/dist/component/scheduledPublish.d.ts +182 -0
- package/dist/component/scheduledPublish.d.ts.map +1 -0
- package/dist/component/scheduledPublish.js +304 -0
- package/dist/component/scheduledPublish.js.map +1 -0
- package/dist/component/schema.d.ts +4114 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +469 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/taxonomies.d.ts +476 -0
- package/dist/component/taxonomies.d.ts.map +1 -0
- package/dist/component/taxonomies.js +785 -0
- package/dist/component/taxonomies.js.map +1 -0
- package/dist/component/taxonomyMutations.d.ts +206 -0
- package/dist/component/taxonomyMutations.d.ts.map +1 -0
- package/dist/component/taxonomyMutations.js +1001 -0
- package/dist/component/taxonomyMutations.js.map +1 -0
- package/dist/component/trash.d.ts +265 -0
- package/dist/component/trash.d.ts.map +1 -0
- package/dist/component/trash.js +621 -0
- package/dist/component/trash.js.map +1 -0
- package/dist/component/types.d.ts +4 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component/types.js +2 -0
- package/dist/component/types.js.map +1 -0
- package/dist/component/userContext.d.ts +508 -0
- package/dist/component/userContext.d.ts.map +1 -0
- package/dist/component/userContext.js +615 -0
- package/dist/component/userContext.js.map +1 -0
- package/dist/component/validation.d.ts +387 -0
- package/dist/component/validation.d.ts.map +1 -0
- package/dist/component/validation.js +1052 -0
- package/dist/component/validation.js.map +1 -0
- package/dist/component/validators.d.ts +4645 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +641 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/component/versionMutations.d.ts +216 -0
- package/dist/component/versionMutations.d.ts.map +1 -0
- package/dist/component/versionMutations.js +321 -0
- package/dist/component/versionMutations.js.map +1 -0
- package/dist/component/webhookTrigger.d.ts +770 -0
- package/dist/component/webhookTrigger.d.ts.map +1 -0
- package/dist/component/webhookTrigger.js +1413 -0
- package/dist/component/webhookTrigger.js.map +1 -0
- package/dist/react/index.d.ts +316 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +558 -0
- package/dist/react/index.js.map +1 -0
- package/dist/test.d.ts +2230 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +1107 -0
- package/dist/test.js.map +1 -0
- package/package.json +95 -0
- package/src/cli/commands/admin.ts +104 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/utils/detectConvexUrl.ts +54 -0
- package/src/cli/utils/openBrowser.ts +16 -0
- package/src/client/admin-config.ts +138 -0
- package/src/client/adminApi.ts +942 -0
- package/src/client/agentTools.ts +1311 -0
- package/src/client/argTypes.ts +316 -0
- package/src/client/field-types.ts +187 -0
- package/src/client/index.ts +1301 -0
- package/src/client/queryBuilder.ts +1100 -0
- package/src/client/schema/codegen.ts +500 -0
- package/src/client/schema/defineContentType.ts +501 -0
- package/src/client/schema/index.ts +169 -0
- package/src/client/schema/schemaDrift.ts +574 -0
- package/src/client/schema/typedClient.ts +688 -0
- package/src/client/schema/types.ts +666 -0
- package/src/client/types.ts +723 -0
- package/src/client/workflows.ts +141 -0
- package/src/client/wrapper.ts +4304 -0
- package/src/component/_generated/api.ts +140 -0
- package/src/component/_generated/component.ts +5029 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/authorization.ts +647 -0
- package/src/component/authorizationHooks.ts +668 -0
- package/src/component/bulkOperations.ts +687 -0
- package/src/component/contentEntries.ts +1976 -0
- package/src/component/contentEntryMutations.ts +1223 -0
- package/src/component/contentEntryValidation.ts +707 -0
- package/src/component/contentLock.ts +550 -0
- package/src/component/contentTypeMigration.ts +1064 -0
- package/src/component/contentTypeMutations.ts +969 -0
- package/src/component/contentTypes.ts +346 -0
- package/src/component/convex.config.ts +44 -0
- package/src/component/documentTypes.ts +240 -0
- package/src/component/eventEmitter.ts +485 -0
- package/src/component/exportImport.ts +1169 -0
- package/src/component/index.ts +491 -0
- package/src/component/lib/deepReferenceResolver.ts +999 -0
- package/src/component/lib/errors.ts +816 -0
- package/src/component/lib/index.ts +145 -0
- package/src/component/lib/mediaReferenceResolver.ts +495 -0
- package/src/component/lib/metadataExtractor.ts +792 -0
- package/src/component/lib/mutationAuth.ts +199 -0
- package/src/component/lib/queries.ts +79 -0
- package/src/component/lib/ragContentChunker.ts +1371 -0
- package/src/component/lib/referenceResolver.ts +430 -0
- package/src/component/lib/slugGenerator.ts +262 -0
- package/src/component/lib/slugUniqueness.ts +333 -0
- package/src/component/lib/softDelete.ts +44 -0
- package/src/component/localeFallbackChain.ts +673 -0
- package/src/component/localeFields.ts +896 -0
- package/src/component/mediaAssetMutations.ts +725 -0
- package/src/component/mediaAssets.ts +932 -0
- package/src/component/mediaFolderMutations.ts +1046 -0
- package/src/component/mediaUploadMutations.ts +224 -0
- package/src/component/mediaVariantMutations.ts +900 -0
- package/src/component/mediaVariants.ts +793 -0
- package/src/component/ragContentIndexer.ts +1067 -0
- package/src/component/rateLimitHooks.ts +572 -0
- package/src/component/roles.ts +1360 -0
- package/src/component/scheduledPublish.ts +358 -0
- package/src/component/schema.ts +617 -0
- package/src/component/taxonomies.ts +949 -0
- package/src/component/taxonomyMutations.ts +1210 -0
- package/src/component/trash.ts +724 -0
- package/src/component/userContext.ts +898 -0
- package/src/component/validation.ts +1388 -0
- package/src/component/validators.ts +949 -0
- package/src/component/versionMutations.ts +392 -0
- package/src/component/webhookTrigger.ts +1922 -0
- package/src/react/index.ts +898 -0
- package/src/test.ts +1580 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Metadata Extractor
|
|
3
|
+
*
|
|
4
|
+
* Internal utility functions for extracting and normalizing metadata from uploaded files.
|
|
5
|
+
* This module provides type-safe metadata extraction based on file MIME types.
|
|
6
|
+
*
|
|
7
|
+
* Since Convex functions don't have direct file access (files are in storage),
|
|
8
|
+
* this extractor works with:
|
|
9
|
+
* 1. Client-provided metadata (dimensions, duration from browser APIs)
|
|
10
|
+
* 2. MIME type analysis for capability detection
|
|
11
|
+
* 3. Filename analysis for additional hints
|
|
12
|
+
*
|
|
13
|
+
* The extractor normalizes and validates this information into a structured format
|
|
14
|
+
* that can be stored in the media asset's metadata field.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Type Definitions
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base metadata properties common to all media types.
|
|
23
|
+
*/
|
|
24
|
+
export interface BaseMetadata {
|
|
25
|
+
/** Original filename */
|
|
26
|
+
filename: string;
|
|
27
|
+
/** MIME type of the file */
|
|
28
|
+
mimeType: string;
|
|
29
|
+
/** File size in bytes */
|
|
30
|
+
size: number;
|
|
31
|
+
/** Classified media type */
|
|
32
|
+
mediaType: "image" | "video" | "audio" | "document" | "other";
|
|
33
|
+
/** File extension (lowercase, without dot) */
|
|
34
|
+
extension: string;
|
|
35
|
+
/** Whether this file type is typically web-compatible */
|
|
36
|
+
isWebCompatible: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extended metadata for image files.
|
|
41
|
+
*/
|
|
42
|
+
export interface ImageMetadata extends BaseMetadata {
|
|
43
|
+
mediaType: "image";
|
|
44
|
+
/** Width in pixels */
|
|
45
|
+
width?: number;
|
|
46
|
+
/** Height in pixels */
|
|
47
|
+
height?: number;
|
|
48
|
+
/** Aspect ratio (width / height) */
|
|
49
|
+
aspectRatio?: number;
|
|
50
|
+
/** Whether the format supports transparency */
|
|
51
|
+
supportsTransparency: boolean;
|
|
52
|
+
/** Whether this is a vector format (infinitely scalable) */
|
|
53
|
+
isVector: boolean;
|
|
54
|
+
/** Whether this format supports animation */
|
|
55
|
+
supportsAnimation: boolean;
|
|
56
|
+
/** Suggested image optimization format */
|
|
57
|
+
suggestedFormat?: "webp" | "avif" | "jpeg" | "png" | "svg";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extended metadata for video files.
|
|
62
|
+
*/
|
|
63
|
+
export interface VideoMetadata extends BaseMetadata {
|
|
64
|
+
mediaType: "video";
|
|
65
|
+
/** Width in pixels */
|
|
66
|
+
width?: number;
|
|
67
|
+
/** Height in pixels */
|
|
68
|
+
height?: number;
|
|
69
|
+
/** Aspect ratio (width / height) */
|
|
70
|
+
aspectRatio?: number;
|
|
71
|
+
/** Duration in seconds */
|
|
72
|
+
duration?: number;
|
|
73
|
+
/** Duration formatted as HH:MM:SS */
|
|
74
|
+
durationFormatted?: string;
|
|
75
|
+
/** Video codec (if known) */
|
|
76
|
+
codec?: string;
|
|
77
|
+
/** Whether the format is widely supported in browsers */
|
|
78
|
+
hasBroadBrowserSupport: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extended metadata for audio files.
|
|
83
|
+
*/
|
|
84
|
+
export interface AudioMetadata extends BaseMetadata {
|
|
85
|
+
mediaType: "audio";
|
|
86
|
+
/** Duration in seconds */
|
|
87
|
+
duration?: number;
|
|
88
|
+
/** Duration formatted as HH:MM:SS */
|
|
89
|
+
durationFormatted?: string;
|
|
90
|
+
/** Audio codec (if known) */
|
|
91
|
+
codec?: string;
|
|
92
|
+
/** Whether the format is widely supported in browsers */
|
|
93
|
+
hasBroadBrowserSupport: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extended metadata for document files.
|
|
98
|
+
*/
|
|
99
|
+
export interface DocumentMetadata extends BaseMetadata {
|
|
100
|
+
mediaType: "document";
|
|
101
|
+
/** Page count (if known, typically from PDF) */
|
|
102
|
+
pageCount?: number;
|
|
103
|
+
/** Document category */
|
|
104
|
+
documentCategory:
|
|
105
|
+
| "pdf"
|
|
106
|
+
| "word"
|
|
107
|
+
| "spreadsheet"
|
|
108
|
+
| "presentation"
|
|
109
|
+
| "text"
|
|
110
|
+
| "other";
|
|
111
|
+
/** Whether the document can be previewed in browser */
|
|
112
|
+
canPreviewInBrowser: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Metadata for unrecognized file types.
|
|
117
|
+
*/
|
|
118
|
+
export interface OtherMetadata extends BaseMetadata {
|
|
119
|
+
mediaType: "other";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Union type for all possible metadata shapes.
|
|
124
|
+
*/
|
|
125
|
+
export type ExtractedMetadata =
|
|
126
|
+
| ImageMetadata
|
|
127
|
+
| VideoMetadata
|
|
128
|
+
| AudioMetadata
|
|
129
|
+
| DocumentMetadata
|
|
130
|
+
| OtherMetadata;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Input parameters for metadata extraction.
|
|
134
|
+
*/
|
|
135
|
+
export interface MetadataExtractionInput {
|
|
136
|
+
/** Original filename */
|
|
137
|
+
filename: string;
|
|
138
|
+
/** MIME type of the file */
|
|
139
|
+
mimeType: string;
|
|
140
|
+
/** File size in bytes */
|
|
141
|
+
size: number;
|
|
142
|
+
/** Optional width in pixels (for images/videos, from client) */
|
|
143
|
+
width?: number;
|
|
144
|
+
/** Optional height in pixels (for images/videos, from client) */
|
|
145
|
+
height?: number;
|
|
146
|
+
/** Optional duration in seconds (for audio/video, from client) */
|
|
147
|
+
duration?: number;
|
|
148
|
+
/** Optional page count (for documents, from client) */
|
|
149
|
+
pageCount?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// =============================================================================
|
|
153
|
+
// MIME Type Mappings
|
|
154
|
+
// =============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* MIME types classified as images.
|
|
158
|
+
*/
|
|
159
|
+
const IMAGE_MIME_TYPES = new Set([
|
|
160
|
+
"image/jpeg",
|
|
161
|
+
"image/jpg",
|
|
162
|
+
"image/png",
|
|
163
|
+
"image/gif",
|
|
164
|
+
"image/webp",
|
|
165
|
+
"image/avif",
|
|
166
|
+
"image/svg+xml",
|
|
167
|
+
"image/bmp",
|
|
168
|
+
"image/tiff",
|
|
169
|
+
"image/x-icon",
|
|
170
|
+
"image/ico",
|
|
171
|
+
"image/heic",
|
|
172
|
+
"image/heif",
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* MIME types classified as videos.
|
|
177
|
+
*/
|
|
178
|
+
const VIDEO_MIME_TYPES = new Set([
|
|
179
|
+
"video/mp4",
|
|
180
|
+
"video/webm",
|
|
181
|
+
"video/ogg",
|
|
182
|
+
"video/quicktime",
|
|
183
|
+
"video/x-msvideo",
|
|
184
|
+
"video/mpeg",
|
|
185
|
+
"video/3gpp",
|
|
186
|
+
"video/x-matroska",
|
|
187
|
+
"video/x-flv",
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* MIME types classified as audio.
|
|
192
|
+
*/
|
|
193
|
+
const AUDIO_MIME_TYPES = new Set([
|
|
194
|
+
"audio/mpeg",
|
|
195
|
+
"audio/mp3",
|
|
196
|
+
"audio/ogg",
|
|
197
|
+
"audio/wav",
|
|
198
|
+
"audio/webm",
|
|
199
|
+
"audio/aac",
|
|
200
|
+
"audio/flac",
|
|
201
|
+
"audio/x-m4a",
|
|
202
|
+
"audio/mp4",
|
|
203
|
+
"audio/x-wav",
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* MIME types classified as documents.
|
|
208
|
+
*/
|
|
209
|
+
const DOCUMENT_MIME_TYPES = new Set([
|
|
210
|
+
"application/pdf",
|
|
211
|
+
"application/msword",
|
|
212
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
213
|
+
"application/vnd.ms-excel",
|
|
214
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
215
|
+
"application/vnd.ms-powerpoint",
|
|
216
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
217
|
+
"text/plain",
|
|
218
|
+
"text/csv",
|
|
219
|
+
"text/markdown",
|
|
220
|
+
"application/rtf",
|
|
221
|
+
"application/json",
|
|
222
|
+
"application/xml",
|
|
223
|
+
"text/xml",
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* MIME types that support transparency.
|
|
228
|
+
*/
|
|
229
|
+
const TRANSPARENT_MIME_TYPES = new Set([
|
|
230
|
+
"image/png",
|
|
231
|
+
"image/webp",
|
|
232
|
+
"image/gif",
|
|
233
|
+
"image/avif",
|
|
234
|
+
"image/svg+xml",
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Vector image MIME types.
|
|
239
|
+
*/
|
|
240
|
+
const VECTOR_MIME_TYPES = new Set(["image/svg+xml"]);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* MIME types that support animation.
|
|
244
|
+
*/
|
|
245
|
+
const ANIMATED_MIME_TYPES = new Set([
|
|
246
|
+
"image/gif",
|
|
247
|
+
"image/webp",
|
|
248
|
+
"image/avif",
|
|
249
|
+
"image/png", // APNG
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Web-compatible image MIME types (work natively in all modern browsers).
|
|
254
|
+
*/
|
|
255
|
+
const WEB_COMPATIBLE_IMAGE_TYPES = new Set([
|
|
256
|
+
"image/jpeg",
|
|
257
|
+
"image/jpg",
|
|
258
|
+
"image/png",
|
|
259
|
+
"image/gif",
|
|
260
|
+
"image/webp",
|
|
261
|
+
"image/svg+xml",
|
|
262
|
+
"image/avif",
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Web-compatible video MIME types.
|
|
267
|
+
*/
|
|
268
|
+
const WEB_COMPATIBLE_VIDEO_TYPES = new Set([
|
|
269
|
+
"video/mp4",
|
|
270
|
+
"video/webm",
|
|
271
|
+
"video/ogg",
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Web-compatible audio MIME types.
|
|
276
|
+
*/
|
|
277
|
+
const WEB_COMPATIBLE_AUDIO_TYPES = new Set([
|
|
278
|
+
"audio/mpeg",
|
|
279
|
+
"audio/mp3",
|
|
280
|
+
"audio/ogg",
|
|
281
|
+
"audio/wav",
|
|
282
|
+
"audio/webm",
|
|
283
|
+
"audio/aac",
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
// =============================================================================
|
|
287
|
+
// Helper Functions
|
|
288
|
+
// =============================================================================
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Extracts the file extension from a filename.
|
|
292
|
+
*
|
|
293
|
+
* @param filename - The filename to extract the extension from
|
|
294
|
+
* @returns The lowercase extension without the dot, or empty string if none
|
|
295
|
+
*/
|
|
296
|
+
export function extractExtension(filename: string): string {
|
|
297
|
+
const lastDot = filename.lastIndexOf(".");
|
|
298
|
+
if (lastDot === -1 || lastDot === filename.length - 1) {
|
|
299
|
+
return "";
|
|
300
|
+
}
|
|
301
|
+
return filename.slice(lastDot + 1).toLowerCase();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Classifies a MIME type into a media category.
|
|
306
|
+
*
|
|
307
|
+
* @param mimeType - The MIME type to classify
|
|
308
|
+
* @returns The media type category
|
|
309
|
+
*/
|
|
310
|
+
export function classifyMimeType(
|
|
311
|
+
mimeType: string
|
|
312
|
+
): "image" | "video" | "audio" | "document" | "other" {
|
|
313
|
+
const normalizedMime = mimeType.toLowerCase();
|
|
314
|
+
|
|
315
|
+
if (IMAGE_MIME_TYPES.has(normalizedMime)) {
|
|
316
|
+
return "image";
|
|
317
|
+
}
|
|
318
|
+
if (VIDEO_MIME_TYPES.has(normalizedMime)) {
|
|
319
|
+
return "video";
|
|
320
|
+
}
|
|
321
|
+
if (AUDIO_MIME_TYPES.has(normalizedMime)) {
|
|
322
|
+
return "audio";
|
|
323
|
+
}
|
|
324
|
+
if (DOCUMENT_MIME_TYPES.has(normalizedMime)) {
|
|
325
|
+
return "document";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Fallback: check MIME type prefix
|
|
329
|
+
if (normalizedMime.startsWith("image/")) {
|
|
330
|
+
return "image";
|
|
331
|
+
}
|
|
332
|
+
if (normalizedMime.startsWith("video/")) {
|
|
333
|
+
return "video";
|
|
334
|
+
}
|
|
335
|
+
if (normalizedMime.startsWith("audio/")) {
|
|
336
|
+
return "audio";
|
|
337
|
+
}
|
|
338
|
+
if (
|
|
339
|
+
normalizedMime.startsWith("text/") ||
|
|
340
|
+
normalizedMime.startsWith("application/")
|
|
341
|
+
) {
|
|
342
|
+
// Check for common document patterns
|
|
343
|
+
if (
|
|
344
|
+
normalizedMime.includes("document") ||
|
|
345
|
+
normalizedMime.includes("sheet") ||
|
|
346
|
+
normalizedMime.includes("presentation")
|
|
347
|
+
) {
|
|
348
|
+
return "document";
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return "other";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Calculates aspect ratio from dimensions.
|
|
357
|
+
*
|
|
358
|
+
* @param width - Width in pixels
|
|
359
|
+
* @param height - Height in pixels
|
|
360
|
+
* @returns Aspect ratio rounded to 3 decimal places, or undefined if invalid
|
|
361
|
+
*/
|
|
362
|
+
export function calculateAspectRatio(
|
|
363
|
+
width?: number,
|
|
364
|
+
height?: number
|
|
365
|
+
): number | undefined {
|
|
366
|
+
if (!width || !height || height <= 0 || width <= 0) {
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
return Math.round((width / height) * 1000) / 1000;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Formats duration in seconds to HH:MM:SS format.
|
|
374
|
+
*
|
|
375
|
+
* @param seconds - Duration in seconds
|
|
376
|
+
* @returns Formatted duration string, or undefined if invalid
|
|
377
|
+
*/
|
|
378
|
+
export function formatDuration(seconds?: number): string | undefined {
|
|
379
|
+
if (seconds === undefined || seconds < 0 || !Number.isFinite(seconds)) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const hours = Math.floor(seconds / 3600);
|
|
384
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
385
|
+
const secs = Math.floor(seconds % 60);
|
|
386
|
+
|
|
387
|
+
if (hours > 0) {
|
|
388
|
+
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
389
|
+
}
|
|
390
|
+
return `${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Determines the optimal image format for web delivery.
|
|
395
|
+
*
|
|
396
|
+
* @param mimeType - Original MIME type
|
|
397
|
+
* @param supportsTransparency - Whether transparency is needed
|
|
398
|
+
* @returns Suggested format for optimization
|
|
399
|
+
*/
|
|
400
|
+
export function suggestImageFormat(
|
|
401
|
+
mimeType: string,
|
|
402
|
+
supportsTransparency: boolean
|
|
403
|
+
): "webp" | "avif" | "jpeg" | "png" | "svg" | undefined {
|
|
404
|
+
const normalizedMime = mimeType.toLowerCase();
|
|
405
|
+
|
|
406
|
+
// SVG stays as SVG (vector)
|
|
407
|
+
if (normalizedMime === "image/svg+xml") {
|
|
408
|
+
return "svg";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// For photos/complex images, suggest modern formats
|
|
412
|
+
if (supportsTransparency) {
|
|
413
|
+
return "webp"; // WebP supports transparency and has broad support
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// For photos without transparency, AVIF or WebP
|
|
417
|
+
if (normalizedMime === "image/jpeg" || normalizedMime === "image/jpg") {
|
|
418
|
+
return "webp"; // Better compression than JPEG
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return "webp"; // Default to WebP for best compatibility
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Categorizes document MIME types into subcategories.
|
|
426
|
+
*
|
|
427
|
+
* @param mimeType - The document MIME type
|
|
428
|
+
* @returns Document category
|
|
429
|
+
*/
|
|
430
|
+
export function categorizeDocument(
|
|
431
|
+
mimeType: string
|
|
432
|
+
): "pdf" | "word" | "spreadsheet" | "presentation" | "text" | "other" {
|
|
433
|
+
const normalizedMime = mimeType.toLowerCase();
|
|
434
|
+
|
|
435
|
+
if (normalizedMime === "application/pdf") {
|
|
436
|
+
return "pdf";
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (
|
|
440
|
+
normalizedMime === "application/msword" ||
|
|
441
|
+
normalizedMime.includes("wordprocessingml")
|
|
442
|
+
) {
|
|
443
|
+
return "word";
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
normalizedMime === "application/vnd.ms-excel" ||
|
|
448
|
+
normalizedMime.includes("spreadsheetml") ||
|
|
449
|
+
normalizedMime === "text/csv"
|
|
450
|
+
) {
|
|
451
|
+
return "spreadsheet";
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (
|
|
455
|
+
normalizedMime === "application/vnd.ms-powerpoint" ||
|
|
456
|
+
normalizedMime.includes("presentationml")
|
|
457
|
+
) {
|
|
458
|
+
return "presentation";
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (
|
|
462
|
+
normalizedMime.startsWith("text/") ||
|
|
463
|
+
normalizedMime === "application/json" ||
|
|
464
|
+
normalizedMime === "application/xml"
|
|
465
|
+
) {
|
|
466
|
+
return "text";
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return "other";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Determines if a document can be previewed in the browser.
|
|
474
|
+
*
|
|
475
|
+
* @param mimeType - The document MIME type
|
|
476
|
+
* @returns Whether the document can be previewed
|
|
477
|
+
*/
|
|
478
|
+
export function canPreviewDocument(mimeType: string): boolean {
|
|
479
|
+
const normalizedMime = mimeType.toLowerCase();
|
|
480
|
+
|
|
481
|
+
// PDFs can be previewed in most browsers
|
|
482
|
+
if (normalizedMime === "application/pdf") {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Text-based formats can be displayed
|
|
487
|
+
if (normalizedMime.startsWith("text/")) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// JSON and XML can be displayed
|
|
492
|
+
if (
|
|
493
|
+
normalizedMime === "application/json" ||
|
|
494
|
+
normalizedMime === "application/xml" ||
|
|
495
|
+
normalizedMime === "text/xml"
|
|
496
|
+
) {
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// =============================================================================
|
|
504
|
+
// Main Extraction Function
|
|
505
|
+
// =============================================================================
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Extracts and normalizes metadata from uploaded file information.
|
|
509
|
+
*
|
|
510
|
+
* This is the main function to call for metadata extraction. It analyzes
|
|
511
|
+
* the MIME type, filename, and any provided dimensions/duration to build
|
|
512
|
+
* a comprehensive metadata object specific to the file type.
|
|
513
|
+
*
|
|
514
|
+
* @param input - The file information to extract metadata from
|
|
515
|
+
* @returns Structured metadata object with type-specific properties
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```typescript
|
|
519
|
+
* // Image with dimensions from browser
|
|
520
|
+
* const imageMetadata = extractMetadata({
|
|
521
|
+
* filename: "photo.jpg",
|
|
522
|
+
* mimeType: "image/jpeg",
|
|
523
|
+
* size: 500000,
|
|
524
|
+
* width: 1920,
|
|
525
|
+
* height: 1080,
|
|
526
|
+
* });
|
|
527
|
+
* // Returns ImageMetadata with aspectRatio, suggestedFormat, etc.
|
|
528
|
+
*
|
|
529
|
+
* // Video with duration from browser
|
|
530
|
+
* const videoMetadata = extractMetadata({
|
|
531
|
+
* filename: "clip.mp4",
|
|
532
|
+
* mimeType: "video/mp4",
|
|
533
|
+
* size: 10000000,
|
|
534
|
+
* width: 1920,
|
|
535
|
+
* height: 1080,
|
|
536
|
+
* duration: 120,
|
|
537
|
+
* });
|
|
538
|
+
* // Returns VideoMetadata with durationFormatted, hasBroadBrowserSupport, etc.
|
|
539
|
+
*
|
|
540
|
+
* // Document
|
|
541
|
+
* const docMetadata = extractMetadata({
|
|
542
|
+
* filename: "report.pdf",
|
|
543
|
+
* mimeType: "application/pdf",
|
|
544
|
+
* size: 2000000,
|
|
545
|
+
* pageCount: 15,
|
|
546
|
+
* });
|
|
547
|
+
* // Returns DocumentMetadata with documentCategory, canPreviewInBrowser, etc.
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
export function extractMetadata(input: MetadataExtractionInput): ExtractedMetadata {
|
|
551
|
+
const { filename, mimeType, size, width, height, duration, pageCount } = input;
|
|
552
|
+
|
|
553
|
+
const normalizedMime = mimeType.toLowerCase();
|
|
554
|
+
const extension = extractExtension(filename);
|
|
555
|
+
const mediaType = classifyMimeType(normalizedMime);
|
|
556
|
+
|
|
557
|
+
// Build base metadata
|
|
558
|
+
const base: BaseMetadata = {
|
|
559
|
+
filename,
|
|
560
|
+
mimeType: normalizedMime,
|
|
561
|
+
size,
|
|
562
|
+
mediaType,
|
|
563
|
+
extension,
|
|
564
|
+
isWebCompatible: false, // Will be set by type-specific logic
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
switch (mediaType) {
|
|
568
|
+
case "image":
|
|
569
|
+
return extractImageMetadata(base, normalizedMime, width, height);
|
|
570
|
+
|
|
571
|
+
case "video":
|
|
572
|
+
return extractVideoMetadata(base, normalizedMime, width, height, duration);
|
|
573
|
+
|
|
574
|
+
case "audio":
|
|
575
|
+
return extractAudioMetadata(base, normalizedMime, duration);
|
|
576
|
+
|
|
577
|
+
case "document":
|
|
578
|
+
return extractDocumentMetadata(base, normalizedMime, pageCount);
|
|
579
|
+
|
|
580
|
+
default:
|
|
581
|
+
return {
|
|
582
|
+
...base,
|
|
583
|
+
mediaType: "other",
|
|
584
|
+
isWebCompatible: false,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Extracts image-specific metadata.
|
|
591
|
+
*/
|
|
592
|
+
function extractImageMetadata(
|
|
593
|
+
base: BaseMetadata,
|
|
594
|
+
mimeType: string,
|
|
595
|
+
width?: number,
|
|
596
|
+
height?: number
|
|
597
|
+
): ImageMetadata {
|
|
598
|
+
const supportsTransparency = TRANSPARENT_MIME_TYPES.has(mimeType);
|
|
599
|
+
const isVector = VECTOR_MIME_TYPES.has(mimeType);
|
|
600
|
+
const supportsAnimation = ANIMATED_MIME_TYPES.has(mimeType);
|
|
601
|
+
const isWebCompatible = WEB_COMPATIBLE_IMAGE_TYPES.has(mimeType);
|
|
602
|
+
|
|
603
|
+
const metadata: ImageMetadata = {
|
|
604
|
+
...base,
|
|
605
|
+
mediaType: "image",
|
|
606
|
+
isWebCompatible,
|
|
607
|
+
supportsTransparency,
|
|
608
|
+
isVector,
|
|
609
|
+
supportsAnimation,
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
if (width !== undefined && width > 0) {
|
|
613
|
+
metadata.width = width;
|
|
614
|
+
}
|
|
615
|
+
if (height !== undefined && height > 0) {
|
|
616
|
+
metadata.height = height;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const aspectRatio = calculateAspectRatio(width, height);
|
|
620
|
+
if (aspectRatio !== undefined) {
|
|
621
|
+
metadata.aspectRatio = aspectRatio;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const suggestedFormat = suggestImageFormat(mimeType, supportsTransparency);
|
|
625
|
+
if (suggestedFormat) {
|
|
626
|
+
metadata.suggestedFormat = suggestedFormat;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return metadata;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Extracts video-specific metadata.
|
|
634
|
+
*/
|
|
635
|
+
function extractVideoMetadata(
|
|
636
|
+
base: BaseMetadata,
|
|
637
|
+
mimeType: string,
|
|
638
|
+
width?: number,
|
|
639
|
+
height?: number,
|
|
640
|
+
duration?: number
|
|
641
|
+
): VideoMetadata {
|
|
642
|
+
const hasBroadBrowserSupport = WEB_COMPATIBLE_VIDEO_TYPES.has(mimeType);
|
|
643
|
+
const isWebCompatible = hasBroadBrowserSupport;
|
|
644
|
+
|
|
645
|
+
const metadata: VideoMetadata = {
|
|
646
|
+
...base,
|
|
647
|
+
mediaType: "video",
|
|
648
|
+
isWebCompatible,
|
|
649
|
+
hasBroadBrowserSupport,
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
if (width !== undefined && width > 0) {
|
|
653
|
+
metadata.width = width;
|
|
654
|
+
}
|
|
655
|
+
if (height !== undefined && height > 0) {
|
|
656
|
+
metadata.height = height;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const aspectRatio = calculateAspectRatio(width, height);
|
|
660
|
+
if (aspectRatio !== undefined) {
|
|
661
|
+
metadata.aspectRatio = aspectRatio;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (duration !== undefined && duration >= 0) {
|
|
665
|
+
metadata.duration = duration;
|
|
666
|
+
metadata.durationFormatted = formatDuration(duration);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Infer codec from MIME type
|
|
670
|
+
if (mimeType === "video/mp4") {
|
|
671
|
+
metadata.codec = "H.264/AVC";
|
|
672
|
+
} else if (mimeType === "video/webm") {
|
|
673
|
+
metadata.codec = "VP8/VP9";
|
|
674
|
+
} else if (mimeType === "video/ogg") {
|
|
675
|
+
metadata.codec = "Theora";
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return metadata;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Extracts audio-specific metadata.
|
|
683
|
+
*/
|
|
684
|
+
function extractAudioMetadata(
|
|
685
|
+
base: BaseMetadata,
|
|
686
|
+
mimeType: string,
|
|
687
|
+
duration?: number
|
|
688
|
+
): AudioMetadata {
|
|
689
|
+
const hasBroadBrowserSupport = WEB_COMPATIBLE_AUDIO_TYPES.has(mimeType);
|
|
690
|
+
const isWebCompatible = hasBroadBrowserSupport;
|
|
691
|
+
|
|
692
|
+
const metadata: AudioMetadata = {
|
|
693
|
+
...base,
|
|
694
|
+
mediaType: "audio",
|
|
695
|
+
isWebCompatible,
|
|
696
|
+
hasBroadBrowserSupport,
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
if (duration !== undefined && duration >= 0) {
|
|
700
|
+
metadata.duration = duration;
|
|
701
|
+
metadata.durationFormatted = formatDuration(duration);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Infer codec from MIME type
|
|
705
|
+
if (mimeType === "audio/mpeg" || mimeType === "audio/mp3") {
|
|
706
|
+
metadata.codec = "MP3";
|
|
707
|
+
} else if (mimeType === "audio/ogg") {
|
|
708
|
+
metadata.codec = "Vorbis";
|
|
709
|
+
} else if (mimeType === "audio/wav" || mimeType === "audio/x-wav") {
|
|
710
|
+
metadata.codec = "PCM";
|
|
711
|
+
} else if (mimeType === "audio/aac") {
|
|
712
|
+
metadata.codec = "AAC";
|
|
713
|
+
} else if (mimeType === "audio/flac") {
|
|
714
|
+
metadata.codec = "FLAC";
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return metadata;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Extracts document-specific metadata.
|
|
722
|
+
*/
|
|
723
|
+
function extractDocumentMetadata(
|
|
724
|
+
base: BaseMetadata,
|
|
725
|
+
mimeType: string,
|
|
726
|
+
pageCount?: number
|
|
727
|
+
): DocumentMetadata {
|
|
728
|
+
const documentCategory = categorizeDocument(mimeType);
|
|
729
|
+
const canPreview = canPreviewDocument(mimeType);
|
|
730
|
+
|
|
731
|
+
const metadata: DocumentMetadata = {
|
|
732
|
+
...base,
|
|
733
|
+
mediaType: "document",
|
|
734
|
+
isWebCompatible: canPreview,
|
|
735
|
+
documentCategory,
|
|
736
|
+
canPreviewInBrowser: canPreview,
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
if (pageCount !== undefined && pageCount > 0) {
|
|
740
|
+
metadata.pageCount = pageCount;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return metadata;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// =============================================================================
|
|
747
|
+
// Validation Helpers
|
|
748
|
+
// =============================================================================
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Validates that dimensions are positive integers.
|
|
752
|
+
*
|
|
753
|
+
* @param width - Width to validate
|
|
754
|
+
* @param height - Height to validate
|
|
755
|
+
* @returns Whether both dimensions are valid
|
|
756
|
+
*/
|
|
757
|
+
export function validateDimensions(width?: number, height?: number): boolean {
|
|
758
|
+
if (width !== undefined) {
|
|
759
|
+
if (!Number.isInteger(width) || width <= 0) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (height !== undefined) {
|
|
764
|
+
if (!Number.isInteger(height) || height <= 0) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Validates that duration is a non-negative number.
|
|
773
|
+
*
|
|
774
|
+
* @param duration - Duration to validate
|
|
775
|
+
* @returns Whether the duration is valid
|
|
776
|
+
*/
|
|
777
|
+
export function validateDuration(duration?: number): boolean {
|
|
778
|
+
if (duration === undefined) {
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
return Number.isFinite(duration) && duration >= 0;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Validates that file size is a positive integer.
|
|
786
|
+
*
|
|
787
|
+
* @param size - File size to validate
|
|
788
|
+
* @returns Whether the size is valid
|
|
789
|
+
*/
|
|
790
|
+
export function validateFileSize(size: number): boolean {
|
|
791
|
+
return Number.isInteger(size) && size > 0;
|
|
792
|
+
}
|