ExifCleaner v4.0: Full modernization (#270)
* Add Claude Code project memory for modernization
Create CLAUDE.md with full architecture map, tech stack, commands,
dependency inventory, and build/packaging docs. Add .claude/rules/
with modernization roadmap (8-phase plan) and GitHub context summary
(64 open issues, 8 open PRs categorized). Fix .gitignore to allow
.claude/ directory through the blanket .* rule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Enhance CLAUDE.md with directory reference, build procedures, code patterns
Add directory tree for .resources/, build/, static/, src/ structure.
Document exact dev/compile/pack/release/exiftool-update workflows.
Add code patterns section covering exports, async, DOM, IPC, platform
guards, TypeScript idioms, CSS theming, and i18n conventions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add design vision and dependency philosophy to project memory
Update modernization roadmap: strengthen Phase 7 with hand-roll-first
dependency philosophy, add Phase 9 (UI/UX design overhaul with BEM CSS,
system fonts, micro-animations, native feel), update key constraints.
Update CLAUDE.md conventions with BEM direction, system fonts, and
zero-dependency target.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Swap build system from electron-webpack to electron-vite
Replace abandoned electron-webpack (webpack 4) with electron-vite (Vite 7)
to unblock TypeScript and Electron upgrades.
Changes:
- Remove electron-webpack, electron-webpack-ts, webpack
- Add electron-vite 5.0 and vite 7.3
- Create electron.vite.config.ts with renderer externalization for
nodeIntegration compatibility (temporary until Electron upgrade)
- Rewrite tsconfig.json as standalone (no longer extends electron-webpack)
- Extract IPC event constants to src/common/ipc_events.ts to avoid
cross-process import chains that break Vite's bundling
- Update window_setup.ts to use ELECTRON_RENDERER_URL (replaces
ELECTRON_WEBPACK_WDS_PORT)
- Remove webpack-specific module.hot HMR blocks from both entry points
- Add ELECTRON_RUN_AS_NODE= prefix to dev/start scripts to prevent
Cursor IDE environment contamination
- Delete dead code: electron-webpack.json, src/common/app.ts
- Add safety rule and Docker packaging note to project memory
Verified: tsc --noEmit, electron-vite build, yarn dev, yarn packmactest
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Upgrade TypeScript 3.8 → 5.7 with strict mode, Prettier 3.x
- TypeScript 5.7.3 with strict: true (strictNullChecks, noImplicitAny, etc.)
- tsconfig: target ES2021, module ESNext, moduleResolution bundler
- Prettier 3.8.1 (trailing commas now default to "all")
- Clean up node-exiftool .d.ts: 115 → 31 lines, named result interfaces
- Zero strict mode errors — codebase was already clean
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Playwright e2e, DDD architecture, and CI releases to roadmap
- Playwright e2e tests — deterministic, fast quality gate
- DDD/hexagonal architecture — SRP, DI, functional core
- CI releases with no auto-update (privacy-first)
- Added key constraints: never auto-publish, never auto-update
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Upgrade Electron 11 → 35 with contextIsolation, preload, and sandbox
Move all Node.js/exiftool operations from renderer to main process.
Renderer is now a fully sandboxed browser tab that accesses exiftool,
i18n, and file operations exclusively through window.api (contextBridge).
Three-phase migration:
- Phase A: Upgrade Electron runtime + @types/node, fix API changes
- Phase B: Create preload script, exif IPC handlers, rewire renderer
- Phase C: Enable nodeIntegration: false, contextIsolation: true, sandbox: true
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Enable ESM modules: verbatimModuleSyntax + type: module
- Add verbatimModuleSyntax to tsconfig.json, enforcing import type
- Fix 10 files with type-only imports from electron
- Add "type": "module" to package.json for ESM output
- Update preload path to .mjs (electron-vite ESM output)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reorder modernization roadmap: local quality first, infrastructure later
Mark Phases 1-3 done (build system, core deps, ESM). Reorder remaining
phases per maintainer priorities: verify + cleanup, Playwright tests,
DDD refactor, community features, UI/UX overhaul — then deferred
release infrastructure (CI, dep cleanup, exiftool update, signing).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add QA runbook for Phase 4: Verify + Cleanup
Comprehensive manual testing guide for validating the modernized app
on Apple Silicon Mac, covering dev setup, i18n diagnosis, 47 test
cases across 10 feature areas, and cleanup tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix update_exiftool.pl for new ExifTool Windows distribution format
The Windows zip now extracts to a versioned subdirectory (e.g.
exiftool-13.50_32/) and includes a companion exiftool_files/ dir
alongside the exe stub. Updated copy_windows_binary to handle the
new path structure and copy both the exe and companion directory.
Also fixed undef $ARGV[0] warning when run without arguments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix preload script: output as CJS for Electron sandbox compatibility
Electron's sandboxed preload context does not support ESM imports.
With "type": "module" in package.json, electron-vite was outputting
the preload as index.mjs (ESM), causing "Cannot use import statement
outside a module" at runtime. Force preload output to CJS format and
update the preload path from index.mjs to index.cjs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add dev:debug script and Chrome DevTools MCP documentation
Add yarn dev:debug script for launching with --remote-debugging-port=9222.
Document Chrome DevTools MCP setup in CLAUDE.md. Fix outdated preload
output path (index.cjs not index.mjs) and module style description.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix drag-and-drop: use webUtils.getPathForFile() for Electron 35
The deprecated file.path property is undefined in Electron 35's sandboxed
renderer. Expose webUtils.getPathForFile() through the preload API and
update the drag handler to use it instead of the removed file.path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Achieve zero production dependencies
Phase 4 cleanup:
- Remove source-map-support dependency (never imported, obsolete with Node 22)
Chunk 6: Hand-roll ExifTool wrapper
- Create src/infrastructure/exiftool/ with ExiftoolProcess class (~240 lines)
- Implement -stay_open protocol with command queue and stdout buffering
- Replace node-exiftool import in src/main/exif_handlers.ts
- Remove src/types/node-exiftool/index.d.ts (types now in infrastructure layer)
- Remove node-exiftool dependency from package.json
package.json now has "dependencies": {} - zero production dependencies achieved.
All external code is either in devDependencies (build tools) or hand-rolled.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Update documentation for Phase 4 and Chunk 6 completion
CLAUDE.md updates:
- Tech Stack: Document zero production dependencies and hand-rolled ExifTool wrapper
- Architecture: Add Infrastructure layer section (src/infrastructure/exiftool/)
- Directory Reference: Add infrastructure/, remove types/
- Dependencies: Replace table with zero-deps statement
- Code Patterns: Remove node-exiftool type definitions reference
- Current State: Mark Phase 4 and Chunk 6 as complete
Modernization roadmap updates:
- Phase 4 (Verify + Cleanup): Mark as DONE, add completed tasks
- Phase 6 (DDD Refactor): Note infrastructure layer started
- Phase 10 (Dependency Cleanup): Mark as DONE (completed out of order)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix ExifTool v13.50 compatibility (Phase 11)
Two breaking changes in ExifTool v13.50 required fixes:
1. Write operations no longer return JSON
- Removed -json flag from writeMetadata()
- Added plain text parser for write responses
- Fixed "Unexpected end of JSON input" error
2. Charset argument deprecated
- Removed -charset filename=UTF8 from args
- Unicode filenames still work (v13.50 handles UTF-8 natively)
- Eliminated "Tag 'charset' is not defined" warnings
Files modified:
- src/main/exif_handlers.ts: Remove charset args
- src/infrastructure/exiftool/ExiftoolProcess.ts: Handle text responses
Testing:
- Automated: Chrome DevTools MCP tests pass
- Manual: Unicode filenames work, no stderr warnings
- ExifTool v13.50 confirmed working
Security: Fixes CVE-2021-22204 (4.75 years of patches)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix type safety: replace any with ExifData, string throws with Error objects
- Replace `any` with `ExifData` in table_update_row.ts (2 occurrences)
- Replace string throws with `new Error()` across 8 files (10 instances)
- Add invariant comment for non-null assertion in ExiftoolProcess.ts
Part of Phase 9 DDD architecture refactor (Step 3: type safety fixes).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract domain layer with pure business logic
- Create src/domain/exif.ts: pure cleanExifData() (no mutation, no I/O)
- Move i18n_lookup.ts → domain/ (already pure, zero dependencies)
- Move ipc_events.ts → domain/ipc_channels.ts (pure string constants)
- Update all consumer imports (8 files)
- Domain layer has zero Node/Electron imports
Part of Phase 9 DDD architecture refactor (Step 1: domain extraction).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Consolidate infrastructure and inject dependencies
- Move 5 Node-dependent common/ files to infrastructure/electron/:
binaries.ts, resources.ts, env.ts, browser_window.ts, i18n.ts→i18n_strings.ts
- Refactor exif_handlers.ts: accept injected getProcess, add bounds check
- Refactor app_setup.ts: accept injected onQuit callback
- Make init.ts the composition root: owns ExiftoolProcess singleton,
wires all dependencies explicitly
- common/ reduced to platform.ts only (pragmatic: 9 consumers, 1-line impurity)
Part of Phase 9 DDD architecture refactor (Step 2: infrastructure + DI).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Strengthen tsconfig with 4 additional strict flags
Enable incrementally:
- noFallthroughCasesInSwitch: prevent accidental switch fallthrough
- noImplicitReturns: all code paths must return
- useUnknownInCatchVariables: catch blocks use unknown, not any
- noUncheckedIndexedAccess: array/object indexing returns T | undefined
Fixes for noUncheckedIndexedAccess:
- browser_window.ts: getAllWindows()[0] → ?? null
- ExiftoolProcess.ts: match[1] → match[1]! (regex capture group
guaranteed by match success)
Part of Phase 9 DDD architecture refactor (Step 4: tsconfig strengthening).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* electron and typescript conventions
* Add UI prototype prompts, generated prototypes, and final synthesis
9 prototype prompt specs (A through F + ultimate synthesis) exploring
table, card grid, focused-flow, power-user, and bottom-bar layouts.
7 generated React prototype JSX files from Claude artifact sessions.
6 rounds of iteration on the final prototype with feedback logs.
The ultimate synthesis prompt distills feedback from all variants into
one definitive design: table layout, settings popover, inline metadata
expansion, flat folder paths, and "Clean more" CTA. Remaining visual
polish items captured in ExifCleaner-Final-Remaining-Feedback.md.
Also adds "performance is sacred" principle to CLAUDE.md and roadmap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(01-02): add WebP test fixture with known EXIF metadata
- 100x100 blue WebP image with Artist, GPS, Make, Model, DateTimeOriginal
- Verified ExifTool 13.50 reads and strips metadata via -all= flag
- Fixture preserved with metadata for Phase 8 E2E tests
- 484 bytes, well under 50KB target
* feat(01-01): add Persian and Catalan translations to strings.json
- Cherry-pick Persian (fa) translations from PR #258 for all 45 keys
- Cherry-pick Catalan (ca) translations from PR #239 for all 45 keys
- Confirm Croatian (hr) already present at 45/45 coverage
- Total locale count per key: 24 (22 existing + fa + ca)
* feat(01-01): sync Locale enum and fallback function with all supported locales
- Expand Locale enum from 11 to 25 members matching all 24 locales in strings.json
- Add PortugueseBR as separate enum member (pt-BR has its own translations)
- Add Croatian fallback case using hr-HR (hyphen, fixing PR #206 underscore bug)
- Add Vietnamese fallback case mapping vi to vn (Electron reports vi, strings.json uses vn)
- Update pt-BR fallback to return PortugueseBR instead of Portuguese
* feat(02-01): install Vitest, create Result type, port interfaces, and test fakes
- Add Vitest 3.2.4 with test/test:watch scripts
- Create Result<T> discriminated union in src/common/result.ts
- Create ExifToolPort, SettingsPort, LoggerPort interfaces in src/application/
- Create Settings schema with 5 fields, defaults, validation, migration
- Create FakeExifTool, FakeSettings, FakeLogger test fakes
- All 6 fake tests pass, TypeScript compiles cleanly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(02-01): add domain types with unit tests and barrel file
- Create file_types.ts with SUPPORTED_EXTENSIONS (30 formats) and isSupportedFile()
- Create file_status.ts with FileProcessingStatus enum (6 states)
- Create domain barrel file re-exporting all domain types
- Add settings_schema tests (8 tests: validation, migration, defaults)
- Add file_types tests (6 tests: supported/unsupported/case-insensitive)
- Add domain purity meta-test (verifies zero I/O imports in src/domain/)
- All 21 tests pass, TypeScript compiles cleanly, app build succeeds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(02-02): add failing tests for SettingsService
- 8 tests covering load, save, get, update, atomic write, migration, corruption fallback
- Tests use real filesystem with temp directories for isolation
- Uses FakeLogger from test fakes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(02-02): implement SettingsService with atomic writes and migration
- JSON file-backed settings persistence implementing SettingsPort
- Atomic writes via temp file + rename to prevent corruption
- Schema migration on load when version differs from current
- Falls back to DEFAULT_SETTINGS on missing or corrupt file
- get() returns cached settings synchronously without I/O
- update() merges partial settings and persists atomically
- One retry on save failure, cache stays updated in memory
- All 8 unit tests pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(02-02): add ConsoleLogger and relocate IPC channels to infrastructure
- ConsoleLogger implements LoggerPort with structured console output
- IPC_CHANNELS const object in infrastructure/ipc with all channel names
- New settings channels: settings:get, settings:set, settings:changed
- Domain ipc_channels.ts becomes backward-compatible re-export shim
- All existing imports continue working unchanged
- Build and type-check pass with zero errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(02-03): add failing tests for application layer commands, queries, and use case
- StripMetadataCommand: 6 tests (flags, rotation, timestamps, abort, error, order)
- ReadMetadataQuery: 3 tests (read+clean, empty, error)
- ExpandFolderCommand: 4 tests (recursive, filter, empty, nonexistent)
- ProcessFilesUseCase: 4 tests (progress, continue-on-failure, abort, settings)
* feat(02-03): implement application layer commands, queries, and use case
- StripMetadataCommand: strips metadata with correct flag order (-all= before -TagsFromFile)
- ReadMetadataQuery: reads metadata and filters computed fields via cleanExifData
- ExpandFolderCommand: recursively finds supported files using node:fs readdir
- ProcessFilesUseCase: orchestrates expand-strip pipeline with progress, abort, continue-on-failure
- All 17 application layer tests pass
* feat(02-03): wire composition root, ExifToolAdapter, IPC handler swap, settings IPC, preload expansion
- ExifToolAdapter bridges ExiftoolProcess to ExifToolPort without modifying ExiftoolProcess
- Composition root (container.ts) wires all dependencies at startup
- IPC handlers delegate to commands (zero inline business logic)
- Settings IPC handlers with validation and change notification events
- Preload API exposes settings namespace (get, set, onChanged)
- init.ts now async, awaited after app.whenReady()
- Build succeeds, all 46 tests pass, tsc clean
* feat(03-01): install React 19, Zod 3, and build tooling dependencies
- Add react, react-dom, zod to production dependencies
- Add @vitejs/plugin-react, @types/react, @types/react-dom to devDependencies
- Add typecheck and preview npm scripts
* feat(03-02): create security modules and Zod IPC schemas
- Navigation hardening: block untrusted origins, open HTTPS in system browser
- Permission gate: deny all browser permissions by default
- Zod schemas for all 11 IPC channels (invoke + fire-and-forget)
- Validated handler/listener wrappers with sender ID verification
- Add theme:get and theme:changed channels to IPC_CHANNELS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-01): configure electron-vite React plugin, CSP injection, and renderer tsconfig
- Add React plugin and CSP injection plugin to electron-vite renderer config
- CSP: default-src 'none', no unsafe-eval/unsafe-inline, ws://localhost:* for dev HMR
- Create tsconfig.renderer.json with jsx: react-jsx for automatic runtime
- Update base tsconfig.json with jsx, lib, and .tsx include
- Update lint/format scripts to cover .tsx files
* feat(03-02): wire validated handlers into all IPC handlers
- exif_handlers: createValidatedHandler for exif:read, exif:remove
- settings_handlers: createValidatedHandler for settings:get, settings:set
- i18n: createValidatedHandler for get-locale, get-i18n-strings
- dock: createValidatedListener for files-added, file-processed, all-files-processed
- init: register sender, hardenNavigation, installPermissionGate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(03-02): add unit tests for IPC validation and security modules
- 21 schema tests: exifRead, exifRemove, settingsSet, filesAdded, getLocale, void schemas
- 9 validation tests: sender auth, handler wrapper, listener wrapper
- 7 navigation tests: file://, dev localhost, prod block, window.open deny, openExternal
- 2 permission tests: deny all permission types
- Refactor navigation/permissions to accept deps for testability (no vi.mock)
- 39 new tests, 85 total passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(03-04): add failing tests for window state persistence
- 7 tests for isWithinDisplayBounds (single/dual display, overlap, gap)
- 7 tests for validateAndLoadState (null, invalid, valid, off-screen, missing fields)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-04): implement window state persistence with display bounds validation
- Pure isWithinDisplayBounds() checks rect overlap against any display work area
- validateAndLoadState() parses/validates JSON with type safety
- loadWindowState() reads from window-state.json (sync, runs once at startup)
- saveWindowState() atomic writes via temp file + rename
- setupWindowStatePersistence() debounced resize/move handlers (300ms)
- Off-screen positions fall back to undefined (OS centers window)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-04): add dynamic theme background, theme IPC, DevTools guard, and fix window creation order
- window_setup.ts: dynamic backgroundColor via nativeTheme.shouldUseDarkColors
- window_setup.ts: loadWindowState() for size/position restoration
- window_setup.ts: devTools: !app.isPackaged (disabled in production)
- window_setup.ts: setupWindowStatePersistence() in setupMainWindow
- theme_handlers.ts: theme:get handler with Zod validation, theme:changed broadcast
- init.ts: wire setupThemeHandlers alongside other IPC handlers
- index.ts: fix window creation order (create -> init -> setup)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-03): replace vanilla renderer with React SPA shell
- Delete all 12 vanilla renderer files and 11 old CSS files
- Create React entry point with createRoot and StrictMode
- Add ThemeProvider, I18nProvider, AppProvider contexts
- Add ErrorBoundary class component with fallback UI
- Add useI18n and useTheme hooks consuming contexts
- Add BEM CSS: reset, tokens (light/dark), app layout, error boundary
- Update preload API with ThemeApi interface
- Replace index.html with minimal #root mount point
- Create EmptyState, DropZone, FileList components
- TypeScript compiles cleanly with zero errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-03): add SVG icon to EmptyState component
- Add inline SVG icon from original index.html to EmptyState
- Icon uses currentColor for theme compatibility
- aria-hidden on decorative SVG for screen readers
- All UI components complete: EmptyState, DropZone, FileList
- All BEM CSS complete with prefers-reduced-motion support
- yarn compile and yarn test pass cleanly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(03-03): allow unsafe-inline CSP for scripts and styles in dev mode
Vite's React Refresh plugin injects inline <script> tags for HMR, and
Vite's CSS HMR injects inline <style> tags. Both are blocked by strict
CSP in dev mode. Production builds remain strict (self only).
Also removed frame-ancestors from meta CSP (only valid in HTTP headers).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(04-01): add failing tests for extended AppContext reducer
- 12 test cases covering all 7 action types
- Tests import appReducer (not yet exported) and extended FileEntry type
- Covers ADD_FILES, CLEAR_FILES, UPDATE_FILE_STATUS, UPDATE_FILE_METADATA,
UPDATE_FILE_ERROR, TOGGLE_FOLDER, TOGGLE_ROW_EXPANSION, exhaustive switch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-01): extend AppContext with per-file processing state model
- FileEntry: 10 fields (id, path, name, extension, size, folder, status, beforeTags, afterTags, error)
- AppState: files array, collapsedFolders Set, expandedRowId
- 7 action types: ADD_FILES, CLEAR_FILES, UPDATE_FILE_STATUS, UPDATE_FILE_METADATA,
UPDATE_FILE_ERROR, TOGGLE_FOLDER, TOGGLE_ROW_EXPANSION
- Export appReducer for testability
- Update DropZone to construct full FileEntry with crypto.randomUUID()
- Update FileList to use file.id as React key
- All 12 reducer tests pass, 111 total tests passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(04-01): add failing tests for utility functions
- 8 test cases for formatFileSize (0 B, KB, MB, GB, fractional sizes)
- 5 test cases for getFileExtension (lowercase, uppercase, no ext, compound)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-01): add formatFileSize and getFileExtension utilities
- formatFileSize: human-readable file sizes (B, KB, MB, GB)
- getFileExtension: uppercase extension extraction from filename
- Both pure functions with explicit return types, no dependencies
- 13 tests passing (8 for size, 5 for extension)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-01): complete CSS token system and component stylesheets
- tokens.css: full light/dark color system, spacing (8px grid), typography
(system-ui font stack), animation tokens, type pill colors, backward-compat aliases
- file_table.css: 5-column grid, sticky header, row states, entrance animation
- status_icon.css: CSS spinner, checkmark pop, keyframes (ec-spin, ec-check-pop)
- type_pill.css: color-coded pills for JPG, HEIC, PNG, PDF, MOV, DNG + variants
- status_bar.css: sticky bottom bar with summary and accent-colored button
- folder_row.css: collapsible sections with chevron rotation animation
- toast.css: fixed notification with opacity transition
- drop_zone.css: updated to use gold border/bg for drag-over (D-10)
- app.css: system font stack via --ec-font-family token (UI-10)
- All animations wrapped in @media (prefers-reduced-motion: no-preference) (UI-09)
- Prettier formatting applied to all modified files
- Build compiles, 124 tests pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-02): create atomic presentational components (TypePill, StatusIcon, Toast, ErrorExpansion)
- TypePill renders color-coded extension badges with alias mapping (JPEG->jpg, RAW->dng, video->mov)
- StatusIcon renders pending dot, CSS spinner, animated checkmark, and circle-X per FileProcessingStatus
- Toast renders copy confirmation with role="status" and aria-live="polite"
- ErrorExpansion renders selectable error text with click-to-copy via navigator.clipboard
* feat(04-02): create composite components (FileRow, FolderRow, StatusBar, FileTable) and wire into App
- FileTable renders 5-column header (NAME, TYPE, SIZE, BEFORE, AFTER) with folder grouping
- FileRow renders TypePill, StatusIcon, formatFileSize in 5 cells with keyboard navigation
- FolderRow renders collapsible folder headers with chevron toggle and ARIA labels
- StatusBar shows summary text and "Clean more" button dispatching CLEAR_FILES
- FileTable replaces FileList stub in App.tsx, reads state via useAppContext()
- Toast for copy confirmation, ErrorExpansion for click-to-copy error text
- Staggered entrance animation via --ec-stagger-delay CSS variable (D-11)
- One-shot checkmark animation tracked via React ref (D-12)
* test(04-03): add failing tests for processing pipeline hook
- 9 test cases covering processFileEntries: status transitions, metadata
counts, error handling, sequential processing, IPC notifications
- Tests import processFileEntries which does not exist yet (RED phase)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-03): implement processing pipeline and elapsed time hooks
- use_process_files.ts: processFileEntries orchestrates read->strip->read
pipeline with sequential processing, error handling, and IPC notifications
- useProcessFiles hook wraps pipeline with queue ref for rapid drop handling
- use_elapsed_time.ts: timer hook with start/stop/reset for status bar
- All 10 new tests pass, 134 total passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-03): wire processing pipeline into DropZone and FileTable
- DropZone: filters through isSupportedFile, calls processFiles after
ADD_FILES dispatch for auto-start on drop (D-06)
- DropZone: File > Open menu handler also filters and auto-processes
- FileTable: uses useElapsedTime hook for real-time status bar timer
- FileTable: timer starts on processing, stops when all done, resets on clear
- 134 tests passing, compile and lint clean
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(04-03): move status icon to first column, remove row border-left
- Status icon column is now first (before filename) per UAT feedback
- Removed border-left from complete and error rows per UAT feedback
- Updated grid-template-columns to 32px 1fr 72px 80px 72px 72px
- Simplified renderBeforeCell and renderAfterCell (status shown in dedicated column)
* docs: add animation principles reference from Emil Kowalski research
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(05-01): add failing tests for settings v2 migration and cleaned path
- Settings schema v2 tests: version constant, defaults, validation, v1->v2 migration
- Cleaned path tests: suffix, collision handling, multiple dots, no extension
* feat(05-01): settings schema v2, cleaned path generator, platform API
- Split preserveRotation into preserveOrientation + preserveColorProfile (schema v2)
- v1->v2 migration: preserveRotation maps to both new fields
- Pure domain cleaned path generator with collision-free _cleaned suffix
- Platform API in preload (static isMac boolean, no IPC overhead)
- Updated StripMetadataCommand signature for split settings + save-as-copy
- Updated all existing tests and IPC schemas for new field names
- 147 tests passing, zero type errors
* test(05-01): add tests for split settings arg combinations and xattr service
- StripMetadataCommand: 11 tests covering all 4 setting combinations
- xattr_service: tests for macOS guard, exec call, non-fatal error handling
* feat(05-01): xattr service, xattr command, ProcessFilesUseCase wiring
- xattr_service.ts: macOS-only xattr -cr with isMac() guard, non-fatal
- XattrCommand: application-layer wrapper with port interface
- ProcessFilesUseCase: wires generateCleanedPath for save-as-copy, xattr after strip
- Container: creates and injects XattrCommand with infrastructure adapter
- 158 tests passing, zero type errors
* feat(05-02): ToggleSwitch and GearIcon components with toggle CSS
- ToggleSwitch: accessible pill switch with role="switch", aria-checked, focus ring
- GearIcon: outlined SVG gear with forwardRef and dynamic aria-label
- toggle_switch.css: instant state changes (no transitions per D-08), dark mode support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(05-02): settings drawer with focus trap, animations, and App.tsx integration
- SettingsDrawer: 320px slide-from-right panel with backdrop, 5 privacy toggles
- Focus trap: Tab cycles within drawer, Escape closes
- Animation: 300ms open / 200ms close, prefers-reduced-motion disables all transitions
- App.tsx: gear icon + drawer mounted, aria-hidden on content when drawer open
- Settings persist immediately via window.api.settings IPC
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(05-03): toggle animation, settings menu items, IPC bridge, blank cells
- Add 200ms spring-eased toggle animation respecting prefers-reduced-motion
- Add SETTINGS_TOGGLE IPC channel and onToggle preload bridge
- Add Settings menu item to App menu (macOS), File menu, and dock menu
- Wire Cmd/Ctrl+comma shortcut via CmdOrCtrl+, accelerator
- Add useEffect listener in App.tsx for settings:toggle IPC
- Replace dash placeholders with blank cells in FileRow
- Add menu.app.settings i18n key to strings.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(06-02): metadata groups domain module with grouped key parsing and diff computation
- Create metadata_groups.ts with parseGroupedKey, getFriendlyGroupKey, computeMetadataDiff
- Update cleanExifData to handle grouped keys (e.g., Other:SourceFile)
- Add 16 tests covering all domain logic
* feat(06-01): settings schema v3 with themeMode + accent color parser
- Bump CURRENT_SCHEMA_VERSION to 3, add themeMode field to Settings
- Add v2->v3 migration (themeMode=system default) and v1->v3 chain
- Add validateSettings themeMode handling with fallback to system
- Create accent_color.ts with parseAccentColorHex and ACCENT_COLOR_FALLBACK
- 28 domain tests green (6 accent color + 22 settings schema)
* feat(06-02): ReadMetadataQuery -G2 flag, full metadata capture in AppContext, i18n strings
- Add -G2 flag to ReadMetadataQuery for ExifTool family 2 grouped output
- Extend FileEntry with beforeMetadata/afterMetadata fields
- Update use_process_files to store full metadata objects (not just counts)
- Add 17 i18n strings for metadata groups, theme labels, and metadata UI
- Update existing tests for new action shape
* feat(06-01): theme IPC pipeline with set, accent-color, and preload bridge
- Add THEME_SET, THEME_ACCENT_COLOR, THEME_ACCENT_COLOR_CHANGED IPC channels
- Add themeSetSchema and themeAccentColorSchema Zod validators
- Theme handler sets nativeTheme.themeSource and persists via SettingsService
- Accent color handler serves OS color via systemPreferences with fallback
- Accent color change listener for Windows/Linux, macOS re-reads on nativeTheme.updated
- Extend ThemeApi with set(), getAccentColor(), onAccentColorChanged()
- Wire settingsService into setupThemeHandlers from composition root
- All 186 tests pass, build compiles clean
* feat(06-04): add metadata inspection components and CSS
- ChevronIcon: rotatable SVG with motion-safe transition
- MetadataField: removed/preserved indicator with red/green tint
- MetadataGroup: collapsible category with header count
- MetadataExpansion: container with copy-all and grouped diff
- CSS: BEM styles with dark mode tokens, prefers-reduced-motion
- tokens.css: add removed-bg, preserved-bg, font-family-mono tokens
* feat(06-04): integrate MetadataExpansion and ChevronIcon into FileRow
- Replace placeholder text with real MetadataExpansion component
- Add ChevronIcon in status cell for expandable rows
- Only expandable rows (Complete/NoMetadataFound/Error) are clickable
- NoMetadataFound shows i18n 'noMetadataFound' message
- Add expandable row cursor, chevron positioning, slide-open animation
- All 186 tests pass, build compiles
* feat(06-03): ThemeContext 3-way mode, SegmentedControl, adaptive CSS tokens
- ThemeContext extended with themeMode, setThemeMode, accent color, crossfade
- SegmentedControl component with sliding indicator, keyboard nav, ARIA
- tokens.css: system accent replaces brand plum/wine, muted pill colors
- Adaptive tokens: drawer shadow, backdrop opacity, toggle off-track, status bar border
- Settings drawer: Appearance section with Light/Auto/Dark segmented control
- Toast dark mode inversion, crossfade transition class, ::selection accent
- IPC channel for theme mode sync from View menu
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(06-03): View menu Appearance submenu with Light/Auto/Dark radio items
- Appearance submenu with radio items syncing with nativeTheme
- Broadcasts theme mode to renderer via IPC for segmented control sync
- Menu items use i18n keys with English fallbacks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): settings spacing, checkmark placement, clipboard permission, dark scrollbar
- Remove separator line and tighten padding in settings drawer
- Move checkmark from status column to right of AFTER count
- Allow clipboard-write permission for Copy All button
- Add dark mode scrollbar styles for file table body
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): spacing polish, native dark scrollbars, column widths
- Add more padding above Appearance section in settings drawer
- Narrow status column (32→24px) to tighten chevron-to-name gap
- Widen BEFORE/AFTER columns (72→80px) for breathing room
- Add color-scheme: light/dark to tokens.css for native scrollbars
- Remove custom webkit scrollbar overrides (native is better)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): widen BEFORE/AFTER columns for more breathing room (80→88px)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): gear icon to status bar, native table headers, persistent bottom bar
- Move gear icon from absolute top-right to persistent status bar (bottom-left)
- Status bar now renders on both EmptyState and FileTable views
- Lift file stats and elapsed timer from FileTable to App for status bar
- Table column headers: 11px uppercase with letter-spacing (native feel)
- Status bar height 36px, compact layout with gear/stats/clean-more
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): add beforeMetadata/afterMetadata to buildFileEntry in DropZone
Fixes TypeScript type contract — FileEntry interface requires these fields
after Plan 06-02 extended it with full metadata capture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(07-01): domain logic + IPC infrastructure for folder operations
- middleTruncatePath with 8 unit tests for long folder path display
- IPC channels: folder:classify and folder:expand with Zod schemas
- folder_handlers.ts: classify paths as files/folders, expand via ExpandFolderCommand
- Preload bridge: FolderApi with classify and expand methods
- Wired setupFolderHandlers into init.ts alongside existing handlers
* feat(07-02): language names module, i18n override, IPC, menu submenus
- Add LANGUAGE_NAMES domain module with 25 entries sorted by nativeName
- Add language_names.test.ts with 5 tests (count, sort, uniqueness, coverage, non-empty)
- Add language/languageSystem i18n strings for all 24 languages
- Modify i18n.ts locale() to check settings language override
- Add rebuildMenusForLanguageChange and handleLanguageChange
- Add Language submenu to View menu and dock menu with radio items
- Add LANGUAGE_CHANGED IPC channel and onLanguageChanged preload listener
- Wire language change handlers in init.ts for menu-driven changes
* feat(07-01): renderer folder pipeline with DropZone, AppContext, FolderRow, FileTable
- AppContext: FolderDiscoveryStatus type, FolderState interface, folderStates Map
- Three new reducer actions: ADD_FOLDER_SCANNING, UPDATE_FOLDER_STATE, COLLAPSE_FOLDER
- DropZone: classify paths via IPC, expand folders, mixed-drop ordering (loose first)
- FolderRow: discoveryStatus prop, middleTruncatePath display, aria-live count, pulse animation
- folder_row.css: 36px height, 14px font, scanning pulse, prefers-reduced-motion gate
- FileTable: passes discoveryStatus from folderStates, includes scanning folders with no files
- Empty folders auto-collapse after 1.5s, skip toast for unreadable folders
* feat(07-02): LanguageDropdown component, I18nContext hot-swap, SettingsDrawer integration
- Create LanguageDropdown with 25 languages + System option in native script
- Full keyboard navigation (Arrow, Enter, Escape, Home, End)
- ARIA listbox with role=option, aria-selected, aria-expanded
- Create language_dropdown.css with 280px max-height, animation, prefers-reduced-motion
- Update I18nContext to listen for onLanguageChanged and hot-swap locale
- Add Language section to SettingsDrawer between Appearance and toggles
* feat(07-03): file reveal in system file manager + StatusBar Clear button
- Add file:reveal and file:reveal-context-menu IPC channels with Zod validation
- Create reveal_handlers.ts with shell.showItemInFolder and existsSync checks
- Add RevealApi to preload with showInFolder and showContextMenu methods
- Add reveal icon (fade-on-hover) to FileRow for completed files
- Context menu for save-as-copy mode with Reveal Cleaned Copy / Reveal Original
- Rename StatusBar button from "Clean more" to "Clear" with aria-label
- Wire saveAsCopy setting into FileTable for reveal behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(07-03): restore file list scrollbar and align folder/file chevrons
- Fix scrollbar by changing drop-zone min-height from 100vh to 0, allowing
flex children to shrink and file-table__body overflow-y: auto to activate
- Align folder row chevron with file row chevron by reusing shared ChevronIcon
component (12px) instead of custom 16px SVG, and using flex-start alignment
in the toggle button instead of center
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(07-03): add min-height:0 to .file-table to restore scrollbar
* fix(07-03): folder icon, subfolder collapse, reveal position, statusbar i18n, Clear crash
- Add folder icon SVG to FolderRow for visual distinction from file rows
- Fix subfolder rows showing when parent folder is collapsed
- Move reveal icon from name cell to after cell (rightmost, always visible)
- Translate status bar strings (interpolated i18n keys with French)
- Add missing French translations for all untranslated keys
- Fix Clear button crash: dispatch was not destructured in AppContent
* feat(08-01): install Playwright and create E2E test infrastructure
- Install @playwright/test with browser download skipped
- Add playwright.config.ts (workers:1, retries:0, 10s timeout)
- Create 4 shared helpers: app_launcher, fixture_copier, metadata_assertions, wait_for_processing
- Create fixture generation script and 9 fixture files (JPEG/PNG/PDF/MP4/WebP + error fixtures)
- Add test:e2e and test:all scripts to package.json
* feat(08-01): add app-launch E2E spec with 4 passing tests
- Window visibility, title, empty state content, DevTools closed
- Console.error monitoring with ExifTool noise filtering
- App launcher uses development env for correct resource paths
- Waits for React mount before returning window handle
* chore(08-01): add Playwright output dirs to gitignore
- playwright-report/ and test-results/ are generated artifacts
* feat(08-02): add file-processing and error-handling E2E specs
- file-processing.spec.ts: 6 tests covering single file, batch, type pills,
status bar, Clean more cycle, and drag-over CSS class
- error-handling.spec.ts: 4 tests covering corrupted file, zero-byte file,
unsupported format filtering, and recovery after problematic files
- Updated wait_for_processing helper to poll for spinner absence (more reliable)
- Updated metadata_assertions with PNG/video/PDF structural tag names
* feat(08-02): add settings, folder-recursion, and metadata-inspection E2E specs
- settings.spec.ts: 6 tests covering drawer open/close, 3 toggle switches
(orientation, timestamps, xattr with macOS skip), orientation pipeline,
and save-as-copy setting propagation
- folder-recursion.spec.ts: 2 tests covering nested folder expansion via
folder:expand IPC and flat path verification
- metadata-inspection.spec.ts: 3 tests covering expansion panel visibility,
metadata group fields with known tags, and removed/preserved indicators
* feat(08-03): add dark-mode, i18n, security, and accessibility E2E specs
- dark-mode.spec.ts: 4 tests (activate, render check, switch back, system theme)
- i18n.spec.ts: 3 tests (English default, French switch, Japanese switch)
- security.spec.ts: 2 tests (CSP meta tag, navigation blocking without sleeps)
- accessibility.spec.ts: 4 tests (tab navigation, focus visible, ARIA roles, focus trap)
* feat(08-03): add E2E README, optimize suite for 30s budget, validate stability
- README.md with run/add/fixture/helper/spec documentation
- Optimized new specs to use beforeAll/afterAll (shared app instance)
- Reduced waitForTimeout values for faster execution
- Full 10-spec suite: 38 tests in ~27s (under 30s budget)
- 5 consecutive runs: all green (zero flaky tests)
- 199 Vitest unit tests unaffected
* chore(09-01): commit ExifTool v13.50 binaries, update gitignore, delete Travis CI
- Track ExifTool nix/win binaries in git (required for CI, Windows lacks Perl)
- Update .gitignore to allow .resources binaries and .github/ directory
- Remove .travis.yml (replaced by GitHub Actions in Plan 03)
- Remove !.travis.yml gitignore negation, add !.github/ negation
* chore(09-02): upgrade electron-builder from v22 to v26
- electron-builder ^22.8 -> ^26 (installed 26.8.1)
- Local macOS build verified: 119MB .dmg produced successfully
- All 199 tests pass, typecheck clean
- No build config changes required
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci(09-03): add GitHub Actions CI workflow with cross-platform builds
* feat(10-01): configure macOS code signing, notarization, and universal binary
- Create entitlements.mac.plist with JIT, unsigned-executable-memory, and user-selected file access
- Create entitlements.mac.inherit.plist with same entitlements for child processes
- Update build.mac config: universal arch, hardenedRuntime, entitlements paths, notarize
- Simplify build.dmg by removing custom contents array (D-04)
- Retain packmac identity=null for local unsigned builds (D-19)
* docs(10-01): add SIGNING-SETUP.md maintainer guide for code signing
- Apple Developer enrollment and Developer ID Application certificate steps
- App Store Connect API key generation for notarization
- GitHub Secrets configuration (5 signing + 1 optional Homebrew)
- Release triggering instructions (workflow_dispatch)
- Signed build verification commands (codesign, spctl, stapler)
- Windows signing deferral documented per D-07
* feat(10-02): create release.yml workflow with signing, notarization, checksums, draft release
- Manual workflow_dispatch trigger only (CI-09)
- Test gate re-runs full suite before building (D-15)
- macOS build imports cert to temp keychain, signs + notarizes via electron-builder
- Windows and Linux builds produce unsigned artifacts
- Release job generates SHASUMS256.txt (CI-08), creates git tag, draft release
- Concurrency group prevents parallel release runs
* feat(10-02): create homebrew-cask.yml workflow for auto Homebrew PR on release
- Triggers on release:published event (decoupled from release workflow)
- Uses macauley/action-homebrew-bump-cask@v1 to auto-submit PR to homebrew-cask
- Requires HOMEBREW_TOKEN secret (GitHub PAT with public_repo scope)
* feat(11-02): add Playwright screenshot generation script
- Create scripts/generate-screenshots.ts that captures 5 app states
- Screenshots: light-processed, dark-processed, settings-open, metadata-diff, language-switch
- Outputs to exifcleaner-website/static/images/screenshots/ (no window chrome -- added via CSS in Plan 03)
- Add 'screenshots' script to package.json
* fix(11-02): improve screenshot script — more files, smaller window, English locale
- Increase fixture files from 4 to 12 with realistic filenames to fill the table
- Reduce window size from 1200x800 to 960x700 for better marketing appearance
- Force English language via settings API before taking screenshots
- Use Japanese (not system locale) for language-switch screenshot only
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ae09e1a2eb
commit
0cabf8a58b
1001 changed files with 954038 additions and 8394 deletions
1399
.claude/rules/electron-best-practices.md
Normal file
1399
.claude/rules/electron-best-practices.md
Normal file
File diff suppressed because it is too large
Load diff
165
.claude/rules/github-context.md
Normal file
165
.claude/rules/github-context.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# GitHub Context
|
||||
|
||||
Last updated: Feb 2025. Last release: v3.6.0 (May 4, 2021). Last commit: March 2022.
|
||||
|
||||
## Open PRs (8)
|
||||
|
||||
### Dependabot Bumps (4)
|
||||
|
||||
- **#267** Bump lodash 4.17.21 → 4.17.23
|
||||
- **#265** Bump electron 11.4.10 → 35.7.5 (will not work without build system migration)
|
||||
- **#263** Bump tmp 0.2.1 → 0.2.4
|
||||
- **#259** Bump pbkdf2 3.1.2 → 3.1.3
|
||||
|
||||
### Translations (3)
|
||||
|
||||
- **#258** Persian translations (from RamtinA, Feb 2025)
|
||||
- **#239** Catalan translation (from marcarmengou, Jul 2023)
|
||||
- **#206** Croatian locale strings (from milotype, Apr 2022) — note: Croatian was already merged in PR #155, this may be an update
|
||||
|
||||
### CI
|
||||
|
||||
- **#174** GitHub Actions CI (from PolpOnline, Sep 2021) — owner reviewed positively but never merged. Includes exiftool download caching. The Prettier config was fixed in a separate merged PR #177.
|
||||
|
||||
## Open Issues by Category (64 total)
|
||||
|
||||
### Bugs (8)
|
||||
|
||||
| Issue | Title | Platform |
|
||||
| --- | --- | --- |
|
||||
| #255 | Downgrades PPI/DPI | — |
|
||||
| #241 | RAF files corrupt | — |
|
||||
| #215 | AVIF metadata not fully removed | — |
|
||||
| #217 | Removing Source data from MP4 files (Mac) | Mac |
|
||||
| #211 | Drag and drop not working Fedora 36 AppImage | Linux |
|
||||
| #199 | Better cleaning of TIFF files | — |
|
||||
| #194 | LargeFileSupport error with larger files | — |
|
||||
| #83 | Occasional "Exiftool process is not open" error | Windows |
|
||||
|
||||
### Enhancements — High Priority (4)
|
||||
|
||||
| Issue | Title | Labels |
|
||||
| --- | --- | --- |
|
||||
| #247 | Add ESM node module support | high priority |
|
||||
| #244 | Change language from the menu | enhancement, high priority |
|
||||
| #236 | Update ExifTool | high priority |
|
||||
| #86 | Remove extended filesystem attributes (xattr/mdls) | enhancement, high priority, mac |
|
||||
|
||||
### Enhancements — Features (16)
|
||||
|
||||
| Issue | Title |
|
||||
| --- | --- |
|
||||
| #266 | Remove all tags except Colour Profile/Colour Space |
|
||||
| #264 | WebP support |
|
||||
| #250 | Preserve file modification date/time |
|
||||
| #249 | Drag images to program shortcut |
|
||||
| #242 | AI metadata removal support |
|
||||
| #234 | Option to not strip image rotation metadata |
|
||||
| #218 | Save as a new file |
|
||||
| #209 | Keep landscape/portrait rotation metadata option |
|
||||
| #207 | Clearly show full list of removed metadata |
|
||||
| #182 | Replace Metadata for MKV Files |
|
||||
| #172 | "New Releases" menu item linking to GitHub releases |
|
||||
| #171 | Remove metadata for items inside a folder |
|
||||
| #170 | Open/show file after processing |
|
||||
| #167 | View Exif data removed/remaining diff |
|
||||
| #166 | "Always on top" option |
|
||||
| #162 | Office file metadata cleaning |
|
||||
| #124 | Save as new file (duplicate of #218) |
|
||||
| #121 | RTL languages support |
|
||||
| #114 | Keep original filesystem created-at timestamp |
|
||||
| #106 | Remove hover tooltip transparency in dark mode |
|
||||
|
||||
### Platform & DevOps (10)
|
||||
|
||||
| Issue | Title | Platform |
|
||||
| --- | --- | --- |
|
||||
| #261 | Portable version filename should say "portable" | Windows |
|
||||
| #254 | .rpm package does not open | Linux |
|
||||
| #208 | Add to Pacstall repository | Linux |
|
||||
| #198 | Universal app for Apple Silicon | Mac |
|
||||
| #189 | Update macOS Big Sur icon | Mac |
|
||||
| #186 | Add to Chocolatey package repository | Windows |
|
||||
| #181 | Add Snapcraft support | Linux |
|
||||
| #175 | 32-bit Windows support | Windows |
|
||||
| #141 | Release checksum not generated for Linux RPM | Linux |
|
||||
| #134 | Can't install on Mac Big Sur (permissions) | Mac |
|
||||
| #89 | Flatpak distribution for Linux | Linux |
|
||||
|
||||
### Context Menu Integration (2)
|
||||
|
||||
| Issue | Title |
|
||||
| --- | --- |
|
||||
| #165 | Right-click context menu integration (CLI params) |
|
||||
| #161 | Add context menu support |
|
||||
|
||||
### Translations (2)
|
||||
|
||||
| Issue | Title |
|
||||
| --- | --- |
|
||||
| #156 | Use online translation platform |
|
||||
| #185 | Open app localized |
|
||||
|
||||
### Documentation & Questions (5)
|
||||
|
||||
| Issue | Title |
|
||||
| --- | --- |
|
||||
| #262 | VirusTotal false alarm |
|
||||
| #257 | Image shows nothing on drop (#256 duplicate) |
|
||||
| #252 | ExifCleaner doesn't work |
|
||||
| #238 | GPU process isn't usable error |
|
||||
| #225 | Does not work on M1 Mac |
|
||||
| #235 | ExifCleaner as web app? |
|
||||
| #240 | Remove date fields in MP4 files |
|
||||
|
||||
### Other
|
||||
|
||||
| Issue | Title |
|
||||
| --- | --- |
|
||||
| #231 | Cannot read subdirectories | Windows |
|
||||
| #229 | Program exit after timeline | — |
|
||||
| #228 | App system abort timeline | Mac |
|
||||
| #216 | PDF metadata leftovers | — |
|
||||
| #164 | Dark mode not working Windows 7 | Windows |
|
||||
| #139 | Can't drag multiple files Windows 10 | Windows |
|
||||
| #116 | Firewall sees outbound connection | Windows |
|
||||
| #111 | Linearize PDFs for better metadata removal | — |
|
||||
| #91 | Fix source-map-support stack traces | DevOps |
|
||||
|
||||
## Key Community Themes
|
||||
|
||||
- **"Is this project dead?"** — Issues #246 and #251 directly ask. Owner responded in Jan 2024 saying they'd update "this year" after Electron added ESM support.
|
||||
- **Security urgency** — Electron 11 has known Chromium vulnerabilities. ExifTool needs updating. Users report VirusTotal flags (#262).
|
||||
- **Rotation metadata** — Multiple users (#209, #234, #168) report images flipping after EXIF removal because orientation tag is stripped.
|
||||
- **Folder support** — Users want to drop folders and process all files recursively (#171, #231).
|
||||
- **Save as copy** — Users want option to save cleaned file separately instead of overwriting (#218, #124).
|
||||
- **Package managers** — Requests for Chocolatey (#186), Flatpak (#89), Snapcraft (#181), Pacstall (#208).
|
||||
- **PDF limitations** — PDF metadata removal is inherently partial per exiftool limitations (#111, #216).
|
||||
|
||||
## Recently Merged PRs (notable)
|
||||
|
||||
- **#205** Vietnamese translation (Mar 2022)
|
||||
- **#202** Czech translation (Mar 2022)
|
||||
- **#196** Malayalam translation (Feb 2022)
|
||||
- **#193** ExifTool download caching for CI (Jan 2022)
|
||||
- **#191** Swedish translation (Jan 2022)
|
||||
- **#184** Hungarian translation (Nov 2021)
|
||||
- **#178** Link contributor GitHub accounts in README (Sep 2021)
|
||||
- **#177** Fix Prettier to use tabs (Sep 2021)
|
||||
- **#159** Upgrade NPM dependencies (Jul 2021)
|
||||
- **#158** Remove Spectre.css and node-sass (Jul 2021) — replaced with plain CSS + CSS variables
|
||||
- **#160** Upgrade to Electron 13 + remove electron-webpack — **CLOSED, NOT MERGED** (blocked by ESM issues)
|
||||
|
||||
## Release History
|
||||
|
||||
20 releases from v1.2.0 (Dec 2019) to v3.6.0 (May 2021). Rapid early development, then slowed to translations-only commits.
|
||||
|
||||
| Version | Date | Key Changes |
|
||||
| --- | --- | --- |
|
||||
| 3.6.0 | May 2021 | XSS + remote shell security fix |
|
||||
| 3.5.1 | May 2021 | Windows portable build |
|
||||
| 3.5.0 | May 2021 | ExifTool CVE fix, M1 support, Electron 11 |
|
||||
| 3.4.0 | Oct 2020 | Multi-core speed boost, i18n, Electron 10 |
|
||||
| 3.3.1 | Jul 2020 | JS → TypeScript migration, Electron 9 |
|
||||
| 3.2.0 | Apr 2020 | Linux fixes, File→Open menu |
|
||||
| 3.0.0 | Jan 2020 | Disable auto-update, ESM removal |
|
||||
193
.claude/rules/modernization-roadmap.md
Normal file
193
.claude/rules/modernization-roadmap.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
# Modernization Roadmap
|
||||
|
||||
This is the sequenced plan for modernizing ExifCleaner. Phases are ordered by the maintainer's priorities: **local app quality first, release infrastructure later**.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Replace Build System ✅ DONE
|
||||
|
||||
- ✅ Removed `electron-webpack`, `electron-webpack-ts`, and `webpack`
|
||||
- ✅ Replaced with `electron-vite` 5.x + Vite 7.x + esbuild
|
||||
- ✅ Configured 3 build targets: main, preload, renderer
|
||||
- ✅ HMR preserved for dev mode
|
||||
- ✅ CSS imports work (imported in `renderer/index.ts`)
|
||||
- ✅ All `package.json` scripts updated (`dev`, `compile`, `start`)
|
||||
|
||||
## Phase 2: Upgrade Core Dependencies ✅ DONE
|
||||
|
||||
- ✅ TypeScript 3.8 → 5.7 with `strict: true` + `verbatimModuleSyntax: true`
|
||||
- ✅ Prettier 2.1 → 3.x
|
||||
- ✅ Electron 11 → 35 with contextIsolation + sandbox + preload
|
||||
- ✅ @types/node ^12 → ^22
|
||||
- ✅ Renderer fully sandboxed — no Node.js, no Electron imports
|
||||
- ✅ Preload script with contextBridge API (exif, i18n, files namespaces)
|
||||
- ✅ Exiftool operations moved from renderer to main process (single long-lived process)
|
||||
|
||||
## Phase 3: ESM Module Support ✅ DONE
|
||||
|
||||
- ✅ Added `"type": "module"` to package.json
|
||||
- ✅ Enabled `verbatimModuleSyntax: true` — enforces `import type` for type-only imports
|
||||
- ✅ Build output is ESM for all targets (main, preload, renderer)
|
||||
- ✅ `node-exiftool` CJS module works through esbuild's interop layer
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Verify + Cleanup ✅ DONE
|
||||
|
||||
**Why**: Four chunks of infrastructure changes with zero automated tests. Before building anything new, confirm what works and clean up dead weight.
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- ✅ Manual walkthrough of every code path (drag-drop, File > Open, context menu, dark mode, packaged app)
|
||||
- ✅ **Removed `source-map-support`** from `dependencies` — never imported anywhere in source, dead weight. Modern Node 22 has built-in `--enable-source-maps`
|
||||
- ✅ Fixed drag-and-drop bug: `file.path` deprecated in Electron 35 sandboxed renderer → exposed `webUtils.getPathForFile()` through preload API
|
||||
- ✅ Fixed `update_exiftool.pl` for new Windows distribution format (versioned subdirectory + `exiftool_files/` companion directory)
|
||||
- ✅ Set up Chrome DevTools MCP for AI-assisted debugging (`yarn dev:debug` + MCP server config)
|
||||
|
||||
## Phase 5: Playwright E2E Tests
|
||||
|
||||
**Why**: Safety net BEFORE any refactoring or feature work. The DDD refactor will be gradual — every file move should pass the test suite. Every new feature should get a test.
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- Add `@playwright/test` as devDependency
|
||||
- Create test fixtures (small JPEG with EXIF, PNG without EXIF, PDF, MP4)
|
||||
- Write core tests:
|
||||
- App launches, shows empty state with i18n text
|
||||
- Files process via IPC path (simulating drag-and-drop via `file-open-add-files`)
|
||||
- Before-count > 0, after-count = 0, correct filename displayed
|
||||
- Multiple files process and all complete
|
||||
- Dark mode CSS applies with `prefers-color-scheme: dark`
|
||||
- All `[i18n]` elements have non-empty text
|
||||
- Add `"test:e2e"` script to `package.json`
|
||||
- Target: < 30 seconds for full suite, zero flakey tests
|
||||
|
||||
## Phase 6: DDD Architecture Refactor (In Progress)
|
||||
|
||||
**Why**: Clean layer boundaries make the codebase easier to extend and maintain. Gradual, not all-at-once — each step is a file-move PR that passes all E2E tests.
|
||||
|
||||
**Architecture**:
|
||||
|
||||
- **Domain layer**: Core types, value objects, pure business logic (i18n lookup is already pure)
|
||||
- **Application layer**: Commands (strip metadata) and Queries (read metadata)
|
||||
- **Infrastructure layer**: ExifTool binary wrapper, file I/O, Electron APIs ✅ **Started** — `src/infrastructure/exiftool/` created
|
||||
- **Presentation layer**: Renderer DOM, preload bridge
|
||||
|
||||
**Principles**: SRP classes + dependency injection, functional programming for business logic, strong type system design (branded types, discriminated unions, `Result<T,E>`), no `any`.
|
||||
|
||||
**Suggested sequence**:
|
||||
|
||||
1. Extract domain types — `src/domain/` with value objects (`FilePath`, `ExifData`, `ProcessingResult`), enums (`Platform`, `Locale`). Move pure logic out of `common/`
|
||||
2. ✅ **Extract infrastructure layer** — ExifTool wrapper → `src/infrastructure/exiftool/` (completed out of order in Phase 10). Remaining: file dialog → `src/infrastructure/electron/`, resources/binaries → `src/infrastructure/filesystem/`
|
||||
3. Extract application layer — command/query objects: `StripMetadataCommand`, `ReadMetadataQuery`, `ExpandFolderQuery`
|
||||
4. Extract presentation layer — renderer → `src/presentation/renderer/`, preload → `src/presentation/preload/`
|
||||
|
||||
## Phase 7: Community Features (High Priority)
|
||||
|
||||
**Why**: After architecture is cleaner, ship the most-requested user features. Each feature gets a Playwright test.
|
||||
|
||||
**Features**:
|
||||
|
||||
- **Preserve rotation/orientation metadata** (issues #209, #234) — option to keep EXIF orientation tag so images don't flip. Highest user demand.
|
||||
- **Folder recursion** (issues #171, #231) — process all files inside a dropped folder. Requires new IPC channel (renderer is sandboxed, main process does `fs.readdirSync(path, { recursive: true })`)
|
||||
- **Language switching from menu** (issue #244, labeled high priority) — allow users to change language without changing system locale. Store preference, override `app.getLocale()`
|
||||
- **Extended filesystem attributes** (issue #86, labeled high priority) — remove macOS xattr/mdls metadata. Post-processing step: `xattr -cr <filepath>` guarded by `isMac()`
|
||||
- **WebP support verification** (issue #264) — WebP should already be supported via exiftool, may just need docs/testing
|
||||
- **Save as new file** (issues #218, #124) — option to output cleaned files separately instead of overwriting
|
||||
|
||||
## Phase 8: UI/UX Design Overhaul
|
||||
|
||||
**Why**: The current UI is functional but visually dated. After DDD separates the presentation layer cleanly and community features add new UI surface, do a single coherent visual design pass.
|
||||
|
||||
**Design Principles**:
|
||||
|
||||
- **System-native feel** — match platform design language, respect OS-level preferences (dark mode, accent colors, reduced motion)
|
||||
- **Focused utility** — this is a single-purpose tool, keep the interface minimal and elegant, don't add visual clutter
|
||||
- **Zero frameworks** — everything is vanilla CSS + TypeScript, hand-rolled
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- **System font stack**: replace any custom/generic fonts with `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`
|
||||
- **BEM CSS methodology**: migrate from current flat class names (`.card`, `.empty-title`, `.popover`) to BEM naming (`.file-list__row`, `.file-list__row--processing`, `.empty-state__icon`) — rename all classes and update HTML/TS references
|
||||
- **Micro-animations**: subtle CSS transitions for file processing states, drag-drop feedback, completion checkmarks, hover states — CSS `transition`/`@keyframes` only, no JS animation libraries
|
||||
- **Respect `prefers-reduced-motion`**: wrap animations in `@media (prefers-reduced-motion: no-preference)` so users who disable motion aren't affected
|
||||
- **Drag-drop polish**: visual feedback for drag-over state, file landing animation
|
||||
- **Processing states**: progress indicators per file, success/error state styling
|
||||
- **Dark mode refinement**: ensure dark mode looks intentionally designed, not just inverted colors
|
||||
- **Spacing and layout**: refine the CSS custom property scale, ensure consistent visual rhythm
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: GitHub Actions CI
|
||||
|
||||
**Why**: No CI exists. PR #174 has a community-contributed GitHub Actions config that was never merged. Now that E2E tests exist (Phase 5), CI has tests to run.
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- Review PR #174 for useful patterns
|
||||
- Set up GitHub Actions workflow:
|
||||
- Build on all 3 platforms (macOS, Windows, Linux)
|
||||
- Run Prettier lint check, typecheck, E2E tests
|
||||
- Cache exiftool downloads using `--cache-downloads-working-dir` flag (already supported by `update_exiftool.pl`)
|
||||
- Build artifacts for each platform
|
||||
- **Future improvement**: Containerize cross-platform packaging with Docker for reproducible builds
|
||||
|
||||
## Phase 10: Dependency Cleanup ✅ DONE (completed out of order)
|
||||
|
||||
**Why**: Hand-roll what you can. Target zero production dependencies. The hand-rolled exiftool wrapper naturally lives in `src/infrastructure/exiftool/`, which jumpstarts the DDD architecture (Phase 6).
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- ✅ **Replaced `node-exiftool`** with hand-rolled wrapper (~240 lines) in `src/infrastructure/exiftool/ExiftoolProcess.ts`
|
||||
- Implements `-stay_open` protocol with command queue and stdout buffering
|
||||
- Handles `{ready<N>}` markers, 30s command timeout, graceful shutdown
|
||||
- Drop-in replacement: same method signatures, same `{ data, error }` result format
|
||||
- ✅ **Achieved zero production dependencies** — `package.json` has `"dependencies": {}`
|
||||
- Removed `node-exiftool` 2.3.0 (unmaintained since 2018, CJS bloat)
|
||||
- Removed `source-map-support` (already handled in Phase 4)
|
||||
- ✅ Verified with `npx tsc --noEmit` (zero errors) and `yarn compile` (build successful)
|
||||
|
||||
## Phase 11: ExifTool Binary Update
|
||||
|
||||
**Why**: Issue #236. Current bundled exiftool is v12.25 (from May 2021). Security-critical — exiftool CVEs have been found before (CVE-2021-22204). Deferred until CI + tests are in place to catch regressions.
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- Run `update_exiftool.pl` to pull latest binaries (requires Perl, Linux/macOS)
|
||||
- Verify checksums match official exiftool site
|
||||
- Run E2E tests — confirm all pass
|
||||
- Run CI — confirm builds on all platforms
|
||||
- Test that metadata removal still works for all supported file types
|
||||
|
||||
## Phase 12: Apple Silicon & Code Signing
|
||||
|
||||
**Why**: Issue #198. macOS users on M1/M2/M3 need a universal binary. Currently x64 only. Requires CI (Phase 9) and Apple Developer certificate.
|
||||
|
||||
**Tasks**:
|
||||
|
||||
- Configure electron-builder for universal macOS builds (`target: "universal"`)
|
||||
- Set up code signing for macOS (requires Apple Developer certificate)
|
||||
- Set up code signing for Windows (optional but reduces false positives — issue #262 reports VirusTotal flags)
|
||||
- Ensure GitHub Actions CI produces signed artifacts
|
||||
- Generate and publish checksums for all release artifacts (issue #141 — Linux RPM checksum missing)
|
||||
|
||||
## Phase 13: CI Releases (No Auto-Update)
|
||||
|
||||
**Why**: The final "ship it" phase. Everything else must be done first. Releases are **never** auto-published — maintainer explicitly triggers them. **No auto-update** in the app (users are privacy-conscious, zero network traffic is a feature). SHASUMS256.txt for every release.
|
||||
|
||||
---
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- Keep the app simple — this is a focused tool, avoid feature bloat
|
||||
- **Minimize dependencies** — prefer hand-rolling over npm packages; target zero production deps
|
||||
- No JS frameworks — vanilla TypeScript/CSS is a deliberate design choice
|
||||
- **BEM CSS naming** — all new CSS must use BEM convention (Phase 8 migrates existing)
|
||||
- **System fonts only** — no web font downloads, no bundled fonts
|
||||
- **Respect motion preferences** — wrap animations in `prefers-reduced-motion` media query
|
||||
- Support Windows, macOS, and Linux
|
||||
- Maintain backward compatibility for existing translation contributors
|
||||
- ExifTool binaries must be bundled (no runtime download requirement)
|
||||
- **NEVER auto-publish releases** — CI builds artifacts, maintainer publishes manually
|
||||
- **NEVER enable auto-update** — no network traffic in production, ever
|
||||
- **Performance is sacred** — ExifCleaner processes hundreds of files in seconds; never add per-file latency to the processing pipeline
|
||||
913
.claude/rules/typescript-conventions.md
Normal file
913
.claude/rules/typescript-conventions.md
Normal file
|
|
@ -0,0 +1,913 @@
|
|||
# TypeScript Conventions for ExifCleaner
|
||||
|
||||
**Last updated**: February 15, 2026
|
||||
**Aligns with**: Phase 6 DDD Architecture Refactor
|
||||
|
||||
This document codifies TypeScript best practices for ExifCleaner. It complements [CLAUDE.md](../../CLAUDE.md) with type-specific rules optimized for LLM consumption.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Critical rules** (enforce these first):
|
||||
|
||||
- Use `strict: true` + additional safety flags (noUncheckedIndexedAccess, exactOptionalPropertyTypes)
|
||||
- Never introduce `any` — use `unknown` for boundaries, `Record<string, unknown>` for truly dynamic data
|
||||
- Prefer `undefined` over `null` for absence
|
||||
- Use named arguments (single object parameter) for non-trivial functions
|
||||
- Add explicit return type annotations on all exported functions
|
||||
- Export return types (`ReturnType<typeof fn>`), keep param types inline (duck typing)
|
||||
- Use Error objects, not string throws
|
||||
- Convert `unknown` via type guards, never via `as` assertions
|
||||
- Add comments for meaning (invariants, business rules), not for restating types
|
||||
- Use discriminated unions + `never` checks for exhaustive switch handling
|
||||
|
||||
---
|
||||
|
||||
## Configuration Best Practices
|
||||
|
||||
### Current State
|
||||
|
||||
ExifCleaner's `tsconfig.json` currently enables:
|
||||
```json
|
||||
{
|
||||
"strict": true, // ✅ All strictness checks
|
||||
"verbatimModuleSyntax": true, // ✅ Enforces type-only imports
|
||||
"forceConsistentCasingInFileNames": true, // ✅ Prevents case bugs
|
||||
"skipLibCheck": true // ✅ Faster builds
|
||||
}
|
||||
```
|
||||
|
||||
### Recommended Additions
|
||||
|
||||
Add these flags incrementally to catch more bugs at compile time:
|
||||
|
||||
```json
|
||||
{
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale**:
|
||||
|
||||
| Flag | Bug Class Prevented | Example |
|
||||
|------|---------------------|---------|
|
||||
| `noUncheckedIndexedAccess` | Prevents unsafe array/object access | `result.data[0]` returns `T \| undefined`, forces bounds check |
|
||||
| `exactOptionalPropertyTypes` | Distinguishes absent from `undefined` | `prop?: T` means "absent or T", not "`T \| undefined`" |
|
||||
| `noPropertyAccessFromIndexSignature` | Prevents unsafe property access | Requires `obj["key"]` for index signatures, not `obj.key` |
|
||||
| `useUnknownInCatchVariables` | Catch blocks are `unknown`, not `any` | Forces type narrowing before using error properties |
|
||||
| `noImplicitReturns` | All code paths must return | Catches missing returns in conditional branches |
|
||||
| `noFallthroughCasesInSwitch` | Prevents accidental switch fallthrough | Each case must `break`, `return`, or `throw` |
|
||||
|
||||
**Action**: Enable these flags incrementally. When errors appear, fix them properly (don't disable the flag).
|
||||
|
||||
---
|
||||
|
||||
## Type Safety Rules
|
||||
|
||||
### Null and Undefined
|
||||
|
||||
**House rules for absence**:
|
||||
|
||||
- **Prefer `undefined` over `null`** for representing absence
|
||||
- Exception: When boundary forces `null` (e.g., JSON APIs, DOM APIs)
|
||||
- Rationale: `undefined` is JavaScript's natural "absence" value
|
||||
|
||||
- **Optional properties mean absent OR present**
|
||||
- With `exactOptionalPropertyTypes`, `prop?: T` means either absent or `T` when present
|
||||
- Do NOT assign `undefined` unless the type explicitly includes it: `prop?: T | undefined`
|
||||
- Why: Distinguishes `"key" in obj` (absent) from `obj.key === undefined` (present but undefined)
|
||||
|
||||
- **Handle indexed access safely**
|
||||
- With `noUncheckedIndexedAccess`, `array[0]` and `obj[key]` return `T | undefined`
|
||||
- Always check for `undefined` before use
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Current anti-pattern in exif_handlers.ts:27
|
||||
return cleanExifData(result.data[0]); // No bounds check
|
||||
|
||||
// ✅ GOOD: Explicit undefined handling
|
||||
const firstItem = result.data[0];
|
||||
if (firstItem === undefined) {
|
||||
return {};
|
||||
}
|
||||
return cleanExifData(firstItem);
|
||||
```
|
||||
|
||||
- **Avoid non-null assertions (`!`)**
|
||||
- Only use in constrained glue code where invariant is guaranteed
|
||||
- Always add a comment explaining the invariant
|
||||
|
||||
```typescript
|
||||
// ✅ ACCEPTABLE: Non-null assertion with explanation
|
||||
// Safe: process and stdin guaranteed non-null after sendCommand validation
|
||||
this.process!.stdin!.write(command + "\n");
|
||||
```
|
||||
|
||||
- **Don't use truthiness for type narrowing**
|
||||
- `if (value)` treats `0`, `""`, and `false` as absent
|
||||
- Use explicit checks: `value !== undefined`, `value !== null`, `value != null` (both null and undefined)
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Truthiness check fails for empty string
|
||||
if (filePath) { // Empty string is falsy but might be valid
|
||||
processFile(filePath);
|
||||
}
|
||||
|
||||
// ✅ GOOD: Explicit undefined check
|
||||
if (filePath !== undefined) {
|
||||
processFile(filePath);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### any, unknown, and Boundary Validation
|
||||
|
||||
**House rule**: Never introduce `any`. Use `unknown` for untrusted inputs at boundaries.
|
||||
|
||||
**Exception**: `Record<string, unknown>` is allowed for truly dynamic data (e.g., ExifTool metadata that varies by file type).
|
||||
|
||||
**Current good pattern** (already in codebase):
|
||||
```typescript
|
||||
// api_types.ts - ExifTool output is inherently dynamic
|
||||
export interface ExifData {
|
||||
[key: string]: unknown; // ✅ Bounded "any" for dynamic metadata
|
||||
}
|
||||
```
|
||||
|
||||
**Anti-pattern found** (fix during refactoring):
|
||||
```typescript
|
||||
// ❌ BAD: table_update_row.ts lines 5 and 37
|
||||
export function updateRowWithExif(
|
||||
tdNode: HTMLTableCellElement,
|
||||
exifData: any, // Unbounded any
|
||||
): void {
|
||||
|
||||
// ✅ GOOD: Use typed interface
|
||||
import type { ExifData } from "../preload/api_types";
|
||||
export function updateRowWithExif(
|
||||
tdNode: HTMLTableCellElement,
|
||||
exifData: ExifData, // Properly typed
|
||||
): void {
|
||||
```
|
||||
|
||||
**Convert `unknown` via type guards, not assertions**:
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Type assertions don't add runtime checks
|
||||
const data = jsonResponse as MyType; // Fails silently if wrong shape
|
||||
|
||||
// ✅ GOOD: User-defined type guard with runtime check
|
||||
function isMyType(value: unknown): value is MyType {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"requiredField" in value &&
|
||||
typeof (value as Record<string, unknown>).requiredField === "string"
|
||||
);
|
||||
}
|
||||
|
||||
if (isMyType(jsonResponse)) {
|
||||
// TypeScript knows jsonResponse is MyType here
|
||||
console.log(jsonResponse.requiredField); // Type-safe access
|
||||
}
|
||||
```
|
||||
|
||||
**Catch blocks should use `unknown`**:
|
||||
|
||||
With `useUnknownInCatchVariables`:
|
||||
```typescript
|
||||
try {
|
||||
await riskyOperation();
|
||||
} catch (err: unknown) { // Explicit unknown (or implicit with flag)
|
||||
if (err instanceof Error) {
|
||||
console.error(err.message); // Type-safe after narrowing
|
||||
} else {
|
||||
console.error(String(err)); // Fallback for non-Error throws
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Return Types and Exports
|
||||
|
||||
**Rule**: For exported functions and public class methods, add explicit return type annotations.
|
||||
|
||||
**Current good pattern** (already followed):
|
||||
```typescript
|
||||
// ExiftoolProcess.ts - Explicit return types on all public methods
|
||||
async open(): Promise<number> {
|
||||
// ...
|
||||
}
|
||||
|
||||
async close(): Promise<ExifToolCloseResult> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Export named return types** using `ReturnType<typeof fn>`:
|
||||
|
||||
```typescript
|
||||
export const makeCommand = () => ({
|
||||
async execute(): Promise<{ ok: true } | { ok: false; error: string }> {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
|
||||
// Export derived types for consumers
|
||||
export type Command = ReturnType<typeof makeCommand>;
|
||||
export type CommandResult = ReturnType<Command["execute"]>;
|
||||
```
|
||||
|
||||
**Caveat**: For overloaded functions, `ReturnType` uses the last signature. Prefer single-signature exported functions unless overloads provide real clarity.
|
||||
|
||||
**Do NOT export parameter types by default** — keep arg shapes inline or file-local:
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Inline parameter type (duck typing)
|
||||
export function processFile({
|
||||
filePath,
|
||||
dryRun = false,
|
||||
}: {
|
||||
filePath: string;
|
||||
dryRun?: boolean;
|
||||
}): ProcessResult {
|
||||
// Parameter type not exported, but call sites get full type info via contextual typing
|
||||
}
|
||||
|
||||
// ❌ AVOID: Exporting parameter interface (nominal coupling)
|
||||
export interface ProcessFileParams {
|
||||
filePath: string;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export function processFile(params: ProcessFileParams): ProcessResult {
|
||||
// Now consumers must import ProcessFileParams, breaking duck typing
|
||||
}
|
||||
```
|
||||
|
||||
**Why inline types**: Enables structural typing (duck typing). Consumers can pass any object with the required shape without importing an interface.
|
||||
|
||||
---
|
||||
|
||||
### Index Access and Array Safety
|
||||
|
||||
**With `noUncheckedIndexedAccess`, handle possibly-undefined results**:
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Assumes array has elements
|
||||
const firstMetadata = metadataArray[0];
|
||||
firstMetadata.FileName; // Might be undefined!
|
||||
|
||||
// ✅ GOOD: Handle undefined explicitly
|
||||
const firstMetadata = metadataArray[0];
|
||||
if (firstMetadata === undefined) {
|
||||
throw new Error("Expected at least one metadata entry");
|
||||
}
|
||||
console.log(firstMetadata.FileName); // Type-safe
|
||||
```
|
||||
|
||||
**For dictionary-like objects**:
|
||||
|
||||
```typescript
|
||||
// With noPropertyAccessFromIndexSignature:
|
||||
const value = obj[key]; // ✅ OK: Bracket notation
|
||||
const value2 = obj.key; // ❌ Error: Use bracket notation for index signatures
|
||||
|
||||
// This prevents:
|
||||
const wrongKey = obj.typoKey; // Would silently be undefined without the flag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Design Patterns
|
||||
|
||||
### Named Arguments (Single Object Parameter)
|
||||
|
||||
**Rule**: Use named arguments (a single object parameter) for all non-trivial functions.
|
||||
|
||||
**Pattern**:
|
||||
```typescript
|
||||
// ✅ GOOD: Named arguments with inline types
|
||||
export const makeExiftoolAdapter = ({
|
||||
binPath,
|
||||
logger,
|
||||
}: {
|
||||
binPath: string;
|
||||
logger?: { info(msg: string): void };
|
||||
}) => {
|
||||
return {
|
||||
async process({ filePath }: { filePath: string }) {
|
||||
logger?.info(`Processing ${filePath}`);
|
||||
// ...
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Self-documenting at call sites
|
||||
- Easy to add optional parameters without breaking changes
|
||||
- Destructuring makes dependencies explicit
|
||||
- Works naturally with duck typing (structural interfaces)
|
||||
|
||||
**Exception**: Tiny, obvious utilities can use positional parameters:
|
||||
```typescript
|
||||
// ✅ OK: Simple utility with obvious parameters
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
**Destructuring guidelines**:
|
||||
- Keep destructuring shallow (one level deep)
|
||||
- Use defaults on the left-hand side: `{ timeout = 5000 } = {}`
|
||||
- Avoid nested destructuring in function signatures (hard to read)
|
||||
|
||||
---
|
||||
|
||||
### Duck Typing (Structural Over Nominal)
|
||||
|
||||
**Philosophy**: Accept "shapes" (structure), not nominal type dependencies. TypeScript uses structural typing — if it has the right properties, it's compatible.
|
||||
|
||||
**Pattern**:
|
||||
```typescript
|
||||
// ✅ GOOD: Accept structural shape (duck typing)
|
||||
export function useLogger(logger: { info(msg: string): void }) {
|
||||
logger.info("Started");
|
||||
// Works with ANY object that has an info(string) method
|
||||
}
|
||||
|
||||
// ❌ AVOID: Nominal dependency (tight coupling)
|
||||
import { Logger } from "../infrastructure/logger";
|
||||
export function useLogger(logger: Logger) {
|
||||
// Now coupled to Logger interface — breaks duck typing
|
||||
}
|
||||
```
|
||||
|
||||
**Current good example** (already in codebase):
|
||||
```typescript
|
||||
// api_types.ts uses structural interfaces
|
||||
export interface ExifApi {
|
||||
readMetadata: (filePath: string) => Promise<ExifData>;
|
||||
removeMetadata: (filePath: string) => Promise<object>;
|
||||
}
|
||||
```
|
||||
|
||||
**Handling excess property checking friction**:
|
||||
|
||||
TypeScript rejects object literals with extra properties:
|
||||
```typescript
|
||||
// ❌ Error: Object literal may only specify known properties
|
||||
doThing({ entityId: "123", extraField: "value" });
|
||||
```
|
||||
|
||||
**Solution 1**: Generic constraint (allows extra fields):
|
||||
```typescript
|
||||
export function doThing<T extends { entityId: string }>(args: T) {
|
||||
// args can have more fields without error
|
||||
}
|
||||
```
|
||||
|
||||
**Solution 2**: Explicit intersection:
|
||||
```typescript
|
||||
type Args = { entityId: string } & Record<string, unknown>;
|
||||
export function doThing(args: Args) { }
|
||||
```
|
||||
|
||||
**Solution 3**: Use `satisfies` for strict validation (when you want only known keys):
|
||||
```typescript
|
||||
const cmd = {
|
||||
entityId: "123",
|
||||
dryRun: true,
|
||||
// typoField: "oops" // Would error here
|
||||
} satisfies { entityId: string; dryRun?: boolean };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Error Handling (Result Type Pattern)
|
||||
|
||||
**Standard pattern**: Use `{ data: T | null; error: string | null }` for operations that can fail.
|
||||
|
||||
**Current good example** (already implemented):
|
||||
```typescript
|
||||
// infrastructure/exiftool/types.ts
|
||||
export interface ExifToolResult {
|
||||
data: Record<string, unknown>[] | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Usage in ExiftoolProcess.ts
|
||||
async readMetadata(filePath: string, args: string[]): Promise<ExifToolResult> {
|
||||
try {
|
||||
// ...
|
||||
return { data: parsed, error: null };
|
||||
} catch (err) {
|
||||
return { data: null, error: `Failed: ${err}` };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Consumer pattern**:
|
||||
```typescript
|
||||
const result = await exiftool.readMetadata(filePath, args);
|
||||
if (result.error !== null) {
|
||||
console.error(result.error);
|
||||
return;
|
||||
}
|
||||
// Here, result.data is guaranteed non-null
|
||||
processData(result.data);
|
||||
```
|
||||
|
||||
**Use Error objects, not string throws**:
|
||||
|
||||
**Anti-pattern found** (fix during refactoring):
|
||||
```typescript
|
||||
// ❌ BAD: String throws (8 instances in renderer)
|
||||
throw "Could not find file list pane element";
|
||||
|
||||
// ✅ GOOD: Error objects preserve stack traces
|
||||
throw new Error("Could not find file list pane element");
|
||||
```
|
||||
|
||||
**Why Error objects**:
|
||||
- Preserve stack traces for debugging
|
||||
- Enable `instanceof Error` checks
|
||||
- Allow error subclassing for different error types
|
||||
- Standard JavaScript/TypeScript convention
|
||||
|
||||
**For Phase 6 DDD refactor**: Standardize on Result type everywhere (infrastructure, application, domain layers).
|
||||
|
||||
**Discriminated unions for multi-state results**:
|
||||
|
||||
```typescript
|
||||
// Future pattern for Phase 6
|
||||
type ProcessingResult =
|
||||
| { type: "success"; tagsRemoved: number }
|
||||
| { type: "failure"; reason: string }
|
||||
| { type: "skipped"; reason: string };
|
||||
|
||||
function handleResult(result: ProcessingResult): void {
|
||||
switch (result.type) {
|
||||
case "success":
|
||||
console.log(`Removed ${result.tagsRemoved} tags`);
|
||||
break;
|
||||
case "failure":
|
||||
console.error(`Failed: ${result.reason}`);
|
||||
break;
|
||||
case "skipped":
|
||||
console.warn(`Skipped: ${result.reason}`);
|
||||
break;
|
||||
default:
|
||||
// Exhaustiveness check with never
|
||||
const _never: never = result;
|
||||
throw new Error(`Unhandled result type: ${_never}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Quality
|
||||
|
||||
### Comments (Add Meaning, Not Types)
|
||||
|
||||
**Rule**: Do not write comments that restate TypeScript types. Comments must add meaning (invariants, business rules, edge cases).
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Comment restates the type
|
||||
// Returns a promise of a number
|
||||
async function open(): Promise<number>
|
||||
|
||||
// ✅ GOOD: Comment adds context beyond the type
|
||||
// Returns process PID for external monitoring/cleanup
|
||||
async function open(): Promise<number>
|
||||
```
|
||||
|
||||
**When to add comments**:
|
||||
- **Invariants**: `// Safe: process checked above, guaranteed non-null`
|
||||
- **Business rules**: `// User emails must be verified before sending notifications`
|
||||
- **Edge cases**: `// Empty arrays are valid - represents "no metadata found"`
|
||||
- **Non-obvious performance**: `// Uses stay_open mode to avoid spawning Perl for each file (~90ms saved per file)`
|
||||
|
||||
**Don't comment**:
|
||||
- Parameter types (visible in signature)
|
||||
- Return types (visible in signature)
|
||||
- What the code does (should be self-evident from names)
|
||||
|
||||
---
|
||||
|
||||
### Exhaustiveness (Discriminated Unions + never)
|
||||
|
||||
**Pattern for exhaustive switch statements**:
|
||||
|
||||
```typescript
|
||||
type FileType = "image" | "video" | "pdf";
|
||||
|
||||
function processFile(type: FileType): void {
|
||||
switch (type) {
|
||||
case "image":
|
||||
processImage();
|
||||
break;
|
||||
case "video":
|
||||
processVideo();
|
||||
break;
|
||||
case "pdf":
|
||||
processPdf();
|
||||
break;
|
||||
default:
|
||||
// Exhaustiveness check: if a new FileType is added, this errors
|
||||
const _exhaustive: never = type;
|
||||
throw new Error(`Unhandled file type: ${_exhaustive}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**How it works**:
|
||||
- If all cases are handled, `type` is narrowed to `never` in the default branch
|
||||
- If a new union member is added, TypeScript errors: "Type 'newMember' is not assignable to type 'never'"
|
||||
- Forces you to update all switch statements when extending the union
|
||||
|
||||
**Combine with `noFallthroughCasesInSwitch`** to prevent accidental fallthrough bugs.
|
||||
|
||||
---
|
||||
|
||||
### Verbosity (Trust Inference)
|
||||
|
||||
**Rule**: Keep types minimal. Let inference work internally; annotate only at boundaries or when clarity improves.
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Inference works internally
|
||||
function processFiles(paths: string[]) {
|
||||
const results = paths.map(p => readFile(p)); // Type inferred: Array<FileResult | null>
|
||||
return results.filter(r => r !== null); // Type inferred: Array<FileResult>
|
||||
}
|
||||
|
||||
// ✅ GOOD: Explicit type at module boundary
|
||||
export function processFiles(paths: string[]): FileResult[] {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Avoid redundant annotations**:
|
||||
```typescript
|
||||
// ❌ BAD: Redundant type annotation
|
||||
const count: number = files.length;
|
||||
|
||||
// ✅ GOOD: Let inference work
|
||||
const count = files.length;
|
||||
```
|
||||
|
||||
**When to annotate explicitly**:
|
||||
- Module boundaries (exported functions)
|
||||
- Complex generic types where inference fails
|
||||
- When type annotation clarifies intent (e.g., `const results: string[] = []` vs `const results = []`)
|
||||
|
||||
---
|
||||
|
||||
## Codebase Examples
|
||||
|
||||
### Good Patterns to Follow
|
||||
|
||||
**1. ExiftoolProcess.ts** — Infrastructure layer class design
|
||||
|
||||
✅ **Explicit return types on all public methods**:
|
||||
```typescript
|
||||
async open(): Promise<number> { /* ... */ }
|
||||
async close(): Promise<ExifToolCloseResult> { /* ... */ }
|
||||
async readMetadata(filePath: string, args: string[]): Promise<ExifToolResult> { /* ... */ }
|
||||
```
|
||||
|
||||
✅ **Private methods for internal state management**:
|
||||
```typescript
|
||||
private sendCommand(executeNum: number, command: string): Promise<ExifToolResult> { /* ... */ }
|
||||
private parseStdout(chunk: string): void { /* ... */ }
|
||||
```
|
||||
|
||||
✅ **Union return types** (Result pattern):
|
||||
```typescript
|
||||
export interface ExifToolResult {
|
||||
data: Record<string, unknown>[] | null;
|
||||
error: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Proper async/await patterns**, no callback hell
|
||||
|
||||
✅ **Error handling with Error objects** (not strings)
|
||||
|
||||
---
|
||||
|
||||
**2. api_types.ts** — Type definitions for IPC
|
||||
|
||||
✅ **Interface-based API design**:
|
||||
```typescript
|
||||
export interface ExifApi {
|
||||
readMetadata: (filePath: string) => Promise<ExifData>;
|
||||
removeMetadata: (filePath: string) => Promise<object>;
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Clean callback signatures with unsubscribe pattern**:
|
||||
```typescript
|
||||
onFileOpenAddFiles: (callback: (filePaths: string[]) => void) => () => void;
|
||||
// Returns cleanup function for removing listener
|
||||
```
|
||||
|
||||
✅ **Type re-exports for cross-module usage**:
|
||||
```typescript
|
||||
export type { I18nStringsDictionary } from "../common/i18n";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**3. i18n_lookup.ts** — Pure domain logic
|
||||
|
||||
✅ **Zero dependencies** — fully testable without I/O mocks
|
||||
|
||||
✅ **Enum for constants**:
|
||||
```typescript
|
||||
export enum Locale {
|
||||
Chinese = "zh",
|
||||
English = "en",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Type aliases for complex structures**:
|
||||
```typescript
|
||||
export type I18nStringSet = { [locale: string]: string };
|
||||
```
|
||||
|
||||
✅ **Explicit error throwing on invariant violations**:
|
||||
```typescript
|
||||
if (!allStrings[locale]) {
|
||||
throw new Error(`Locale ${locale} not found in strings`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Anti-Patterns to Fix (Future Work)
|
||||
|
||||
**1. table_update_row.ts** — Usage of `any`
|
||||
|
||||
❌ **Lines 5 and 37**: `exifData: any` should be `exifData: ExifData`
|
||||
|
||||
**Fix**:
|
||||
```typescript
|
||||
// Before
|
||||
export function updateRowWithExif(
|
||||
tdNode: HTMLTableCellElement,
|
||||
exifData: any,
|
||||
): void {
|
||||
|
||||
// After
|
||||
import type { ExifData } from "../preload/api_types";
|
||||
export function updateRowWithExif(
|
||||
tdNode: HTMLTableCellElement,
|
||||
exifData: ExifData,
|
||||
): void {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**2. Renderer files** — String throws instead of Error objects
|
||||
|
||||
❌ **8 instances across renderer files**:
|
||||
- `add_files.ts`
|
||||
- `select_files.ts`
|
||||
- `table_update_row.ts`
|
||||
- etc.
|
||||
|
||||
**Fix**:
|
||||
```typescript
|
||||
// Before
|
||||
throw "Could not find file list pane element";
|
||||
|
||||
// After
|
||||
throw new Error("Could not find file list pane element");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**3. ExiftoolProcess.ts** — Non-null assertion without comment
|
||||
|
||||
⚠️ **Line 188**: `this.process!.stdin!.write(...)`
|
||||
|
||||
**Fix**: Add comment explaining invariant:
|
||||
```typescript
|
||||
// Safe: process and stdin guaranteed non-null after sendCommand validation (line 126-127)
|
||||
this.process!.stdin!.write(command + "\n");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**4. exif_handlers.ts** — Unchecked array access
|
||||
|
||||
⚠️ **Line 27**: `result.data[0]` without bounds check
|
||||
|
||||
**Fix** (when `noUncheckedIndexedAccess` enabled):
|
||||
```typescript
|
||||
// Before
|
||||
return cleanExifData(result.data[0]);
|
||||
|
||||
// After
|
||||
const firstItem = result.data[0];
|
||||
if (firstItem === undefined) {
|
||||
return {};
|
||||
}
|
||||
return cleanExifData(firstItem);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Wins (Future Implementation)
|
||||
|
||||
These are prioritized fixes to be done during normal development or Phase 6 DDD refactor.
|
||||
|
||||
### Priority 1: Replace `any` with `ExifData`
|
||||
|
||||
**Effort**: 5 minutes
|
||||
**Impact**: High (type safety for EXIF display)
|
||||
**Files**: `src/renderer/table_update_row.ts` (2 occurrences)
|
||||
|
||||
```diff
|
||||
- export function updateRowWithExif(tdNode: HTMLTableCellElement, exifData: any): void {
|
||||
- function buildExifString({ exifData }: { exifData: any }): string {
|
||||
+ import type { ExifData } from "../preload/api_types";
|
||||
+ export function updateRowWithExif(tdNode: HTMLTableCellElement, exifData: ExifData): void {
|
||||
+ function buildExifString({ exifData }: { exifData: ExifData }): string {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Priority 2: Change string throws to Error objects
|
||||
|
||||
**Effort**: 10 minutes
|
||||
**Impact**: Medium (better error messages, stack traces)
|
||||
**Files**: 8 instances across renderer files
|
||||
|
||||
```diff
|
||||
- throw "Could not find file list pane element";
|
||||
+ throw new Error("Could not find file list pane element");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Priority 3: Add comment to non-null assertion
|
||||
|
||||
**Effort**: 2 minutes
|
||||
**Impact**: Low (code clarity)
|
||||
**Files**: `src/infrastructure/exiftool/ExiftoolProcess.ts` line 188
|
||||
|
||||
```diff
|
||||
+ // Safe: process and stdin guaranteed non-null after sendCommand validation
|
||||
this.process!.stdin!.write(command + "\n");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Priority 4: Enable strict tsconfig flags
|
||||
|
||||
**Effort**: 2-3 hours (fix 5-10 array accesses and property accesses)
|
||||
**Impact**: High (prevent undefined access bugs)
|
||||
**Action**: Add to `tsconfig.json` incrementally
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Strategy**: Enable one flag at a time, fix errors, commit. Repeat for next flag.
|
||||
|
||||
---
|
||||
|
||||
## Integration with Phase 6: DDD Architecture Refactor
|
||||
|
||||
These TypeScript patterns align with the DDD refactor plan in [`devplans/07-ddd-architecture-refactor.md`](../../devplans/07-ddd-architecture-refactor.md).
|
||||
|
||||
### Value Objects (Phase 6) → Use Branded Types
|
||||
|
||||
```typescript
|
||||
// From DDD plan - Branded type for FilePath
|
||||
type FilePath = string & { readonly __brand: "FilePath" };
|
||||
|
||||
function createFilePath(path: string): FilePath {
|
||||
if (!path) throw new Error("File path cannot be empty");
|
||||
if (!path.startsWith("/")) throw new Error("File path must be absolute");
|
||||
return path as FilePath;
|
||||
}
|
||||
|
||||
// Usage: Type system prevents string/FilePath mixups
|
||||
function processFile(path: FilePath): void {
|
||||
// Only accepts FilePath, not raw strings
|
||||
}
|
||||
|
||||
const path = createFilePath("/Users/jon/file.jpg");
|
||||
processFile(path); // ✅ OK
|
||||
processFile("/raw/string"); // ❌ Error: string is not assignable to FilePath
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Result Types (Phase 6) → Extend Current Pattern
|
||||
|
||||
```typescript
|
||||
// Standardize on Result<T, E> everywhere (not just infrastructure)
|
||||
type Result<T, E = string> =
|
||||
| { ok: true; value: T }
|
||||
| { ok: false; error: E };
|
||||
|
||||
// Application layer
|
||||
export type StripMetadataResult = Result<
|
||||
{ tagsRemoved: number },
|
||||
"FileNotFound" | "ExifToolError" | "PermissionDenied"
|
||||
>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command/Query Interfaces (Phase 6) → Follow Named Args + Duck Typing
|
||||
|
||||
```typescript
|
||||
// From DDD plan - Commands use named args, accept duck-typed deps
|
||||
export const makeStripMetadataCommand = ({
|
||||
exiftoolAdapter,
|
||||
logger,
|
||||
}: {
|
||||
exiftoolAdapter: { remove(path: string): Promise<Result<void, string>> };
|
||||
logger?: { info(msg: string): void };
|
||||
}) => ({
|
||||
async execute({ filePath }: { filePath: string }): Promise<StripMetadataResult> {
|
||||
logger?.info(`Stripping metadata from ${filePath}`);
|
||||
const result = await exiftoolAdapter.remove(filePath);
|
||||
if (!result.ok) {
|
||||
return { ok: false, error: result.error };
|
||||
}
|
||||
return { ok: true, value: { tagsRemoved: 42 } };
|
||||
}
|
||||
});
|
||||
|
||||
export type StripMetadataCommand = ReturnType<typeof makeStripMetadataCommand>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Discriminated Unions (Phase 6) → Use for Domain Events and States
|
||||
|
||||
```typescript
|
||||
// Domain events
|
||||
type ProcessingEvent =
|
||||
| { type: "started"; filePath: string; timestamp: Date }
|
||||
| { type: "completed"; filePath: string; tagsRemoved: number; timestamp: Date }
|
||||
| { type: "failed"; filePath: string; error: string; timestamp: Date };
|
||||
|
||||
// Exhaustive handling
|
||||
function handleEvent(event: ProcessingEvent): void {
|
||||
switch (event.type) {
|
||||
case "started":
|
||||
console.log(`Started ${event.filePath} at ${event.timestamp}`);
|
||||
break;
|
||||
case "completed":
|
||||
console.log(`Removed ${event.tagsRemoved} tags from ${event.filePath}`);
|
||||
break;
|
||||
case "failed":
|
||||
console.error(`Failed ${event.filePath}: ${event.error}`);
|
||||
break;
|
||||
default:
|
||||
const _never: never = event;
|
||||
throw new Error(`Unhandled event: ${_never}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
**Deep dive**: [devplans/deep-research-report-typescript-best-practices.md](../../devplans/deep-research-report-typescript-best-practices.md)
|
||||
**General conventions**: [CLAUDE.md](../../CLAUDE.md)
|
||||
**DDD architecture**: [devplans/07-ddd-architecture-refactor.md](../../devplans/07-ddd-architecture-refactor.md)
|
||||
**Modernization roadmap**: [.claude/rules/modernization-roadmap.md](./modernization-roadmap.md)
|
||||
|
||||
---
|
||||
|
||||
*TypeScript conventions codified: February 15, 2026*
|
||||
*Estimated implementation effort for Quick Wins: 3-4 hours total*
|
||||
143
.github/workflows/ci.yml
vendored
Normal file
143
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
branches: ['**']
|
||||
|
||||
# Cancel in-progress runs for same branch/PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint, Typecheck & Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Lint (Prettier)
|
||||
run: yarn lint
|
||||
|
||||
- name: Typecheck (tsc --noEmit)
|
||||
run: yarn typecheck
|
||||
|
||||
- name: Unit tests (Vitest)
|
||||
run: yarn test
|
||||
|
||||
- name: Compile (electron-vite build)
|
||||
run: yarn compile
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Install Playwright
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: npx playwright install-deps chromium
|
||||
|
||||
- name: Disable AppArmor restriction (Ubuntu 24.04)
|
||||
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
|
||||
- name: E2E tests (Playwright)
|
||||
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" yarn test:e2e
|
||||
|
||||
build-macos:
|
||||
name: Build macOS
|
||||
needs: test
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build macOS artifacts
|
||||
run: yarn packmac --publish never
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ExifCleaner-macOS
|
||||
path: dist/*.dmg
|
||||
retention-days: 7
|
||||
|
||||
build-windows:
|
||||
name: Build Windows
|
||||
needs: test
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build Windows artifacts
|
||||
run: yarn packwin --publish never
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ExifCleaner-Windows
|
||||
path: |
|
||||
dist/*.exe
|
||||
retention-days: 7
|
||||
|
||||
build-linux:
|
||||
name: Build Linux
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build Linux artifacts
|
||||
run: yarn packlinux --publish never
|
||||
|
||||
- name: Upload Linux artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ExifCleaner-Linux
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
dist/*.rpm
|
||||
retention-days: 7
|
||||
16
.github/workflows/homebrew-cask.yml
vendored
Normal file
16
.github/workflows/homebrew-cask.yml
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: Update Homebrew Cask
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
bump-cask:
|
||||
name: Update Homebrew Cask
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Update Homebrew cask
|
||||
uses: macauley/action-homebrew-bump-cask@v1
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW_TOKEN }}
|
||||
cask: exifcleaner
|
||||
223
.github/workflows/release.yml
vendored
Normal file
223
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint, Typecheck & Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Lint (Prettier)
|
||||
run: yarn lint
|
||||
|
||||
- name: Typecheck (tsc --noEmit)
|
||||
run: yarn typecheck
|
||||
|
||||
- name: Unit tests (Vitest)
|
||||
run: yarn test
|
||||
|
||||
- name: Compile (electron-vite build)
|
||||
run: yarn compile
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Install Playwright
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: npx playwright install-deps chromium
|
||||
|
||||
- name: Disable AppArmor restriction (Ubuntu 24.04)
|
||||
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
|
||||
- name: E2E tests (Playwright)
|
||||
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" yarn test:e2e
|
||||
|
||||
build-macos:
|
||||
name: Build macOS (Signed)
|
||||
needs: test
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
|
||||
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain-db
|
||||
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
|
||||
|
||||
echo "$CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 3600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
security import "$CERTIFICATE_PATH" -P "$CERTIFICATE_PASSWORD" \
|
||||
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
||||
|
||||
rm "$CERTIFICATE_PATH"
|
||||
|
||||
- name: Decode Apple API key
|
||||
env:
|
||||
API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
|
||||
run: |
|
||||
mkdir -p ~/private_keys
|
||||
echo "$API_KEY_BASE64" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8
|
||||
|
||||
- name: Build macOS (signed + notarized)
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8
|
||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
run: yarn compile && electron-builder --macos --publish never
|
||||
|
||||
- name: Cleanup keychain
|
||||
if: always()
|
||||
run: security delete-keychain $RUNNER_TEMP/build.keychain-db 2>/dev/null || true
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-macos
|
||||
path: dist/*.dmg
|
||||
retention-days: 30
|
||||
|
||||
build-windows:
|
||||
name: Build Windows
|
||||
needs: test
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build Windows artifacts
|
||||
run: yarn packwin --publish never
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-windows
|
||||
path: dist/*.exe
|
||||
retention-days: 30
|
||||
|
||||
build-linux:
|
||||
name: Build Linux
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build Linux artifacts
|
||||
run: yarn packlinux --publish never
|
||||
|
||||
- name: Upload Linux artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-linux
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
dist/*.rpm
|
||||
retention-days: 30
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [build-macos, build-windows, build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Generate SHASUMS256.txt
|
||||
run: |
|
||||
cd artifacts
|
||||
find . -type f \( -name "*.dmg" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec sha256sum {} \; | sed 's| \./[^/]*/| |' > ../SHASUMS256.txt
|
||||
cd ..
|
||||
cat SHASUMS256.txt
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=v$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create git tag
|
||||
run: |
|
||||
git tag ${{ steps.version.outputs.version }}
|
||||
git push origin ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.version }}
|
||||
name: ExifCleaner ${{ steps.version.outputs.version }}
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
artifacts/release-macos/*
|
||||
artifacts/release-windows/*
|
||||
artifacts/release-linux/*
|
||||
SHASUMS256.txt
|
||||
28
.gitignore
vendored
28
.gitignore
vendored
|
|
@ -86,7 +86,6 @@ ts-gen
|
|||
#
|
||||
# Normal rules (sorted alphabetically)
|
||||
#
|
||||
.*
|
||||
*.a
|
||||
*.asn1.[ch]
|
||||
*.bin
|
||||
|
|
@ -284,22 +283,29 @@ Temporary Items
|
|||
# git repo for ExifTool, used to pull down latest binary
|
||||
gitignore
|
||||
exiftool_downloads
|
||||
.resources/nix/bin/**
|
||||
.resources/win/bin/**
|
||||
|
||||
yarn-error.log
|
||||
|
||||
|
||||
# Explicitly include the exiftool binaries
|
||||
# that were being hit by the .gitignore rule .*
|
||||
# ExifTool binaries committed to git (required for CI, especially Windows which lacks Perl)
|
||||
!.resources
|
||||
!.resources/nix/bin/.keep
|
||||
!.resources/win/bin/.keep
|
||||
!.resources/nix/
|
||||
!.resources/nix/bin/
|
||||
!.resources/nix/bin/**
|
||||
!.resources/win/
|
||||
!.resources/win/bin/
|
||||
!.resources/win/bin/**
|
||||
|
||||
# Explicitly include Travis CI configuration
|
||||
# Explicitly include GitHub Actions and config files
|
||||
# that were being hit by the .gitignore rule .*
|
||||
!.travis.yml
|
||||
!.github/
|
||||
|
||||
# Explicitly include Prettier configuration
|
||||
# Explicitly include Prettier configuration
|
||||
# that was being hit by the .gitignore rule .*
|
||||
!.prettierrc
|
||||
!.prettierrc
|
||||
|
||||
devplans
|
||||
|
||||
# Playwright
|
||||
playwright-report/
|
||||
test-results/
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||
8127
.resources/nix/bin/exiftool
Executable file
8127
.resources/nix/bin/exiftool
Executable file
File diff suppressed because it is too large
Load diff
447
.resources/nix/bin/lib/File/RandomAccess.pm
Normal file
447
.resources/nix/bin/lib/File/RandomAccess.pm
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: RandomAccess.pm
|
||||
#
|
||||
# Description: Buffer to support random access reading of sequential file
|
||||
#
|
||||
# Revisions: 02/11/2004 - P. Harvey Created
|
||||
# 02/20/2004 - P. Harvey Added flag to disable SeekTest in new()
|
||||
# 11/18/2004 - P. Harvey Fixed bug with seek relative to end of file
|
||||
# 01/02/2005 - P. Harvey Added DEBUG code
|
||||
# 01/09/2006 - P. Harvey Fixed bug in ReadLine() when using
|
||||
# multi-character EOL sequences
|
||||
# 02/20/2006 - P. Harvey Fixed bug where seek past end of file could
|
||||
# generate "substr outside string" warning
|
||||
# 06/10/2006 - P. Harvey Decreased $CHUNK_SIZE from 64k to 8k
|
||||
# 11/23/2006 - P. Harvey Limit reads to < 0x80000000 bytes
|
||||
# 11/26/2008 - P. Harvey Fixed bug in ReadLine when reading from a
|
||||
# scalar with a multi-character newline
|
||||
# 01/24/2009 - PH Protect against reading too much at once
|
||||
# 10/04/2018 - PH Added NoBuffer option
|
||||
# 01/20/2024 - PH Set ERROR on file read error
|
||||
#
|
||||
# Notes: Calls the normal file i/o routines unless SeekTest() fails, in
|
||||
# which case the file is buffered in memory to allow random access.
|
||||
# SeekTest() is called automatically when the object is created
|
||||
# unless specified.
|
||||
#
|
||||
# May also be used for string i/o (just pass a scalar reference)
|
||||
#
|
||||
# Sets internal ERROR member from $! if there is an error reading
|
||||
# the file.
|
||||
#
|
||||
# Legal: Copyright (c) 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the same terms as Perl itself.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package File::RandomAccess;
|
||||
|
||||
use strict;
|
||||
require 5.002;
|
||||
require Exporter;
|
||||
|
||||
use vars qw($VERSION @ISA @EXPORT_OK);
|
||||
$VERSION = '1.13';
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
sub Read($$$);
|
||||
|
||||
# constants
|
||||
my $CHUNK_SIZE = 8192; # size of chunks to read from file (must be power of 2)
|
||||
my $SKIP_SIZE = 65536; # size to skip when fast-forwarding over sequential data
|
||||
my $SLURP_CHUNKS = 16; # read this many chunks at a time when slurping
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Create new RandomAccess object
|
||||
# Inputs: 0) reference to RandomAccess object or RandomAccess class name
|
||||
# 1) file reference or scalar reference
|
||||
# 2) flag set if file is already random access (disables automatic SeekTest)
|
||||
sub new($$;$)
|
||||
{
|
||||
my ($that, $filePt, $isRandom) = @_;
|
||||
my $class = ref($that) || $that;
|
||||
my $self;
|
||||
|
||||
if (ref $filePt eq 'SCALAR') {
|
||||
# string i/o
|
||||
$self = {
|
||||
BUFF_PT => $filePt,
|
||||
BASE => 0,
|
||||
POS => 0,
|
||||
LEN => length($$filePt),
|
||||
TESTED => -1,
|
||||
};
|
||||
bless $self, $class;
|
||||
} else {
|
||||
# file i/o
|
||||
my $buff = '';
|
||||
$self = {
|
||||
FILE_PT => $filePt, # file pointer
|
||||
BUFF_PT => \$buff, # reference to file data
|
||||
BASE => 0, # location of start of buffer in file
|
||||
POS => 0, # current position in buffer
|
||||
LEN => 0, # length of data in buffer
|
||||
TESTED => 0, # 0=untested, 1=passed, -1=failed (requires buffering)
|
||||
};
|
||||
bless $self, $class;
|
||||
$self->SeekTest() unless $isRandom;
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Enable DEBUG code
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
sub Debug($)
|
||||
{
|
||||
my $self = shift;
|
||||
$self->{DEBUG} = { };
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Perform seek test and turn on buffering if necessary
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
# Returns: 1 if seek test passed (ie. no buffering required)
|
||||
# Notes: Must be done before any other i/o
|
||||
sub SeekTest($)
|
||||
{
|
||||
my $self = shift;
|
||||
unless ($self->{TESTED}) {
|
||||
my $fp = $self->{FILE_PT};
|
||||
if (seek($fp, 1, 1) and seek($fp, -1, 1)) {
|
||||
$self->{TESTED} = 1; # test passed
|
||||
} else {
|
||||
$self->{TESTED} = -1; # test failed (requires buffering)
|
||||
}
|
||||
}
|
||||
return $self->{TESTED} == 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Get current position in file
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
# Returns: current position in file
|
||||
sub Tell($)
|
||||
{
|
||||
my $self = shift;
|
||||
my $rtnVal;
|
||||
if ($self->{TESTED} < 0) {
|
||||
$rtnVal = $self->{POS} + $self->{BASE};
|
||||
} else {
|
||||
$rtnVal = tell($self->{FILE_PT});
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Seek to position in file
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
# 1) position, 2) whence (0 or undef=from start, 1=from cur pos, 2=from end)
|
||||
# Returns: 1 on success
|
||||
# Notes: When buffered, this doesn't quite behave like seek() since it will return
|
||||
# success even if you seek outside the limits of the file. However if you
|
||||
# do this, you will get an error on your next Read().
|
||||
sub Seek($$;$)
|
||||
{
|
||||
my ($self, $num, $whence) = @_;
|
||||
$whence = 0 unless defined $whence;
|
||||
my $rtnVal;
|
||||
if ($self->{TESTED} < 0) {
|
||||
my $newPos;
|
||||
if ($whence == 0) {
|
||||
$newPos = $num - $self->{BASE}; # from start of file
|
||||
} elsif ($whence == 1) {
|
||||
$newPos = $num + $self->{POS}; # relative to current position
|
||||
} elsif ($self->{NoBuffer} and $self->{FILE_PT}) {
|
||||
$newPos = -1; # (can't seek relative to end if no buffering)
|
||||
} else {
|
||||
$self->Slurp(); # read whole file into buffer
|
||||
$newPos = $num + $self->{LEN}; # relative to end of file
|
||||
}
|
||||
if ($newPos >= 0 and
|
||||
# can't go backwards in unbuffered non-seekable file
|
||||
(not $self->{NoBuffer} or $newPos >= $self->{POS}))
|
||||
{
|
||||
$self->{POS} = $newPos;
|
||||
$rtnVal = 1;
|
||||
}
|
||||
} else {
|
||||
$rtnVal = seek($self->{FILE_PT}, $num, $whence);
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read from the file
|
||||
# Inputs: 0) reference to RandomAccess object, 1) buffer, 2) bytes to read
|
||||
# Returns: Number of bytes read
|
||||
sub Read($$$)
|
||||
{
|
||||
my $self = shift;
|
||||
my $len = $_[1];
|
||||
my $rtnVal;
|
||||
|
||||
# protect against reading too much at once
|
||||
# (also from dying with a "Negative length" error)
|
||||
if ($len & 0xf8000000) {
|
||||
return 0 if $len < 0;
|
||||
# read in smaller blocks because Windows attempts to pre-allocate
|
||||
# memory for the full size, which can lead to an out-of-memory error
|
||||
my $maxLen = 0x4000000; # (MUST be less than bitmask in "if" above)
|
||||
my $num = Read($self, $_[0], $maxLen);
|
||||
return $num if $num < $maxLen;
|
||||
for (;;) {
|
||||
$len -= $maxLen;
|
||||
last if $len <= 0;
|
||||
my $l = $len < $maxLen ? $len : $maxLen;
|
||||
my $buff;
|
||||
my $n = Read($self, $buff, $l);
|
||||
last unless $n;
|
||||
$_[0] .= $buff;
|
||||
$num += $n;
|
||||
last if $n < $l;
|
||||
}
|
||||
return $num;
|
||||
}
|
||||
# read through our buffer if necessary
|
||||
if ($self->{TESTED} < 0) {
|
||||
# purge old data before reading in NoBuffer mode
|
||||
$self->Purge() or return 0 if $self->{NoBuffer};
|
||||
my $buff;
|
||||
my $newPos = $self->{POS} + $len;
|
||||
# number of bytes to read from file
|
||||
my $num = $newPos - $self->{LEN};
|
||||
if ($num > 0 and $self->{FILE_PT}) {
|
||||
# read data from file in multiples of $CHUNK_SIZE
|
||||
$num = (($num - 1) | ($CHUNK_SIZE - 1)) + 1;
|
||||
$num = read($self->{FILE_PT}, $buff, $num);
|
||||
if ($num) {
|
||||
${$self->{BUFF_PT}} .= $buff;
|
||||
$self->{LEN} += $num;
|
||||
} elsif (not defined $num) {
|
||||
$self->{ERROR} = $!;
|
||||
}
|
||||
}
|
||||
# number of bytes left in data buffer
|
||||
$num = $self->{LEN} - $self->{POS};
|
||||
if ($len <= $num) {
|
||||
$rtnVal = $len;
|
||||
} elsif ($num <= 0) {
|
||||
$_[0] = '';
|
||||
return 0;
|
||||
} else {
|
||||
$rtnVal = $num;
|
||||
}
|
||||
# return data from our buffer
|
||||
$_[0] = substr(${$self->{BUFF_PT}}, $self->{POS}, $rtnVal);
|
||||
$self->{POS} += $rtnVal;
|
||||
} else {
|
||||
# read directly from file
|
||||
$_[0] = '' unless defined $_[0];
|
||||
$rtnVal = read($self->{FILE_PT}, $_[0], $len);
|
||||
unless (defined $rtnVal) {
|
||||
$self->{ERROR} = $!;
|
||||
$rtnVal = 0;
|
||||
}
|
||||
}
|
||||
if ($self->{DEBUG}) {
|
||||
my $pos = $self->Tell() - $rtnVal;
|
||||
unless ($self->{DEBUG}->{$pos} and $self->{DEBUG}->{$pos} > $rtnVal) {
|
||||
$self->{DEBUG}->{$pos} = $rtnVal;
|
||||
}
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read a line from file (end of line is $/)
|
||||
# Inputs: 0) reference to RandomAccess object, 1) buffer
|
||||
# Returns: Number of bytes read
|
||||
sub ReadLine($$)
|
||||
{
|
||||
my $self = shift;
|
||||
my $rtnVal;
|
||||
my $fp = $self->{FILE_PT};
|
||||
|
||||
if ($self->{TESTED} < 0) {
|
||||
my ($num, $buff);
|
||||
$self->Purge() or return 0 if $self->{NoBuffer};
|
||||
my $pos = $self->{POS};
|
||||
if ($fp) {
|
||||
# make sure we have some data after the current position
|
||||
while ($self->{LEN} <= $pos) {
|
||||
$num = read($fp, $buff, $CHUNK_SIZE);
|
||||
unless ($num) {
|
||||
defined $num or $self->{ERROR} = $!;
|
||||
return 0;
|
||||
}
|
||||
${$self->{BUFF_PT}} .= $buff;
|
||||
$self->{LEN} += $num;
|
||||
}
|
||||
# scan and read until we find the EOL (or hit EOF)
|
||||
for (;;) {
|
||||
$pos = index(${$self->{BUFF_PT}}, $/, $pos);
|
||||
if ($pos >= 0) {
|
||||
$pos += length($/);
|
||||
last;
|
||||
}
|
||||
$pos = $self->{LEN}; # have scanned to end of buffer
|
||||
$num = read($fp, $buff, $CHUNK_SIZE);
|
||||
unless ($num) {
|
||||
defined $num or $self->{ERROR} = $!;
|
||||
last;
|
||||
}
|
||||
${$self->{BUFF_PT}} .= $buff;
|
||||
$self->{LEN} += $num;
|
||||
}
|
||||
} else {
|
||||
# string i/o
|
||||
$pos = index(${$self->{BUFF_PT}}, $/, $pos);
|
||||
if ($pos < 0) {
|
||||
$pos = $self->{LEN};
|
||||
$self->{POS} = $pos if $self->{POS} > $pos;
|
||||
} else {
|
||||
$pos += length($/);
|
||||
}
|
||||
}
|
||||
# read the line from our buffer
|
||||
$rtnVal = $pos - $self->{POS};
|
||||
$_[0] = substr(${$self->{BUFF_PT}}, $self->{POS}, $rtnVal);
|
||||
$self->{POS} = $pos;
|
||||
} else {
|
||||
$_[0] = <$fp>;
|
||||
if (defined $_[0]) {
|
||||
$rtnVal = length($_[0]);
|
||||
} else {
|
||||
$rtnVal = 0;
|
||||
}
|
||||
}
|
||||
if ($self->{DEBUG}) {
|
||||
my $pos = $self->Tell() - $rtnVal;
|
||||
unless ($self->{DEBUG}->{$pos} and $self->{DEBUG}->{$pos} > $rtnVal) {
|
||||
$self->{DEBUG}->{$pos} = $rtnVal;
|
||||
}
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read whole file into buffer (without changing read pointer)
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
sub Slurp($)
|
||||
{
|
||||
my $self = shift;
|
||||
my $fp = $self->{FILE_PT} || return;
|
||||
# read whole file into buffer (in large chunks)
|
||||
my ($buff, $num);
|
||||
for (;;) {
|
||||
$num = read($fp, $buff, $CHUNK_SIZE * $SLURP_CHUNKS);
|
||||
unless ($num) {
|
||||
defined $num or $self->{ERROR} = $!;
|
||||
last;
|
||||
}
|
||||
${$self->{BUFF_PT}} .= $buff;
|
||||
$self->{LEN} += $num;
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Purge internal buffer [internal use only]
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
# Returns: 1 on success, or 0 if current buffer position is negative
|
||||
# Notes: This is called only in NoBuffer mode
|
||||
sub Purge($)
|
||||
{
|
||||
my $self = shift;
|
||||
return 1 unless $self->{FILE_PT};
|
||||
return 0 if $self->{POS} < 0; # error if we can't read from here
|
||||
if ($self->{POS} > $CHUNK_SIZE) {
|
||||
my $purge = $self->{POS} - ($self->{POS} % $CHUNK_SIZE);
|
||||
if ($purge >= $self->{LEN}) {
|
||||
# read up to current position in 64k chunks, discarding as we go
|
||||
while ($self->{POS} > $self->{LEN}) {
|
||||
$self->{BASE} += $self->{LEN};
|
||||
$self->{POS} -= $self->{LEN};
|
||||
${$self->{BUFF_PT}} = '';
|
||||
$self->{LEN} = read($self->{FILE_PT}, ${$self->{BUFF_PT}}, $SKIP_SIZE);
|
||||
if (not defined $self->{LEN}) {
|
||||
$self->{ERROR} = $!;
|
||||
last;
|
||||
}
|
||||
last if $self->{LEN} < $SKIP_SIZE;
|
||||
}
|
||||
} elsif ($purge > 0) {
|
||||
${$self->{BUFF_PT}} = substr ${$self->{BUFF_PT}}, $purge;
|
||||
$self->{BASE} += $purge;
|
||||
$self->{POS} -= $purge;
|
||||
$self->{LEN} -= $purge;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Set binary mode
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
sub BinMode($)
|
||||
{
|
||||
my $self = shift;
|
||||
binmode($self->{FILE_PT}) if $self->{FILE_PT};
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Close the file and free the buffer
|
||||
# Inputs: 0) reference to RandomAccess object
|
||||
sub Close($)
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
if ($self->{DEBUG}) {
|
||||
local $_;
|
||||
if ($self->Seek(0,2)) {
|
||||
$self->{DEBUG}->{$self->Tell()} = 0; # set EOF marker
|
||||
my $last;
|
||||
my $tot = 0;
|
||||
my $bad = 0;
|
||||
foreach (sort { $a <=> $b } keys %{$self->{DEBUG}}) {
|
||||
my $pos = $_;
|
||||
my $len = $self->{DEBUG}->{$_};
|
||||
if (defined $last and $last < $pos) {
|
||||
my $bytes = $pos - $last;
|
||||
$tot += $bytes;
|
||||
$self->Seek($last);
|
||||
my $buff;
|
||||
$self->Read($buff, $bytes);
|
||||
my $warn = '';
|
||||
if ($buff =~ /[^\0]/) {
|
||||
$bad += ($pos - $last);
|
||||
$warn = ' - NON-ZERO!';
|
||||
}
|
||||
printf "0x%.8x - 0x%.8x (%d bytes)$warn\n", $last, $pos, $bytes;
|
||||
}
|
||||
my $cur = $pos + $len;
|
||||
$last = $cur unless defined $last and $last > $cur;
|
||||
}
|
||||
print "$tot bytes missed";
|
||||
$bad and print ", $bad non-zero!";
|
||||
print "\n";
|
||||
} else {
|
||||
warn "File::RandomAccess DEBUG not working (file already closed?)\n";
|
||||
}
|
||||
delete $self->{DEBUG};
|
||||
}
|
||||
# close the file
|
||||
if ($self->{FILE_PT}) {
|
||||
close($self->{FILE_PT});
|
||||
delete $self->{FILE_PT};
|
||||
}
|
||||
# reset the buffer
|
||||
my $emptyBuff = '';
|
||||
$self->{BUFF_PT} = \$emptyBuff;
|
||||
$self->{BASE} = 0;
|
||||
$self->{LEN} = 0;
|
||||
$self->{POS} = 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
1; # end
|
||||
250
.resources/nix/bin/lib/File/RandomAccess.pod
Normal file
250
.resources/nix/bin/lib/File/RandomAccess.pod
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: RandomAccess.pod -- Documentation for File::RandomAccess
|
||||
#
|
||||
# Description: Buffer to support random access reading of sequential file
|
||||
#
|
||||
# Legal: Copyright (c) 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the same terms as Perl itself.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
=head1 NAME
|
||||
|
||||
File::RandomAccess - Random access reads of sequential file or scalar
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use File::RandomAccess;
|
||||
|
||||
$raf = File::RandomAccess->new(\*FILE, $disableSeekTest);
|
||||
|
||||
$raf = File::RandomAccess->new(\$data);
|
||||
|
||||
$err = $raf->Seek($pos);
|
||||
$num = $raf->Read($buff, $bytes);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Allows random access to sequential file by buffering the file if necessary.
|
||||
Also allows access to data in memory to be accessed as if it were a file.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<new>
|
||||
|
||||
Creates a new RandomAccess object given a file reference or
|
||||
reference to data in memory.
|
||||
|
||||
# Read from open file or pipe
|
||||
$raf = File::RandomAccess->new(\*FILE);
|
||||
|
||||
# Read from data in memory
|
||||
$raf = File::RandomAccess->new(\$data);
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object or RandomAccess class name.
|
||||
|
||||
1) File reference or scalar reference.
|
||||
|
||||
2) Flag set if file is already random access (disables automatic SeekTest).
|
||||
|
||||
=item Returns:
|
||||
|
||||
Reference to RandomAccess object.
|
||||
|
||||
=back
|
||||
|
||||
=item B<SeekTest>
|
||||
|
||||
Performs test seek() on file to determine if buffering is necessary. If
|
||||
the seek() fails, then the file is buffered to allow random access.
|
||||
B<SeekTest>() is automatically called from B<new> unless specified.
|
||||
|
||||
$result = $raf->SeekTest();
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
=item Returns:
|
||||
|
||||
1 if seek test passed (ie. no buffering required).
|
||||
|
||||
=item Notes:
|
||||
|
||||
Must be called before any other i/o.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Tell>
|
||||
|
||||
Get current position in file
|
||||
|
||||
$pos = $raf->Tell();
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
=item Returns:
|
||||
|
||||
Current position in file
|
||||
|
||||
=back
|
||||
|
||||
=item B<Seek>
|
||||
|
||||
Seek to specified position in file. When buffered, this doesn't quite
|
||||
behave like seek() since it returns success even if you seek outside the
|
||||
limits of the file.
|
||||
|
||||
$success = $raf->Seek($pos, 0);
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
1) Position.
|
||||
|
||||
2) Whence (0=from start, 1=from cur pos, 2=from end).
|
||||
|
||||
=item Returns:
|
||||
|
||||
1 on success, 0 otherwise
|
||||
|
||||
=back
|
||||
|
||||
=item B<Read>
|
||||
|
||||
Read data from the file.
|
||||
|
||||
$num = $raf->Read($buff, 1024);
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
1) Buffer.
|
||||
|
||||
2) Number of bytes to read.
|
||||
|
||||
=item Returns:
|
||||
|
||||
Number of bytes actually read.
|
||||
|
||||
=back
|
||||
|
||||
=item B<ReadLine>
|
||||
|
||||
Read a line from file (end of line is $/).
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
1) Buffer.
|
||||
|
||||
=item Returns:
|
||||
|
||||
Number of bytes read.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Slurp>
|
||||
|
||||
Read whole file into buffer, without changing read pointer.
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
=item Returns:
|
||||
|
||||
Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=item B<BinMode>
|
||||
|
||||
Set binary mode for file.
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
=item Returns:
|
||||
|
||||
Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Close>
|
||||
|
||||
Close the file and free the buffer.
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Reference to RandomAccess object.
|
||||
|
||||
=item Returns:
|
||||
|
||||
Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<NoBuffer>
|
||||
|
||||
Avoid buffering sequential files.
|
||||
|
||||
$raf->{NoBuffer} = 1;
|
||||
|
||||
When this option is set, old data is purged from the internal buffer before
|
||||
a read operation on a sequential file. In this mode, memory requirements
|
||||
may be significantly reduced when reading sequential files, but seeking
|
||||
backward is limited to within the size of the internal buffer (which will be
|
||||
at least as large as the last returned data block), and seeking relative to
|
||||
the end of file is not allowed.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
# end
|
||||
10187
.resources/nix/bin/lib/Image/ExifTool.pm
Normal file
10187
.resources/nix/bin/lib/Image/ExifTool.pm
Normal file
File diff suppressed because it is too large
Load diff
3091
.resources/nix/bin/lib/Image/ExifTool.pod
Normal file
3091
.resources/nix/bin/lib/Image/ExifTool.pod
Normal file
File diff suppressed because it is too large
Load diff
793
.resources/nix/bin/lib/Image/ExifTool/7Z.pm
Normal file
793
.resources/nix/bin/lib/Image/ExifTool/7Z.pm
Normal file
|
|
@ -0,0 +1,793 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: 7Z.pm
|
||||
#
|
||||
# Description: Read 7z archive meta information
|
||||
#
|
||||
# Revisions: 2023/04/28 - Amir Gooran (Cyberno)
|
||||
# 2023-05-06 - PH Minor changes in ExifTool interfacing
|
||||
#
|
||||
# References: 1) https://py7zr.readthedocs.io/en/latest/archive_format.html
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::7Z;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.01';
|
||||
|
||||
sub ReadUInt32 {
|
||||
my $buff;
|
||||
|
||||
$_[0]->Read($buff, 4);
|
||||
my ($output) = unpack('L', $buff);
|
||||
return $output;
|
||||
}
|
||||
|
||||
sub ReadUInt64 {
|
||||
my $buff;
|
||||
my $output;
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $b = ord($buff);
|
||||
if($b == 255){ # read real uint64
|
||||
$_[0]->Read($buff, 8);
|
||||
my ($output) = unpack('Q', $buff);
|
||||
return $output;
|
||||
}
|
||||
my @blen = (0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE);
|
||||
|
||||
my $mask = 0x80;
|
||||
my $vlen = 8;
|
||||
|
||||
for (my $l = 0 ; $l < scalar(@blen) ; $l++) {
|
||||
my $v = $blen[$l];
|
||||
if($b <= $v){
|
||||
$vlen = $l;
|
||||
last;
|
||||
}
|
||||
$mask >>= 1;
|
||||
}
|
||||
if($vlen == 0){
|
||||
return $b & ($mask - 1);
|
||||
}
|
||||
$_[0]->Read($buff, $vlen);
|
||||
$buff .= "\0\0\0\0\0\0\0\0";
|
||||
|
||||
my $value = unpack('Q', $buff);
|
||||
my $highpart = $b & ($mask - 1);
|
||||
return $value + ($highpart << ($vlen * 8));
|
||||
}
|
||||
|
||||
sub ReadRealUInt64 {
|
||||
my $buff;
|
||||
|
||||
$_[0]->Read($buff, 8);
|
||||
my $value = unpack('Q', $buff);
|
||||
return $value;
|
||||
}
|
||||
|
||||
sub ReadBoolean {
|
||||
my $buff;
|
||||
my $count = $_[1];
|
||||
my $checkall = $_[2];
|
||||
my @result = ();
|
||||
|
||||
if($checkall){
|
||||
$_[0]->Read($buff, 1);
|
||||
my $all_defined = ord($buff);
|
||||
if($all_defined != 0){
|
||||
@result = (1)x$count;
|
||||
return @result;
|
||||
}
|
||||
}
|
||||
|
||||
my $b = 0;
|
||||
my $mask = 0;
|
||||
|
||||
for (my $i = 0 ; $i < $count ; $i++) {
|
||||
if($mask == 0){
|
||||
$_[0]->Read($buff, 1);
|
||||
$b = ord($buff);
|
||||
$mask = 0x80;
|
||||
}
|
||||
push(@result, ($b & $mask) != 0);
|
||||
$mask >>= 1;
|
||||
}
|
||||
return @result;
|
||||
}
|
||||
|
||||
sub ReadUTF16 {
|
||||
my $val = "";
|
||||
my $ch;
|
||||
|
||||
for(my $i=0; $i < 65536; $i++){
|
||||
$_[0]->Read($ch, 2);
|
||||
if($ch eq "\0\0"){
|
||||
last;
|
||||
}
|
||||
$val .= $ch;
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
sub ReadPackInfo {
|
||||
my $et = shift;
|
||||
|
||||
my $buff;
|
||||
my %out_packinfo = ();
|
||||
$out_packinfo{"packsizes"} = ();
|
||||
|
||||
$out_packinfo{"packpos"} = ReadUInt64($_[0]);
|
||||
my $numstreams = ReadUInt64($_[0]);
|
||||
$et->VPrint(0, "Number Of Streams: $numstreams\n");
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
|
||||
my @packsizes;
|
||||
if($pid == 9){ # size
|
||||
for (my $i = 0 ; $i < $numstreams ; $i++) {
|
||||
push(@{ $out_packinfo{"packsizes"} }, ReadUInt64($_[0]));
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
if($pid == 10){ # crc
|
||||
my @crcs;
|
||||
my @digestdefined = ReadBoolean($_[0], $numstreams, 1);
|
||||
foreach my $crcexist (@digestdefined) {
|
||||
if($crcexist){
|
||||
push(@crcs, ReadUInt32($_[0]));
|
||||
}
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
}
|
||||
if($pid != 0) { # end id expected
|
||||
return 0;
|
||||
}
|
||||
return \%out_packinfo;
|
||||
}
|
||||
|
||||
sub findInBinPair {
|
||||
my @bindpairs = @{$_[0]};
|
||||
my $index = $_[1];
|
||||
|
||||
for (my $i = 0; $i < scalar(@bindpairs); $i++) {
|
||||
if($bindpairs[$i] == $index){
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
sub ReadFolder {
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
my $totalin = 0;
|
||||
my $totalout = 0;
|
||||
my %out_folder = ();
|
||||
$out_folder{"packed_indices"} = ();
|
||||
$out_folder{"bindpairs"} = ();
|
||||
$out_folder{"coders"} = ();
|
||||
|
||||
my $num_coders = ReadUInt64($_[0]);
|
||||
$et->VPrint(0, "Number of coders: $num_coders\n");
|
||||
|
||||
for (my $i = 0; $i < $num_coders; $i++) {
|
||||
my %c = ();
|
||||
$_[0]->Read($buff, 1);
|
||||
my $b = ord($buff);
|
||||
my $methodsize = $b & 0xF;
|
||||
my $iscomplex = ($b & 0x10) == 0x10;
|
||||
my $hasattributes = ($b & 0x20) == 0x20;
|
||||
if($methodsize > 0){
|
||||
$_[0]->Read($buff, $methodsize);
|
||||
$c{"method"} = $buff;
|
||||
}
|
||||
else{
|
||||
$c{"method"} = "\0";
|
||||
}
|
||||
if($iscomplex){
|
||||
$c{"numinstreams"} = ReadUInt64($_[0]);
|
||||
$c{"numoutstreams"} = ReadUInt64($_[0]);
|
||||
}
|
||||
else{
|
||||
$c{"numinstreams"} = 1;
|
||||
$c{"numoutstreams"} = 1;
|
||||
}
|
||||
$totalin += $c{"numinstreams"};
|
||||
$totalout += $c{"numoutstreams"};
|
||||
if($hasattributes){
|
||||
my $proplen = ReadUInt64($_[0]);
|
||||
$_[0]->Read($buff, $proplen);
|
||||
$c{"properties"} = $buff;
|
||||
}
|
||||
else {
|
||||
$c{"properties"} = undef;
|
||||
}
|
||||
$et->VPrint(0, "Reading coder $i\n");
|
||||
push(@{ $out_folder{"coders"} }, \%c);
|
||||
}
|
||||
my $num_bindpairs = $totalout - 1;
|
||||
for (my $i = 0; $i < $num_bindpairs; $i++) {
|
||||
my @bond = (ReadUInt64($_[0]), ReadUInt64($_[0]));
|
||||
push(@{ $out_folder{"bindpairs"} }, @bond);
|
||||
}
|
||||
my $num_packedstreams = $totalin - $num_bindpairs;
|
||||
if($num_packedstreams == 1){
|
||||
for (my $i = 0; $i < $totalin; $i++) {
|
||||
if(findInBinPair(\@{ $out_folder{"bindpairs"} }, $i) < 0){
|
||||
push(@{ $out_folder{"packed_indices"} }, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (my $i = 0; $i < $num_packedstreams; $i++) {
|
||||
push(@{ $out_folder{"packed_indices"} }, ReadUInt64($_[0]));
|
||||
}
|
||||
}
|
||||
|
||||
return \%out_folder;
|
||||
}
|
||||
|
||||
sub RetrieveCodersInfo{
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
my @folders = @{ $_[1] };
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
|
||||
if($pid != 0x0c){ # coders unpack size id expected
|
||||
return 0;
|
||||
}
|
||||
foreach my $folder (@folders) {
|
||||
$folder->{"unpacksizes"} = ();
|
||||
foreach my $c (@{ $folder->{"coders"} }) {
|
||||
for (my $i = 0 ; $i < $c->{"numoutstreams"} ; $i++) {
|
||||
push(@{ $folder->{"unpacksizes" } }, ReadUInt64($_[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
|
||||
if($pid == 0x0a){ #crc
|
||||
my $numfolders = scalar(@folders);
|
||||
$et->VPrint(0, "Number of folders: $numfolders\n");
|
||||
my @defined = ReadBoolean($_[0], $numfolders, 1);
|
||||
my @crcs;
|
||||
foreach my $crcexist (@defined) {
|
||||
if($crcexist){
|
||||
push(@crcs, ReadUInt32($_[0]));
|
||||
}
|
||||
}
|
||||
for (my $i = 0 ; $i < $numfolders ; $i++) {
|
||||
$folders[$i]->{"digestdefined"} = $defined[$i];
|
||||
$folders[$i]->{"crc"} = $crcs[$i];
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
|
||||
if($pid != 0x00){ # end id expected
|
||||
$et->VPrint(0, "Invalid PID: $pid\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub ReadUnpackInfo {
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
my %out_unpackinfo = ();
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
|
||||
if($pid != 0xb) { # folder id expected
|
||||
return 0;
|
||||
}
|
||||
|
||||
$out_unpackinfo{"numfolders"} = ReadUInt64($_[0]);
|
||||
$out_unpackinfo{"folders"} = ();
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $external = ord($buff);
|
||||
|
||||
if($external == 0x00){
|
||||
for (my $i = 0 ; $i < $out_unpackinfo{"numfolders"}; $i++) {
|
||||
$et->VPrint(0, "Reading folder $i\n");
|
||||
my $folder = ReadFolder($et, $_[0]);
|
||||
push(@{ $out_unpackinfo{"folders"} }, $folder);
|
||||
}
|
||||
}
|
||||
return 0 unless RetrieveCodersInfo($et, $_[0], $out_unpackinfo{"folders"});
|
||||
return \%out_unpackinfo;
|
||||
}
|
||||
|
||||
sub ReadSubstreamsInfo {
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
my %out_substreamsinfo = ();
|
||||
$out_substreamsinfo{"num_unpackstreams_folders"} = ();
|
||||
|
||||
my $numfolders = $_[1];
|
||||
my $folders = $_[2];
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
if($pid == 13){ # num unpack stream
|
||||
$et->VPrint(0, "Num unpack stream detected.\n");
|
||||
for (my $i = 0 ; $i < $numfolders; $i++) {
|
||||
push(@{ $out_substreamsinfo{"num_unpackstreams_folders"} }, ReadUInt64($_[0]));
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
else{
|
||||
@{ $out_substreamsinfo{"num_unpackstreams_folders"} } = (1)x$numfolders;
|
||||
}
|
||||
if($pid == 9){ # size property
|
||||
$et->VPrint(0, "Size property detected.\n");
|
||||
$out_substreamsinfo{"unpacksizes"} = ();
|
||||
for(my $i=0; $i< scalar(@{ $out_substreamsinfo{"num_unpackstreams_folders"} }); $i++){
|
||||
my $totalsize = 0;
|
||||
for(my $j=1; $j < @{ $out_substreamsinfo{"num_unpackstreams_folders"} }[$i]; $j++){
|
||||
my $size = ReadUInt64($_[0]);
|
||||
push(@{ $out_substreamsinfo{"unpacksizes"} }, $size);
|
||||
$totalsize += $size;
|
||||
}
|
||||
# self.unpacksizes.append(folders[i].get_unpack_size() - totalsize)
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
my $num_digests = 0;
|
||||
my $num_digests_total = 0;
|
||||
for (my $i = 0 ; $i < $numfolders; $i++) {
|
||||
my $numsubstreams = @{ $out_substreamsinfo{"num_unpackstreams_folders"} }[$i];
|
||||
if($numsubstreams != 1 or not @{ $folders }[$i]->{"digestdefined"}){
|
||||
$num_digests += $numsubstreams;
|
||||
}
|
||||
$num_digests_total += $numsubstreams;
|
||||
}
|
||||
$et->VPrint(0, "Num Digests Total: $num_digests_total\n");
|
||||
if($pid == 10) { # crc property
|
||||
$et->VPrint(0, "CRC property detected.\n");
|
||||
my @crcs;
|
||||
my @defined = ReadBoolean($_[0], $num_digests, 1);
|
||||
foreach my $crcexist (@defined) {
|
||||
push(@crcs, ReadUInt32($_[0]));
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
if($pid != 0x00){ # end id expected
|
||||
return 0;
|
||||
}
|
||||
return \%out_substreamsinfo;
|
||||
}
|
||||
|
||||
sub ReadStreamsInfo {
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
my $unpackinfo;
|
||||
my %out_streamsinfo = ();
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
if($pid == 6){ # pack info
|
||||
my $packinfo = ReadPackInfo($et, $_[0]);
|
||||
return 0 unless $packinfo;
|
||||
$out_streamsinfo{"packinfo"} = $packinfo;
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
if($pid == 7) { # unpack info
|
||||
$et->VPrint(0, "Unpack info data detected.\n");
|
||||
$unpackinfo = ReadUnpackInfo($et, $_[0]);
|
||||
return 0 unless $unpackinfo;
|
||||
$out_streamsinfo{"unpackinfo"} = $unpackinfo;
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
if($pid == 8){ # substreams info
|
||||
$et->VPrint(0, "Substreams info data detected.\n");
|
||||
my $substreamsinfo = ReadSubstreamsInfo($et, $_[0], $unpackinfo->{"numfolders"}, $unpackinfo->{"folders"});
|
||||
return 0 unless $substreamsinfo;
|
||||
$out_streamsinfo{"substreamsinfo"} = $substreamsinfo;
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
if($pid != 0x00){ # end id expected
|
||||
$et->VPrint(0, "Invalid PID: $pid\n");
|
||||
return 0;
|
||||
}
|
||||
return \%out_streamsinfo;
|
||||
}
|
||||
|
||||
sub IsNativeCoder {
|
||||
my $coder = $_[0];
|
||||
|
||||
if(ord(substr($coder->{"method"}, 0, 1)) == 3){
|
||||
if(ord(substr($coder->{"method"}, 1, 1)) == 1) {
|
||||
if(ord(substr($coder->{"method"}, 2, 1)) == 1) {
|
||||
return "LZMA";
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif(ord(substr($coder->{"method"}, 0, 1)) == 6){
|
||||
if(ord(substr($coder->{"method"}, 1, 1)) == 0xf1) {
|
||||
if(ord(substr($coder->{"method"}, 2, 1)) == 7) {
|
||||
if(ord(substr($coder->{"method"}, 3, 1)) == 1) {
|
||||
return "7zAES";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub GetDecompressor {
|
||||
my $et = shift;
|
||||
|
||||
my $folder = $_[0];
|
||||
my %out_decompressor = ();
|
||||
$out_decompressor{"chain"} = ();
|
||||
$out_decompressor{"input_size"} = $_[1];
|
||||
$out_decompressor{"_unpacksizes"} = $folder->{"unpacksizes"};
|
||||
@{ $out_decompressor{"_unpacked"} } = (0) x scalar(@{ $out_decompressor{"_unpacksizes"} });
|
||||
$out_decompressor{"consumed"} = 0;
|
||||
$out_decompressor{"block_size"} = 32768;
|
||||
$out_decompressor{"_unused"} = [];
|
||||
|
||||
foreach my $coder (@{ $folder->{"coders"} }) {
|
||||
my $algorithm = IsNativeCoder($coder);
|
||||
if($algorithm eq "7zAES") {
|
||||
$et->Warn("File is encrypted.", 0);
|
||||
return 0;
|
||||
}
|
||||
else{
|
||||
push(@{ $out_decompressor{"chain"} }, $algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
return \%out_decompressor;
|
||||
}
|
||||
|
||||
sub ReadData {
|
||||
my $et = shift;
|
||||
my $decompressor = $_[1];
|
||||
my $rest_size = $decompressor->{"input_size"} - $decompressor->{"consumed"};
|
||||
my $unused_s = scalar(@{ $decompressor->{"_unused"} });
|
||||
my $read_size = $rest_size - $unused_s;
|
||||
my $data = "";
|
||||
if($read_size > $decompressor->{"block_size"} - $unused_s){
|
||||
$read_size = $decompressor->{"block_size"} - $unused_s;
|
||||
}
|
||||
if($read_size > 0){
|
||||
$decompressor->{"consumed"} += $_[0]->Read($data, $read_size);
|
||||
$et->VPrint(0, "Compressed size: $read_size\n");
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub Decompress_Internal {
|
||||
my $data = "";
|
||||
for(my $i=0; $i < scalar(@{ $_[0]->{"chain"} }); $i++){
|
||||
if(@{ $_[0]->{"_unpacked"} }[$i] < @{ $_[0]->{"_unpacksizes"} }[$i]){
|
||||
my %opts = ();
|
||||
$opts{"Filter"} = Lzma::Filter::Lzma1();
|
||||
my ($z, $status) = Compress::Raw::Lzma::RawDecoder->new( %opts );
|
||||
$status = $z->code($_[1], $data);
|
||||
@{ $_[0]->{"_unpacked"} }[$i] += length($data);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub Decompress {
|
||||
my $et = shift;
|
||||
my $max_length = $_[1];
|
||||
my $data = ReadData($et, $_[0], $_[1]);
|
||||
my $tmp = Decompress_Internal($_[1], $data);
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
sub ReadName {
|
||||
my $numfiles = $_[1];
|
||||
|
||||
for(my $i=0; $i < $numfiles; $i++){
|
||||
@{ $_[2] }[$i]->{"filename"} = ReadUTF16($_[0]);
|
||||
}
|
||||
}
|
||||
|
||||
sub ReadTimes {
|
||||
my $et = shift;
|
||||
my $external;
|
||||
my $numfiles = $_[1];
|
||||
my $name = $_[2];
|
||||
|
||||
my @defined = ReadBoolean($_[0], $numfiles, 1);
|
||||
$_[0]->Read($external, 1);
|
||||
if(ord($external) != 0){
|
||||
$et->Warn("Invalid or corrupted file. (ReadTimes)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(my $i=0; $i < $numfiles; $i++){
|
||||
if($defined[$i]){
|
||||
my $value = ReadRealUInt64($_[0]);
|
||||
$value = $value / 10000000.0 - 11644473600;
|
||||
@{ $_[3] }[$i]->{$name} = $value;
|
||||
}
|
||||
else{
|
||||
@{ $_[3] }[$i]->{$name} = undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub ReadAttributes {
|
||||
my $numfiles = $_[1];
|
||||
|
||||
for(my $i=0; $i < $numfiles; $i++){
|
||||
if($_[2][$i]){
|
||||
my $value = ReadUInt32($_[0]);
|
||||
@{ $_[3] }[$i]->{"attributes"} = $value >> 8;
|
||||
}
|
||||
else{
|
||||
@{ $_[3] }[$i]->{"attributes"} = undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub ReadFilesInfo {
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
|
||||
my $numfiles = ReadUInt64($_[0]);
|
||||
my @out_files = ();
|
||||
for(my $i = 0; $i < $numfiles; $i++){
|
||||
my %new_file = ();
|
||||
$new_file{"emptystream"} = 0;
|
||||
push(@out_files, \%new_file);
|
||||
}
|
||||
my $numemptystreams = 0;
|
||||
$et->VPrint(0, "Number of files: $numfiles\n");
|
||||
while(1){
|
||||
$_[0]->Read($buff, 1);
|
||||
my $prop = ord($buff);
|
||||
if($prop == 0){ # end
|
||||
return \@out_files;
|
||||
}
|
||||
my $size = ReadUInt64($_[0]);
|
||||
if($prop == 25) { # dummy
|
||||
$_[0]->Seek($size, 1);
|
||||
next;
|
||||
}
|
||||
$_[0]->Read($buff, $size);
|
||||
my $buffer = File::RandomAccess->new(\$buff);
|
||||
if($prop == 14){ # empty stream
|
||||
my @isempty = ReadBoolean($buffer, $numfiles, 0);
|
||||
my $numemptystreams = 0;
|
||||
for(my $i = 0; $i < $numfiles; $i++){
|
||||
if($isempty[$i] == 0){
|
||||
$out_files[$i]->{"emptystream"} = 0;
|
||||
}
|
||||
else{
|
||||
$out_files[$i]->{"emptystream"} = 1;
|
||||
$numemptystreams++;
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif($prop == 15) { # empty file
|
||||
|
||||
}
|
||||
elsif($prop == 17){ # name
|
||||
$et->VPrint(0, "Name prop detected.\n");
|
||||
my $external;
|
||||
$buffer->Read($external, 1);
|
||||
my $is_external = ord($external);
|
||||
if($is_external == 0){
|
||||
ReadName($buffer, $numfiles, \@out_files);
|
||||
}
|
||||
}
|
||||
elsif($prop == 20){ # last write time
|
||||
$et->VPrint(0, "Last write time detected.\n");
|
||||
ReadTimes($et, $buffer, $numfiles, "lastwritetime", \@out_files);
|
||||
}
|
||||
elsif($prop == 21){ # attributes
|
||||
$et->VPrint(0, "File attributes detected.\n");
|
||||
my $external;
|
||||
my @defined = ReadBoolean($buffer, $numfiles, 1);
|
||||
$_[0]->Read($external, 1);
|
||||
if(ord($external) == 0){
|
||||
ReadAttributes($buffer, $numfiles, \@defined, \@out_files);
|
||||
}
|
||||
else{
|
||||
my $dataindex = ReadUINT64($buffer);
|
||||
#TODO: try to read external data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub ExtractHeaderInfo {
|
||||
my $et = shift;
|
||||
my $buff;
|
||||
my %out_headerinfo = ();
|
||||
$out_headerinfo{"files_info"} = ();
|
||||
my $files_info;
|
||||
|
||||
$_[0]->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
|
||||
if($pid == 0x04){
|
||||
my $mainstreams = ReadStreamsInfo($et, $_[0]);
|
||||
if($mainstreams == 0){
|
||||
$et->Warn("Invalid or corrupted file. (ExtractHeaderInfo)");
|
||||
return 0;
|
||||
}
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
if($pid == 0x05){
|
||||
$et->VPrint(0, "File info pid reached.\n");
|
||||
$files_info = ReadFilesInfo($et, $_[0]);
|
||||
push(@{ $out_headerinfo{"files_info"} }, $files_info);
|
||||
$_[0]->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
}
|
||||
if($pid != 0x00){ # end id expected
|
||||
$et->VPrint(0, "Invalid PID: $pid\n");
|
||||
return 0;
|
||||
}
|
||||
return \%out_headerinfo;
|
||||
}
|
||||
|
||||
sub DisplayFiles {
|
||||
my $et = shift;
|
||||
my $docNum = 0;
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5');
|
||||
|
||||
foreach my $currentfile (@{ $_[0] }){
|
||||
$$et{DOC_NUM} = ++$docNum;
|
||||
$et->HandleTag($tagTablePtr, 'ModifyDate', $currentfile->{"lastwritetime"});
|
||||
$et->HandleTag($tagTablePtr, 'ArchivedFileName', $currentfile->{"filename"});
|
||||
}
|
||||
delete $$et{DOC_NUM};
|
||||
if($docNum > 1 and not $et->Options('Duplicates')){
|
||||
$et->Warn("Use the Duplicates option to extract tags for all $docNum files", 1);
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from a 7z file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid 7z file
|
||||
sub Process7Z($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($flags, $buff);
|
||||
|
||||
return 0 unless $raf->Read($buff, 6) and $buff eq "7z\xbc\xaf\x27\x1c";
|
||||
|
||||
$et->SetFileType();
|
||||
|
||||
$raf->Read($buff, 2);
|
||||
my ($major_version, $minor_version) = unpack('cc', $buff);
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5');
|
||||
$et->HandleTag($tagTablePtr, 'FileVersion', sprintf('7z v%d.%.2d',$major_version,$minor_version));
|
||||
|
||||
$raf->Seek(4, 1); # skip Start Header CRC
|
||||
|
||||
$raf->Read($buff, 20);
|
||||
my ($nextheaderoffset, $nextheadersize) = unpack('QQx', $buff);
|
||||
$et->VPrint(0, "NextHeaderOffset: $nextheaderoffset, NextHeaderSize: $nextheadersize\n");
|
||||
|
||||
$raf->Seek($nextheaderoffset, 1); # going to next header offset
|
||||
$raf->Read($buff, 1);
|
||||
my $pid = ord($buff);
|
||||
if($pid == 1){ # normal header
|
||||
$et->VPrint(0,"Normal header detected. trying to decode\n");
|
||||
my $headerinfo = ExtractHeaderInfo($et, $raf);
|
||||
if($headerinfo == 0){
|
||||
$et->Warn("Invalid or corrupted file.");
|
||||
return 1;
|
||||
}
|
||||
DisplayFiles($et, @{ $headerinfo->{"files_info"} });
|
||||
}
|
||||
elsif($pid == 23){ # encoded header
|
||||
unless (eval { require Compress::Raw::Lzma }) {
|
||||
$et->Warn("Install Compress::Raw::Lzma to read encoded 7z information");
|
||||
return 1;
|
||||
}
|
||||
$et->VPrint(0, "Encoded Header detected. trying to decode\n");
|
||||
my $streamsinfo = ReadStreamsInfo($et, $raf);
|
||||
if($streamsinfo == 0){
|
||||
$et->Warn("Invalid or corrupted file.");
|
||||
return 1;
|
||||
}
|
||||
my $buffer2 = ();
|
||||
foreach my $folder (@{ $streamsinfo->{"unpackinfo"}->{"folders"} }) {
|
||||
my @uncompressed = @{ $folder->{"unpacksizes"} };
|
||||
my $compressed_size = $streamsinfo->{"packinfo"}->{"packsizes"}[0];
|
||||
my $uncompressed_size = @uncompressed[scalar(@uncompressed) - 1];
|
||||
my $decomporessor = GetDecompressor($et, $folder, $compressed_size);
|
||||
if($decomporessor == 0){
|
||||
$et->Warn("Invalid or corrupted file.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $src_start = 32;
|
||||
$src_start += $streamsinfo->{"packinfo"}->{"packpos"};
|
||||
$raf->Seek($src_start, 0);
|
||||
my $remaining = $uncompressed_size;
|
||||
my $folder_data = "";
|
||||
while($remaining > 0){
|
||||
$folder_data .= Decompress($et, $raf, $decomporessor, $remaining);
|
||||
$remaining = $uncompressed_size - length($folder_data);
|
||||
}
|
||||
$buffer2 = File::RandomAccess->new(\$folder_data);
|
||||
}
|
||||
$buffer2->Seek(0, 0);
|
||||
$buffer2->Read($buff, 1);
|
||||
$pid = ord($buff);
|
||||
if($pid != 0x01){ # header field expected
|
||||
return 0;
|
||||
}
|
||||
my $headerinfo = ExtractHeaderInfo($et, $buffer2);
|
||||
if($headerinfo == 0){
|
||||
$et->Warn("Invalid or corrupted file.");
|
||||
return 1;
|
||||
}
|
||||
DisplayFiles($et, @{ $headerinfo->{"files_info"} });
|
||||
}else{ # Unknown header
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::7Z - Read 7z archives
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to extract meta
|
||||
information from 7z archives.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2023, Amir Gooran
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://py7zr.readthedocs.io/en/latest/archive_format.html>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/ZIP RAR5 Tags>
|
||||
|
||||
=cut
|
||||
|
||||
177
.resources/nix/bin/lib/Image/ExifTool/AAC.pm
Normal file
177
.resources/nix/bin/lib/Image/ExifTool/AAC.pm
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: AAC.pm
|
||||
#
|
||||
# Description: Read AAC audio files
|
||||
#
|
||||
# Revisions: 2023-12-29 - P. Harvey Created
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::AAC;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::FLAC;
|
||||
|
||||
$VERSION = '1.00';
|
||||
|
||||
my %convSampleRate = (
|
||||
0 => 96000, 7 => 22050,
|
||||
1 => 88200, 8 => 16000,
|
||||
2 => 64000, 9 => 12000,
|
||||
3 => 48000, 10 => 11025,
|
||||
4 => 44100, 11 => 8000,
|
||||
5 => 32000, 12 => 7350,
|
||||
6 => 24000,
|
||||
);
|
||||
|
||||
%Image::ExifTool::AAC::Main = (
|
||||
PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream,
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
NOTES => 'Tags extracted from Advanced Audio Coding (AAC) files.',
|
||||
# Bit000-011 - sync word (all 1's)
|
||||
# Bit012 - ID (seems to be always 0)
|
||||
# Bit013-014 - layer (00)
|
||||
# Bit015 - CRC absent (0=crc exists, 1=no crc)
|
||||
'Bit016-017' => {
|
||||
Name => 'ProfileType',
|
||||
PrintConv => {
|
||||
0 => 'Main',
|
||||
1 => 'Low Complexity',
|
||||
2 => 'Scalable Sampling Rate',
|
||||
},
|
||||
},
|
||||
'Bit018-021' => {
|
||||
Name => 'SampleRate',
|
||||
ValueConv => \%convSampleRate,
|
||||
},
|
||||
# Bit022 - private
|
||||
'Bit023-025' => {
|
||||
Name => 'Channels',
|
||||
PrintConv => {
|
||||
0 => '?',
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
3 => 3,
|
||||
4 => 4,
|
||||
5 => 5,
|
||||
6 => '5+1',
|
||||
7 => '7+1',
|
||||
},
|
||||
},
|
||||
# Bit026 - original/copy
|
||||
# Bit027 - home
|
||||
# Bit028 - copyright ID
|
||||
# Bit029 - copyright start
|
||||
# Bit030-042 - FrameLength
|
||||
# Bit043-053 - buffer fullness
|
||||
# Bit054-055 - BlocksInFrame (minus 1)
|
||||
# Note: Bitrate for frame = FrameLength * 8 * SampleRate / ((BlocksInFrame+1) * 1024)
|
||||
# - but all frames must be scanned to calculate average bitrate
|
||||
Encoder => {
|
||||
Name => 'Encoder',
|
||||
Notes => 'taken from filler payload of first frame',
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from an AAC file
|
||||
# Inputs: 0) ExifTool object reference, 1) Directory information reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid AAC file
|
||||
sub ProcessAAC($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $buf2);
|
||||
|
||||
# format of frame header (7 bytes):
|
||||
# SSSS SSSS SSSS ILLC PPRR RRpC CCoh csff ffff ffff fffb bbbb bbbb bbNN
|
||||
# 1111 1111 1111 0001 0110 0000 0100 0000 0000 0101 0101 1111 1111 1100 (eg.)
|
||||
# S = sync word o = original/copy
|
||||
# I = ID h = home
|
||||
# L = layer (00) c = copyright ID
|
||||
# C = CRC absent s = copyright start
|
||||
# P = profile object type f = frame length
|
||||
# R = sampling rate index b = buffer fullness
|
||||
# p = private N = number of raw data blocks in frame
|
||||
# C = channel configuration
|
||||
|
||||
$raf->Read($buff, 7) == 7 or return 0;
|
||||
return 0 unless $buff =~ /^\xff[\xf0\xf1]/;
|
||||
my @t = unpack('NnC', $buff);
|
||||
return 0 if (($t[0] >> 16) & 0x03) == 3; # (reserved profile type)
|
||||
return 0 if (($t[0] >> 12) & 0x0f) > 12; # validate sampling frequency index
|
||||
my $len = (($t[0] << 11) & 0x1800) | (($t[1] >> 5) & 0x07ff);
|
||||
return 0 if $len < 7;
|
||||
|
||||
$et->SetFileType();
|
||||
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::AAC::Main');
|
||||
$et->ProcessDirectory({ DataPt => \$buff }, $tagTablePtr);
|
||||
|
||||
# read the first frame data to check for a filler with the encoder name
|
||||
while ($len > 8 and $raf->Read($buff, $len-7) == $len-7) {
|
||||
my $noCRC = ($t[0] & 0x00010000);
|
||||
my $blocks = ($t[2] & 0x03);
|
||||
my $pos = 0;
|
||||
$pos += 2 + 2 * $blocks unless $noCRC;
|
||||
last if $pos + 2 > length($buff);
|
||||
my $tmp = unpack("x${pos}n", $buff);
|
||||
my $id = $tmp >> 13;
|
||||
# read filler payload
|
||||
if ($id == 6) {
|
||||
my $cnt = ($tmp >> 9) & 0x0f;
|
||||
++$pos;
|
||||
if ($cnt == 15) {
|
||||
$cnt += (($tmp >> 1) & 0xff) - 1;
|
||||
++$pos;
|
||||
}
|
||||
if ($pos + $cnt <= length($buff)) {
|
||||
my $dat = substr($buff, $pos, $cnt);
|
||||
$dat =~ s/^\0+//;
|
||||
$dat =~ s/\0+$//;
|
||||
$et->HandleTag($tagTablePtr, Encoder => $dat) if $dat =~ /^[\x20-\x7e]+$/;
|
||||
}
|
||||
}
|
||||
last;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::AAC - Read AAC audio files
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read
|
||||
Advanced Audio Coding (AAC) files.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
Since ISO charges money for the official AAC specification, this module is
|
||||
based on unofficial sources which may be incomplete, inaccurate or outdated.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/AAC Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
501
.resources/nix/bin/lib/Image/ExifTool/AES.pm
Normal file
501
.resources/nix/bin/lib/Image/ExifTool/AES.pm
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: AES.pm
|
||||
#
|
||||
# Description: AES encryption with cipher-block chaining
|
||||
#
|
||||
# Revisions: 2010/10/14 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.hoozi.com/Articles/AESEncryption.htm
|
||||
# 2) http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf
|
||||
# 3) http://www.faqs.org/rfcs/rfc3602.html
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::AES;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION @ISA @EXPORT_OK);
|
||||
require Exporter;
|
||||
|
||||
$VERSION = '1.01';
|
||||
@ISA = qw(Exporter);
|
||||
@EXPORT_OK = qw(Crypt);
|
||||
|
||||
my $seeded; # flag set if we already seeded random number generator
|
||||
my $nr; # number of rounds in AES cipher
|
||||
my @cbc; # cipher-block chaining bytes
|
||||
|
||||
# arrays (all unsigned character) to hold intermediate results during encryption
|
||||
my @state = ([],[],[],[]); # the 2-dimensional state array
|
||||
my @RoundKey; # round keys
|
||||
|
||||
my @sbox = (
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
|
||||
);
|
||||
|
||||
# reverse sbox
|
||||
my @rsbox = (
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
|
||||
);
|
||||
|
||||
# the round constant word array, $rcon[i], contains the values given by
|
||||
# x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
|
||||
# Note that i starts at 1, not 0).
|
||||
my @rcon = (
|
||||
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb,
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# This function produces 4*($nr+1) round keys.
|
||||
# The round keys are used in each round to encrypt the states.
|
||||
# Inputs: 0) key string (must be 16, 24 or 32 bytes long)
|
||||
sub KeyExpansion($)
|
||||
{
|
||||
my $key = shift;
|
||||
my @key = unpack 'C*', $key; # convert the key into a byte array
|
||||
my $nk = int(length($key) / 4); # number of 32-bit words in the key
|
||||
$nr = $nk + 6; # number of rounds
|
||||
|
||||
# temporary variables (all unsigned characters)
|
||||
my ($i,@temp);
|
||||
|
||||
# The first round key is the key itself.
|
||||
for ($i=0; $i<$nk; ++$i) {
|
||||
@RoundKey[$i*4..$i*4+3] = @key[$i*4..$i*4+3];
|
||||
}
|
||||
# All other round keys are found from the previous round keys.
|
||||
while ($i < (4 * ($nr+1))) {
|
||||
|
||||
@temp[0..3] = @RoundKey[($i-1)*4..($i-1)*4+3];
|
||||
|
||||
if ($i % $nk == 0) {
|
||||
# rotate the 4 bytes in a word to the left once
|
||||
# [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
|
||||
@temp[0..3] = @temp[1,2,3,0];
|
||||
|
||||
# take a four-byte input word and apply the S-box
|
||||
# to each of the four bytes to produce an output word.
|
||||
@temp[0..3] = @sbox[@temp[0..3]];
|
||||
|
||||
$temp[0] = $temp[0] ^ $rcon[$i/$nk];
|
||||
|
||||
} elsif ($nk > 6 && $i % $nk == 4) {
|
||||
|
||||
@temp[0..3] = @sbox[@temp[0..3]];
|
||||
}
|
||||
$RoundKey[$i*4+0] = $RoundKey[($i-$nk)*4+0] ^ $temp[0];
|
||||
$RoundKey[$i*4+1] = $RoundKey[($i-$nk)*4+1] ^ $temp[1];
|
||||
$RoundKey[$i*4+2] = $RoundKey[($i-$nk)*4+2] ^ $temp[2];
|
||||
$RoundKey[$i*4+3] = $RoundKey[($i-$nk)*4+3] ^ $temp[3];
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# This function adds the round key to state.
|
||||
# The round key is added to the state by an XOR function.
|
||||
sub AddRoundKey($)
|
||||
{
|
||||
my $round = shift;
|
||||
my ($i,$j);
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
my $k = $round*16 + $i*4;
|
||||
for ($j=0; $j<4; ++$j) {
|
||||
$state[$j][$i] ^= $RoundKey[$k + $j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Substitute the values in the state matrix with values in an S-box
|
||||
sub SubBytes()
|
||||
{
|
||||
my $i;
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
@{$state[$i]}[0..3] = @sbox[@{$state[$i]}[0..3]];
|
||||
}
|
||||
}
|
||||
|
||||
sub InvSubBytes()
|
||||
{
|
||||
my $i;
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
@{$state[$i]}[0..3] = @rsbox[@{$state[$i]}[0..3]];
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Shift the rows in the state to the left.
|
||||
# Each row is shifted with different offset.
|
||||
# Offset = Row number. So the first row is not shifted.
|
||||
sub ShiftRows()
|
||||
{
|
||||
# rotate first row 1 columns to left
|
||||
@{$state[1]}[0,1,2,3] = @{$state[1]}[1,2,3,0];
|
||||
|
||||
# rotate second row 2 columns to left
|
||||
@{$state[2]}[0,1,2,3] = @{$state[2]}[2,3,0,1];
|
||||
|
||||
# rotate third row 3 columns to left
|
||||
@{$state[3]}[0,1,2,3] = @{$state[3]}[3,0,1,2];
|
||||
}
|
||||
|
||||
sub InvShiftRows()
|
||||
{
|
||||
# rotate first row 1 columns to right
|
||||
@{$state[1]}[0,1,2,3] = @{$state[1]}[3,0,1,2];
|
||||
|
||||
# rotate second row 2 columns to right
|
||||
@{$state[2]}[0,1,2,3] = @{$state[2]}[2,3,0,1];
|
||||
|
||||
# rotate third row 3 columns to right
|
||||
@{$state[3]}[0,1,2,3] = @{$state[3]}[1,2,3,0];
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Find the product of {02} and the argument to xtime modulo 0x1b
|
||||
# Note: returns an integer which may need to be trimmed to 8 bits
|
||||
sub xtime($)
|
||||
{
|
||||
return ($_[0]<<1) ^ ((($_[0]>>7) & 1) * 0x1b);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Multiply numbers in the field GF(2^8)
|
||||
sub Mult($$)
|
||||
{
|
||||
my ($x, $y) = @_;
|
||||
return (($y & 1) * $x) ^
|
||||
(($y>>1 & 1) * xtime($x)) ^
|
||||
(($y>>2 & 1) * xtime(xtime($x))) ^
|
||||
(($y>>3 & 1) * xtime(xtime(xtime($x)))) ^
|
||||
(($y>>4 & 1) * xtime(xtime(xtime(xtime($x)))));
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Mix the columns of the state matrix
|
||||
sub MixColumns()
|
||||
{
|
||||
my ($i,$t0,$t1,$t2);
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
$t0 = $state[0][$i];
|
||||
$t2 = $state[0][$i] ^ $state[1][$i] ^ $state[2][$i] ^ $state[3][$i];
|
||||
$t1 = $state[0][$i] ^ $state[1][$i] ; $t1 = xtime($t1) & 0xff; $state[0][$i] ^= $t1 ^ $t2 ;
|
||||
$t1 = $state[1][$i] ^ $state[2][$i] ; $t1 = xtime($t1) & 0xff; $state[1][$i] ^= $t1 ^ $t2 ;
|
||||
$t1 = $state[2][$i] ^ $state[3][$i] ; $t1 = xtime($t1) & 0xff; $state[2][$i] ^= $t1 ^ $t2 ;
|
||||
$t1 = $state[3][$i] ^ $t0 ; $t1 = xtime($t1) & 0xff; $state[3][$i] ^= $t1 ^ $t2 ;
|
||||
}
|
||||
}
|
||||
|
||||
sub InvMixColumns()
|
||||
{
|
||||
my $i;
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
my $a = $state[0][$i];
|
||||
my $b = $state[1][$i];
|
||||
my $c = $state[2][$i];
|
||||
my $d = $state[3][$i];
|
||||
$state[0][$i] = (Mult($a,0x0e) ^ Mult($b,0x0b) ^ Mult($c,0x0d) ^ Mult($d,0x09)) & 0xff;
|
||||
$state[1][$i] = (Mult($a,0x09) ^ Mult($b,0x0e) ^ Mult($c,0x0b) ^ Mult($d,0x0d)) & 0xff;
|
||||
$state[2][$i] = (Mult($a,0x0d) ^ Mult($b,0x09) ^ Mult($c,0x0e) ^ Mult($d,0x0b)) & 0xff;
|
||||
$state[3][$i] = (Mult($a,0x0b) ^ Mult($b,0x0d) ^ Mult($c,0x09) ^ Mult($d,0x0e)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Encrypt (Cipher) or decrypt (InvCipher) a block of data with CBC
|
||||
# Inputs: 0) string to cipher (must be 16 bytes long)
|
||||
# Returns: cipher'd string
|
||||
sub Cipher($)
|
||||
{
|
||||
my @in = unpack 'C*', $_[0]; # unpack input plaintext
|
||||
my ($i, $j, $round);
|
||||
|
||||
# copy the input PlainText to state array and apply the CBC
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
for ($j=0; $j<4; ++$j) {
|
||||
my $k = $i*4 + $j;
|
||||
$state[$j][$i] = $in[$k] ^ $cbc[$k];
|
||||
}
|
||||
}
|
||||
|
||||
# add the First round key to the state before starting the rounds
|
||||
AddRoundKey(0);
|
||||
|
||||
# there will be $nr rounds; the first $nr-1 rounds are identical
|
||||
for ($round=1; ; ++$round) {
|
||||
SubBytes();
|
||||
ShiftRows();
|
||||
if ($round < $nr) {
|
||||
MixColumns();
|
||||
AddRoundKey($round);
|
||||
} else {
|
||||
# MixColumns() is not used in the last round
|
||||
AddRoundKey($nr);
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# the encryption process is over
|
||||
# copy the state array to output array (and save for CBC)
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
for ($j=0; $j<4; ++$j) {
|
||||
$cbc[$i*4+$j] = $state[$j][$i];
|
||||
}
|
||||
}
|
||||
return pack 'C*', @cbc; # return packed ciphertext
|
||||
}
|
||||
|
||||
sub InvCipher($)
|
||||
{
|
||||
my @in = unpack 'C*', $_[0]; # unpack input ciphertext
|
||||
my (@out, $i, $j, $round);
|
||||
|
||||
# copy the input CipherText to state array
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
for ($j=0; $j<4; ++$j) {
|
||||
$state[$j][$i] = $in[$i*4 + $j];
|
||||
}
|
||||
}
|
||||
|
||||
# add the First round key to the state before starting the rounds
|
||||
AddRoundKey($nr);
|
||||
|
||||
# there will be $nr rounds; the first $nr-1 rounds are identical
|
||||
for ($round=$nr-1; ; --$round) {
|
||||
InvShiftRows();
|
||||
InvSubBytes();
|
||||
AddRoundKey($round);
|
||||
# InvMixColumns() is not used in the last round
|
||||
last if $round <= 0;
|
||||
InvMixColumns();
|
||||
}
|
||||
|
||||
# copy the state array to output array and reverse the CBC
|
||||
for ($i=0; $i<4; ++$i) {
|
||||
for ($j=0; $j<4; ++$j) {
|
||||
my $k = $i*4 + $j;
|
||||
$out[$k] = $state[$j][$i] ^ $cbc[$k];
|
||||
}
|
||||
}
|
||||
@cbc = @in; # update CBC for next block
|
||||
return pack 'C*', @out; # return packed plaintext
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Encrypt/Decrypt using AES-CBC algorithm (with fixed 16-byte blocks)
|
||||
# Inputs: 0) data reference (with leading 16-byte initialization vector when decrypting)
|
||||
# 1) encryption key (16, 24 or 32 bytes for AES-128, AES-192 or AES-256)
|
||||
# 2) encrypt flag (false for decryption, true with length 16 bytes to
|
||||
# encrypt using this as the CBC IV, or true with other length to
|
||||
# encrypt with a randomly-generated IV)
|
||||
# 3) flag to disable padding
|
||||
# Returns: error string, or undef on success
|
||||
# Notes: encrypts/decrypts data in place (encrypted data returned with leading IV)
|
||||
sub Crypt($$;$$)
|
||||
{
|
||||
my ($dataPt, $key, $encrypt, $noPad) = @_;
|
||||
|
||||
# validate key length
|
||||
my $keyLen = length $key;
|
||||
unless ($keyLen == 16 or $keyLen == 24 or $keyLen == 32) {
|
||||
return "Invalid AES key length ($keyLen)";
|
||||
}
|
||||
my $partLen = length($$dataPt) % 16;
|
||||
my ($pos, $i);
|
||||
if ($encrypt) {
|
||||
if (length($encrypt) == 16) {
|
||||
@cbc = unpack 'C*', $encrypt;
|
||||
} else {
|
||||
# generate a random 16-byte CBC initialization vector
|
||||
unless ($seeded) {
|
||||
srand(time() & ($$ + ($$<<15)));
|
||||
$seeded = 1;
|
||||
}
|
||||
for ($i=0; $i<16; ++$i) {
|
||||
$cbc[$i] = int(rand(256));
|
||||
}
|
||||
$encrypt = pack 'C*', @cbc;
|
||||
}
|
||||
$$dataPt = $encrypt . $$dataPt; # add IV to the start of the data
|
||||
# add required padding so we can recover the
|
||||
# original string length after decryption
|
||||
# (padding bytes have value set to padding length)
|
||||
my $padLen = 16 - $partLen;
|
||||
$$dataPt .= (chr($padLen)) x $padLen unless $padLen == 16 and $noPad;
|
||||
$pos = 16; # start encrypting at byte 16 (after the IV)
|
||||
} elsif ($partLen) {
|
||||
return 'Invalid AES ciphertext length';
|
||||
} elsif (length $$dataPt >= 32) {
|
||||
# take the CBC initialization vector from the start of the data
|
||||
@cbc = unpack 'C16', $$dataPt;
|
||||
$$dataPt = substr($$dataPt, 16);
|
||||
$pos = 0; # start decrypting from byte 0 (now that IV is removed)
|
||||
} else {
|
||||
$$dataPt = ''; # empty text
|
||||
return undef;
|
||||
}
|
||||
# the KeyExpansion routine must be called before encryption
|
||||
KeyExpansion($key);
|
||||
|
||||
# loop through the data and convert in blocks
|
||||
my $dataLen = length $$dataPt;
|
||||
my $last = $dataLen - 16;
|
||||
my $func = $encrypt ? \&Cipher : \&InvCipher;
|
||||
while ($pos <= $last) {
|
||||
# cipher this block
|
||||
substr($$dataPt, $pos, 16) = &$func(substr($$dataPt, $pos, 16));
|
||||
$pos += 16;
|
||||
}
|
||||
unless ($encrypt or $noPad) {
|
||||
# remove padding if necessary (padding byte value gives length of padding)
|
||||
my $padLen = ord(substr($$dataPt, -1, 1));
|
||||
return 'AES decryption error (invalid pad byte)' if $padLen > 16;
|
||||
$$dataPt = substr($$dataPt, 0, $dataLen - $padLen);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::AES - AES encryption with cipher-block chaining
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Image::ExifTool::AES qw(Crypt);
|
||||
|
||||
$err = Crypt(\$plaintext, $key, 1); # encryption
|
||||
|
||||
$err = Crypt(\$ciphertext, $key); # decryption
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains an implementation of the AES encryption/decryption
|
||||
algorithms with cipher-block chaining (CBC) and RFC 2898 PKCS #5 padding.
|
||||
This is the AESV2 and AESV3 encryption mode used in PDF documents.
|
||||
|
||||
=head1 EXPORTS
|
||||
|
||||
Exports nothing by default, but L</Crypt> may be exported.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Crypt
|
||||
|
||||
Implement AES encryption/decryption with cipher-block chaining.
|
||||
|
||||
=over 4
|
||||
|
||||
=item Inputs:
|
||||
|
||||
0) Scalar reference for data to encrypt/decrypt.
|
||||
|
||||
1) Encryption key string (must have length 16, 24 or 32).
|
||||
|
||||
2) [optional] Encrypt flag (false to decrypt).
|
||||
|
||||
3) [optional] Flag to avoid removing padding after decrypting, or to avoid
|
||||
adding 16 bytes of padding before encrypting when data length is already a
|
||||
multiple of 16 bytes.
|
||||
|
||||
=item Returns:
|
||||
|
||||
On success, the return value is undefined and the data is encrypted or
|
||||
decrypted as specified. Otherwise returns an error string and the data is
|
||||
left in an indeterminate state.
|
||||
|
||||
=item Notes:
|
||||
|
||||
The length of the encryption key dictates the AES mode, with lengths of 16,
|
||||
24 and 32 bytes resulting in AES-128, AES-192 and AES-256.
|
||||
|
||||
When encrypting, the input data may be any length and will be padded to an
|
||||
even 16-byte block size using the specified padding technique. If the
|
||||
encrypt flag has length 16, it is used as the initialization vector for
|
||||
the cipher-block chaining, otherwise a random IV is generated. Upon
|
||||
successful return the data will be encrypted, with the first 16 bytes of
|
||||
the data being the CBC IV.
|
||||
|
||||
When decrypting, the input data begins with the 16-byte CBC initialization
|
||||
vector.
|
||||
|
||||
=back
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
This code is blindingly slow. But in truth, slowing down processing is the
|
||||
main purpose of encryption, so this really can't be considered a bug.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.hoozi.com/Articles/AESEncryption.htm>
|
||||
|
||||
=item L<http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf>
|
||||
|
||||
=item L<http://www.faqs.org/rfcs/rfc3602.html>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
281
.resources/nix/bin/lib/Image/ExifTool/AFCP.pm
Normal file
281
.resources/nix/bin/lib/Image/ExifTool/AFCP.pm
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: AFCP.pm
|
||||
#
|
||||
# Description: Read/write AFCP trailer
|
||||
#
|
||||
# Revisions: 12/26/2005 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::AFCP;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.10';
|
||||
|
||||
sub ProcessAFCP($$);
|
||||
|
||||
%Image::ExifTool::AFCP::Main = (
|
||||
PROCESS_PROC => \&ProcessAFCP,
|
||||
NOTES => q{
|
||||
AFCP stands for AXS File Concatenation Protocol, and is a poorly designed
|
||||
protocol for appending information to the end of files. This can be used as
|
||||
an auxiliary technique to store IPTC information in images, but is
|
||||
incompatible with some file formats.
|
||||
|
||||
ExifTool will read and write (but not create) AFCP IPTC information in JPEG
|
||||
and TIFF images.
|
||||
|
||||
See
|
||||
L<http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf>
|
||||
for the AFCP specification.
|
||||
},
|
||||
IPTC => { SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' } },
|
||||
TEXT => 'Text',
|
||||
Nail => {
|
||||
Name => 'ThumbnailImage',
|
||||
Groups => { 2 => 'Preview' },
|
||||
# (the specification allows for a variable amount of padding before
|
||||
# the image after a 10-byte header, so look for the JPEG SOI marker,
|
||||
# otherwise assume a fixed 8 bytes of padding)
|
||||
RawConv => q{
|
||||
pos($val) = 10;
|
||||
my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18;
|
||||
my $img = substr($val, $start);
|
||||
return $self->ValidateImage(\$img, $tag);
|
||||
},
|
||||
},
|
||||
PrVw => {
|
||||
Name => 'PreviewImage',
|
||||
Groups => { 2 => 'Preview' },
|
||||
RawConv => q{
|
||||
pos($val) = 10;
|
||||
my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18;
|
||||
my $img = substr($val, $start);
|
||||
return $self->ValidateImage(\$img, $tag);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read/write AFCP information in a file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# (Set 'ScanForTrailer' member in dirInfo to scan from current position for AFCP)
|
||||
# Returns: 1 on success, 0 if this file didn't contain AFCP information
|
||||
# -1 on write error or if the offsets were incorrect on reading
|
||||
# - updates DataPos to point to actual AFCP start if ScanForTrailer is set
|
||||
# - updates DirLen to trailer length
|
||||
# - returns Fixup reference in dirInfo hash when writing
|
||||
sub ProcessAFCP($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $curPos = $raf->Tell();
|
||||
my $offset = $$dirInfo{Offset} || 0; # offset from end of file
|
||||
my $rtnVal = 0;
|
||||
|
||||
NoAFCP: for (;;) {
|
||||
my ($buff, $fix, $dirBuff, $valBuff, $fixup, $vers);
|
||||
# look for AXS trailer
|
||||
last unless $raf->Seek(-12-$offset, 2) and
|
||||
$raf->Read($buff, 12) == 12 and
|
||||
$buff =~ /^(AXS(!|\*))/;
|
||||
my $endPos = $raf->Tell();
|
||||
my $hdr = $1;
|
||||
SetByteOrder($2 eq '!' ? 'MM' : 'II');
|
||||
my $startPos = Get32u(\$buff, 4);
|
||||
if ($raf->Seek($startPos, 0) and $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/) {
|
||||
$fix = 0;
|
||||
} else {
|
||||
$rtnVal = -1;
|
||||
# look for start of AXS trailer if 'ScanForTrailer'
|
||||
last unless $$dirInfo{ScanForTrailer} and $raf->Seek($curPos, 0);
|
||||
my $actualPos = $curPos;
|
||||
# first look for header right at current position
|
||||
for (;;) {
|
||||
last if $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/;
|
||||
last NoAFCP if $actualPos != $curPos;
|
||||
# scan for AXS header (could be after preview image)
|
||||
for (;;) {
|
||||
my $buf2;
|
||||
$raf->Read($buf2, 65536) or last NoAFCP;
|
||||
$buff .= $buf2;
|
||||
if ($buff =~ /$hdr/g) {
|
||||
$actualPos += pos($buff) - length($hdr);
|
||||
last; # ok, now go back and re-read header
|
||||
}
|
||||
$buf2 = substr($buf2, -3); # only need last 3 bytes for next test
|
||||
$actualPos += length($buff) - length($buf2);
|
||||
$buff = $buf2;
|
||||
}
|
||||
last unless $raf->Seek($actualPos, 0); # seek to start of AFCP
|
||||
}
|
||||
# calculate shift for fixing AFCP offsets
|
||||
$fix = $actualPos - $startPos;
|
||||
}
|
||||
# set variables returned in dirInfo hash
|
||||
$$dirInfo{DataPos} = $startPos + $fix; # actual start position
|
||||
$$dirInfo{DirLen} = $endPos - ($startPos + $fix);
|
||||
|
||||
$rtnVal = 1;
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $out = $et->Options('TextOut');
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
if ($outfile) {
|
||||
# allow all AFCP information to be deleted
|
||||
if ($$et{DEL_GROUP}{AFCP}) {
|
||||
$verbose and print $out " Deleting AFCP\n";
|
||||
++$$et{CHANGED};
|
||||
last;
|
||||
}
|
||||
$dirBuff = $valBuff = '';
|
||||
require Image::ExifTool::Fixup;
|
||||
$fixup = $$dirInfo{Fixup};
|
||||
$fixup or $fixup = $$dirInfo{Fixup} = Image::ExifTool::Fixup->new;
|
||||
$vers = substr($buff, 4, 2); # get version number
|
||||
} else {
|
||||
$et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP};
|
||||
}
|
||||
# read AFCP directory data
|
||||
my $numEntries = Get16u(\$buff, 6);
|
||||
my $dir;
|
||||
unless ($raf->Read($dir, 12 * $numEntries) == 12 * $numEntries) {
|
||||
$et->Error('Error reading AFCP directory', 1);
|
||||
last;
|
||||
}
|
||||
if ($verbose > 2 and not $outfile) {
|
||||
my $dat = $buff . $dir;
|
||||
print $out " AFCP Directory:\n";
|
||||
$et->VerboseDump(\$dat, Addr => $$dirInfo{DataPos}, Width => 12);
|
||||
}
|
||||
$fix and $et->Warn("Adjusted AFCP offsets by $fix", 1);
|
||||
#
|
||||
# process AFCP directory
|
||||
#
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::AFCP::Main');
|
||||
my ($index, $entry);
|
||||
for ($index=0; $index<$numEntries; ++$index) {
|
||||
my $entry = 12 * $index;
|
||||
my $tag = substr($dir, $entry, 4);
|
||||
my $size = Get32u(\$dir, $entry + 4);
|
||||
my $offset = Get32u(\$dir, $entry + 8);
|
||||
if ($size < 0x80000000 and
|
||||
$raf->Seek($offset+$fix, 0) and
|
||||
$raf->Read($buff, $size) == $size)
|
||||
{
|
||||
if ($outfile) {
|
||||
# rewrite this information
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
if ($tagInfo and $$tagInfo{SubDirectory}) {
|
||||
my %subdirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => $size,
|
||||
DataPos => $offset + $fix,
|
||||
Parent => 'AFCP',
|
||||
);
|
||||
my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
|
||||
my $newDir = $et->WriteDirectory(\%subdirInfo, $subTable);
|
||||
if (defined $newDir) {
|
||||
$size = length $newDir;
|
||||
$buff = $newDir;
|
||||
}
|
||||
}
|
||||
$fixup->AddFixup(length($dirBuff) + 8);
|
||||
$dirBuff .= $tag . Set32u($size) . Set32u(length $valBuff);
|
||||
$valBuff .= $buff;
|
||||
} else {
|
||||
# extract information
|
||||
$et->HandleTag($tagTablePtr, $tag, $buff,
|
||||
DataPt => \$buff,
|
||||
Size => $size,
|
||||
Index => $index,
|
||||
DataPos => $offset + $fix,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$et->Warn("Bad AFCP directory");
|
||||
$rtnVal = -1 if $outfile;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($outfile and length($dirBuff)) {
|
||||
my $outPos = Tell($outfile); # get current outfile position
|
||||
# apply fixup to directory pointers
|
||||
my $valPos = $outPos + 12; # start of value data
|
||||
$fixup->{Shift} += $valPos + length($dirBuff);
|
||||
$fixup->ApplyFixup(\$dirBuff);
|
||||
# write the AFCP header, directory, value data and EOF record (with zero checksums)
|
||||
Write($outfile, $hdr, $vers, Set16u(length($dirBuff)/12), Set32u(0),
|
||||
$dirBuff, $valBuff, $hdr, Set32u($outPos), Set32u(0)) or $rtnVal = -1;
|
||||
# complete fixup so the calling routine can apply further shifts
|
||||
$fixup->AddFixup(length($dirBuff) + length($valBuff) + 4);
|
||||
$fixup->{Start} += $valPos;
|
||||
$fixup->{Shift} -= $valPos;
|
||||
}
|
||||
last;
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::AFCP - Read/write AFCP trailer
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to extract
|
||||
information from the AFCP trailer. Although the AFCP specification is
|
||||
compatible with various file formats, ExifTool currently only processes AFCP
|
||||
in JPEG images.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
AFCP is a specification which allows meta information (including IPTC) to be
|
||||
appended to the end of a file.
|
||||
|
||||
It is a poorly designed protocol because (like TIFF) it uses absolute
|
||||
offsets to specify data locations. This is a huge blunder because it makes
|
||||
the AFCP information dependent on the file length, so it is easily
|
||||
invalidated by image editing software which doesn't recognize the AFCP
|
||||
trailer to fix up these offsets when the file length changes. ExifTool will
|
||||
attempt to fix these invalid offsets if possible.
|
||||
|
||||
Scanning for AFCP information may be time consuming, especially when reading
|
||||
from a sequential device, since the information is at the end of the file.
|
||||
In these instances, the ExifTool FastScan option may be used to disable
|
||||
scanning for AFCP information.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.tocarte.com/media/axs_afcp_spec.pdf>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/AFCP Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
316
.resources/nix/bin/lib/Image/ExifTool/AIFF.pm
Normal file
316
.resources/nix/bin/lib/Image/ExifTool/AIFF.pm
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: AIFF.pm
|
||||
#
|
||||
# Description: Read AIFF meta information
|
||||
#
|
||||
# Revisions: 01/06/2006 - P. Harvey Created
|
||||
# 09/22/2008 - PH Added DjVu support
|
||||
#
|
||||
# References: 1) http://developer.apple.com/documentation/QuickTime/INMAC/SOUND/imsoundmgr.30.htm#pgfId=3190
|
||||
# 2) http://astronomy.swin.edu.au/~pbourke/dataformats/aiff/
|
||||
# 3) http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::AIFF;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::ID3;
|
||||
|
||||
$VERSION = '1.13';
|
||||
|
||||
# information for time/date-based tags (time zero is Jan 1, 1904)
|
||||
my %timeInfo = (
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => 'ConvertUnixTime($val - ((66 * 365 + 17) * 24 * 3600))',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
);
|
||||
|
||||
# AIFF info
|
||||
%Image::ExifTool::AIFF::Main = (
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
NOTES => q{
|
||||
Tags extracted from Audio Interchange File Format (AIFF) files. See
|
||||
L<http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html> for
|
||||
the AIFF specification.
|
||||
},
|
||||
# FORM => 'Format',
|
||||
FVER => {
|
||||
Name => 'FormatVersion',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::AIFF::FormatVers' },
|
||||
},
|
||||
COMM => {
|
||||
Name => 'Common',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::AIFF::Common' },
|
||||
},
|
||||
COMT => {
|
||||
Name => 'Comment',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::AIFF::Comment' },
|
||||
},
|
||||
NAME => {
|
||||
Name => 'Name',
|
||||
ValueConv => '$self->Decode($val, "MacRoman")',
|
||||
},
|
||||
AUTH => {
|
||||
Name => 'Author',
|
||||
Groups => { 2 => 'Author' },
|
||||
ValueConv => '$self->Decode($val, "MacRoman")',
|
||||
},
|
||||
'(c) ' => {
|
||||
Name => 'Copyright',
|
||||
Groups => { 2 => 'Author' },
|
||||
ValueConv => '$self->Decode($val, "MacRoman")',
|
||||
},
|
||||
ANNO => {
|
||||
Name => 'Annotation',
|
||||
ValueConv => '$self->Decode($val, "MacRoman")',
|
||||
},
|
||||
'ID3 ' => {
|
||||
Name => 'ID3',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::ID3::Main',
|
||||
ProcessProc => \&Image::ExifTool::ID3::ProcessID3,
|
||||
},
|
||||
},
|
||||
APPL => 'ApplicationData', # (first 4 bytes are the application signature)
|
||||
# SSND => 'SoundData',
|
||||
# MARK => 'Marker',
|
||||
# INST => 'Instrument',
|
||||
# MIDI => 'MidiData',
|
||||
# AESD => 'AudioRecording',
|
||||
);
|
||||
|
||||
%Image::ExifTool::AIFF::Common = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
FORMAT => 'int16u',
|
||||
0 => 'NumChannels',
|
||||
1 => { Name => 'NumSampleFrames', Format => 'int32u' },
|
||||
3 => 'SampleSize',
|
||||
4 => { Name => 'SampleRate', Format => 'extended' }, #3
|
||||
9 => {
|
||||
Name => 'CompressionType',
|
||||
Format => 'string[4]',
|
||||
PrintConv => {
|
||||
NONE => 'None',
|
||||
ACE2 => 'ACE 2-to-1',
|
||||
ACE8 => 'ACE 8-to-3',
|
||||
MAC3 => 'MAC 3-to-1',
|
||||
MAC6 => 'MAC 6-to-1',
|
||||
sowt => 'Little-endian, no compression',
|
||||
alaw => 'a-law',
|
||||
ALAW => 'A-law',
|
||||
ulaw => 'mu-law',
|
||||
ULAW => 'Mu-law',
|
||||
'GSM '=> 'GSM',
|
||||
G722 => 'G722',
|
||||
G726 => 'G726',
|
||||
G728 => 'G728',
|
||||
},
|
||||
},
|
||||
11 => { #PH
|
||||
Name => 'CompressorName',
|
||||
Format => 'pstring',
|
||||
ValueConv => '$self->Decode($val, "MacRoman")',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::AIFF::FormatVers = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
FORMAT => 'int32u',
|
||||
0 => { Name => 'FormatVersionTime', %timeInfo },
|
||||
);
|
||||
|
||||
%Image::ExifTool::AIFF::Comment = (
|
||||
PROCESS_PROC => \&Image::ExifTool::AIFF::ProcessComment,
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
0 => { Name => 'CommentTime', %timeInfo },
|
||||
1 => 'MarkerID',
|
||||
2 => {
|
||||
Name => 'Comment',
|
||||
ValueConv => '$self->Decode($val, "MacRoman")',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::AIFF::Composite = (
|
||||
Duration => {
|
||||
Require => {
|
||||
0 => 'AIFF:SampleRate',
|
||||
1 => 'AIFF:NumSampleFrames',
|
||||
},
|
||||
RawConv => '($val[0] and $val[1]) ? $val[1] / $val[0] : undef',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
);
|
||||
|
||||
# add our composite tags
|
||||
Image::ExifTool::AddCompositeTags('Image::ExifTool::AIFF');
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process AIFF Comment chunk
|
||||
# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessComment($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
return 0 unless $dirLen > 2;
|
||||
my $numComments = unpack('n',$$dataPt);
|
||||
my $pos = 2;
|
||||
my $i;
|
||||
$verbose and $et->VerboseDir('Comment', $numComments);
|
||||
for ($i=0; $i<$numComments; ++$i) {
|
||||
last if $pos + 8 > $dirLen;
|
||||
my ($time, $markerID, $size) = unpack("x${pos}Nnn", $$dataPt);
|
||||
$et->HandleTag($tagTablePtr, 0, $time);
|
||||
$et->HandleTag($tagTablePtr, 1, $markerID) if $markerID;
|
||||
$pos += 8;
|
||||
last if $pos + $size > $dirLen;
|
||||
my $val = substr($$dataPt, $pos, $size);
|
||||
$et->HandleTag($tagTablePtr, 2, $val);
|
||||
++$size if $size & 0x01; # account for padding byte if necessary
|
||||
$pos += $size;
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from a AIFF file
|
||||
# Inputs: 0) ExifTool object reference, 1) DirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid AIFF file
|
||||
sub ProcessAIFF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $err, $tagTablePtr, $page, $type, $n);
|
||||
|
||||
# verify this is a valid AIFF file
|
||||
return 0 unless $raf->Read($buff, 12) == 12;
|
||||
my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3;
|
||||
my $pos = 12;
|
||||
# check for DjVu image
|
||||
if ($buff =~ /^AT&TFORM/) {
|
||||
# http://www.djvu.org/
|
||||
# http://djvu.sourceforge.net/specs/djvu3changes.txt
|
||||
my $buf2;
|
||||
return 0 unless $raf->Read($buf2, 4) == 4 and $buf2 =~ /^(DJVU|DJVM)/;
|
||||
$pos += 4;
|
||||
$buff = substr($buff, 4) . $buf2;
|
||||
$et->SetFileType('DJVU');
|
||||
return 1 if $fast3;
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::DjVu::Main');
|
||||
# modify FileType to indicate a multi-page document
|
||||
$$et{VALUE}{FileType} .= " (multi-page)" if $buf2 eq 'DJVM' and $$et{VALUE}{FileType};
|
||||
$type = 'DjVu';
|
||||
} else {
|
||||
return 0 unless $buff =~ /^FORM....(AIF(F|C))/s;
|
||||
$et->SetFileType($1);
|
||||
return 1 if $fast3;
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::AIFF::Main');
|
||||
$type = 'AIFF';
|
||||
}
|
||||
SetByteOrder('MM');
|
||||
my $verbose = $et->Options('Verbose');
|
||||
#
|
||||
# Read through the IFF chunks
|
||||
#
|
||||
for ($n=0;;++$n) {
|
||||
$raf->Read($buff, 8) == 8 or last;
|
||||
$pos += 8;
|
||||
my ($tag, $len) = unpack('a4N', $buff);
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
$et->VPrint(0, "AIFF '${tag}' chunk ($len bytes of data): ", $raf->Tell(),"\n");
|
||||
# AIFF chunks are padded to an even number of bytes
|
||||
my $len2 = $len + ($len & 0x01);
|
||||
if ($len2 > 100000000) {
|
||||
if ($len2 >= 0x80000000) {
|
||||
if (not $et->Options('LargeFileSupport')) {
|
||||
$et->Warn('End of processing at large chunk (LargeFileSupport not enabled)');
|
||||
last;
|
||||
} elsif ($et->Options('LargeFileSupport') eq '2') {
|
||||
$et->Warn('Skipping large chunk (LargeFileSupport is 2)');
|
||||
}
|
||||
}
|
||||
if ($tagInfo) {
|
||||
$et->Warn("Skipping large $$tagInfo{Name} chunk (> 100 MB)");
|
||||
undef $tagInfo;
|
||||
}
|
||||
}
|
||||
if ($tagInfo) {
|
||||
if ($$tagInfo{TypeOnly}) {
|
||||
$len = $len2 = 4;
|
||||
$page = ($page || 0) + 1;
|
||||
$et->VPrint(0, $$et{INDENT} . "Page $page:\n");
|
||||
}
|
||||
$raf->Read($buff, $len2) >= $len or $err=1, last;
|
||||
unless ($$tagInfo{SubDirectory} or $$tagInfo{Binary}) {
|
||||
$buff =~ s/\0+$//; # remove trailing nulls
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, $buff,
|
||||
DataPt => \$buff,
|
||||
DataPos => $pos,
|
||||
Start => 0,
|
||||
Size => $len,
|
||||
);
|
||||
} elsif (not $len) {
|
||||
next if ++$n < 100;
|
||||
$et->Warn('Aborting scan. Too many empty chunks');
|
||||
last;
|
||||
} elsif ($verbose > 2 and $len2 < 1024000) {
|
||||
$raf->Read($buff, $len2) == $len2 or $err = 1, last;
|
||||
$et->VerboseDump(\$buff);
|
||||
} else {
|
||||
$raf->Seek($len2, 1) or $err=1, last;
|
||||
}
|
||||
$pos += $len2;
|
||||
$n = 0;
|
||||
}
|
||||
$err and $et->Warn("Error reading $type file (corrupted?)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::AIFF - Read AIFF meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains routines required by Image::ExifTool to extract
|
||||
information from AIFF (Audio Interchange File Format) audio files.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://developer.apple.com/documentation/QuickTime/INMAC/SOUND/imsoundmgr.30.htm#pgfId=3190>
|
||||
|
||||
=item L<http://astronomy.swin.edu.au/~pbourke/dataformats/aiff/>
|
||||
|
||||
=item L<http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/AIFF Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
287
.resources/nix/bin/lib/Image/ExifTool/APE.pm
Normal file
287
.resources/nix/bin/lib/Image/ExifTool/APE.pm
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: APE.pm
|
||||
#
|
||||
# Description: Read Monkey's Audio meta information
|
||||
#
|
||||
# Revisions: 11/13/2006 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.monkeysaudio.com/
|
||||
# 2) http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::APE;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.07';
|
||||
|
||||
# APE metadata blocks
|
||||
%Image::ExifTool::APE::Main = (
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
NOTES => q{
|
||||
Tags found in Monkey's Audio (APE) information. Only a few common tags are
|
||||
listed below, but ExifTool will extract any tag found. ExifTool supports
|
||||
APEv1 and APEv2 tags, as well as ID3 information in APE files, and will also
|
||||
read APE metadata from MP3 and MPC files.
|
||||
},
|
||||
Album => { },
|
||||
Artist => { },
|
||||
Genre => { },
|
||||
Title => { },
|
||||
Track => { },
|
||||
Year => { },
|
||||
DURATION => {
|
||||
Name => 'Duration',
|
||||
ValueConv => '$val += 4294967296 if $val < 0 and $val >= -2147483648; $val * 1e-7',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
'Tool Version' => { Name => 'ToolVersion' },
|
||||
'Tool Name' => { Name => 'ToolName' },
|
||||
);
|
||||
|
||||
# APE MAC header version 3.97 or earlier
|
||||
%Image::ExifTool::APE::OldHeader = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 1 => 'MAC', 2 => 'Audio' },
|
||||
FORMAT => 'int16u',
|
||||
NOTES => 'APE MAC audio header for version 3.97 or earlier.',
|
||||
0 => {
|
||||
Name => 'APEVersion',
|
||||
ValueConv => '$val / 1000',
|
||||
},
|
||||
1 => 'CompressionLevel',
|
||||
# 2 => 'FormatFlags',
|
||||
3 => 'Channels',
|
||||
4 => { Name => 'SampleRate', Format => 'int32u' },
|
||||
# 6 => { Name => 'HeaderBytes', Format => 'int32u' }, # WAV header bytes
|
||||
# 8 => { Name => 'TerminatingBytes', Format => 'int32u' },
|
||||
10 => { Name => 'TotalFrames', Format => 'int32u' },
|
||||
12 => { Name => 'FinalFrameBlocks', Format => 'int32u' },
|
||||
);
|
||||
|
||||
# APE MAC header version 3.98 or later
|
||||
%Image::ExifTool::APE::NewHeader = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 1 => 'MAC', 2 => 'Audio' },
|
||||
FORMAT => 'int16u',
|
||||
NOTES => 'APE MAC audio header for version 3.98 or later.',
|
||||
0 => 'CompressionLevel',
|
||||
# 1 => 'FormatFlags',
|
||||
2 => { Name => 'BlocksPerFrame', Format => 'int32u' },
|
||||
4 => { Name => 'FinalFrameBlocks', Format => 'int32u' },
|
||||
6 => { Name => 'TotalFrames', Format => 'int32u' },
|
||||
8 => 'BitsPerSample',
|
||||
9 => 'Channels',
|
||||
10 => { Name => 'SampleRate', Format => 'int32u' },
|
||||
);
|
||||
|
||||
# APE Composite tags
|
||||
%Image::ExifTool::APE::Composite = (
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
Duration => {
|
||||
Require => {
|
||||
0 => 'APE:SampleRate',
|
||||
1 => 'APE:TotalFrames',
|
||||
2 => 'APE:BlocksPerFrame',
|
||||
3 => 'APE:FinalFrameBlocks',
|
||||
},
|
||||
RawConv => '($val[0] && $val[1]) ? (($val[1] - 1) * $val[2] + $val[3]) / $val[0]: undef',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
);
|
||||
|
||||
# add our composite tags
|
||||
Image::ExifTool::AddCompositeTags('Image::ExifTool::APE');
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Make tag info hash for specified tag
|
||||
# Inputs: 0) tag name, 1) tag table ref
|
||||
# - must only call if tag doesn't exist
|
||||
sub MakeTag($$)
|
||||
{
|
||||
my ($tag, $tagTablePtr) = @_;
|
||||
my $name = ucfirst(lc($tag));
|
||||
# remove invalid characters in tag name and capitalize following letters
|
||||
$name =~ s/[^\w-]+(.?)/\U$1/sg;
|
||||
$name =~ s/([a-z0-9])_([a-z])/$1\U$2/g;
|
||||
my %tagInfo = ( Name => $name );
|
||||
$tagInfo{Groups} = { 2 => 'Preview' } if $tag =~ /^Cover Art/ and $tag !~ /Desc$/;
|
||||
AddTagToTable($tagTablePtr, $tag, \%tagInfo);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from an APE file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# - Just looks for APE trailer if FileType is already set
|
||||
# Returns: 1 on success, 0 if this wasn't a valid APE file
|
||||
sub ProcessAPE($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
|
||||
# must first check for leading/trailing ID3 information
|
||||
unless ($$et{DoneID3}) {
|
||||
require Image::ExifTool::ID3;
|
||||
Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1;
|
||||
}
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my ($buff, $i, $header, $tagTablePtr, $dataPos, $oldIndent);
|
||||
|
||||
$$et{DoneAPE} = 1;
|
||||
|
||||
# check APE signature and process audio information
|
||||
# unless this is some other type of file
|
||||
unless ($$et{FileType}) {
|
||||
$raf->Read($buff, 32) == 32 or return 0;
|
||||
$buff =~ /^(MAC |APETAGEX)/ or return 0;
|
||||
$et->SetFileType();
|
||||
SetByteOrder('II');
|
||||
|
||||
if ($buff =~ /^APETAGEX/) {
|
||||
# we already read the APE header
|
||||
$header = 1;
|
||||
} else {
|
||||
# process the MAC header
|
||||
my $vers = Get16u(\$buff, 4);
|
||||
my $table;
|
||||
if ($vers <= 3970) {
|
||||
$buff = substr($buff, 4);
|
||||
$table = GetTagTable('Image::ExifTool::APE::OldHeader');
|
||||
} else {
|
||||
my $dlen = Get32u(\$buff, 8);
|
||||
my $hlen = Get32u(\$buff, 12);
|
||||
unless ($dlen & 0x80000000 or $hlen & 0x80000000) {
|
||||
if ($raf->Seek($dlen, 0) and $raf->Read($buff, $hlen) == $hlen) {
|
||||
$table = GetTagTable('Image::ExifTool::APE::NewHeader');
|
||||
}
|
||||
}
|
||||
}
|
||||
$et->ProcessDirectory( { DataPt => \$buff }, $table) if $table;
|
||||
}
|
||||
}
|
||||
# look for APE trailer unless we already found an APE header
|
||||
unless ($header) {
|
||||
# look for the APE trailer footer...
|
||||
my $footPos = -32;
|
||||
# (...but before the ID3v1 trailer if it exists)
|
||||
$footPos -= $$et{DoneID3} if $$et{DoneID3} > 1;
|
||||
$raf->Seek($footPos, 2) or return 1;
|
||||
$raf->Read($buff, 32) == 32 or return 1;
|
||||
$buff =~ /^APETAGEX/ or return 1;
|
||||
SetByteOrder('II');
|
||||
}
|
||||
#
|
||||
# Read the APE data (we have just read the APE header or footer into $buff)
|
||||
#
|
||||
my ($version, $size, $count, $flags) = unpack('x8V4', $buff);
|
||||
$version /= 1000;
|
||||
$size -= 32; # get size of data only
|
||||
if (($size & 0x80000000) == 0 and
|
||||
($header or $raf->Seek(-$size-32, 1)) and
|
||||
$raf->Read($buff, $size) == $size)
|
||||
{
|
||||
if ($verbose) {
|
||||
$oldIndent = $$et{INDENT};
|
||||
$$et{INDENT} .= '| ';
|
||||
$et->VerboseDir("APEv$version", $count, $size);
|
||||
$et->VerboseDump(\$buff, DataPos => $raf->Tell() - $size);
|
||||
}
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::APE::Main');
|
||||
$dataPos = $raf->Tell() - $size;
|
||||
} else {
|
||||
$count = -1;
|
||||
}
|
||||
#
|
||||
# Process the APE tags
|
||||
#
|
||||
my $pos = 0;
|
||||
for ($i=0; $i<$count; ++$i) {
|
||||
# read next APE tag
|
||||
last if $pos + 8 > $size;
|
||||
my $len = Get32u(\$buff, $pos);
|
||||
my $flags = Get32u(\$buff, $pos + 4);
|
||||
pos($buff) = $pos + 8;
|
||||
last unless $buff =~ /\G(.*?)\0/sg;
|
||||
my $tag = $1;
|
||||
# avoid conflicts with our special table entries
|
||||
$tag .= '.' if $Image::ExifTool::specialTags{$tag};
|
||||
$pos = pos($buff);
|
||||
last if $pos + $len > $size;
|
||||
my $val = substr($buff, $pos, $len);
|
||||
MakeTag($tag, $tagTablePtr) unless $$tagTablePtr{$tag};
|
||||
# handle binary-value tags
|
||||
if (($flags & 0x06) == 0x02) {
|
||||
my $buf2 = $val;
|
||||
$val = \$buf2;
|
||||
# extract cover art description separately (hackitty hack)
|
||||
if ($tag =~ /^Cover Art/) {
|
||||
$buf2 =~ s/^([\x20-\x7e]*)\0//;
|
||||
if ($1) {
|
||||
my $t = "$tag Desc";
|
||||
my $v = $1;
|
||||
MakeTag($t, $tagTablePtr) unless $$tagTablePtr{$t};
|
||||
$et->HandleTag($tagTablePtr, $t, $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
Index => $i,
|
||||
DataPt => \$buff,
|
||||
DataPos => $dataPos,
|
||||
Start => $pos,
|
||||
Size => $len,
|
||||
);
|
||||
$pos += $len;
|
||||
}
|
||||
$i == $count or $et->Warn('Bad APE trailer');
|
||||
$$et{INDENT} = $oldIndent if defined $oldIndent;
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::APE - Read Monkey's Audio meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to extract meta
|
||||
information from Monkey's Audio (APE) audio files.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Currently doesn't parse MAC header unless it is at the start of the file.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.monkeysaudio.com/>
|
||||
|
||||
=item L<http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/APE Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
323
.resources/nix/bin/lib/Image/ExifTool/APP12.pm
Normal file
323
.resources/nix/bin/lib/Image/ExifTool/APP12.pm
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: APP12.pm
|
||||
#
|
||||
# Description: Read APP12 meta information
|
||||
#
|
||||
# Revisions: 10/18/2005 - P. Harvey Created
|
||||
#
|
||||
# References: 1) Heinrich Giesen private communication
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::APP12;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.14';
|
||||
|
||||
sub ProcessAPP12($$$);
|
||||
sub ProcessDucky($$$);
|
||||
sub WriteDucky($$$);
|
||||
|
||||
# APP12 tags (ref PH)
|
||||
%Image::ExifTool::APP12::PictureInfo = (
|
||||
PROCESS_PROC => \&ProcessAPP12,
|
||||
GROUPS => { 0 => 'APP12', 1 => 'PictureInfo', 2 => 'Image' },
|
||||
PRIORITY => 0,
|
||||
NOTES => q{
|
||||
The JPEG APP12 "Picture Info" segment was used by some older cameras, and
|
||||
contains ASCII-based meta information. Below are some tags which have been
|
||||
observed Agfa and Polaroid images, however ExifTool will extract information
|
||||
from any tags found in this segment.
|
||||
},
|
||||
FNumber => {
|
||||
ValueConv => '$val=~s/^[A-Za-z ]*//;$val', # Agfa leads with an 'F'
|
||||
PrintConv => 'sprintf("%.1f",$val)',
|
||||
},
|
||||
Aperture => {
|
||||
PrintConv => 'sprintf("%.1f",$val)',
|
||||
},
|
||||
TimeDate => {
|
||||
Name => 'DateTimeOriginal',
|
||||
Description => 'Date/Time Original',
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => '$val=~/^\d+$/ ? ConvertUnixTime($val) : $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
Shutter => {
|
||||
Name => 'ExposureTime',
|
||||
ValueConv => '$val * 1e-6',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
shtr => {
|
||||
Name => 'ExposureTime',
|
||||
ValueConv => '$val * 1e-6',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'Serial#' => {
|
||||
Name => 'SerialNumber',
|
||||
Groups => { 2 => 'Camera' },
|
||||
},
|
||||
Flash => { PrintConv => { 0 => 'Off', 1 => 'On' } },
|
||||
Macro => { PrintConv => { 0 => 'Off', 1 => 'On' } },
|
||||
StrobeTime => { },
|
||||
Ytarget => { Name => 'YTarget' },
|
||||
ylevel => { Name => 'YLevel' },
|
||||
FocusPos => { },
|
||||
FocusMode => { },
|
||||
Quality => { },
|
||||
ExpBias => 'ExposureCompensation',
|
||||
FWare => 'FirmwareVersion',
|
||||
StrobeTime => { },
|
||||
Resolution => { },
|
||||
Protect => { },
|
||||
ContTake => { },
|
||||
ImageSize => { PrintConv => '$val=~tr/-/x/;$val' },
|
||||
ColorMode => { },
|
||||
Zoom => { },
|
||||
ZoomPos => { },
|
||||
LightS => { },
|
||||
Type => {
|
||||
Name => 'CameraType',
|
||||
Groups => { 2 => 'Camera' },
|
||||
DataMember => 'CameraType',
|
||||
RawConv => '$self->{CameraType} = $val',
|
||||
},
|
||||
Version => { Groups => { 2 => 'Camera' } },
|
||||
ID => { Groups => { 2 => 'Camera' } },
|
||||
);
|
||||
|
||||
# APP12 segment written in Photoshop "Save For Web" images
|
||||
# (from tests with Photoshop 7 files - PH/1)
|
||||
%Image::ExifTool::APP12::Ducky = (
|
||||
PROCESS_PROC => \&ProcessDucky,
|
||||
WRITE_PROC => \&WriteDucky,
|
||||
GROUPS => { 0 => 'Ducky', 1 => 'Ducky', 2 => 'Image' },
|
||||
WRITABLE => 'string',
|
||||
NOTES => q{
|
||||
Photoshop uses the JPEG APP12 "Ducky" segment to store some information in
|
||||
"Save for Web" images.
|
||||
},
|
||||
1 => { #PH
|
||||
Name => 'Quality',
|
||||
Priority => 0,
|
||||
Avoid => 1,
|
||||
Writable => 'int32u',
|
||||
ValueConv => 'unpack("N",$val)', # 4-byte integer
|
||||
ValueConvInv => 'pack("N",$val)',
|
||||
PrintConv => '"$val%"',
|
||||
PrintConvInv => '$val=~/(\d+)/ ? $1 : undef',
|
||||
},
|
||||
2 => { #1
|
||||
Name => 'Comment',
|
||||
Priority => 0,
|
||||
Avoid => 1,
|
||||
# (ignore 4-byte character count at start of value)
|
||||
ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")',
|
||||
ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")',
|
||||
},
|
||||
3 => { #PH
|
||||
Name => 'Copyright',
|
||||
Priority => 0,
|
||||
Avoid => 1,
|
||||
Groups => { 2 => 'Author' },
|
||||
# (ignore 4-byte character count at start of value)
|
||||
ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")',
|
||||
ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")',
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Write APP12 Ducky segment
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: New directory data or undefined on error
|
||||
sub WriteDucky($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
$et or return 1; # allow dummy access to autoload this package
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $pos = $$dirInfo{DirStart};
|
||||
my $newTags = $et->GetNewTagInfoHash($tagTablePtr);
|
||||
my @addTags = sort { $a <=> $b } keys(%$newTags);
|
||||
my ($dirEnd, %doneTags);
|
||||
if ($dataPt) {
|
||||
$dirEnd = $pos + $$dirInfo{DirLen};
|
||||
} else {
|
||||
my $tmp = '';
|
||||
$dataPt = \$tmp;
|
||||
$pos = $dirEnd = 0;
|
||||
}
|
||||
my $newData = '';
|
||||
SetByteOrder('MM');
|
||||
# process all data blocks in Ducky segment
|
||||
for (;;) {
|
||||
my ($tag, $len, $val);
|
||||
if ($pos + 4 <= $dirEnd) {
|
||||
$tag = Get16u($dataPt, $pos);
|
||||
$len = Get16u($dataPt, $pos + 2);
|
||||
$pos += 4;
|
||||
if ($pos + $len > $dirEnd) {
|
||||
$et->Warn('Invalid Ducky block length');
|
||||
return undef;
|
||||
}
|
||||
$val = substr($$dataPt, $pos, $len);
|
||||
$pos += $len;
|
||||
} else {
|
||||
last unless @addTags;
|
||||
$tag = pop @addTags;
|
||||
next if $doneTags{$tag};
|
||||
}
|
||||
$doneTags{$tag} = 1;
|
||||
my $tagInfo = $$newTags{$tag};
|
||||
if ($tagInfo) {
|
||||
my $nvHash = $et->GetNewValueHash($tagInfo);
|
||||
my $isNew;
|
||||
if (defined $val) {
|
||||
if ($et->IsOverwriting($nvHash, $val)) {
|
||||
$et->VerboseValue("- Ducky:$$tagInfo{Name}", $val);
|
||||
$isNew = 1;
|
||||
}
|
||||
} else {
|
||||
next unless $$nvHash{IsCreating};
|
||||
$isNew = 1;
|
||||
}
|
||||
if ($isNew) {
|
||||
$val = $et->GetNewValue($nvHash);
|
||||
++$$et{CHANGED};
|
||||
next unless defined $val; # next if tag is being deleted
|
||||
$et->VerboseValue("+ Ducky:$$tagInfo{Name}", $val);
|
||||
}
|
||||
}
|
||||
$newData .= pack('nn', $tag, length $val) . $val;
|
||||
}
|
||||
$newData .= "\0\0" if length $newData;
|
||||
return $newData;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process APP12 Ducky segment (ref PH)
|
||||
# Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref
|
||||
# Returns: 1 on success, 0 if this wasn't a recognized Ducky segment
|
||||
# Notes: This segment has the following format:
|
||||
# 1) 5 bytes: "Ducky"
|
||||
# 2) multiple data blocks (all integers are big endian):
|
||||
# a) 2 bytes: block type (0=end, 1=Quality, 2=Comment, 3=Copyright)
|
||||
# b) 2 bytes: block length (N)
|
||||
# c) N bytes: block data
|
||||
sub ProcessDucky($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $pos = $$dirInfo{DirStart};
|
||||
my $dirEnd = $pos + $$dirInfo{DirLen};
|
||||
SetByteOrder('MM');
|
||||
# process all data blocks in Ducky segment
|
||||
for (;;) {
|
||||
last if $pos + 4 > $dirEnd;
|
||||
my $tag = Get16u($dataPt, $pos);
|
||||
my $len = Get16u($dataPt, $pos + 2);
|
||||
$pos += 4;
|
||||
if ($pos + $len > $dirEnd) {
|
||||
$et->Warn('Invalid Ducky block length');
|
||||
last;
|
||||
}
|
||||
my $val = substr($$dataPt, $pos, $len);
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $$dirInfo{DataPos},
|
||||
Start => $pos,
|
||||
Size => $len,
|
||||
);
|
||||
$pos += $len;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process APP12 Picture Info segment (ref PH)
|
||||
# Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref
|
||||
# Returns: 1 on success, 0 if this wasn't a recognized APP12
|
||||
sub ProcessAPP12($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirStart = $$dirInfo{DirStart} || 0;
|
||||
my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
|
||||
if ($dirLen != $dirStart + length($$dataPt)) {
|
||||
my $buff = substr($$dataPt, $dirStart, $dirLen);
|
||||
$dataPt = \$buff;
|
||||
} else {
|
||||
pos($$dataPt) = $$dirInfo{DirStart};
|
||||
}
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $success = 0;
|
||||
my $section = '';
|
||||
pos($$dataPt) = 0;
|
||||
|
||||
# this regular expression is a bit complex, but basically we are looking for
|
||||
# section headers (eg. "[Camera Info]") and tag/value pairs (eg. "tag=value",
|
||||
# where "value" may contain white space), separated by spaces or CR/LF.
|
||||
# (APP12 uses CR/LF, but Olympus TextualInfo is similar and uses spaces)
|
||||
while ($$dataPt =~ /(\[.*?\]|[\w#-]+=[\x20-\x7e]+?(?=\s*([\n\r\0]|[\w#-]+=|\[|$)))/g) {
|
||||
my $token = $1;
|
||||
# was this a section name?
|
||||
if ($token =~ /^\[(.*)\]/) {
|
||||
$et->VerboseDir($1) if $verbose;
|
||||
$section = ($token =~ /\[(\S+) ?Info\]/i) ? $1 : '';
|
||||
$success = 1;
|
||||
next;
|
||||
}
|
||||
$et->VerboseDir($$dirInfo{DirName}) if $verbose and not $success;
|
||||
$success = 1;
|
||||
my ($tag, $val) = ($token =~ /(\S+)=(.+)/);
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
$verbose and $et->VerboseInfo($tag, $tagInfo, Value => $val);
|
||||
unless ($tagInfo) {
|
||||
# add new tag to table
|
||||
$tagInfo = { Name => ucfirst $tag };
|
||||
# put in Camera group if information in "Camera" section
|
||||
$$tagInfo{Groups} = { 2 => 'Camera' } if $section =~ /camera/i;
|
||||
$et->VPrint(0, $$et{INDENT}, "[adding APP12:$$tagInfo{Name}]\n");
|
||||
AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
||||
}
|
||||
$et->FoundTag($tagInfo, $val);
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
1; #end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::APP12 - Read APP12 meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to interpret
|
||||
APP12 meta information.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 ACKNOWLEDGEMENTS
|
||||
|
||||
Thanks to Heinrich Giesen for his help decoding APP12 "Ducky" information.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/APP12 Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
901
.resources/nix/bin/lib/Image/ExifTool/ASF.pm
Normal file
901
.resources/nix/bin/lib/Image/ExifTool/ASF.pm
Normal file
|
|
@ -0,0 +1,901 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: ASF.pm
|
||||
#
|
||||
# Description: Read ASF/WMA/WMV meta information
|
||||
#
|
||||
# Revisions: 12/23/2005 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx
|
||||
# 2) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::ASF;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::Exif;
|
||||
use Image::ExifTool::RIFF;
|
||||
|
||||
$VERSION = '1.26';
|
||||
|
||||
sub ProcessASF($$;$);
|
||||
sub ProcessContentDescription($$$);
|
||||
sub ProcessExtendedContentDescription($$$);
|
||||
sub ProcessMetadata($$$);
|
||||
sub ProcessPicture($$$);
|
||||
sub ProcessCodecList($$$);
|
||||
|
||||
# GUID definitions
|
||||
my %errorCorrection = (
|
||||
'20FB5700-5B55-11CF-A8FD-00805F5C442B' => 'No Error Correction',
|
||||
'BFC3CD50-618F-11CF-8BB2-00AA00B4E220' => 'Audio Spread',
|
||||
);
|
||||
|
||||
my %streamType = (
|
||||
'F8699E40-5B4D-11CF-A8FD-00805F5C442B' => 'Audio',
|
||||
'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B' => 'Video',
|
||||
'59DACFC0-59E6-11D0-A3AC-00A0C90348F6' => 'Command',
|
||||
'B61BE100-5B4E-11CF-A8FD-00805F5C442B' => 'JFIF',
|
||||
'35907DE0-E415-11CF-A917-00805F5C442B' => 'Degradable JPEG',
|
||||
'91BD222C-F21C-497A-8B6D-5AA86BFC0185' => 'File Transfer',
|
||||
'3AFB65E2-47EF-40F2-AC2C-70A90D71D343' => 'Binary',
|
||||
);
|
||||
|
||||
my %mutex = (
|
||||
'D6E22A00-35DA-11D1-9034-00A0C90349BE' => 'MutexLanguage',
|
||||
'D6E22A01-35DA-11D1-9034-00A0C90349BE' => 'MutexBitrate',
|
||||
'D6E22A02-35DA-11D1-9034-00A0C90349BE' => 'MutexUnknown',
|
||||
);
|
||||
|
||||
my %bandwidthSharing = (
|
||||
'AF6060AA-5197-11D2-B6AF-00C04FD908E9' => 'SharingExclusive',
|
||||
'AF6060AB-5197-11D2-B6AF-00C04FD908E9' => 'SharingPartial',
|
||||
);
|
||||
|
||||
my %typeSpecific = (
|
||||
'776257D4-C627-41CB-8F81-7AC7FF1C40CC' => 'WebStreamMediaSubtype',
|
||||
'DA1E6B13-8359-4050-B398-388E965BF00C' => 'WebStreamFormat',
|
||||
);
|
||||
|
||||
my %advancedContentEncryption = (
|
||||
'7A079BB6-DAA4-4e12-A5CA-91D38DC11A8D' => 'DRMNetworkDevices',
|
||||
);
|
||||
|
||||
# ASF top level objects
|
||||
%Image::ExifTool::ASF::Main = (
|
||||
PROCESS_PROC => \&ProcessASF,
|
||||
NOTES => q{
|
||||
The ASF format is used by Windows WMA and WMV files, and DIVX videos. Tag
|
||||
ID's aren't listed because they are huge 128-bit GUID's that would ruin the
|
||||
formatting of this table.
|
||||
},
|
||||
'75B22630-668E-11CF-A6D9-00AA0062CE6C' => {
|
||||
Name => 'Header',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::Header', Size => 6 },
|
||||
},
|
||||
'75B22636-668E-11CF-A6D9-00AA0062CE6C' => 'Data',
|
||||
'33000890-E5B1-11CF-89F4-00A0C90349CB' => 'SimpleIndex',
|
||||
'D6E229D3-35DA-11D1-9034-00A0C90349BE' => 'Index',
|
||||
'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C' => 'MediaIndex',
|
||||
'3CB73FD0-0C4A-4803-953D-EDF7B6228F0C' => 'TimecodeIndex',
|
||||
'BE7ACFCB-97A9-42E8-9C71-999491E3AFAC' => { #2
|
||||
Name => 'XMP',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
|
||||
},
|
||||
);
|
||||
|
||||
# ASF header objects
|
||||
%Image::ExifTool::ASF::Header = (
|
||||
PROCESS_PROC => \&ProcessASF,
|
||||
'8CABDCA1-A947-11CF-8EE4-00C00C205365' => {
|
||||
Name => 'FileProperties',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::FileProperties' },
|
||||
},
|
||||
'B7DC0791-A9B7-11CF-8EE6-00C00C205365' => {
|
||||
Name => 'StreamProperties',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::StreamProperties' },
|
||||
},
|
||||
'5FBF03B5-A92E-11CF-8EE3-00C00C205365' => {
|
||||
Name => 'HeaderExtension',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::HeaderExtension', Size => 22 },
|
||||
},
|
||||
'86D15240-311D-11D0-A3A4-00A0C90348F6' => {
|
||||
Name => 'CodecList',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::CodecList' },
|
||||
},
|
||||
'1EFB1A30-0B62-11D0-A39B-00A0C90348F6' => 'ScriptCommand',
|
||||
'F487CD01-A951-11CF-8EE6-00C00C205365' => 'Marker',
|
||||
'D6E229DC-35DA-11D1-9034-00A0C90349BE' => 'BitrateMutualExclusion',
|
||||
'75B22635-668E-11CF-A6D9-00AA0062CE6C' => 'ErrorCorrection',
|
||||
'75B22633-668E-11CF-A6D9-00AA0062CE6C' => {
|
||||
Name => 'ContentDescription',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentDescr' },
|
||||
},
|
||||
'2211B3FA-BD23-11D2-B4B7-00A0C955FC6E' => {
|
||||
Name => 'ContentBranding',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentBranding' },
|
||||
},
|
||||
'D2D0A440-E307-11D2-97F0-00A0C95EA850' => {
|
||||
Name => 'ExtendedContentDescr',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ASF::ExtendedDescr' },
|
||||
},
|
||||
'7BF875CE-468D-11D1-8D82-006097C9A2B2' => 'StreamBitrateProps',
|
||||
'2211B3FB-BD23-11D2-B4B7-00A0C955FC6E' => 'ContentEncryption',
|
||||
'298AE614-2622-4C17-B935-DAE07EE9289C' => 'ExtendedContentEncryption',
|
||||
'2211B3FC-BD23-11D2-B4B7-00A0C955FC6E' => 'DigitalSignature',
|
||||
'1806D474-CADF-4509-A4BA-9AABCB96AAE8' => 'Padding',
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::ContentDescr = (
|
||||
PROCESS_PROC => \&ProcessContentDescription,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
0 => 'Title',
|
||||
1 => { Name => 'Author', Groups => { 2 => 'Author' } },
|
||||
2 => { Name => 'Copyright', Groups => { 2 => 'Author' } },
|
||||
3 => 'Description',
|
||||
4 => 'Rating',
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::ContentBranding = (
|
||||
PROCESS_PROC => \&ProcessContentBranding,
|
||||
GROUPS => { 2 => 'Author' },
|
||||
0 => {
|
||||
Name => 'BannerImageType',
|
||||
PrintConv => {
|
||||
0 => 'None',
|
||||
1 => 'Bitmap',
|
||||
2 => 'JPEG',
|
||||
3 => 'GIF',
|
||||
},
|
||||
},
|
||||
1 => { Name => 'BannerImage', Groups => { 2 => 'Preview' }, Binary => 1 },
|
||||
2 => 'BannerImageURL',
|
||||
3 => 'CopyrightURL',
|
||||
);
|
||||
|
||||
# Note: Many of these tags are similar to those in Image::ExifTool::Microsoft::Xtra
|
||||
# and Image::ExifTool::WTV::Metadata
|
||||
# (tags in this table may have a leading "WM/" removed)
|
||||
%Image::ExifTool::ASF::ExtendedDescr = (
|
||||
PROCESS_PROC => \&ProcessExtendedContentDescription,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
ASFLeakyBucketPairs => { Binary => 1 },
|
||||
AspectRatioX => {},
|
||||
AspectRatioY => {},
|
||||
Author => { Groups => { 2 => 'Author' } },
|
||||
AverageLevel => {},
|
||||
BannerImageData => {},
|
||||
BannerImageType => {},
|
||||
BannerImageURL => {},
|
||||
Bitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||||
Broadcast => {},
|
||||
BufferAverage => {},
|
||||
Can_Skip_Backward => {},
|
||||
Can_Skip_Forward => {},
|
||||
Copyright => { Groups => { 2 => 'Author' } },
|
||||
CopyrightURL => { Groups => { 2 => 'Author' } },
|
||||
CurrentBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||||
Description => {},
|
||||
DRM_ContentID => {},
|
||||
DRM_DRMHeader_ContentDistributor => {},
|
||||
DRM_DRMHeader_ContentID => {},
|
||||
DRM_DRMHeader_IndividualizedVersion => {},
|
||||
DRM_DRMHeader_KeyID => {},
|
||||
DRM_DRMHeader_LicenseAcqURL => {},
|
||||
DRM_DRMHeader_SubscriptionContentID => {},
|
||||
DRM_DRMHeader => {},
|
||||
DRM_IndividualizedVersion => {},
|
||||
DRM_KeyID => {},
|
||||
DRM_LASignatureCert => {},
|
||||
DRM_LASignatureLicSrvCert => {},
|
||||
DRM_LASignaturePrivKey => {},
|
||||
DRM_LASignatureRootCert => {},
|
||||
DRM_LicenseAcqURL => {},
|
||||
DRM_V1LicenseAcqURL => {},
|
||||
Duration => { PrintConv => 'ConvertDuration($val)' },
|
||||
FileSize => {},
|
||||
HasArbitraryDataStream => {},
|
||||
HasAttachedImages => {},
|
||||
HasAudio => {},
|
||||
HasFileTransferStream => {},
|
||||
HasImage => {},
|
||||
HasScript => {},
|
||||
HasVideo => {},
|
||||
Is_Protected => {},
|
||||
Is_Trusted => {},
|
||||
IsVBR => {},
|
||||
NSC_Address => {},
|
||||
NSC_Description => {},
|
||||
NSC_Email => {},
|
||||
NSC_Name => {},
|
||||
NSC_Phone => {},
|
||||
NumberOfFrames => {},
|
||||
OptimalBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||||
PeakValue => {},
|
||||
Rating => {},
|
||||
Seekable => {},
|
||||
Signature_Name => {},
|
||||
Stridable => {},
|
||||
Title => {},
|
||||
VBRPeak => {},
|
||||
# "WM/" tags...
|
||||
AlbumArtist => {},
|
||||
AlbumCoverURL => {},
|
||||
AlbumTitle => {},
|
||||
ASFPacketCount => {},
|
||||
ASFSecurityObjectsSize => {},
|
||||
AudioFileURL => {},
|
||||
AudioSourceURL => {},
|
||||
AuthorURL => { Groups => { 2 => 'Author' } },
|
||||
BeatsPerMinute => {},
|
||||
Category => {},
|
||||
Codec => {},
|
||||
Composer => {},
|
||||
Conductor => {},
|
||||
ContainerFormat => {},
|
||||
ContentDistributor => {},
|
||||
ContentGroupDescription => {},
|
||||
Director => {},
|
||||
DRM => {},
|
||||
DVDID => {},
|
||||
EncodedBy => {},
|
||||
EncodingSettings => {},
|
||||
EncodingTime => { Groups => { 2 => 'Time' }, PrintConv => '$self->ConvertDateTime($val)' },
|
||||
Genre => {},
|
||||
GenreID => {},
|
||||
InitialKey => {},
|
||||
ISRC => {},
|
||||
Language => {},
|
||||
Lyrics => {},
|
||||
Lyrics_Synchronised => {},
|
||||
MCDI => {},
|
||||
MediaClassPrimaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' },
|
||||
MediaClassSecondaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' },
|
||||
MediaCredits => {},
|
||||
MediaIsDelay => {},
|
||||
MediaIsFinale => {},
|
||||
MediaIsLive => {},
|
||||
MediaIsPremiere => {},
|
||||
MediaIsRepeat => {},
|
||||
MediaIsSAP => {},
|
||||
MediaIsStereo => {},
|
||||
MediaIsSubtitled => {},
|
||||
MediaIsTape => {},
|
||||
MediaNetworkAffiliation => {},
|
||||
MediaOriginalBroadcastDateTime => {
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => '$val=~tr/-T/: /; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
MediaOriginalChannel => {},
|
||||
MediaStationCallSign => {},
|
||||
MediaStationName => {},
|
||||
ModifiedBy => {},
|
||||
Mood => {},
|
||||
OriginalAlbumTitle => {},
|
||||
OriginalArtist => {},
|
||||
OriginalFilename => 'OriginalFileName',
|
||||
OriginalLyricist => {},
|
||||
OriginalReleaseTime => {
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => '$val=~tr/-T/: /; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
OriginalReleaseYear => { Groups => { 2 => 'Time' } },
|
||||
ParentalRating => {},
|
||||
ParentalRatingReason => {},
|
||||
PartOfSet => {},
|
||||
PeakBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||||
Period => {},
|
||||
Picture => {
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::ASF::Picture',
|
||||
},
|
||||
},
|
||||
PlaylistDelay => {},
|
||||
Producer => {},
|
||||
PromotionURL => {},
|
||||
ProtectionType => {},
|
||||
Provider => {},
|
||||
ProviderCopyright => {},
|
||||
ProviderRating => {},
|
||||
ProviderStyle => {},
|
||||
Publisher => {},
|
||||
RadioStationName => {},
|
||||
RadioStationOwner => {},
|
||||
SharedUserRating => {},
|
||||
StreamTypeInfo => {},
|
||||
SubscriptionContentID => {},
|
||||
SubTitle => 'Subtitle',
|
||||
SubTitleDescription => 'SubtitleDescription',
|
||||
Text => {},
|
||||
ToolName => {},
|
||||
ToolVersion => {},
|
||||
Track => {},
|
||||
TrackNumber => {},
|
||||
UniqueFileIdentifier => {},
|
||||
UserWebURL => {},
|
||||
VideoClosedCaptioning => {},
|
||||
VideoFrameRate => {},
|
||||
VideoHeight => {},
|
||||
VideoWidth => {},
|
||||
WMADRCAverageReference => {},
|
||||
WMADRCAverageTarget => {},
|
||||
WMADRCPeakReference => {},
|
||||
WMADRCPeakTarget => {},
|
||||
WMCollectionGroupID => {},
|
||||
WMCollectionID => {},
|
||||
WMContentID => {},
|
||||
Writer => { Groups => { 2 => 'Author' } },
|
||||
Year => { Groups => { 2 => 'Time' } },
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::Picture = (
|
||||
PROCESS_PROC => \&ProcessPicture,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
0 => {
|
||||
Name => 'PictureType',
|
||||
PrintConv => { # (Note: Duplicated in ID3, ASF and FLAC modules!)
|
||||
0 => 'Other',
|
||||
1 => '32x32 PNG Icon',
|
||||
2 => 'Other Icon',
|
||||
3 => 'Front Cover',
|
||||
4 => 'Back Cover',
|
||||
5 => 'Leaflet',
|
||||
6 => 'Media',
|
||||
7 => 'Lead Artist',
|
||||
8 => 'Artist',
|
||||
9 => 'Conductor',
|
||||
10 => 'Band',
|
||||
11 => 'Composer',
|
||||
12 => 'Lyricist',
|
||||
13 => 'Recording Studio or Location',
|
||||
14 => 'Recording Session',
|
||||
15 => 'Performance',
|
||||
16 => 'Capture from Movie or Video',
|
||||
17 => 'Bright(ly) Colored Fish',
|
||||
18 => 'Illustration',
|
||||
19 => 'Band Logo',
|
||||
20 => 'Publisher Logo',
|
||||
},
|
||||
},
|
||||
1 => 'PictureMIMEType',
|
||||
2 => 'PictureDescription',
|
||||
3 => {
|
||||
Name => 'Picture',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Binary => 1,
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::FileProperties = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
0 => {
|
||||
Name => 'FileID',
|
||||
Format => 'binary[16]',
|
||||
ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
|
||||
},
|
||||
16 => { Name => 'FileLength', Format => 'int64u' },
|
||||
24 => {
|
||||
Name => 'CreationDate',
|
||||
Format => 'int64u',
|
||||
Groups => { 2 => 'Time' },
|
||||
# time is in 100 ns intervals since 0:00 UTC Jan 1, 1601
|
||||
ValueConv => q{ # (89 leap years between 1601 and 1970)
|
||||
my $t = $val / 1e7 - (((1970-1601)*365+89)*24*3600);
|
||||
return Image::ExifTool::ConvertUnixTime($t) . 'Z';
|
||||
},
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
32 => { Name => 'DataPackets', Format => 'int64u' },
|
||||
40 => {
|
||||
Name => 'Duration',
|
||||
Format => 'int64u',
|
||||
Notes => 'called PlayDuration by the ASF spec',
|
||||
Priority => 0,
|
||||
ValueConv => '$val / 1e7',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
48 => {
|
||||
Name => 'SendDuration',
|
||||
Format => 'int64u',
|
||||
ValueConv => '$val / 1e7',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
56 => { Name => 'Preroll', Format => 'int64u' },
|
||||
64 => { Name => 'Flags', Format => 'int32u' },
|
||||
68 => { Name => 'MinPacketSize',Format => 'int32u' },
|
||||
72 => { Name => 'MaxPacketSize',Format => 'int32u' },
|
||||
76 => { Name => 'MaxBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' },
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::StreamProperties = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
NOTES => 'Tags with index 54 and greater are conditional based on the StreamType.',
|
||||
0 => {
|
||||
Name => 'StreamType',
|
||||
Format => 'binary[16]',
|
||||
RawConv => sub { # set ASF_STREAM_TYPE for use in conditional tags
|
||||
my ($val, $et) = @_;
|
||||
$$et{ASF_STREAM_TYPE} = $streamType{GetGUID($val)} || '';
|
||||
return $val;
|
||||
},
|
||||
ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
|
||||
PrintConv => \%streamType,
|
||||
},
|
||||
16 => {
|
||||
Name => 'ErrorCorrectionType',
|
||||
Format => 'binary[16]',
|
||||
ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
|
||||
PrintConv => \%errorCorrection,
|
||||
},
|
||||
32 => {
|
||||
Name => 'TimeOffset',
|
||||
Format => 'int64u',
|
||||
ValueConv => '$val / 1e7',
|
||||
PrintConv => '"$val s"',
|
||||
},
|
||||
48 => {
|
||||
Name => 'StreamNumber',
|
||||
Format => 'int16u',
|
||||
PrintConv => '($val & 0x7f) . ($val & 0x8000 ? " (encrypted)" : "")',
|
||||
},
|
||||
54 => [
|
||||
{
|
||||
Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
|
||||
Name => 'AudioCodecID',
|
||||
Format => 'int16u',
|
||||
PrintHex => 1,
|
||||
SeparateTable => 'RIFF AudioEncoding',
|
||||
PrintConv => \%Image::ExifTool::RIFF::audioEncoding,
|
||||
},
|
||||
{
|
||||
Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/',
|
||||
Name => 'ImageWidth',
|
||||
Format => 'int32u',
|
||||
},
|
||||
],
|
||||
56 => {
|
||||
Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
|
||||
Name => 'AudioChannels',
|
||||
Format => 'int16u',
|
||||
},
|
||||
58 => [
|
||||
{
|
||||
Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
|
||||
Name => 'AudioSampleRate',
|
||||
Format => 'int32u',
|
||||
},
|
||||
{
|
||||
Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/',
|
||||
Name => 'ImageHeight',
|
||||
Format => 'int32u',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::HeaderExtension = (
|
||||
PROCESS_PROC => \&ProcessASF,
|
||||
'14E6A5CB-C672-4332-8399-A96952065B5A' => 'ExtendedStreamProps',
|
||||
'A08649CF-4775-4670-8A16-6E35357566CD' => 'AdvancedMutualExcl',
|
||||
'D1465A40-5A79-4338-B71B-E36B8FD6C249' => 'GroupMutualExclusion',
|
||||
'D4FED15B-88D3-454F-81F0-ED5C45999E24' => 'StreamPrioritization',
|
||||
'A69609E6-517B-11D2-B6AF-00C04FD908E9' => 'BandwidthSharing',
|
||||
'7C4346A9-EFE0-4BFC-B229-393EDE415C85' => 'LanguageList',
|
||||
'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA' => {
|
||||
Name => 'Metadata',
|
||||
SubDirectory => {
|
||||
# have seen some tags same as ExtendedDescr, so use this table - PH
|
||||
TagTable => 'Image::ExifTool::ASF::ExtendedDescr',
|
||||
ProcessProc => \&ProcessMetadata,
|
||||
},
|
||||
},
|
||||
'44231C94-9498-49D1-A141-1D134E457054' => {
|
||||
Name => 'MetadataLibrary',
|
||||
SubDirectory => {
|
||||
# have seen some tags same as ExtendedDescr, so use this table - PH
|
||||
TagTable => 'Image::ExifTool::ASF::ExtendedDescr',
|
||||
ProcessProc => \&ProcessMetadata,
|
||||
},
|
||||
},
|
||||
'D6E229DF-35DA-11D1-9034-00A0C90349BE' => 'IndexParameters',
|
||||
'6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7' => 'TimecodeIndexParms',
|
||||
'75B22630-668E-11CF-A6D9-00AA0062CE6C' => 'Compatibility',
|
||||
'43058533-6981-49E6-9B74-AD12CB86D58C' => 'AdvancedContentEncryption',
|
||||
'ABD3D211-A9BA-11cf-8EE6-00C00C205365' => 'Reserved1',
|
||||
);
|
||||
|
||||
%Image::ExifTool::ASF::CodecList = (
|
||||
PROCESS_PROC => \&ProcessCodecList,
|
||||
VideoCodecName => {},
|
||||
VideoCodecDescription => {},
|
||||
AudioCodecName => {},
|
||||
AudioCodecDescription => {},
|
||||
OtherCodecName => {},
|
||||
OtherCodecDescription => {},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Generate GUID from 16 bytes of binary data
|
||||
# Inputs: 0) data
|
||||
# Returns: GUID
|
||||
sub GetGUID($)
|
||||
{
|
||||
# must do some byte swapping
|
||||
my $val = shift;
|
||||
return $val unless length($val) == 16;
|
||||
my $buff = unpack('H*',pack('NnnNN',unpack('VvvNN',$val)));
|
||||
$buff =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/;
|
||||
return uc($buff);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process ASF content description
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessContentDescription($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
return 0 if $dirLen < 10;
|
||||
my @len = unpack('v5', $$dataPt);
|
||||
my $pos = 10;
|
||||
my $tag;
|
||||
foreach $tag (0..4) {
|
||||
my $len = shift @len;
|
||||
next unless $len;
|
||||
return 0 if $pos + $len > $dirLen;
|
||||
my $val = $et->Decode(substr($$dataPt,$pos,$len),'UCS2','II');
|
||||
$et->HandleTag($tagTablePtr, $tag, $val);
|
||||
$pos += $len;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process ASF content branding
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessContentBranding($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
return 0 if $dirLen < 40;
|
||||
# decode banner image type
|
||||
$et->HandleTag($tagTablePtr, 0, unpack('V', $$dataPt));
|
||||
# decode banner image, banner URL and copyright URL
|
||||
my $pos = 4;
|
||||
my $tag;
|
||||
foreach $tag (1..3) {
|
||||
return 0 if $pos + 4 > $dirLen;
|
||||
my $size = unpack("x${pos}V", $$dataPt);
|
||||
$pos += 4;
|
||||
next unless $size;
|
||||
return 0 if $pos + $size > $dirLen;
|
||||
my $val = substr($$dataPt, $pos, $size);
|
||||
$et->HandleTag($tagTablePtr, $tag, $val);
|
||||
$pos += $size;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read ASF value
|
||||
# Inputs: 0) ExifTool object ref, 1) data reference, 2) value offset,
|
||||
# 3) format number, 4) size
|
||||
# Returns: converted value
|
||||
sub ReadASF($$$$$)
|
||||
{
|
||||
my ($et, $dataPt, $pos, $format, $size) = @_;
|
||||
my @vals;
|
||||
if ($format == 0) { # unicode string
|
||||
$vals[0] = $et->Decode(substr($$dataPt,$pos,$size),'UCS2','II');
|
||||
} elsif ($format == 2) { # 4-byte boolean
|
||||
@vals = ReadValue($dataPt, $pos, 'int32u', undef, $size);
|
||||
foreach (@vals) {
|
||||
$_ = $_ ? 'True' : 'False';
|
||||
}
|
||||
} elsif ($format == 3) { # int32u
|
||||
@vals = ReadValue($dataPt, $pos, 'int32u', undef, $size);
|
||||
} elsif ($format == 4) { # int64u
|
||||
@vals = ReadValue($dataPt, $pos, 'int64u', undef, $size);
|
||||
} elsif ($format == 5) { # int16u
|
||||
@vals = ReadValue($dataPt, $pos, 'int16u', undef, $size);
|
||||
} else { # any other format (including 1, byte array): return raw data
|
||||
$vals[0] = substr($$dataPt,$pos,$size);
|
||||
}
|
||||
return join ' ', @vals;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process extended content description
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessExtendedContentDescription($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
return 0 if $dirLen < 2;
|
||||
my $count = Get16u($dataPt, 0);
|
||||
$et->VerboseDir($dirInfo, $count);
|
||||
my $pos = 2;
|
||||
my $i;
|
||||
for ($i=0; $i<$count; ++$i) {
|
||||
return 0 if $pos + 6 > $dirLen;
|
||||
my $nameLen = unpack("x${pos}v", $$dataPt);
|
||||
$pos += 2;
|
||||
return 0 if $pos + $nameLen + 4 > $dirLen;
|
||||
my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin');
|
||||
$tag =~ s/^WM\///; # remove leading "WM/"
|
||||
$pos += $nameLen;
|
||||
my ($dType, $dLen) = unpack("x${pos}v2", $$dataPt);
|
||||
$pos += 4;
|
||||
return 0 if $pos + $dLen > $dirLen;
|
||||
my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen);
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPt => $dataPt,
|
||||
Start => $pos,
|
||||
Size => $dLen,
|
||||
);
|
||||
$pos += $dLen;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process ASF metadata library (similar to ProcessExtendedContentDescription above)
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessMetadata($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
return 0 if $dirLen < 2;
|
||||
my $count = Get16u($dataPt, 0);
|
||||
$et->VerboseDir($dirInfo, $count);
|
||||
my $pos = 2;
|
||||
my $i;
|
||||
for ($i=0; $i<$count; ++$i) {
|
||||
return 0 if $pos + 12 > $dirLen;
|
||||
my ($index, $stream, $nameLen, $dType, $dLen) = unpack("x${pos}v4V", $$dataPt);
|
||||
$pos += 12;
|
||||
return 0 if $pos + $nameLen + $dLen > $dirLen;
|
||||
my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin');
|
||||
$tag =~ s/^WM\///; # remove leading "WM/"
|
||||
$pos += $nameLen;
|
||||
my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen);
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPt => $dataPt,
|
||||
Start => $pos,
|
||||
Size => $dLen,
|
||||
);
|
||||
$pos += $dLen;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process WM/Picture preview
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessPicture($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirStart = $$dirInfo{DirStart};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
return 0 unless $dirLen > 9;
|
||||
# extract picture type and length
|
||||
my ($type, $picLen) = unpack("x${dirStart}CV", $$dataPt);
|
||||
$et->VerboseDir('Picture');
|
||||
$et->HandleTag($tagTablePtr, 0, $type);
|
||||
# extract mime type and description strings (null-terminated unicode strings)
|
||||
my $n = $dirLen - 5 - $picLen;
|
||||
return 0 if $n & 0x01 or $n < 4;
|
||||
my $str = substr($$dataPt, $dirStart+5, $n);
|
||||
if ($str =~ /^((?:..)*?)\0\0((?:..)*?)\0\0/s) {
|
||||
my ($mime, $desc) = ($1, $2);
|
||||
$et->HandleTag($tagTablePtr, 1, $et->Decode($mime,'UCS2','II'));
|
||||
$et->HandleTag($tagTablePtr, 2, $et->Decode($desc,'UCS2','II')) if length $desc;
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, 3, substr($$dataPt, $dirStart+5+$n, $picLen));
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process codec list
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessCodecList($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
return 0 if $dirLen < 20;
|
||||
my $count = Get32u($dataPt, 16);
|
||||
$et->VerboseDir($dirInfo, $count);
|
||||
my $pos = 20;
|
||||
my $i;
|
||||
my %codecType = ( 1 => 'Video', 2 => 'Audio' );
|
||||
for ($i=0; $i<$count; ++$i) {
|
||||
return 0 if $pos + 8 > $dirLen;
|
||||
my $type = ($codecType{Get16u($dataPt, $pos)} || 'Other') . 'Codec';
|
||||
# stupid Windows programmers: these lengths are in characters (others are in bytes)
|
||||
my $nameLen = Get16u($dataPt, $pos + 2) * 2;
|
||||
$pos += 4;
|
||||
return 0 if $pos + $nameLen + 2 > $dirLen;
|
||||
my $name = $et->Decode(substr($$dataPt,$pos,$nameLen),'UCS2','II');
|
||||
$et->HandleTag($tagTablePtr, "${type}Name", $name);
|
||||
my $descLen = Get16u($dataPt, $pos + $nameLen) * 2;
|
||||
$pos += $nameLen + 2;
|
||||
return 0 if $pos + $descLen + 2 > $dirLen;
|
||||
my $desc = $et->Decode(substr($$dataPt,$pos,$descLen),'UCS2','II');
|
||||
$et->HandleTag($tagTablePtr, "${type}Description", $desc);
|
||||
my $infoLen = Get16u($dataPt, $pos + $descLen);
|
||||
$pos += $descLen + 2 + $infoLen;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from a ASF file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid ASF file
|
||||
sub ProcessASF($$;$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $rtnVal = 0;
|
||||
my $pos = 0;
|
||||
my ($buff, $err, @parentTable, @childEnd);
|
||||
|
||||
for (;;) {
|
||||
last unless $raf->Read($buff, 24) == 24;
|
||||
$pos += 24;
|
||||
my $tag = GetGUID(substr($buff,0,16));
|
||||
unless ($tagTablePtr) {
|
||||
# verify this is a valid ASF file
|
||||
last unless $tag eq '75B22630-668E-11CF-A6D9-00AA0062CE6C';
|
||||
my $fileType = $$et{FILE_EXT};
|
||||
$fileType = 'ASF' unless $fileType and $fileType =~ /^(ASF|WMV|WMA|DIVX)$/;
|
||||
$et->SetFileType($fileType);
|
||||
SetByteOrder('II');
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::ASF::Main');
|
||||
$rtnVal = 1;
|
||||
}
|
||||
my $size = Image::ExifTool::Get64u(\$buff, 16) - 24;
|
||||
if ($size < 0) {
|
||||
$err = 'Invalid ASF object size';
|
||||
last;
|
||||
}
|
||||
if ($size > 0x7fffffff) {
|
||||
if ($size > 0x7fffffff * 4294967296) {
|
||||
$err = 'Invalid ASF object size';
|
||||
} elsif ($et->Options('LargeFileSupport')) {
|
||||
if ($et->Options('LargeFileSupport') eq '2') {
|
||||
$et->Warn('Skipping large ASF object (LargeFileSupport is 2)');
|
||||
}
|
||||
if ($raf->Seek($size, 1)) {
|
||||
$et->VPrint(0, " Skipped large ASF object ($size bytes)\n");
|
||||
$pos += $size;
|
||||
next;
|
||||
}
|
||||
$err = 'Error seeking past large ASF object';
|
||||
} else {
|
||||
$err = 'Large ASF objects not supported (LargeFileSupport not set)';
|
||||
}
|
||||
last;
|
||||
}
|
||||
# go back to parent tag table if done with previous children
|
||||
if (@childEnd and $pos >= $childEnd[-1]) {
|
||||
pop @childEnd;
|
||||
$tagTablePtr = pop @parentTable;
|
||||
$$et{INDENT} = substr($$et{INDENT},0,-2);
|
||||
}
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
$verbose and $et->VerboseInfo($tag, $tagInfo);
|
||||
if ($tagInfo) {
|
||||
my $subdir = $$tagInfo{SubDirectory};
|
||||
if ($subdir) {
|
||||
my $subTable = GetTagTable($$subdir{TagTable});
|
||||
if ($$subTable{PROCESS_PROC} eq \&ProcessASF) {
|
||||
if (defined $$subdir{Size}) {
|
||||
my $s = $$subdir{Size};
|
||||
if ($verbose > 2) {
|
||||
$raf->Read($buff, $s) == $s or $err = 'Truncated file', last;
|
||||
$et->VerboseDump(\$buff);
|
||||
} elsif (not $raf->Seek($s, 1)) {
|
||||
$err = 'Seek error';
|
||||
last;
|
||||
}
|
||||
# continue processing linearly using subTable
|
||||
push @parentTable, $tagTablePtr;
|
||||
push @childEnd, $pos + $size;
|
||||
$tagTablePtr = $subTable;
|
||||
$pos += $$subdir{Size};
|
||||
if ($verbose) {
|
||||
$$et{INDENT} .= '| ';
|
||||
$et->VerboseDir($$tagInfo{Name});
|
||||
}
|
||||
next;
|
||||
}
|
||||
} elsif ($raf->Read($buff, $size) == $size) {
|
||||
my %subdirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => $size,
|
||||
DirName => $$tagInfo{Name},
|
||||
);
|
||||
$et->VerboseDump(\$buff) if $verbose > 2;
|
||||
unless ($et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc})) {
|
||||
$et->Warn("Error processing $$tagInfo{Name} directory");
|
||||
}
|
||||
$pos += $size;
|
||||
next;
|
||||
} else {
|
||||
$err = 'Unexpected end of file';
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($verbose > 2) {
|
||||
$raf->Read($buff, $size) == $size or $err = 'Truncated file', last;
|
||||
$et->VerboseDump(\$buff);
|
||||
} elsif (not $raf->Seek($size, 1)) { # skip the block
|
||||
$err = 'Seek error';
|
||||
last;
|
||||
}
|
||||
$pos += $size;
|
||||
}
|
||||
$err and $et->Warn($err);
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::ASF - Read ASF/WMA/WMV meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains routines required by Image::ExifTool to extract
|
||||
information from Microsoft Advanced Systems Format (ASF) files, including
|
||||
Windows Media Audio (WMA) and Windows Media Video (WMV) files.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/ASF Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
412
.resources/nix/bin/lib/Image/ExifTool/Apple.pm
Normal file
412
.resources/nix/bin/lib/Image/ExifTool/Apple.pm
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Apple.pm
|
||||
#
|
||||
# Description: Apple EXIF maker notes tags
|
||||
#
|
||||
# Revisions: 2013-09-13 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.photoinvestigator.co/blog/the-mystery-of-maker-apple-metadata/
|
||||
# 2) Frank Rupprecht private communication
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::Apple;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool::Exif;
|
||||
use Image::ExifTool::PLIST;
|
||||
|
||||
$VERSION = '1.15';
|
||||
|
||||
sub ConvertPLIST($$);
|
||||
|
||||
# Apple iPhone metadata (ref PH)
|
||||
%Image::ExifTool::Apple::Main = (
|
||||
WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
|
||||
CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
|
||||
WRITABLE => 1,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
NOTES => 'Tags extracted from the maker notes of iPhone images.',
|
||||
0x0001 => { # (Version, ref 2)
|
||||
Name => 'MakerNoteVersion',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
0x0002 => { #2
|
||||
Name => 'AEMatrix',
|
||||
Unknown => 1,
|
||||
# (not currently writable)
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x0003 => { # (Timestamp, ref 2)
|
||||
Name => 'RunTime', # (includes time plugged in, but not when suspended, ref 1)
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Apple::RunTime' },
|
||||
},
|
||||
0x0004 => { #2
|
||||
Name => 'AEStable',
|
||||
Writable => 'int32s',
|
||||
PrintConv => { 0 => 'No', 1 => 'Yes' },
|
||||
},
|
||||
0x0005 => { #2
|
||||
Name => 'AETarget',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
0x0006 => { #2
|
||||
Name => 'AEAverage',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
0x0007 => { #2
|
||||
Name => 'AFStable',
|
||||
Writable => 'int32s',
|
||||
PrintConv => { 0 => 'No', 1 => 'Yes' },
|
||||
},
|
||||
0x0008 => { #1 (FocusAccelerometerVector, ref 2)
|
||||
Name => 'AccelerationVector',
|
||||
Groups => { 2 => 'Camera' },
|
||||
Writable => 'rational64s',
|
||||
Count => 3,
|
||||
# Note: the directions are contrary to the Apple documentation (which have the
|
||||
# signs of all axes reversed -- apparently the Apple geeks aren't very good
|
||||
# with basic physics, and don't understand the concept of acceleration. See
|
||||
# http://nscookbook.com/2013/03/ios-programming-recipe-19-using-core-motion-to-access-gyro-and-accelerometer/
|
||||
# for one of the few correct descriptions of this). Note that this leads to
|
||||
# a left-handed coordinate system for acceleration.
|
||||
Notes => q{
|
||||
XYZ coordinates of the acceleration vector in units of g. As viewed from
|
||||
the front of the phone, positive X is toward the left side, positive Y is
|
||||
toward the bottom, and positive Z points into the face of the phone
|
||||
},
|
||||
},
|
||||
# 0x0009 - int32s: seen 19,275,531,4371 (SISMethod, ref 2)
|
||||
0x000a => { # (HDRMethod, ref 2)
|
||||
Name => 'HDRImageType',
|
||||
Writable => 'int32s',
|
||||
PrintConv => {
|
||||
# 2 => ? (iPad mini 2)
|
||||
3 => 'HDR Image',
|
||||
4 => 'Original Image',
|
||||
},
|
||||
},
|
||||
0x000b => { # (BurstUUID, ref 2)
|
||||
Name => 'BurstUUID',
|
||||
Writable => 'string',
|
||||
Notes => 'unique ID for all images in a burst',
|
||||
},
|
||||
0x000c => { # ref forum13710 (Neal Krawetz) (SphereHealthTrackingError, ref 2)
|
||||
Name => 'FocusDistanceRange',
|
||||
Writable => 'rational64s',
|
||||
Count => 2,
|
||||
PrintConv => q{
|
||||
my @a = split ' ', $val;
|
||||
sprintf('%.2f - %.2f m', $a[0] <= $a[1] ? @a : reverse @a);
|
||||
},
|
||||
PrintConvInv => '$val =~ s/ - / /; $val =~ s/ ?m$//; $val',
|
||||
},
|
||||
# 0x000d - int32s: 0,1,6,20,24,32,40 (SphereHealthAverageCurrent, ref 2)
|
||||
# 0x000e - int32s: 0,1,4,12 (Orientation? 0=landscape? 4=portrait? ref 1) (SphereMotionDataStatus, ref 2)
|
||||
0x000f => { #2
|
||||
Name => 'OISMode',
|
||||
Writable => 'int32s',
|
||||
# seen: 2,3,5
|
||||
},
|
||||
# 0x0010 - int32s: 1 (SphereStatus, ref 2)
|
||||
0x0011 => { # (if defined, there is a live photo associated with the video, #forum13565) (AssetIdentifier, ref 2)
|
||||
Name => 'ContentIdentifier',
|
||||
Notes => 'called MediaGroupUUID when it appears as an XAttr',
|
||||
# - originally called ContentIdentifier, forum8750
|
||||
# - changed in 12.19 to MediaGroupUUID, NealKrawetz private communication
|
||||
# - changed back to ContentIdentifier since Apple writes this to Keys content.identifier (forum14874)
|
||||
Writable => 'string',
|
||||
},
|
||||
# 0x0012 - (QRMOutputType, ref 2)
|
||||
# 0x0013 - (SphereExternalForceOffset, ref 2)
|
||||
0x0014 => { # (StillImageCaptureType, ref 2)
|
||||
Name => 'ImageCaptureType',
|
||||
Writable => 'int32s',
|
||||
# seen: 1,2,3,4,5,10,12
|
||||
PrintConv => { #forum15096
|
||||
1 => 'ProRAW',
|
||||
2 => 'Portrait',
|
||||
10 => 'Photo',
|
||||
11 => 'Manual Focus', #forum16044
|
||||
12 => 'Scene', #forum16044
|
||||
},
|
||||
},
|
||||
0x0015 => { # (ImageGroupIdentifier, ref 2)
|
||||
Name => 'ImageUniqueID',
|
||||
Writable => 'string',
|
||||
},
|
||||
# 0x0016 - string[29]: "AXZ6pMTOh2L+acSh4Kg630XCScoO\0" (PhotosOriginatingSignature, ref 2)
|
||||
0x0017 => { #forum13565 (only valid if MediaGroupUUID/ContentIdentifier exists) (StillImageCaptureFlags, ref 2)
|
||||
Name => 'LivePhotoVideoIndex',
|
||||
Notes => 'divide by RunTimeScale to get time in seconds',
|
||||
},
|
||||
# 0x0018 - (PhotosRenderOriginatingSignature, ref 2)
|
||||
0x0019 => { # (StillImageProcessingFlags, ref 2)
|
||||
Name => 'ImageProcessingFlags',
|
||||
Writable => 'int32s',
|
||||
Unknown => 1,
|
||||
PrintConv => { BITMASK => { } },
|
||||
},
|
||||
0x001a => { # (PhotoTranscodeQualityHint, ref 2)
|
||||
Name => 'QualityHint',
|
||||
Writable => 'string',
|
||||
Unknown => 1,
|
||||
# seen: "q825s\0", "q750n\0", "q900n\0"
|
||||
},
|
||||
# 0x001b - (PhotosRenderEffect, ref 2)
|
||||
# 0x001c - (BracketedCaptureSequenceNumber, ref 2)
|
||||
# 0x001c - Flash, 2="On" (ref PH)
|
||||
0x001d => { #2
|
||||
Name => 'LuminanceNoiseAmplitude',
|
||||
Writable => 'rational64s',
|
||||
},
|
||||
# 0x001e - (OriginatingAppID, ref 2)
|
||||
0x001f => {
|
||||
Name => 'PhotosAppFeatureFlags', #2
|
||||
Notes => 'set if person or pet detected in image', #PH
|
||||
Writable => 'int32s',
|
||||
},
|
||||
0x0020 => { # (ImageCaptureRequestIdentifier, ref 2)
|
||||
Name => 'ImageCaptureRequestID',
|
||||
Writable => 'string',
|
||||
Unknown => 1,
|
||||
},
|
||||
0x0021 => { # (MeteorHeadroom, ref 2)
|
||||
Name => 'HDRHeadroom',
|
||||
Writable => 'rational64s',
|
||||
},
|
||||
# 0x0022 - (ARKitPhoto, ref 2)
|
||||
0x0023 => {
|
||||
Name => 'AFPerformance', #2
|
||||
Writable => 'int32s',
|
||||
Count => 2,
|
||||
Notes => q{
|
||||
first number maybe related to focus distance, last number maybe related to
|
||||
focus accuracy
|
||||
},
|
||||
PrintConv => 'my @a=split " ",$val; sprintf("%d %d %d",$a[0],$a[1]>>28,$a[1]&0xfffffff)',
|
||||
PrintConvInv => 'my @a=split " ",$val; sprintf("%d %d",$a[0],($a[1]<<28)+$a[2])',
|
||||
},
|
||||
# 0x0023 - int32s[2] (AFPerformance, ref 2)
|
||||
# 0x0024 - (AFExternalOffset, ref 2)
|
||||
0x0025 => { # (StillImageSceneFlags, ref 2)
|
||||
Name => 'SceneFlags',
|
||||
Writable => 'int32s',
|
||||
Unknown => 1,
|
||||
PrintConv => { BITMASK => { } },
|
||||
},
|
||||
0x0026 => { # (StillImageSNRType, ref 2)
|
||||
Name => 'SignalToNoiseRatioType',
|
||||
Writable => 'int32s',
|
||||
Unknown => 1,
|
||||
},
|
||||
0x0027 => { # (StillImageSNR, ref 2)
|
||||
Name => 'SignalToNoiseRatio',
|
||||
Writable => 'rational64s',
|
||||
},
|
||||
# 0x0028 - int32s (UBMethod, ref 2)
|
||||
# 0x0029 - string (SpatialOverCaptureGroupIdentifier, ref 2)
|
||||
# 0x002A - (iCloudServerSoftwareVersionForDynamicallyGeneratedMedia, ref 2)
|
||||
0x002b => {
|
||||
Name => 'PhotoIdentifier', #2
|
||||
Writable => 'string',
|
||||
},
|
||||
# 0x002C - (SpatialOverCaptureImageType, ref 2)
|
||||
# 0x002D - (CCT, ref 2)
|
||||
0x002d => { #PH
|
||||
Name => 'ColorTemperature',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
# 0x002E - (ApsMode, ref 2)
|
||||
0x002e => { #PH
|
||||
Name => 'CameraType',
|
||||
Writable => 'int32s',
|
||||
PrintConv => {
|
||||
0 => 'Back Wide Angle',
|
||||
1 => 'Back Normal',
|
||||
6 => 'Front',
|
||||
},
|
||||
},
|
||||
# 0x002e - set to 0 for 0.5x (crop?) (ref PH)
|
||||
0x002F => { #2
|
||||
Name => 'FocusPosition',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
0x0030 => { # (MeteorPlusGainMap, ref 2)
|
||||
Name => 'HDRGain',
|
||||
Writable => 'rational64s',
|
||||
},
|
||||
# 0x0031 - (StillImageProcessingHomography, ref 2)
|
||||
# 0x0032 - (IntelligentDistortionCorrection, ref 2)
|
||||
# 0x0033 - (NRFStatus, ref 2)
|
||||
# 0x0034 - (NRFInputBracketCount, ref 2)
|
||||
# 0x0034 - 1 for flash on, otherwise doesn't exist (ref PH)
|
||||
# 0x0035 - (NRFRegisteredBracketCount, ref 2)
|
||||
# 0x0035 - 0 for flash on, otherwise doesn't exist (ref PH)
|
||||
# 0x0036 - (LuxLevel, ref 2)
|
||||
# 0x0037 - (LastFocusingMethod, ref 2)
|
||||
0x0038 => { # (TimeOfFlightAssistedAutoFocusEstimatorMeasuredDepth, ref 2)
|
||||
Name => 'AFMeasuredDepth',
|
||||
Notes => 'from the time-of-flight-assisted auto-focus estimator',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
# 0x0039 - (TimeOfFlightAssistedAutoFocusEstimatorROIType, ref 2)
|
||||
# 0x003A - (NRFSRLStatus, ref 2)
|
||||
# 0x003a - non-zero if a person was in the image? (ref PH)
|
||||
# 0x003B - (SystemPressureLevel, ref 2)
|
||||
# 0x003C - (CameraControlsStatisticsMaster, ref 2)
|
||||
# 0x003c - 4=rear cam, 1=front cam? (ref PH)
|
||||
0x003D => { # (TimeOfFlightAssistedAutoFocusEstimatorSensorConfidence, ref 2)
|
||||
Name => 'AFConfidence',
|
||||
Writable => 'int32s',
|
||||
},
|
||||
0x003E => { # (ColorCorrectionMatrix, ref 2)
|
||||
Name => 'ColorCorrectionMatrix',
|
||||
Unknown => 1,
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x003F => { #2
|
||||
Name => 'GreenGhostMitigationStatus',
|
||||
Writable => 'int32s',
|
||||
Unknown => 1,
|
||||
},
|
||||
0x0040 => { #2
|
||||
Name => 'SemanticStyle',
|
||||
Notes => '_1=Tone, _2=Warm, _3=1.Std,2.Vibrant,3.Rich Contrast,4.Warm,5.Cool', #PH
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x0041 => { # (SemanticStyleKey_RenderingVersion, ref 2)
|
||||
Name => 'SemanticStyleRenderingVer',
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x0042 => { # (SemanticStyleKey_Preset, ref 2)
|
||||
Name => 'SemanticStylePreset',
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
# 0x0043 - (SemanticStyleKey_ToneBias, ref 2)
|
||||
# 0x0044 - (SemanticStyleKey_WarmthBias, ref 2)
|
||||
# 0x0045 - (FrontFacing, ref 2) (not for iPhone15, ref PH)
|
||||
# 0x0046 - (TimeOfFlightAssistedAutoFocusEstimatorContainsBlindSpot, ref 2)
|
||||
# 0x0047 - (LeaderFollowerAutoFocusLeaderDepth, ref 2)
|
||||
# 0x0048 - (LeaderFollowerAutoFocusLeaderFocusMethod, ref 2)
|
||||
# 0x0049 - (LeaderFollowerAutoFocusLeaderConfidence, ref 2)
|
||||
# 0x004a - (LeaderFollowerAutoFocusLeaderROIType, ref 2)
|
||||
# 0x004a - 2=back normal, 4=back wide angle, 5=front (ref PH)
|
||||
# 0x004b - (ZeroShutterLagFailureReason, ref 2)
|
||||
# 0x004c - (TimeOfFlightAssistedAutoFocusEstimatorMSPMeasuredDepth, ref 2)
|
||||
# 0x004d - (TimeOfFlightAssistedAutoFocusEstimatorMSPSensorConfidence, ref 2)
|
||||
# 0x004e - (Camera, ref 2)
|
||||
0x004e => {
|
||||
Name => 'Apple_0x004e',
|
||||
Unknown => 1,
|
||||
# first number is 0 for front cam, 1 for either back cam (ref PH)
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x004f => {
|
||||
Name => 'Apple_0x004f',
|
||||
Unknown => 1,
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x0054 => {
|
||||
Name => 'Apple_0x0054',
|
||||
Unknown => 1,
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
0x005a => {
|
||||
Name => 'Apple_0x005a',
|
||||
Unknown => 1,
|
||||
ValueConv => \&ConvertPLIST,
|
||||
},
|
||||
);
|
||||
|
||||
# PLIST-format CMTime structure (ref PH)
|
||||
# (CMTime ref https://developer.apple.com/library/ios/documentation/CoreMedia/Reference/CMTime/Reference/reference.html)
|
||||
%Image::ExifTool::Apple::RunTime = (
|
||||
PROCESS_PROC => \&Image::ExifTool::PLIST::ProcessBinaryPLIST,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
NOTES => q{
|
||||
This PLIST-format information contains the elements of a CMTime structure
|
||||
representing the amount of time the phone has been running since the last
|
||||
boot, not including standby time.
|
||||
},
|
||||
timescale => { Name => 'RunTimeScale' }, # (seen 1000000000 --> ns)
|
||||
epoch => { Name => 'RunTimeEpoch' }, # (seen 0)
|
||||
value => { Name => 'RunTimeValue' }, # (should divide by RunTimeScale to get seconds)
|
||||
flags => {
|
||||
Name => 'RunTimeFlags',
|
||||
PrintConv => { BITMASK => {
|
||||
0 => 'Valid',
|
||||
1 => 'Has been rounded',
|
||||
2 => 'Positive infinity',
|
||||
3 => 'Negative infinity',
|
||||
4 => 'Indefinite',
|
||||
}},
|
||||
},
|
||||
);
|
||||
|
||||
# Apple composite tags
|
||||
%Image::ExifTool::Apple::Composite = (
|
||||
GROUPS => { 2 => 'Camera' },
|
||||
RunTimeSincePowerUp => {
|
||||
Require => {
|
||||
0 => 'Apple:RunTimeValue',
|
||||
1 => 'Apple:RunTimeScale',
|
||||
},
|
||||
ValueConv => '$val[1] ? $val[0] / $val[1] : undef',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
);
|
||||
|
||||
# add our composite tags
|
||||
Image::ExifTool::AddCompositeTags('Image::ExifTool::Apple');
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Convert from binary PLIST format to a tag value we can use
|
||||
# Inputs: 0) binary plist data, 1) ExifTool ref
|
||||
# Returns: converted value
|
||||
sub ConvertPLIST($$)
|
||||
{
|
||||
my ($val, $et) = @_;
|
||||
my $dirInfo = { DataPt => \$val, NoVerboseDir => 1 };
|
||||
my $oldOrder = $et->GetByteOrder();
|
||||
require Image::ExifTool::PLIST;
|
||||
Image::ExifTool::PLIST::ProcessBinaryPLIST($et, $dirInfo);
|
||||
$val = $$dirInfo{Value};
|
||||
if (ref $val eq 'HASH' and not $et->Options('Struct')) {
|
||||
require 'Image/ExifTool/XMPStruct.pl';
|
||||
$val = Image::ExifTool::XMP::SerializeStruct($et, $val);
|
||||
}
|
||||
$et->SetByteOrder($oldOrder);
|
||||
return $val;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::Apple - Apple EXIF maker notes tags
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to interpret
|
||||
Apple maker notes in EXIF information.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/Apple Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
317
.resources/nix/bin/lib/Image/ExifTool/Audible.pm
Normal file
317
.resources/nix/bin/lib/Image/ExifTool/Audible.pm
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Audible.pm
|
||||
#
|
||||
# Description: Read metadata from Audible audio books
|
||||
#
|
||||
# Revisions: 2015/04/05 - P. Harvey Created
|
||||
#
|
||||
# References: 1) https://github.com/jteeuwen/audible
|
||||
# 2) https://code.google.com/p/pyaudibletags/
|
||||
# 3) http://wiki.multimedia.cx/index.php?title=Audible_Audio
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::Audible;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.02';
|
||||
|
||||
sub ProcessAudible_meta($$$);
|
||||
sub ProcessAudible_cvrx($$$);
|
||||
|
||||
%Image::ExifTool::Audible::Main = (
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
NOTES => q{
|
||||
ExifTool will extract any information found in the metadata dictionary of
|
||||
Audible .AA files, even if not listed in the table below.
|
||||
},
|
||||
# tags found in the metadata dictionary (chunk 2)
|
||||
pubdate => { Name => 'PublishDate', Groups => { 2 => 'Time' } },
|
||||
pub_date_start => { Name => 'PublishDateStart', Groups => { 2 => 'Time' } },
|
||||
author => { Name => 'Author', Groups => { 2 => 'Author' } },
|
||||
copyright => { Name => 'Copyright', Groups => { 2 => 'Author' } },
|
||||
# also seen (ref PH):
|
||||
# product_id, parent_id, title, provider, narrator, price, description,
|
||||
# long_description, short_title, is_aggregation, title_id, codec, HeaderSeed,
|
||||
# EncryptedBlocks, HeaderKey, license_list, CPUType, license_count, <12 hex digits>,
|
||||
# parent_short_title, parent_title, aggregation_id, short_description, user_alias
|
||||
|
||||
# information extracted from other chunks
|
||||
_chapter_count => { Name => 'ChapterCount' }, # from chunk 6
|
||||
_cover_art => { # from chunk 11
|
||||
Name => 'CoverArt',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Binary => 1,
|
||||
},
|
||||
);
|
||||
|
||||
# 'tags' atoms observed in Audible .m4b audio books (ref PH)
|
||||
%Image::ExifTool::Audible::tags = (
|
||||
GROUPS => { 0 => 'QuickTime', 2 => 'Audio' },
|
||||
NOTES => 'Information found in "tags" atom of Audible M4B audio books.',
|
||||
meta => {
|
||||
Name => 'Audible_meta',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Audible::meta' },
|
||||
},
|
||||
cvrx => {
|
||||
Name => 'Audible_cvrx',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Audible::cvrx' },
|
||||
},
|
||||
tseg => {
|
||||
Name => 'Audible_tseg',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Audible::tseg' },
|
||||
},
|
||||
);
|
||||
|
||||
# 'meta' information observed in Audible .m4b audio books (ref PH)
|
||||
%Image::ExifTool::Audible::meta = (
|
||||
PROCESS_PROC => \&ProcessAudible_meta,
|
||||
GROUPS => { 0 => 'QuickTime', 2 => 'Audio' },
|
||||
NOTES => 'Information found in Audible M4B "meta" atom.',
|
||||
Album => 'Album',
|
||||
ALBUMARTIST => { Name => 'AlbumArtist', Groups => { 2 => 'Author' } },
|
||||
Artist => { Name => 'Artist', Groups => { 2 => 'Author' } },
|
||||
Comment => 'Comment',
|
||||
Genre => 'Genre',
|
||||
itunesmediatype => { Name => 'iTunesMediaType', Description => 'iTunes Media Type' },
|
||||
SUBTITLE => 'Subtitle',
|
||||
Title => 'Title',
|
||||
TOOL => 'CreatorTool',
|
||||
Year => { Name => 'Year', Groups => { 2 => 'Time' } },
|
||||
track => 'ChapterName', # (found in 'meta' of 'tseg' atom)
|
||||
);
|
||||
|
||||
# 'cvrx' information observed in Audible .m4b audio books (ref PH)
|
||||
%Image::ExifTool::Audible::cvrx = (
|
||||
PROCESS_PROC => \&ProcessAudible_cvrx,
|
||||
GROUPS => { 0 => 'QuickTime', 2 => 'Audio' },
|
||||
NOTES => 'Audible cover art information in M4B audio books.',
|
||||
VARS => { ID_FMT => 'none' },
|
||||
CoverArtType => 'CoverArtType',
|
||||
CoverArt => {
|
||||
Name => 'CoverArt',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Binary => 1,
|
||||
},
|
||||
);
|
||||
|
||||
# 'tseg' information observed in Audible .m4b audio books (ref PH)
|
||||
%Image::ExifTool::Audible::tseg = (
|
||||
GROUPS => { 0 => 'QuickTime', 2 => 'Audio' },
|
||||
tshd => {
|
||||
Name => 'ChapterNumber',
|
||||
Format => 'int32u',
|
||||
ValueConv => '$val + 1', # start counting from 1
|
||||
},
|
||||
meta => {
|
||||
Name => 'Audible_meta2',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Audible::meta' },
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Audible 'meta' tags from M4B files (ref PH)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessAudible_meta($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirLen = length $$dataPt;
|
||||
return 0 if $dirLen < 4;
|
||||
my $num = Get32u($dataPt, 0);
|
||||
$et->VerboseDir('Audible_meta', $num);
|
||||
my $pos = 4;
|
||||
my $index;
|
||||
for ($index=0; $index<$num; ++$index) {
|
||||
last if $pos + 3 > $dirLen;
|
||||
my $unk = Get8u($dataPt, $pos); # ? (0x80 or 0x00)
|
||||
last unless $unk == 0x80 or $unk == 0x00;
|
||||
my $len = Get16u($dataPt, $pos + 1); # tag length
|
||||
$pos += 3;
|
||||
last if $pos + $len + 6 > $dirLen or not $len;
|
||||
my $tag = substr($$dataPt, $pos, $len); # tag ID
|
||||
my $ver = Get16u($dataPt, $pos + $len); # version?
|
||||
last unless $ver == 0x0001;
|
||||
my $size = Get32u($dataPt, $pos + $len + 2);# data size
|
||||
$pos += $len + 6;
|
||||
last if $pos + $size > $dirLen;
|
||||
my $val = $et->Decode(substr($$dataPt, $pos, $size), 'UTF8');
|
||||
unless ($$tagTablePtr{$tag}) {
|
||||
my $name = Image::ExifTool::MakeTagName(($tag =~ /[a-z]/) ? $tag : lc($tag));
|
||||
AddTagToTable($tagTablePtr, $tag, { Name => $name });
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => $pos,
|
||||
Size => $size,
|
||||
Index => $index,
|
||||
);
|
||||
$pos += $size;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Audible 'cvrx' cover art atom from M4B files (ref PH)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessAudible_cvrx($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirLen = length $$dataPt;
|
||||
return 0 if 0x0a > $dirLen;
|
||||
my $len = Get16u($dataPt, 0x08);
|
||||
return 0 if 0x0a + $len + 6 > $dirLen;
|
||||
my $size = Get32u($dataPt, 0x0a + $len + 2);
|
||||
return 0 if 0x0a + $len + 6 + $size > $dirLen;
|
||||
$et->VerboseDir('Audible_cvrx', undef, $dirLen);
|
||||
$et->HandleTag($tagTablePtr, 'CoverArtType', undef,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => 0x0a,
|
||||
Size => $len,
|
||||
);
|
||||
$et->HandleTag($tagTablePtr, 'CoverArt', undef,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => 0x0a + $len + 6,
|
||||
Size => $size,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from an Audible .AA file
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid AA file
|
||||
sub ProcessAA($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $toc, $entry, $i);
|
||||
|
||||
# check magic number
|
||||
return 0 unless $raf->Read($buff, 16) == 16 and $buff=~/^.{4}\x57\x90\x75\x36/s;
|
||||
# check file size
|
||||
if (defined $$et{VALUE}{FileSize}) {
|
||||
# first 4 bytes of the file should be the filesize
|
||||
unpack('N', $buff) == $$et{VALUE}{FileSize} or return 0;
|
||||
}
|
||||
$et->SetFileType();
|
||||
SetByteOrder('MM');
|
||||
my $bytes = 12 * Get32u(\$buff, 8); # table of contents size in bytes
|
||||
$bytes > 0xc00 and $et->Warn('Invalid TOC'), return 1;
|
||||
# read the table of contents
|
||||
$raf->Read($toc, $bytes) == $bytes or $et->Warn('Truncated TOC'), return 1;
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Audible::Main');
|
||||
# parse table of contents (in $toc)
|
||||
for ($entry=0; $entry<$bytes; $entry+=12) {
|
||||
my $type = Get32u(\$toc, $entry);
|
||||
next unless $type == 2 or $type == 6 or $type == 11;
|
||||
my $offset = Get32u(\$toc, $entry + 4);
|
||||
my $length = Get32u(\$toc, $entry + 8) or next;
|
||||
$raf->Seek($offset, 0) or $et->Warn("Chunk $type seek error"), last;
|
||||
if ($type == 6) { # offset table
|
||||
next if $length < 4 or $raf->Read($buff, 4) != 4; # only read the chapter count
|
||||
$et->HandleTag($tagTablePtr, '_chapter_count', Get32u(\$buff, 0));
|
||||
next;
|
||||
}
|
||||
# read the chunk
|
||||
$length > 100000000 and $et->Warn("Chunk $type too big"), next;
|
||||
$raf->Read($buff, $length) == $length or $et->Warn("Chunk $type read error"), last;
|
||||
if ($type == 11) { # cover art
|
||||
next if $length < 8;
|
||||
my $len = Get32u(\$buff, 0);
|
||||
my $off = Get32u(\$buff, 4);
|
||||
next if $off < $offset + 8 or $off - $offset + $len > $length;
|
||||
$et->HandleTag($tagTablePtr, '_cover_art', substr($buff, $off-$offset, $len));
|
||||
next;
|
||||
}
|
||||
# parse metadata dictionary (in $buff)
|
||||
$length < 4 and $et->Warn('Bad dictionary'), next;
|
||||
my $num = Get32u(\$buff, 0);
|
||||
$num > 0x200 and $et->Warn('Bad dictionary count'), next;
|
||||
my $pos = 4; # dictionary starts immediately after count
|
||||
require Image::ExifTool::HTML; # (for UnescapeHTML)
|
||||
$et->VerboseDir('Audible Metadata', $num);
|
||||
for ($i=0; $i<$num; ++$i) {
|
||||
my $tagPos = $pos + 9; # position of tag string
|
||||
$tagPos > $length and $et->Warn('Truncated dictionary'), last;
|
||||
# (1 unknown byte ignored at start of each dictionary entry)
|
||||
my $tagLen = Get32u(\$buff, $pos + 1); # tag string length
|
||||
my $valLen = Get32u(\$buff, $pos + 5); # value string length
|
||||
my $valPos = $tagPos + $tagLen; # position of value string
|
||||
my $nxtPos = $valPos + $valLen; # position of next entry
|
||||
$nxtPos > $length and $et->Warn('Bad dictionary entry'), last;
|
||||
my $tag = substr($buff, $tagPos, $tagLen);
|
||||
my $val = substr($buff, $valPos, $valLen);
|
||||
unless ($$tagTablePtr{$tag}) {
|
||||
my $name = Image::ExifTool::MakeTagName($tag);
|
||||
$name =~ s/_(.)/\U$1/g; # change from underscore-separated to mixed case
|
||||
AddTagToTable($tagTablePtr, $tag, { Name => $name });
|
||||
}
|
||||
# unescape HTML character references and convert from UTF-8
|
||||
$val = $et->Decode(Image::ExifTool::HTML::UnescapeHTML($val), 'UTF8');
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPos => $offset,
|
||||
DataPt => \$buff,
|
||||
Start => $valPos,
|
||||
Size => $valLen,
|
||||
Index => $i,
|
||||
);
|
||||
$pos = $nxtPos; # step to next dictionary entry
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::Audible - Read meta information from Audible audio books
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read meta
|
||||
information from Audible audio books.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://github.com/jteeuwen/audible>
|
||||
|
||||
=item L<https://code.google.com/p/pyaudibletags/>
|
||||
|
||||
=item L<http://wiki.multimedia.cx/index.php?title=Audible_Audio>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/Audible Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
360
.resources/nix/bin/lib/Image/ExifTool/BMP.pm
Normal file
360
.resources/nix/bin/lib/Image/ExifTool/BMP.pm
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: BMP.pm
|
||||
#
|
||||
# Description: Read BMP meta information
|
||||
#
|
||||
# Revisions: 07/16/2005 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
|
||||
# 2) http://www.fourcc.org/rgb.php
|
||||
# 3) https://msdn.microsoft.com/en-us/library/dd183381(v=vs.85).aspx
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::BMP;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.09';
|
||||
|
||||
# conversions for fixed-point 2.30 format values
|
||||
my %fixed2_30 = (
|
||||
ValueConv => q{
|
||||
my @a = split ' ', $val;
|
||||
$_ /= 0x40000000 foreach @a;
|
||||
"@a";
|
||||
},
|
||||
PrintConv => q{
|
||||
my @a = split ' ', $val;
|
||||
$_ = sprintf('%.6f', $_) foreach @a;
|
||||
"@a";
|
||||
},
|
||||
);
|
||||
|
||||
# BMP chunks
|
||||
%Image::ExifTool::BMP::Main = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
NOTES => q{
|
||||
There really isn't much meta information in a BMP file as such, just a bit
|
||||
of image related information.
|
||||
},
|
||||
0 => {
|
||||
Name => 'BMPVersion',
|
||||
Format => 'int32u',
|
||||
Notes => q{
|
||||
this is actually the size of the BMP header, but used to determine the BMP
|
||||
version
|
||||
},
|
||||
RawConv => '$$self{BMPVersion} = $val',
|
||||
PrintConv => {
|
||||
40 => 'Windows V3',
|
||||
68 => 'AVI BMP structure?', #PH (seen in AVI movies from some Casio and Nikon cameras)
|
||||
108 => 'Windows V4',
|
||||
124 => 'Windows V5',
|
||||
},
|
||||
},
|
||||
4 => {
|
||||
Name => 'ImageWidth',
|
||||
Format => 'int32u',
|
||||
},
|
||||
8 => {
|
||||
Name => 'ImageHeight',
|
||||
Format => 'int32s', # (negative when stored in top-to-bottom order)
|
||||
ValueConv => 'abs($val)',
|
||||
},
|
||||
12 => {
|
||||
Name => 'Planes',
|
||||
Format => 'int16u',
|
||||
# values: 0,1,4,8,16,24,32
|
||||
},
|
||||
14 => {
|
||||
Name => 'BitDepth',
|
||||
Format => 'int16u',
|
||||
},
|
||||
16 => {
|
||||
Name => 'Compression',
|
||||
Format => 'int32u',
|
||||
RawConv => '$$self{BMPCompression} = $val',
|
||||
# (formatted as string[4] for some values in AVI images)
|
||||
ValueConv => '$val > 256 ? unpack("A4",pack("V",$val)) : $val',
|
||||
PrintConv => {
|
||||
0 => 'None',
|
||||
1 => '8-Bit RLE',
|
||||
2 => '4-Bit RLE',
|
||||
3 => 'Bitfields',
|
||||
4 => 'JPEG', #2
|
||||
5 => 'PNG', #2
|
||||
# pass through ASCII video compression codec ID's
|
||||
OTHER => sub {
|
||||
my $val = shift;
|
||||
# convert non-ascii characters
|
||||
$val =~ s/([\0-\x1f\x7f-\xff])/sprintf('\\x%.2x',ord $1)/eg;
|
||||
return $val;
|
||||
},
|
||||
},
|
||||
},
|
||||
20 => {
|
||||
Name => 'ImageLength',
|
||||
Format => 'int32u',
|
||||
RawConv => '$$self{BMPImageLength} = $val',
|
||||
},
|
||||
24 => {
|
||||
Name => 'PixelsPerMeterX',
|
||||
Format => 'int32u',
|
||||
},
|
||||
28 => {
|
||||
Name => 'PixelsPerMeterY',
|
||||
Format => 'int32u',
|
||||
},
|
||||
32 => {
|
||||
Name => 'NumColors',
|
||||
Format => 'int32u',
|
||||
PrintConv => '$val ? $val : "Use BitDepth"',
|
||||
},
|
||||
36 => {
|
||||
Name => 'NumImportantColors',
|
||||
Format => 'int32u',
|
||||
Hook => '$varSize += $size if $$self{BMPVersion} == 68', # (the rest is invalid for AVI BMP's)
|
||||
PrintConv => '$val ? $val : "All"',
|
||||
},
|
||||
40 => {
|
||||
Name => 'RedMask',
|
||||
Format => 'int32u',
|
||||
PrintConv => 'sprintf("0x%.8x",$val)',
|
||||
},
|
||||
44 => {
|
||||
Name => 'GreenMask',
|
||||
Format => 'int32u',
|
||||
PrintConv => 'sprintf("0x%.8x",$val)',
|
||||
},
|
||||
48 => {
|
||||
Name => 'BlueMask',
|
||||
Format => 'int32u',
|
||||
PrintConv => 'sprintf("0x%.8x",$val)',
|
||||
},
|
||||
52 => {
|
||||
Name => 'AlphaMask',
|
||||
Format => 'int32u',
|
||||
PrintConv => 'sprintf("0x%.8x",$val)',
|
||||
},
|
||||
56 => {
|
||||
Name => 'ColorSpace',
|
||||
Format => 'undef[4]',
|
||||
RawConv => '$$self{BMPColorSpace} = $val =~ /\0/ ? Get32u(\$val, 0) : pack("N",unpack("V",$val))',
|
||||
PrintConv => {
|
||||
0 => 'Calibrated RGB',
|
||||
1 => 'Device RGB',
|
||||
2 => 'Device CMYK',
|
||||
LINK => 'Linked Color Profile',
|
||||
MBED => 'Embedded Color Profile',
|
||||
sRGB => 'sRGB',
|
||||
'Win ' => 'Windows Color Space',
|
||||
},
|
||||
},
|
||||
60 => {
|
||||
Name => 'RedEndpoint',
|
||||
Condition => '$$self{BMPColorSpace} eq "0"',
|
||||
Format => 'int32u[3]',
|
||||
%fixed2_30,
|
||||
},
|
||||
72 => {
|
||||
Name => 'GreenEndpoint',
|
||||
Condition => '$$self{BMPColorSpace} eq "0"',
|
||||
Format => 'int32u[3]',
|
||||
%fixed2_30,
|
||||
},
|
||||
84 => {
|
||||
Name => 'BlueEndpoint',
|
||||
Condition => '$$self{BMPColorSpace} eq "0"',
|
||||
Format => 'int32u[3]',
|
||||
%fixed2_30,
|
||||
},
|
||||
96 => {
|
||||
Name => 'GammaRed',
|
||||
Condition => '$$self{BMPColorSpace} eq "0"',
|
||||
Format => 'fixed32u',
|
||||
},
|
||||
100 => {
|
||||
Name => 'GammaGreen',
|
||||
Condition => '$$self{BMPColorSpace} eq "0"',
|
||||
Format => 'fixed32u',
|
||||
},
|
||||
104 => {
|
||||
Name => 'GammaBlue',
|
||||
Condition => '$$self{BMPColorSpace} eq "0"',
|
||||
Format => 'fixed32u',
|
||||
},
|
||||
108 => {
|
||||
Name => 'RenderingIntent',
|
||||
Format => 'int32u',
|
||||
PrintConv => {
|
||||
1 => 'Graphic (LCS_GM_BUSINESS)',
|
||||
2 => 'Proof (LCS_GM_GRAPHICS)',
|
||||
4 => 'Picture (LCS_GM_IMAGES)',
|
||||
8 => 'Absolute Colorimetric (LCS_GM_ABS_COLORIMETRIC)',
|
||||
},
|
||||
},
|
||||
112 => {
|
||||
Name => 'ProfileDataOffset',
|
||||
Condition => '$$self{BMPColorSpace} eq "LINK" or $$self{BMPColorSpace} eq "MBED"',
|
||||
Format => 'int32u',
|
||||
RawConv => '$$self{BMPProfileOffset} = $val',
|
||||
},
|
||||
116 => {
|
||||
Name => 'ProfileSize',
|
||||
Condition => '$$self{BMPColorSpace} eq "LINK" or $$self{BMPColorSpace} eq "MBED"',
|
||||
Format => 'int32u',
|
||||
RawConv => '$$self{BMPProfileSize} = $val',
|
||||
},
|
||||
# 120 - reserved
|
||||
);
|
||||
|
||||
# OS/2 12-byte bitmap header (ref http://www.fileformat.info/format/bmp/egff.htm)
|
||||
%Image::ExifTool::BMP::OS2 = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
NOTES => 'Information extracted from OS/2-format BMP images.',
|
||||
0 => {
|
||||
Name => 'BMPVersion',
|
||||
Format => 'int32u',
|
||||
Notes => 'again, the header size is used to determine the BMP version',
|
||||
PrintConv => {
|
||||
12 => 'OS/2 V1',
|
||||
64 => 'OS/2 V2',
|
||||
},
|
||||
},
|
||||
4 => { Name => 'ImageWidth', Format => 'int16u' },
|
||||
6 => { Name => 'ImageHeight', Format => 'int16u' },
|
||||
8 => { Name => 'Planes', Format => 'int16u' },
|
||||
10 => { Name => 'BitDepth', Format => 'int16u' },
|
||||
);
|
||||
|
||||
%Image::ExifTool::BMP::Extra = (
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
NOTES => 'Extra information extracted from some BMP images.',
|
||||
VARS => { ID_FMT => 'none' },
|
||||
LinkedProfileName => { },
|
||||
ICC_Profile => { SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' } },
|
||||
EmbeddedJPG => {
|
||||
Groups => { 2 => 'Preview' },
|
||||
Binary => 1,
|
||||
},
|
||||
EmbeddedPNG => {
|
||||
Groups => { 2 => 'Preview' },
|
||||
Binary => 1,
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract metadata from a BMP image
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid BMP file
|
||||
sub ProcessBMP($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $tagTablePtr);
|
||||
|
||||
# verify this is a valid BMP file
|
||||
return 0 unless $raf->Read($buff, 18) == 18;
|
||||
return 0 unless $buff =~ /^BM/;
|
||||
SetByteOrder('II');
|
||||
my $len = Get32u(\$buff, 14);
|
||||
# len = v1:12, v4:108, v5:124
|
||||
return 0 unless $len == 12 or $len == 16 or ($len >= 40 and $len < 1000000);
|
||||
return 0 unless $raf->Seek(-4, 1) and $raf->Read($buff, $len) == $len;
|
||||
$et->SetFileType(); # set the FileType tag
|
||||
#
|
||||
# process the BMP header
|
||||
#
|
||||
my %dirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => length($buff),
|
||||
);
|
||||
if ($len == 12 or $len == 16 or $len == 64) { # old OS/2 format BMP
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::BMP::OS2');
|
||||
} else {
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::BMP::Main');
|
||||
}
|
||||
$et->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
||||
#
|
||||
# extract any embedded images
|
||||
#
|
||||
my $extraTable = GetTagTable('Image::ExifTool::BMP::Extra');
|
||||
if ($$et{BMPCompression} and $$et{BMPImageLength} and
|
||||
($$et{BMPCompression} == 4 or $$et{BMPCompression} == 5))
|
||||
{
|
||||
my $tag = $$et{BMPCompression} == 4 ? 'EmbeddedJPG' : 'EmbeddedPNG';
|
||||
my $val = $et->ExtractBinary($raf->Tell(), $$et{BMPImageLength}, $tag);
|
||||
if ($val) {
|
||||
$et->HandleTag($extraTable, $tag, $val);
|
||||
}
|
||||
}
|
||||
#
|
||||
# process profile data if it exists (v5 header only)
|
||||
#
|
||||
if ($len == 124 and $$et{BMPProfileOffset}) {
|
||||
my $pos = $$et{BMPProfileOffset} + 14; # (note the 14-byte shift!)
|
||||
my $size = $$et{BMPProfileSize};
|
||||
if ($raf->Seek($pos, 0) and $raf->Read($buff, $size) == $size) {
|
||||
my $tag;
|
||||
if ($$et{BMPColorSpace} eq 'LINK') {
|
||||
$buff =~ s/\0+$//; # remove null terminator(s)
|
||||
$buff = $et->Decode($buff, 'Latin'); # convert from Latin
|
||||
$tag = 'LinkedProfileName';
|
||||
} else {
|
||||
$tag = 'ICC_Profile';
|
||||
}
|
||||
$et->HandleTag($extraTable, $tag => $buff, Size => $size, DataPos => $pos);
|
||||
} else {
|
||||
$et->Warn('Error loading profile data', 1);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::BMP - Read BMP meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read BMP
|
||||
(Windows Bitmap) images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html>
|
||||
|
||||
=item L<http://www.fourcc.org/rgb.php>
|
||||
|
||||
=item L<https://msdn.microsoft.com/en-us/library/dd183381(v=vs.85).aspx>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/BMP Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
253
.resources/nix/bin/lib/Image/ExifTool/BPG.pm
Normal file
253
.resources/nix/bin/lib/Image/ExifTool/BPG.pm
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: BPG.pm
|
||||
#
|
||||
# Description: Read BPG meta information
|
||||
#
|
||||
# Revisions: 2016-07-05 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://bellard.org/bpg/
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::BPG;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.01';
|
||||
|
||||
# BPG information
|
||||
%Image::ExifTool::BPG::Main = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
NOTES => q{
|
||||
The information listed below is extracted from BPG (Better Portable
|
||||
Graphics) images. See L<http://bellard.org/bpg/> for the specification.
|
||||
},
|
||||
4 => {
|
||||
Name => 'PixelFormat',
|
||||
Format => 'int16u',
|
||||
Mask => 0xe000,
|
||||
PrintConv => {
|
||||
0 => 'Grayscale',
|
||||
1 => '4:2:0 (chroma at 0.5, 0.5)',
|
||||
2 => '4:2:2 (chroma at 0.5, 0)',
|
||||
3 => '4:4:4',
|
||||
4 => '4:2:0 (chroma at 0, 0.5)',
|
||||
5 => '4:2:2 (chroma at 0, 0)',
|
||||
},
|
||||
},
|
||||
4.1 => {
|
||||
Name => 'Alpha',
|
||||
Format => 'int16u',
|
||||
Mask => 0x1004,
|
||||
BitShift => 0,
|
||||
PrintHex => 1,
|
||||
PrintConv => {
|
||||
0x0000 => 'No Alpha Plane',
|
||||
0x1000 => 'Alpha Exists (color not premultiplied)',
|
||||
0x1004 => 'Alpha Exists (color premultiplied)',
|
||||
0x0004 => 'Alpha Exists (W color component)',
|
||||
},
|
||||
},
|
||||
4.2 => {
|
||||
Name => 'BitDepth',
|
||||
Format => 'int16u',
|
||||
Mask => 0x0f00,
|
||||
ValueConv => '$val + 8',
|
||||
},
|
||||
4.3 => {
|
||||
Name => 'ColorSpace',
|
||||
Format => 'int16u',
|
||||
Mask => 0x00f0,
|
||||
PrintConv => {
|
||||
0 => 'YCbCr (BT 601)',
|
||||
1 => 'RGB',
|
||||
2 => 'YCgCo',
|
||||
3 => 'YCbCr (BT 709)',
|
||||
4 => 'YCbCr (BT 2020)',
|
||||
5 => 'BT 2020 Constant Luminance',
|
||||
},
|
||||
},
|
||||
4.4 => {
|
||||
Name => 'Flags',
|
||||
Format => 'int16u',
|
||||
Mask => 0x000b,
|
||||
PrintConv => { BITMASK => {
|
||||
0 => 'Animation',
|
||||
1 => 'Limited Range',
|
||||
3 => 'Extension Present',
|
||||
}},
|
||||
},
|
||||
6 => { Name => 'ImageWidth', Format => 'var_ue7' },
|
||||
7 => { Name => 'ImageHeight', Format => 'var_ue7' },
|
||||
# length of image data or 0 to EOF
|
||||
# (must be decoded so we know where the extension data starts)
|
||||
8 => { Name => 'ImageLength', Format => 'var_ue7' },
|
||||
);
|
||||
|
||||
%Image::ExifTool::BPG::Extensions = (
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
VARS => { ALPHA_FIRST => 1 },
|
||||
1 => {
|
||||
Name => 'EXIF',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Exif::Main',
|
||||
ProcessProc => \&Image::ExifTool::ProcessTIFF,
|
||||
},
|
||||
},
|
||||
2 => {
|
||||
Name => 'ICC_Profile',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
|
||||
},
|
||||
3 => {
|
||||
Name => 'XMP',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
|
||||
},
|
||||
4 => {
|
||||
Name => 'ThumbnailBPG',
|
||||
Binary => 1,
|
||||
},
|
||||
5 => {
|
||||
Name => 'AnimationControl',
|
||||
Binary => 1,
|
||||
Unknown => 1,
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Get ue7 integer from binary data (max 32 bits)
|
||||
# Inputs: 0) data ref, 1) location in data (undef for 0)
|
||||
# Returns: 0) ue7 as integer or undef on error, 1) length of ue7 in bytes
|
||||
sub Get_ue7($;$)
|
||||
{
|
||||
my $dataPt = shift;
|
||||
my $pos = shift || 0;
|
||||
my $size = length $$dataPt;
|
||||
my $val = 0;
|
||||
my $i;
|
||||
for ($i=0; ; ) {
|
||||
return() if $pos+$i >= $size or $i >= 5;
|
||||
my $byte = Get8u($dataPt, $pos + $i);
|
||||
$val = ($val << 7) | ($byte & 0x7f);
|
||||
unless ($byte & 0x80) {
|
||||
return() if $i == 4 and $byte & 0x70; # error if bits 32-34 are set
|
||||
last; # this was the last byte
|
||||
}
|
||||
return() if $i == 0 and $byte == 0x80; # error if first byte is 0x80
|
||||
++$i; # step to the next byte
|
||||
}
|
||||
return($val, $i+1);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract EXIF information from a BPG image
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid BPG file
|
||||
sub ProcessBPG($$)
|
||||
{
|
||||
local $_;
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $size, $n, $len, $pos);
|
||||
|
||||
# verify this is a valid BPG file
|
||||
return 0 unless $raf->Read($buff, 21) == 21; # (21 bytes is maximum header length)
|
||||
return 0 unless $buff =~ /^BPG\xfb/;
|
||||
$et->SetFileType(); # set the FileType tag
|
||||
|
||||
SetByteOrder('MM');
|
||||
my %dirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => length($buff),
|
||||
VarFormatData => [ ],
|
||||
);
|
||||
$et->ProcessDirectory(\%dirInfo, GetTagTable('Image::ExifTool::BPG::Main'));
|
||||
|
||||
return 1 unless $$et{VALUE}{Flags} & 0x0008; # all done unless extension flag is set
|
||||
|
||||
# add varSize from last entry in VarFormatData to determine
|
||||
# the current read position in the file
|
||||
my $dataPos = 9 + $dirInfo{VarFormatData}[-1][1];
|
||||
# read extension length
|
||||
unless ($raf->Seek($dataPos, 0) and $raf->Read($buff, 5) == 5) {
|
||||
$et->Warn('Missing BPG extension data');
|
||||
return 1;
|
||||
}
|
||||
($size, $n) = Get_ue7(\$buff);
|
||||
defined $size or $et->Warn('Corrupted BPG extension length'), return 1;
|
||||
$dataPos += $n;
|
||||
$size > 10000000 and $et->Warn('BPG extension is too large'), return 1;
|
||||
unless ($raf->Seek($dataPos, 0) and $raf->Read($buff, $size) == $size) {
|
||||
$et->Warn('Truncated BPG extension');
|
||||
return 1;
|
||||
}
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::BPG::Extensions');
|
||||
# loop through the individual extensions
|
||||
for ($pos=0; $pos<$size; $pos+=$len) {
|
||||
my $type = Get8u(\$buff, $pos);
|
||||
# get length of this extension
|
||||
($len, $n) = Get_ue7(\$buff, ++$pos);
|
||||
defined $len or $et->Warn('Corrupted BPG extension'), last;
|
||||
$pos += $n; # point to start of data for this extension
|
||||
$pos + $len > $size and $et->Warn('Invalid BPG extension size'), last;
|
||||
$$tagTablePtr{$type} or $et->Warn("Unrecognized BPG extension $type ($len bytes)", 1), next;
|
||||
# libbpg (in my opinion) incorrectly copies the padding byte after the
|
||||
# "EXIF\0" APP1 header to the start of the BPG EXIF extension, so issue a
|
||||
# minor warning and ignore the padding if we find it before the TIFF header
|
||||
if ($type == 1 and $len > 3 and substr($buff,$pos,3)=~/^.(II|MM)/s) {
|
||||
$et->Warn("Ignored extra byte at start of EXIF extension", 1);
|
||||
++$pos;
|
||||
--$len;
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $type, undef,
|
||||
DataPt => \$buff,
|
||||
DataPos => $dataPos,
|
||||
Start => $pos,
|
||||
Size => $len,
|
||||
Parent => 'BPG',
|
||||
);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::BPG - Read BPG meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read BPG
|
||||
(Better Portable Graphics) images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://bellard.org/bpg/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/BPG Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
472
.resources/nix/bin/lib/Image/ExifTool/BZZ.pm
Normal file
472
.resources/nix/bin/lib/Image/ExifTool/BZZ.pm
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: BZZ.pm
|
||||
#
|
||||
# Description: Utility to decode BZZ compressed data
|
||||
#
|
||||
# Revisions: 09/22/2008 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://djvu.sourceforge.net/
|
||||
# 2) http://www.djvu.org/
|
||||
#
|
||||
# Notes: This code based on ZPCodec and BSByteStream of DjVuLibre 3.5.21
|
||||
# (see NOTES documentation below for license/copyright details)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::BZZ;
|
||||
|
||||
use strict;
|
||||
use integer; # IMPORTANT!! use integer arithmetic throughout
|
||||
require Exporter;
|
||||
use vars qw($VERSION @ISA @EXPORT_OK);
|
||||
|
||||
$VERSION = '1.01';
|
||||
@ISA = qw(Exporter);
|
||||
@EXPORT_OK = qw(Decode);
|
||||
|
||||
# constants
|
||||
sub FREQMAX { 4 }
|
||||
sub CTXIDS { 3 }
|
||||
sub MAXBLOCK { 4096 }
|
||||
|
||||
# This table has been designed for the ZPCoder
|
||||
# by running the following command in file 'zptable.sn':
|
||||
# (fast-crude (steady-mat 0.0035 0.0002) 260)))
|
||||
my @default_ztable_p = (
|
||||
0x8000, 0x8000, 0x8000, 0x6bbd, 0x6bbd, 0x5d45, 0x5d45, 0x51b9, 0x51b9, 0x4813,
|
||||
0x4813, 0x3fd5, 0x3fd5, 0x38b1, 0x38b1, 0x3275, 0x3275, 0x2cfd, 0x2cfd, 0x2825,
|
||||
0x2825, 0x23ab, 0x23ab, 0x1f87, 0x1f87, 0x1bbb, 0x1bbb, 0x1845, 0x1845, 0x1523,
|
||||
0x1523, 0x1253, 0x1253, 0x0fcf, 0x0fcf, 0x0d95, 0x0d95, 0x0b9d, 0x0b9d, 0x09e3,
|
||||
0x09e3, 0x0861, 0x0861, 0x0711, 0x0711, 0x05f1, 0x05f1, 0x04f9, 0x04f9, 0x0425,
|
||||
0x0425, 0x0371, 0x0371, 0x02d9, 0x02d9, 0x0259, 0x0259, 0x01ed, 0x01ed, 0x0193,
|
||||
0x0193, 0x0149, 0x0149, 0x010b, 0x010b, 0x00d5, 0x00d5, 0x00a5, 0x00a5, 0x007b,
|
||||
0x007b, 0x0057, 0x0057, 0x003b, 0x003b, 0x0023, 0x0023, 0x0013, 0x0013, 0x0007,
|
||||
0x0007, 0x0001, 0x0001, 0x5695, 0x24ee, 0x8000, 0x0d30, 0x481a, 0x0481, 0x3579,
|
||||
0x017a, 0x24ef, 0x007b, 0x1978, 0x0028, 0x10ca, 0x000d, 0x0b5d, 0x0034, 0x078a,
|
||||
0x00a0, 0x050f, 0x0117, 0x0358, 0x01ea, 0x0234, 0x0144, 0x0173, 0x0234, 0x00f5,
|
||||
0x0353, 0x00a1, 0x05c5, 0x011a, 0x03cf, 0x01aa, 0x0285, 0x0286, 0x01ab, 0x03d3,
|
||||
0x011a, 0x05c5, 0x00ba, 0x08ad, 0x007a, 0x0ccc, 0x01eb, 0x1302, 0x02e6, 0x1b81,
|
||||
0x045e, 0x24ef, 0x0690, 0x2865, 0x09de, 0x3987, 0x0dc8, 0x2c99, 0x10ca, 0x3b5f,
|
||||
0x0b5d, 0x5695, 0x078a, 0x8000, 0x050f, 0x24ee, 0x0358, 0x0d30, 0x0234, 0x0481,
|
||||
0x0173, 0x017a, 0x00f5, 0x007b, 0x00a1, 0x0028, 0x011a, 0x000d, 0x01aa, 0x0034,
|
||||
0x0286, 0x00a0, 0x03d3, 0x0117, 0x05c5, 0x01ea, 0x08ad, 0x0144, 0x0ccc, 0x0234,
|
||||
0x1302, 0x0353, 0x1b81, 0x05c5, 0x24ef, 0x03cf, 0x2b74, 0x0285, 0x201d, 0x01ab,
|
||||
0x1715, 0x011a, 0x0fb7, 0x00ba, 0x0a67, 0x01eb, 0x06e7, 0x02e6, 0x0496, 0x045e,
|
||||
0x030d, 0x0690, 0x0206, 0x09de, 0x0155, 0x0dc8, 0x00e1, 0x2b74, 0x0094, 0x201d,
|
||||
0x0188, 0x1715, 0x0252, 0x0fb7, 0x0383, 0x0a67, 0x0547, 0x06e7, 0x07e2, 0x0496,
|
||||
0x0bc0, 0x030d, 0x1178, 0x0206, 0x19da, 0x0155, 0x24ef, 0x00e1, 0x320e, 0x0094,
|
||||
0x432a, 0x0188, 0x447d, 0x0252, 0x5ece, 0x0383, 0x8000, 0x0547, 0x481a, 0x07e2,
|
||||
0x3579, 0x0bc0, 0x24ef, 0x1178, 0x1978, 0x19da, 0x2865, 0x24ef, 0x3987, 0x320e,
|
||||
0x2c99, 0x432a, 0x3b5f, 0x447d, 0x5695, 0x5ece, 0x8000, 0x8000, 0x5695, 0x481a,
|
||||
0x481a, 0, 0, 0, 0, 0
|
||||
);
|
||||
my @default_ztable_m = (
|
||||
0x0000, 0x0000, 0x0000, 0x10a5, 0x10a5, 0x1f28, 0x1f28, 0x2bd3, 0x2bd3, 0x36e3,
|
||||
0x36e3, 0x408c, 0x408c, 0x48fd, 0x48fd, 0x505d, 0x505d, 0x56d0, 0x56d0, 0x5c71,
|
||||
0x5c71, 0x615b, 0x615b, 0x65a5, 0x65a5, 0x6962, 0x6962, 0x6ca2, 0x6ca2, 0x6f74,
|
||||
0x6f74, 0x71e6, 0x71e6, 0x7404, 0x7404, 0x75d6, 0x75d6, 0x7768, 0x7768, 0x78c2,
|
||||
0x78c2, 0x79ea, 0x79ea, 0x7ae7, 0x7ae7, 0x7bbe, 0x7bbe, 0x7c75, 0x7c75, 0x7d0f,
|
||||
0x7d0f, 0x7d91, 0x7d91, 0x7dfe, 0x7dfe, 0x7e5a, 0x7e5a, 0x7ea6, 0x7ea6, 0x7ee6,
|
||||
0x7ee6, 0x7f1a, 0x7f1a, 0x7f45, 0x7f45, 0x7f6b, 0x7f6b, 0x7f8d, 0x7f8d, 0x7faa,
|
||||
0x7faa, 0x7fc3, 0x7fc3, 0x7fd7, 0x7fd7, 0x7fe7, 0x7fe7, 0x7ff2, 0x7ff2, 0x7ffa,
|
||||
0x7ffa, 0x7fff, 0x7fff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
);
|
||||
my @default_ztable_up = (
|
||||
84, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
|
||||
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
|
||||
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
|
||||
66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
|
||||
82, 81, 82, 9, 86, 5, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97,
|
||||
82, 99, 76, 101, 70, 103, 66, 105, 106, 107, 66, 109, 60, 111, 56, 69,
|
||||
114, 65, 116, 61, 118, 57, 120, 53, 122, 49, 124, 43, 72, 39, 60, 33,
|
||||
56, 29, 52, 23, 48, 23, 42, 137, 38, 21, 140, 15, 142, 9, 144, 141,
|
||||
146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 70, 157, 66, 81, 62, 75,
|
||||
58, 69, 54, 65, 50, 167, 44, 65, 40, 59, 34, 55, 30, 175, 24, 177,
|
||||
178, 179, 180, 181, 182, 183, 184, 69, 186, 59, 188, 55, 190, 51, 192, 47,
|
||||
194, 41, 196, 37, 198, 199, 72, 201, 62, 203, 58, 205, 54, 207, 50, 209,
|
||||
46, 211, 40, 213, 36, 215, 30, 217, 26, 219, 20, 71, 14, 61, 14, 57,
|
||||
8, 53, 228, 49, 230, 45, 232, 39, 234, 35, 138, 29, 24, 25, 240, 19,
|
||||
22, 13, 16, 13, 10, 7, 244, 249, 10, 89, 230, 0, 0, 0, 0, 0
|
||||
);
|
||||
my @default_ztable_dn = (
|
||||
145, 4, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
|
||||
46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
||||
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
|
||||
78, 79, 80, 85, 226, 6, 176, 143, 138, 141, 112, 135, 104, 133, 100, 129,
|
||||
98, 127, 72, 125, 102, 123, 60, 121, 110, 119, 108, 117, 54, 115, 48, 113,
|
||||
134, 59, 132, 55, 130, 51, 128, 47, 126, 41, 62, 37, 66, 31, 54, 25,
|
||||
50, 131, 46, 17, 40, 15, 136, 7, 32, 139, 172, 9, 170, 85, 168, 248,
|
||||
166, 247, 164, 197, 162, 95, 160, 173, 158, 165, 156, 161, 60, 159, 56, 71,
|
||||
52, 163, 48, 59, 42, 171, 38, 169, 32, 53, 26, 47, 174, 193, 18, 191,
|
||||
222, 189, 218, 187, 216, 185, 214, 61, 212, 53, 210, 49, 208, 45, 206, 39,
|
||||
204, 195, 202, 31, 200, 243, 64, 239, 56, 237, 52, 235, 48, 233, 44, 231,
|
||||
38, 229, 34, 227, 28, 225, 22, 223, 16, 221, 220, 63, 8, 55, 224, 51,
|
||||
2, 47, 87, 43, 246, 37, 244, 33, 238, 27, 236, 21, 16, 15, 8, 241,
|
||||
242, 7, 10, 245, 2, 1, 83, 250, 2, 143, 246, 0, 0, 0, 0, 0
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# New - create new BZZ object
|
||||
# Inputs: 0) reference to BZZ object or BZZ class name
|
||||
# Returns: blessed BZZ object ref
|
||||
sub new
|
||||
{
|
||||
local $_;
|
||||
my $that = shift;
|
||||
my $class = ref($that) || $that || 'Image::ExifTool::BZZ';
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Initialize BZZ object
|
||||
# Inputs: 0) BZZ object ref, 1) data ref, 2) true for DjVu compatibility
|
||||
sub Init($$)
|
||||
{
|
||||
my ($self, $dataPt, $djvucompat) = @_;
|
||||
# Create machine independent ffz table
|
||||
my $ffzt = $$self{ffzt} = [ ];
|
||||
my ($i, $j);
|
||||
for ($i=0; $i<256; $i++) {
|
||||
$$ffzt[$i] = 0;
|
||||
for ($j=$i; $j&0x80; $j<<=1) {
|
||||
$$ffzt[$i] += 1;
|
||||
}
|
||||
}
|
||||
# Initialize table
|
||||
$$self{p} = [ @default_ztable_p ];
|
||||
$$self{'m'} = [ @default_ztable_m ];
|
||||
$$self{up} = [ @default_ztable_up ];
|
||||
$$self{dn} = [ @default_ztable_dn ];
|
||||
# Patch table (and lose DjVu compatibility)
|
||||
unless ($djvucompat) {
|
||||
my ($p, $m, $dn) = ($$self{p}, $$self{'m'}, $$self{dn});
|
||||
for ($j=0; $j<256; $j++) {
|
||||
my $a = (0x10000 - $$p[$j]) & 0xffff;
|
||||
while ($a >= 0x8000) { $a = ($a<<1) & 0xffff }
|
||||
if ($$m[$j]>0 && $a+$$p[$j]>=0x8000 && $a>=$$m[$j]) {
|
||||
$$dn[$j] = $default_ztable_dn[$default_ztable_dn[$j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
$$self{ctx} = [ (0) x 300 ];
|
||||
$$self{DataPt} = $dataPt;
|
||||
$$self{Pos} = 0;
|
||||
$$self{DataLen} = length $$dataPt;
|
||||
$$self{a} = 0;
|
||||
$$self{buffer} = 0;
|
||||
$$self{fence} = 0;
|
||||
$$self{blocksize} = 0;
|
||||
# Read first 16 bits of code
|
||||
if (length($$dataPt) >= 2) {
|
||||
$$self{code} = unpack('n', $$dataPt);
|
||||
$$self{Pos} += 2;
|
||||
} elsif (length($$dataPt) >= 1) {
|
||||
$$self{code} = (unpack('C', $$dataPt) << 8) | 0xff;
|
||||
$$self{Pos}++;
|
||||
} else {
|
||||
$$self{code} = 0xffff;
|
||||
}
|
||||
$$self{byte} = $$self{code} & 0xff;
|
||||
# Preload buffer
|
||||
$$self{delay} = 25;
|
||||
$$self{scount} = 0;
|
||||
# Compute initial fence
|
||||
$$self{fence} = $$self{code} >= 0x8000 ? 0x7fff : $$self{code};
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Decode data block
|
||||
# Inputs: 0) optional BZZ object ref, 1) optional data ref
|
||||
# Returns: decoded data or undefined on error
|
||||
# Notes: If called without a data ref, an input BZZ object ref must be given and
|
||||
# the BZZ object must have been initialized by a previous call to Init()
|
||||
sub Decode($;$)
|
||||
{
|
||||
# Decode input stream
|
||||
local $_;
|
||||
my $self;
|
||||
if (ref $_[0] and UNIVERSAL::isa($_[0],'Image::ExifTool::BZZ')) {
|
||||
$self = shift;
|
||||
} else {
|
||||
$self = Image::ExifTool::BZZ->new;
|
||||
}
|
||||
my $dataPt = shift;
|
||||
if ($dataPt) {
|
||||
$self->Init($dataPt, 1);
|
||||
} else {
|
||||
$dataPt = $$self{DataPt} or return undef;
|
||||
}
|
||||
# Decode block size
|
||||
my $n = 1;
|
||||
my $m = (1 << 24);
|
||||
while ($n < $m) {
|
||||
my $b = $self->decode_sub(0x8000 + ($$self{a}>>1));
|
||||
$n = ($n<<1) | $b;
|
||||
}
|
||||
$$self{size} = $n - $m;
|
||||
|
||||
return '' unless $$self{size};
|
||||
return undef if $$self{size} > MAXBLOCK()*1024;
|
||||
# Allocate
|
||||
if ($$self{blocksize} < $$self{size}) {
|
||||
$$self{blocksize} = $$self{size};
|
||||
}
|
||||
# Decode Estimation Speed
|
||||
my $fshift = 0;
|
||||
if ($self->decode_sub(0x8000 + ($$self{a}>>1))) {
|
||||
$fshift += 1;
|
||||
$fshift += 1 if $self->decode_sub(0x8000 + ($$self{a}>>1));
|
||||
}
|
||||
# Prepare Quasi MTF
|
||||
my @mtf = (0..255);
|
||||
my @freq = (0) x FREQMAX();
|
||||
my $fadd = 4;
|
||||
# Decode
|
||||
my $mtfno = 3;
|
||||
my $markerpos = -1;
|
||||
my $cx = $$self{ctx};
|
||||
my ($i, @dat);
|
||||
byte: for ($i=0; $i<$$self{size}; $i++) {
|
||||
# dummy loop avoids use of "goto" statement
|
||||
dummy: for (;;) {
|
||||
my $ctxid = CTXIDS() - 1;
|
||||
$ctxid = $mtfno if $ctxid > $mtfno;
|
||||
my $cp = 0;
|
||||
my ($imtf, $bits);
|
||||
for ($imtf=0; $imtf<2; ++$imtf) {
|
||||
if ($self->decoder($$cx[$cp+$ctxid])) {
|
||||
$mtfno = $imtf;
|
||||
$dat[$i] = $mtf[$mtfno];
|
||||
# (a "goto" here could give a segfault due to a Perl bug)
|
||||
last dummy; # do rotation
|
||||
}
|
||||
$cp += CTXIDS();
|
||||
}
|
||||
for ($bits=1; $bits<8; ++$bits, $imtf<<=1) {
|
||||
if ($self->decoder($$cx[$cp])) {
|
||||
my $n = 1;
|
||||
my $m = (1 << $bits);
|
||||
while ($n < $m) {
|
||||
my $b = $self->decoder($$cx[$cp+$n]);
|
||||
$n = ($n<<1) | $b;
|
||||
}
|
||||
$mtfno = $imtf + $n - $m;
|
||||
$dat[$i] = $mtf[$mtfno];
|
||||
last dummy; # do rotation
|
||||
}
|
||||
$cp += $imtf;
|
||||
}
|
||||
$mtfno=256;
|
||||
$dat[$i] = 0;
|
||||
$markerpos=$i;
|
||||
next byte; # no rotation necessary
|
||||
}
|
||||
# Rotate mtf according to empirical frequencies (new!)
|
||||
# Adjust frequencies for overflow
|
||||
$fadd = $fadd + ($fadd >> $fshift);
|
||||
if ($fadd > 0x10000000) {
|
||||
$fadd >>= 24;
|
||||
$_ >>= 24 foreach @freq;
|
||||
}
|
||||
# Relocate new char according to new freq
|
||||
my $fc = $fadd;
|
||||
$fc += $freq[$mtfno] if $mtfno < FREQMAX();
|
||||
my $k;
|
||||
for ($k=$mtfno; $k>=FREQMAX(); $k--) {
|
||||
$mtf[$k] = $mtf[$k-1];
|
||||
}
|
||||
for (; $k>0 && $fc>=$freq[$k-1]; $k--) {
|
||||
$mtf[$k] = $mtf[$k-1];
|
||||
$freq[$k] = $freq[$k-1];
|
||||
}
|
||||
$mtf[$k] = $dat[$i];
|
||||
$freq[$k] = $fc;
|
||||
# when "goto" was used, Perl 5.8.6 could segfault here
|
||||
# unless "next" was explicitly stated
|
||||
}
|
||||
#
|
||||
# Reconstruct the string
|
||||
#
|
||||
return undef if $markerpos<1 || $markerpos>=$$self{size};
|
||||
# Allocate pointers
|
||||
# Prepare count buffer
|
||||
my @count = (0) x 256;
|
||||
my @posn;
|
||||
# Fill count buffer
|
||||
no integer;
|
||||
for ($i=0; $i<$markerpos; $i++) {
|
||||
my $c = $dat[$i];
|
||||
$posn[$i] = ($c<<24) | ($count[$c]++ & 0xffffff);
|
||||
}
|
||||
$posn[$i++] = 0; # (initialize marker entry just to be safe)
|
||||
for ( ; $i<$$self{size}; $i++) {
|
||||
my $c = $dat[$i];
|
||||
$posn[$i] = ($c<<24) | ($count[$c]++ & 0xffffff);
|
||||
}
|
||||
use integer;
|
||||
# Compute sorted char positions
|
||||
my $last = 1;
|
||||
for ($i=0; $i<256; $i++) {
|
||||
my $tmp = $count[$i];
|
||||
$count[$i] = $last;
|
||||
$last += $tmp;
|
||||
}
|
||||
# Undo the sort transform
|
||||
$i = 0;
|
||||
$last = $$self{size}-1;
|
||||
while ($last > 0) {
|
||||
my $n = $posn[$i];
|
||||
no integer;
|
||||
my $c = $n >> 24;
|
||||
use integer;
|
||||
$dat[--$last] = $c;
|
||||
$i = $count[$c] + ($n & 0xffffff);
|
||||
}
|
||||
# Final check and return decoded data
|
||||
return undef if $i != $markerpos;
|
||||
pop @dat; # (last byte isn't real)
|
||||
return pack 'C*', @dat;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Inputs: 0) BZZ object ref, 1) ctx
|
||||
# Returns: decoded bit
|
||||
sub decoder($$)
|
||||
{
|
||||
my ($self, $ctx) = @_;
|
||||
my $z = $$self{a} + $self->{p}[$ctx];
|
||||
if ($z <= $$self{fence}) {
|
||||
$$self{a} = $z;
|
||||
return ($ctx & 1);
|
||||
}
|
||||
# must pass $_[1] so subroutine can modify value (darned C++ pass-by-reference!)
|
||||
return $self->decode_sub($z, $_[1]);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Inputs: 0) BZZ object ref, 1) z, 2) ctx (or undef)
|
||||
# Returns: decoded bit
|
||||
sub decode_sub($$;$)
|
||||
{
|
||||
my ($self, $z, $ctx) = @_;
|
||||
|
||||
# ensure that we have at least 16 bits of encoded data available
|
||||
if ($$self{scount} < 16) {
|
||||
# preload byte by byte until we have at least 24 bits
|
||||
while ($$self{scount} <= 24) {
|
||||
if ($$self{Pos} < $$self{DataLen}) {
|
||||
$$self{byte} = ord(substr(${$$self{DataPt}}, $$self{Pos}, 1));
|
||||
++$$self{Pos};
|
||||
} else {
|
||||
$$self{byte} = 0xff;
|
||||
if (--$$self{delay} < 1) {
|
||||
# setting size to zero forces error return from Decode()
|
||||
$$self{size} = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$$self{buffer} = ($$self{buffer}<<8) | $$self{byte};
|
||||
$$self{scount} += 8;
|
||||
}
|
||||
}
|
||||
# Save bit
|
||||
my $a = $$self{a};
|
||||
my ($bit, $code);
|
||||
if (defined $ctx) {
|
||||
$bit = ($ctx & 1);
|
||||
# Avoid interval reversion
|
||||
my $d = 0x6000 + (($z+$a)>>2);
|
||||
$z = $d if $z > $d;
|
||||
} else {
|
||||
$bit = 0;
|
||||
}
|
||||
# Test MPS/LPS
|
||||
if ($z > ($code = $$self{code})) {
|
||||
$bit ^= 1;
|
||||
# LPS branch
|
||||
$z = 0x10000 - $z;
|
||||
$a += $z;
|
||||
$code += $z;
|
||||
# LPS adaptation
|
||||
$_[2] = $self->{dn}[$ctx] if defined $ctx;
|
||||
# LPS renormalization
|
||||
my $sft = $a>=0xff00 ? $self->{ffzt}[$a&0xff] + 8 : $self->{ffzt}[($a>>8)&0xff];
|
||||
$$self{scount} -= $sft;
|
||||
$$self{a} = ($a<<$sft) & 0xffff;
|
||||
$code = (($code<<$sft) & 0xffff) | (($$self{buffer}>>$$self{scount}) & ((1<<$sft)-1));
|
||||
} else {
|
||||
# MPS adaptation
|
||||
$_[2] = $self->{up}[$ctx] if defined $ctx and $a >= $self->{'m'}[$ctx];
|
||||
# MPS renormalization
|
||||
$$self{scount} -= 1;
|
||||
$$self{a} = ($z<<1) & 0xffff;
|
||||
$code = (($code<<1) & 0xffff) | (($$self{buffer}>>$$self{scount}) & 1);
|
||||
}
|
||||
# Adjust fence and save new code
|
||||
$$self{fence} = $code >= 0x8000 ? 0x7fff : $code;
|
||||
$$self{code} = $code;
|
||||
return $bit;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::BZZ - Utility to decode BZZ compressed data
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to decode BZZ
|
||||
compressed data in DjVu images.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
This code is based on ZPCodec and BSByteStream of DjVuLibre 3.5.21 (see
|
||||
additional copyrights and the first reference below), which are covered
|
||||
under the GNU GPL license.
|
||||
|
||||
This is implemented as Image::ExifTool::BZZ instead of Compress::BZZ because
|
||||
I am hoping that someone else will write a proper Compress::BZZ module (with
|
||||
compression ability).
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
Copyright 2002, Leon Bottou and Yann Le Cun
|
||||
Copyright 2001, AT&T
|
||||
Copyright 1999-2001, LizardTech Inc.
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://djvu.sourceforge.net/>
|
||||
|
||||
=item L<http://www.djvu.org/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::DjVu(3pm)|Image::ExifTool::DjVu>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
304
.resources/nix/bin/lib/Image/ExifTool/BigTIFF.pm
Normal file
304
.resources/nix/bin/lib/Image/ExifTool/BigTIFF.pm
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: BigTIFF.pm
|
||||
#
|
||||
# Description: Read Big TIFF meta information
|
||||
#
|
||||
# Revisions: 07/03/2007 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.awaresystems.be/imaging/tiff/bigtiff.html
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::BigTIFF;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::Exif;
|
||||
|
||||
$VERSION = '1.08';
|
||||
|
||||
my $maxOffset = 0x7fffffff; # currently supported maximum data offset/size
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Big IFD directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
sub ProcessBigIFD($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $verbose = $$et{OPTIONS}{Verbose};
|
||||
my $htmlDump = $$et{HTML_DUMP};
|
||||
my $dirName = $$dirInfo{DirName};
|
||||
my $dirStart = $$dirInfo{DirStart};
|
||||
my ($offName, $nextOffName);
|
||||
|
||||
if ($htmlDump) {
|
||||
$verbose = -1; # mix htmlDump into verbose so we can test for both at once
|
||||
$offName = $$dirInfo{OffsetName};
|
||||
}
|
||||
|
||||
# loop through IFD chain
|
||||
for (;;) {
|
||||
if ($dirStart > $maxOffset and not $et->Options('LargeFileSupport')) {
|
||||
$et->Warn('Huge offsets not supported (LargeFileSupport not set)');
|
||||
last;
|
||||
}
|
||||
unless ($raf->Seek($dirStart, 0)) {
|
||||
$et->Warn("Bad $dirName offset");
|
||||
return 0;
|
||||
}
|
||||
my ($dirBuff, $index);
|
||||
unless ($raf->Read($dirBuff, 8) == 8) {
|
||||
$et->Warn("Truncated $dirName count");
|
||||
return 0;
|
||||
}
|
||||
my $numEntries = Image::ExifTool::Get64u(\$dirBuff, 0);
|
||||
$verbose > 0 and $et->VerboseDir($dirName, $numEntries);
|
||||
my $bsize = $numEntries * 20;
|
||||
if ($bsize > $maxOffset) {
|
||||
$et->Warn('Huge directory counts not yet supported');
|
||||
last;
|
||||
}
|
||||
my $bufPos = $raf->Tell();
|
||||
unless ($raf->Read($dirBuff, $bsize) == $bsize) {
|
||||
$et->Warn("Truncated $dirName directory");
|
||||
return 0;
|
||||
}
|
||||
my $nextIFD;
|
||||
$raf->Read($nextIFD, 8) == 8 or undef $nextIFD; # try to read next IFD pointer
|
||||
if ($htmlDump) {
|
||||
$et->HDump($bufPos-8, 8, "$dirName entries", "Entry count: $numEntries", undef, $offName);
|
||||
if (defined $nextIFD) {
|
||||
my $off = Image::ExifTool::Get64u(\$nextIFD, 0);
|
||||
my $tip = sprintf("Offset: 0x%.8x", $off);
|
||||
my $id = $offName;
|
||||
($nextOffName, $id) = Image::ExifTool::Exif::NextOffsetName($et, $id) if $off;
|
||||
$et->HDump($bufPos + 20 * $numEntries, 8, "Next IFD", $tip, 0, $id);
|
||||
}
|
||||
}
|
||||
# loop through all entries in this BigTIFF IFD
|
||||
for ($index=0; $index<$numEntries; ++$index) {
|
||||
my $entry = 20 * $index;
|
||||
my $tagID = Get16u(\$dirBuff, $entry);
|
||||
my $format = Get16u(\$dirBuff, $entry+2);
|
||||
my $count = Image::ExifTool::Get64u(\$dirBuff, $entry+4);
|
||||
my $formatSize = $Image::ExifTool::Exif::formatSize[$format];
|
||||
unless (defined $formatSize) {
|
||||
$et->HDump($bufPos+$entry,20,"[invalid IFD entry]",
|
||||
"Bad format value: $format", 1, $offName);
|
||||
# warn unless the IFD was just padded with zeros
|
||||
$et->Warn(sprintf("Unknown format ($format) for $dirName tag 0x%x",$tagID));
|
||||
return 0; # assume corrupted IFD
|
||||
}
|
||||
my $formatStr = $Image::ExifTool::Exif::formatName[$format];
|
||||
my $size = $count * $formatSize;
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID);
|
||||
next unless defined $tagInfo or $verbose;
|
||||
my $valuePtr = $entry + 12;
|
||||
my ($valBuff, $valBase, $rational, $subOffName);
|
||||
if ($size > 8) {
|
||||
if ($size > $maxOffset) {
|
||||
$et->Warn("Can't handle $dirName entry $index (huge size)");
|
||||
next;
|
||||
}
|
||||
$valuePtr = Image::ExifTool::Get64u(\$dirBuff, $valuePtr);
|
||||
if ($valuePtr > $maxOffset and not $et->Options('LargeFileSupport')) {
|
||||
$et->Warn("Can't handle $dirName entry $index (LargeFileSupport not set)");
|
||||
next;
|
||||
}
|
||||
unless ($raf->Seek($valuePtr, 0) and $raf->Read($valBuff, $size) == $size) {
|
||||
$et->Warn("Error reading $dirName entry $index");
|
||||
next;
|
||||
}
|
||||
$valBase = 0;
|
||||
} else {
|
||||
$valBuff = substr($dirBuff, $valuePtr, $size);
|
||||
$valBase = $bufPos;
|
||||
}
|
||||
if (defined $tagInfo and not $tagInfo) {
|
||||
# GetTagInfo() required the value for a Condition
|
||||
$tagInfo = $et->GetTagInfo($tagTablePtr, $tagID, \$valBuff);
|
||||
}
|
||||
my $val = ReadValue(\$valBuff, 0, $formatStr, $count, $size, \$rational);
|
||||
if ($htmlDump) {
|
||||
my $tval = $val;
|
||||
# show numerator/denominator separately for rational numbers
|
||||
$tval .= " ($rational)" if defined $rational;
|
||||
my ($tagName, $colName);
|
||||
if ($tagID == 0x927c and $dirName eq 'ExifIFD') {
|
||||
$tagName = 'MakerNotes';
|
||||
} elsif ($tagInfo) {
|
||||
$tagName = $$tagInfo{Name};
|
||||
} else {
|
||||
$tagName = sprintf("Tag 0x%.4x",$tagID);
|
||||
}
|
||||
my $dname = sprintf("$dirName-%.2d", $index);
|
||||
# build our tool tip
|
||||
my $tip = sprintf("Tag ID: 0x%.4x\n", $tagID) .
|
||||
"Format: $formatStr\[$count]\nSize: $size bytes\n";
|
||||
if ($size > 8) {
|
||||
$tip .= sprintf("Value offset: 0x%.8x\n", $valuePtr);
|
||||
$colName = "<span class=H>$tagName</span>";
|
||||
} else {
|
||||
$colName = $tagName;
|
||||
}
|
||||
$tval = substr($tval,0,28) . '[...]' if length($tval) > 32;
|
||||
if ($formatStr =~ /^(string|undef|binary)/) {
|
||||
# translate non-printable characters
|
||||
$tval =~ tr/\x00-\x1f\x7f-\xff/./;
|
||||
} elsif ($tagInfo and Image::ExifTool::IsInt($tval)) {
|
||||
if ($$tagInfo{IsOffset}) {
|
||||
$tval = sprintf('0x%.4x', $tval);
|
||||
} elsif ($$tagInfo{PrintHex}) {
|
||||
$tval = sprintf('0x%x', $tval);
|
||||
}
|
||||
}
|
||||
$tip .= "Value: $tval";
|
||||
my ($id, $sid);
|
||||
if ($tagInfo and $$tagInfo{SubIFD}) {
|
||||
($subOffName, $id, $sid) = Image::ExifTool::Exif::NextOffsetName($et, $offName);
|
||||
} else {
|
||||
$id = $offName;
|
||||
}
|
||||
$et->HDump($entry+$bufPos, 20, "$dname $colName", $tip, 1, $id);
|
||||
if ($size > 8) {
|
||||
# add value data block
|
||||
my $flg = ($tagInfo and $$tagInfo{SubDirectory} and $$tagInfo{MakerNotes}) ? 4 : 0;
|
||||
$et->HDump($valuePtr,$size,"$tagName value",'SAME', $flg, $sid);
|
||||
}
|
||||
}
|
||||
if ($tagInfo and $$tagInfo{SubIFD}) {
|
||||
# process all SubIFD's as BigTIFF
|
||||
$verbose > 0 and $et->VerboseInfo($tagID, $tagInfo,
|
||||
Table => $tagTablePtr,
|
||||
Index => $index,
|
||||
Value => $val,
|
||||
DataPt => \$valBuff,
|
||||
DataPos => $valBase + $valuePtr,
|
||||
Start => 0,
|
||||
Size => $size,
|
||||
Format => $formatStr,
|
||||
Count => $count,
|
||||
);
|
||||
my @offsets = split ' ', $val;
|
||||
my $i;
|
||||
for ($i=0; $i<scalar(@offsets); ++$i) {
|
||||
my $subdirName = $$tagInfo{Name};
|
||||
$subdirName .= $i if $i;
|
||||
my %subdirInfo = (
|
||||
RAF => $raf,
|
||||
DataPos => 0,
|
||||
DirStart => $offsets[$i],
|
||||
DirName => $subdirName,
|
||||
Parent => $dirName,
|
||||
OffsetName => $subOffName,
|
||||
);
|
||||
$et->ProcessDirectory(\%subdirInfo, $tagTablePtr, \&ProcessBigIFD);
|
||||
}
|
||||
} else {
|
||||
my $tagKey = $et->HandleTag($tagTablePtr, $tagID, $val,
|
||||
Index => $index,
|
||||
DataPt => \$valBuff,
|
||||
DataPos => $valBase + $valuePtr,
|
||||
Start => 0,
|
||||
Size => $size,
|
||||
Format => $formatStr,
|
||||
TagInfo => $tagInfo,
|
||||
RAF => $raf,
|
||||
);
|
||||
$tagKey and $et->SetGroup($tagKey, $dirName);
|
||||
}
|
||||
}
|
||||
last unless $dirName =~ /^(IFD|SubIFD)(\d*)$/;
|
||||
$dirName = $1 . (($2 || 0) + 1);
|
||||
defined $nextIFD or $et->Warn("Bad $dirName pointer"), return 0;
|
||||
$dirStart = Image::ExifTool::Get64u(\$nextIFD, 0);
|
||||
$dirStart or last;
|
||||
$offName = $nextOffName;
|
||||
# protect against infinite loop
|
||||
if ($$et{PROCESSED}{$dirStart}) {
|
||||
$et->Warn("$dirName pointer references previous $$et{PROCESSED}{$dirStart} directory");
|
||||
last;
|
||||
} else {
|
||||
$$et{PROCESSED}{$dirStart} = $dirName;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract meta information from a BigTIFF image
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid BigTIFF image
|
||||
sub ProcessBTF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $buff;
|
||||
|
||||
return 0 unless $raf->Read($buff, 16) == 16;
|
||||
return 0 unless $buff =~ /^(MM\0\x2b\0\x08\0\0|II\x2b\0\x08\0\0\0)/;
|
||||
if ($$dirInfo{OutFile}) {
|
||||
$et->Error('ExifTool does not support writing of BigTIFF images');
|
||||
return 1;
|
||||
}
|
||||
$et->SetFileType('BTF'); # set the FileType tag
|
||||
SetByteOrder(substr($buff, 0, 2));
|
||||
my $offset = Image::ExifTool::Get64u(\$buff, 8);
|
||||
if ($$et{HTML_DUMP}) {
|
||||
my $o = (GetByteOrder() eq 'II') ? 'Little' : 'Big';
|
||||
$et->HDump(0, 8, "BigTIFF header", "Byte order: $o endian", 0);
|
||||
$et->HDump(8, 8, "IFD0 pointer", sprintf("Offset: 0x%.8x",$offset), 0);
|
||||
}
|
||||
my %dirInfo = (
|
||||
RAF => $raf,
|
||||
DataPos => 0,
|
||||
DirStart => $offset,
|
||||
DirName => 'IFD0',
|
||||
Parent => 'BigTIFF',
|
||||
);
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main');
|
||||
$et->ProcessDirectory(\%dirInfo, $tagTablePtr, \&ProcessBigIFD);
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::BigTIFF - Read Big TIFF meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains routines required by Image::ExifTool to read meta
|
||||
information in BigTIFF images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.awaresystems.be/imaging/tiff/bigtiff.html>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/EXIF Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
2851
.resources/nix/bin/lib/Image/ExifTool/BuildTagLookup.pm
Normal file
2851
.resources/nix/bin/lib/Image/ExifTool/BuildTagLookup.pm
Normal file
File diff suppressed because it is too large
Load diff
350
.resources/nix/bin/lib/Image/ExifTool/CBOR.pm
Normal file
350
.resources/nix/bin/lib/Image/ExifTool/CBOR.pm
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: CBOR.pm
|
||||
#
|
||||
# Description: Read CBOR format metadata
|
||||
#
|
||||
# Revisions: 2021-09-30 - P. Harvey Created
|
||||
#
|
||||
# References: 1) https://c2pa.org/public-draft/
|
||||
# 2) https://datatracker.ietf.org/doc/html/rfc7049
|
||||
# 3) https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::CBOR;
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::JSON;
|
||||
|
||||
$VERSION = '1.03';
|
||||
|
||||
sub ProcessCBOR($$$);
|
||||
sub ReadCBORValue($$$$);
|
||||
|
||||
# optional CBOR type code
|
||||
my %cborType6 = (
|
||||
0 => 'date/time string',
|
||||
1 => 'epoch-based date/time',
|
||||
2 => 'positive bignum',
|
||||
3 => 'negative bignum',
|
||||
4 => 'decimal fraction',
|
||||
5 => 'bigfloat',
|
||||
16 => 'COSE Encrypt0', #3 (COSE Single Recipient Encrypted Data Object)
|
||||
17 => 'COSE Mac0', #3 (COSE Mac w/o Recipients Object)
|
||||
18 => 'COSE Sign1', #3 (COSE Single Signer Data Object)
|
||||
19 => 'COSE Countersignature', #3 (COSE standalone V2 countersignature)
|
||||
21 => 'expected base64url encoding',
|
||||
22 => 'expected base64 encoding',
|
||||
23 => 'expected base16 encoding',
|
||||
24 => 'encoded CBOR data',
|
||||
25 => 'string number', #3 (reference the nth previously seen string)
|
||||
26 => 'serialized Perl', #3 (Serialised Perl object with classname and constructor arguments)
|
||||
27 => 'serialized code', #3 (Serialised language-independent object with type name and constructor arguments)
|
||||
28 => 'shared value', #3 (mark value as (potentially) shared)
|
||||
29 => 'shared value number', #3 (reference nth marked value)
|
||||
30 => 'rational', #3 (Rational number)
|
||||
31 => 'missing array value', #3 (Absent value in a CBOR Array)
|
||||
32 => 'URI',
|
||||
33 => 'base64url',
|
||||
34 => 'base64',
|
||||
35 => 'regular expression',
|
||||
36 => 'MIME message',
|
||||
# (lots more after this in ref 3, but don't include them unless we see them)
|
||||
55799 => 'CBOR magic number',
|
||||
);
|
||||
|
||||
my %cborType7 = (
|
||||
20 => 'False',
|
||||
21 => 'True',
|
||||
22 => 'null',
|
||||
23 => 'undef',
|
||||
);
|
||||
|
||||
%Image::ExifTool::CBOR::Main = (
|
||||
GROUPS => { 0 => 'JUMBF', 1 => 'CBOR', 2 => 'Other' },
|
||||
VARS => { ID_FMT => 'none' },
|
||||
PROCESS_PROC => \&ProcessCBOR,
|
||||
NOTES => q{
|
||||
The tags below are extracted from CBOR (Concise Binary Object
|
||||
Representation) metadata. The C2PA specification uses this format for some
|
||||
metadata. As well as these tags, ExifTool will read any existing tags.
|
||||
},
|
||||
'dc:title' => 'Title',
|
||||
'dc:format' => 'Format',
|
||||
# my sample file has the following 2 tags in CBOR, but they should be JSON
|
||||
authorName => { Name => 'AuthorName', Groups => { 2 => 'Author' } },
|
||||
authorIdentifier=> { Name => 'AuthorIdentifier', Groups => { 2 => 'Author' } },
|
||||
documentID => { },
|
||||
instanceID => { },
|
||||
thumbnailHash => { List => 1 },
|
||||
thumbnailUrl => { Name => 'ThumbnailURL' },
|
||||
relationship => { }
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read CBOR value
|
||||
# Inputs: 0) ExifTool ref, 1) data ref, 2) position in data, 3) data end
|
||||
# Returns: 0) value, 1) error string, 2) new data position
|
||||
sub ReadCBORValue($$$$)
|
||||
{
|
||||
my ($et, $dataPt, $pos, $end) = @_;
|
||||
return(undef, 'Truncated CBOR data', $pos) if $pos >= $end;
|
||||
my $verbose = $$et{OPTIONS}{Verbose};
|
||||
my $indent = $$et{INDENT};
|
||||
my $dumpStart = $pos;
|
||||
my $fmt = Get8u($dataPt, $pos++);
|
||||
my $dat = $fmt & 0x1f;
|
||||
my ($num, $val, $err, $size);
|
||||
$fmt >>= 5;
|
||||
if ($dat < 24) {
|
||||
$num = $dat;
|
||||
} elsif ($dat == 31) { # indefinite count (not used in C2PA)
|
||||
$num = -1; # (flag for indefinite count)
|
||||
$et->VPrint(1, "$$et{INDENT} (indefinite count):\n");
|
||||
} else {
|
||||
my $format = { 24 => 'int8u', 25 => 'int16u', 26 => 'int32u', 27 => 'int64u' }->{$dat};
|
||||
return(undef, "Invalid CBOR integer type $dat", $pos) unless $format;
|
||||
$size = Image::ExifTool::FormatSize($format);
|
||||
return(undef, 'Truncated CBOR integer value', $pos) if $pos + $size > $end;
|
||||
$num = ReadValue($dataPt, $pos, $format, 1, $size);
|
||||
$pos += $size;
|
||||
}
|
||||
my $pre = '';
|
||||
if (defined $$et{cbor_pre} and $fmt != 6) {
|
||||
$pre = $$et{cbor_pre};
|
||||
delete $$et{cbor_pre};
|
||||
}
|
||||
if ($fmt == 0) { # positive integer
|
||||
$val = $num;
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}int+: $val\n");
|
||||
} elsif ($fmt == 1) { # negative integer
|
||||
$val = -1 * $num;
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}int-: $val\n");
|
||||
} elsif ($fmt == 2 or $fmt == 3) { # byte/UTF8 string
|
||||
return(undef, 'Truncated CBOR string value', $pos) if $pos + $num > $end;
|
||||
if ($num < 0) { # (should not happen in C2PA)
|
||||
my $string = '';
|
||||
$$et{INDENT} .= ' ';
|
||||
for (;;) {
|
||||
($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end);
|
||||
return(undef, $err, $pos) if $err;
|
||||
last if not defined $val; # hit the break?
|
||||
# (note: strictly we should be checking that this was a string we read)
|
||||
$string .= $val;
|
||||
}
|
||||
$$et{INDENT} = $indent;
|
||||
return($string, undef, $pos); # return concatenated byte/text string
|
||||
} else {
|
||||
$val = substr($$dataPt, $pos, $num);
|
||||
}
|
||||
$pos += $num;
|
||||
if ($fmt == 2) { # (byte string)
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}byte: <binary data ".length($val)." bytes>\n");
|
||||
my $dat = $val;
|
||||
$val = \$dat; # use scalar reference for binary data
|
||||
} else { # (text string)
|
||||
$val = $et->Decode($val, 'UTF8');
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}text: '${val}'\n");
|
||||
}
|
||||
} elsif ($fmt == 4 or $fmt == 5) { # list/hash
|
||||
if ($fmt == 4) {
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}list: <$num elements>\n");
|
||||
} else {
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}hash: <$num pairs>\n");
|
||||
$num *= 2;
|
||||
}
|
||||
$$et{INDENT} .= ' ';
|
||||
my $i = 0;
|
||||
my @list;
|
||||
Image::ExifTool::HexDump($dataPt, $pos - $dumpStart,
|
||||
Start => $dumpStart,
|
||||
DataPos => $$et{cbor_datapos},
|
||||
Prefix => $$et{INDENT},
|
||||
Out => $et->Options('TextOut'),
|
||||
) if $verbose > 2;
|
||||
while ($num) {
|
||||
$$et{cbor_pre} = "$i) ";
|
||||
if ($fmt == 4) {
|
||||
++$i;
|
||||
} elsif ($num & 0x01) {
|
||||
$$et{cbor_pre} = ' ' x length($$et{cbor_pre});
|
||||
++$i;
|
||||
}
|
||||
($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end);
|
||||
return(undef, $err, $pos) if $err;
|
||||
if (not defined $val) {
|
||||
return(undef, 'Unexpected list terminator', $pos) unless $num < 0;
|
||||
last;
|
||||
}
|
||||
push @list, $val;
|
||||
--$num;
|
||||
}
|
||||
$dumpStart = $pos;
|
||||
$$et{INDENT} = $indent;
|
||||
if ($fmt == 5) {
|
||||
my ($i, @keys);
|
||||
my %hash = ( _ordered_keys_ => \@keys );
|
||||
for ($i=0; $i<@list-1; $i+=2) {
|
||||
$hash{$list[$i]} = $list[$i+1];
|
||||
push @keys, $list[$i]; # save ordered list of keys
|
||||
}
|
||||
$val = \%hash;
|
||||
} else {
|
||||
$val = \@list;
|
||||
}
|
||||
} elsif ($fmt == 6) { # optional tag
|
||||
if ($verbose) {
|
||||
my $str = "$num (" . ($cborType6{$num} || 'unknown') . ')';
|
||||
my $spc = $$et{cbor_pre} ? (' ' x length $$et{cbor_pre}) : '';
|
||||
$et->VPrint(1, "$$et{INDENT} $spc<CBOR optional type $str>\n");
|
||||
Image::ExifTool::HexDump($dataPt, $pos - $dumpStart,
|
||||
Start => $dumpStart,
|
||||
DataPos => $$et{cbor_datapos},
|
||||
Prefix => $$et{INDENT} . ' ',
|
||||
Out => $et->Options('TextOut'),
|
||||
) if $verbose > 2;
|
||||
}
|
||||
# read next value (note: in the case of multiple tags,
|
||||
# this nesting will apply the tags in the correct order)
|
||||
($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end);
|
||||
$dumpStart = $pos;
|
||||
# convert some values according to the optional tag number (untested)
|
||||
if ($num == 0 and not ref $val) { # date/time string
|
||||
require Image::ExifTool::XMP;
|
||||
$val = Image::ExifTool::XMP::ConvertXMPDate($val);
|
||||
} elsif ($num == 1 and not ref $val) { # epoch-based date/time
|
||||
if (Image::ExifTool::IsFloat($val)) {
|
||||
my $dec = ($val == int($val)) ? undef : 6;
|
||||
$val = Image::ExifTool::ConvertUnixTime($val, 1, $dec);
|
||||
}
|
||||
} elsif (($num == 2 or $num == 3) and ref($val) eq 'SCALAR') { # pos/neg bignum
|
||||
my $big = 0;
|
||||
$big = 256 * $big + Get8u($val,$_) foreach 0..(length($$val) - 1);
|
||||
$val = $num==2 ? $big : -$big;
|
||||
} elsif (($num == 4 or $num == 5) and # decimal fraction or bigfloat
|
||||
ref($val) eq 'ARRAY' and @$val == 2 and
|
||||
Image::ExifTool::IsInt($$val[0]) and Image::ExifTool::IsInt($$val[1]))
|
||||
{
|
||||
$val = $$val[1] * ($num == 4 ? 10 : 2) ** $$val[0];
|
||||
}
|
||||
} elsif ($fmt == 7) {
|
||||
if ($dat == 31) {
|
||||
undef $val; # "break" = end of indefinite array/hash (not used in C2PA)
|
||||
} elsif ($dat < 24) {
|
||||
$val = $cborType7{$num};
|
||||
$val = "Unknown ($val)" unless defined $val;
|
||||
} elsif ($dat == 25) { # half-precision float
|
||||
my $exp = ($num >> 10) & 0x1f;
|
||||
my $mant = $num & 0x3ff;
|
||||
if ($exp == 0) {
|
||||
$val = $mant ** -24;
|
||||
$val *= -1 if $num & 0x8000;
|
||||
} elsif (exp != 31) {
|
||||
$val = ($mant + 1024) ** ($exp - 25);
|
||||
$val *= -1 if $num & 0x8000;
|
||||
} else {
|
||||
$val = $mant == 0 ? '<inf>' : '<nan>';
|
||||
}
|
||||
} elsif ($dat == 26) { # float
|
||||
$val = GetFloat($dataPt, $pos - $size);
|
||||
} elsif ($dat == 27) { # double
|
||||
$val = GetDouble($dataPt, $pos - $size);
|
||||
} else {
|
||||
return(undef, "Invalid CBOR type 7 variant $num", $pos);
|
||||
}
|
||||
$et->VPrint(1, "$$et{INDENT} ${pre}typ7: ".(defined $val ? $val : '<break>')."\n");
|
||||
} else {
|
||||
return(undef, "Unknown CBOR format $fmt", $pos);
|
||||
}
|
||||
Image::ExifTool::HexDump($dataPt, $pos - $dumpStart,
|
||||
Start => $dumpStart,
|
||||
DataPos => $$et{cbor_datapos},
|
||||
Prefix => $$et{INDENT} . ' ',
|
||||
MaxLen => $verbose < 5 ? ($verbose == 3 ? 96 : 2048) : undef,
|
||||
Out => $et->Options('TextOut'),
|
||||
) if $verbose > 2;
|
||||
return($val, $err, $pos);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read CBOR box
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessCBOR($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $pos = $$dirInfo{DirStart};
|
||||
my $end = $pos + $$dirInfo{DirLen};
|
||||
my ($val, $err, $tag, $i);
|
||||
|
||||
$et->VerboseDir('CBOR', undef, $$dirInfo{DirLen});
|
||||
SetByteOrder('MM');
|
||||
|
||||
$$et{cbor_datapos} = $$dirInfo{DataPos} + $$dirInfo{Base};
|
||||
|
||||
while ($pos < $end) {
|
||||
($val, $err, $pos) = ReadCBORValue($et, $dataPt, $pos, $end);
|
||||
$err and $et->Warn($err), last;
|
||||
if (ref $val eq 'HASH') {
|
||||
foreach $tag (@{$$val{_ordered_keys_}}) {
|
||||
Image::ExifTool::JSON::ProcessTag($et, $tagTablePtr, $tag, $$val{$tag});
|
||||
}
|
||||
} elsif (ref $val eq 'ARRAY') {
|
||||
for ($i=0; $i<@$val; ++$i) {
|
||||
Image::ExifTool::JSON::ProcessTag($et, $tagTablePtr, "Item$i", $$val[$i]);
|
||||
}
|
||||
} elsif ($val eq '0') {
|
||||
$et->VPrint(1, "$$et{INDENT} <CBOR end>\n");
|
||||
last; # (treat as padding)
|
||||
} else {
|
||||
$et->VPrint(1, "$$et{INDENT} Unknown value: $val\n");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::CBOR - Read CBOR format metadata
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool read Concise
|
||||
Binary Object Representation (CBOR) formatted metadata, used by the C2PA
|
||||
specification.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://c2pa.org/public-draft/>
|
||||
|
||||
=item L<https://datatracker.ietf.org/doc/html/rfc7049>
|
||||
|
||||
=item L<https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/CBOR Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
10813
.resources/nix/bin/lib/Image/ExifTool/Canon.pm
Normal file
10813
.resources/nix/bin/lib/Image/ExifTool/Canon.pm
Normal file
File diff suppressed because it is too large
Load diff
2900
.resources/nix/bin/lib/Image/ExifTool/CanonCustom.pm
Normal file
2900
.resources/nix/bin/lib/Image/ExifTool/CanonCustom.pm
Normal file
File diff suppressed because it is too large
Load diff
923
.resources/nix/bin/lib/Image/ExifTool/CanonRaw.pm
Normal file
923
.resources/nix/bin/lib/Image/ExifTool/CanonRaw.pm
Normal file
|
|
@ -0,0 +1,923 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: CanonRaw.pm
|
||||
#
|
||||
# Description: Read Canon RAW (CRW) meta information
|
||||
#
|
||||
# Revisions: 11/25/2003 - P. Harvey Created
|
||||
# 12/02/2003 - P. Harvey Completely reworked and figured out many
|
||||
# more tags
|
||||
#
|
||||
# References: 1) http://www.cybercom.net/~dcoffin/dcraw/
|
||||
# 2) http://www.wonderland.org/crw/
|
||||
# 3) http://xyrion.org/ciff/CIFFspecV1R04.pdf
|
||||
# 4) Dave Nicholson private communication (PowerShot S30)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::CanonRaw;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION $AUTOLOAD %crwTagFormat);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::Exif;
|
||||
use Image::ExifTool::Canon;
|
||||
|
||||
$VERSION = '1.62';
|
||||
|
||||
sub WriteCRW($$);
|
||||
sub ProcessCanonRaw($$$);
|
||||
sub WriteCanonRaw($$$);
|
||||
sub CheckCanonRaw($$$);
|
||||
sub InitMakerNotes($);
|
||||
sub SaveMakerNotes($);
|
||||
sub BuildMakerNotes($$$$$$);
|
||||
|
||||
# formats for CRW tag types (($tag >> 8) & 0x38)
|
||||
# Note: don't define format for undefined types
|
||||
%crwTagFormat = (
|
||||
0x00 => 'int8u',
|
||||
0x08 => 'string',
|
||||
0x10 => 'int16u',
|
||||
0x18 => 'int32u',
|
||||
# 0x20 => 'undef',
|
||||
# 0x28 => 'undef',
|
||||
# 0x30 => 'undef',
|
||||
);
|
||||
|
||||
# Canon raw file tag table
|
||||
# Note: Tag ID's have upper 2 bits set to zero, since these 2 bits
|
||||
# just specify the location of the information
|
||||
%Image::ExifTool::CanonRaw::Main = (
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
PROCESS_PROC => \&ProcessCanonRaw,
|
||||
WRITE_PROC => \&WriteCanonRaw,
|
||||
CHECK_PROC => \&CheckCanonRaw,
|
||||
WRITABLE => 1,
|
||||
0x0000 => { Name => 'NullRecord', Writable => 'undef' }, #3
|
||||
0x0001 => { #3
|
||||
Name => 'FreeBytes',
|
||||
Format => 'undef',
|
||||
Binary => 1,
|
||||
},
|
||||
0x0032 => { Name => 'CanonColorInfo1', Writable => 0 },
|
||||
0x0805 => [
|
||||
# this tag is found in more than one directory...
|
||||
{
|
||||
Condition => '$self->{DIR_NAME} eq "ImageDescription"',
|
||||
Name => 'CanonFileDescription',
|
||||
Writable => 'string[32]',
|
||||
},
|
||||
{
|
||||
Name => 'UserComment',
|
||||
Writable => 'string[256]',
|
||||
},
|
||||
],
|
||||
0x080a => {
|
||||
Name => 'CanonRawMakeModel',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::MakeModel' },
|
||||
},
|
||||
0x080b => { Name => 'CanonFirmwareVersion', Writable => 'string[32]' },
|
||||
0x080c => { Name => 'ComponentVersion', Writable => 'string' }, #3
|
||||
0x080d => { Name => 'ROMOperationMode', Writable => 'string[8]' }, #3
|
||||
0x0810 => { Name => 'OwnerName', Writable => 'string[32]' },
|
||||
0x0815 => { Name => 'CanonImageType', Writable => 'string[32]' },
|
||||
0x0816 => { Name => 'OriginalFileName', Writable => 'string[32]' },
|
||||
0x0817 => { Name => 'ThumbnailFileName', Writable => 'string[32]' },
|
||||
0x100a => { #3
|
||||
Name => 'TargetImageType',
|
||||
Writable => 'int16u',
|
||||
PrintConv => {
|
||||
0 => 'Real-world Subject',
|
||||
1 => 'Written Document',
|
||||
},
|
||||
},
|
||||
0x1010 => { #3
|
||||
Name => 'ShutterReleaseMethod',
|
||||
Writable => 'int16u',
|
||||
PrintConv => {
|
||||
0 => 'Single Shot',
|
||||
2 => 'Continuous Shooting',
|
||||
},
|
||||
},
|
||||
0x1011 => { #3
|
||||
Name => 'ShutterReleaseTiming',
|
||||
Writable => 'int16u',
|
||||
PrintConv => {
|
||||
0 => 'Priority on shutter',
|
||||
1 => 'Priority on focus',
|
||||
},
|
||||
},
|
||||
0x1016 => { Name => 'ReleaseSetting', Writable => 'int16u' }, #3
|
||||
0x101c => { Name => 'BaseISO', Writable => 'int16u' }, #3
|
||||
0x1028=> { #PH
|
||||
Name => 'CanonFlashInfo',
|
||||
Writable => 'int16u',
|
||||
Count => 4,
|
||||
Unknown => 1,
|
||||
},
|
||||
0x1029 => {
|
||||
Name => 'CanonFocalLength',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Canon::FocalLength' },
|
||||
},
|
||||
0x102a => {
|
||||
Name => 'CanonShotInfo',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Canon::ShotInfo' },
|
||||
},
|
||||
0x102c => {
|
||||
Name => 'CanonColorInfo2',
|
||||
Writable => 0,
|
||||
# for the S30, the following information has been decoded: (ref 4)
|
||||
# offset 66: int32u - shutter half press time in ms
|
||||
# offset 70: int32u - image capture time in ms
|
||||
# offset 74: int16u - custom white balance flag (0=Off, 512=On)
|
||||
},
|
||||
0x102d => {
|
||||
Name => 'CanonCameraSettings',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraSettings' },
|
||||
},
|
||||
0x1030 => { #4
|
||||
Name => 'WhiteSample',
|
||||
Writable => 0,
|
||||
SubDirectory => {
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||||
TagTable => 'Image::ExifTool::CanonRaw::WhiteSample',
|
||||
},
|
||||
},
|
||||
0x1031 => {
|
||||
Name => 'SensorInfo',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Canon::SensorInfo' },
|
||||
},
|
||||
# this tag has only be verified for the 10D in CRW files, but the D30 and D60
|
||||
# also produce CRW images and have CustomFunction information in their JPEG's
|
||||
0x1033 => [
|
||||
{
|
||||
Name => 'CustomFunctions10D',
|
||||
Condition => '$self->{Model} =~ /EOS 10D/',
|
||||
SubDirectory => {
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||||
TagTable => 'Image::ExifTool::CanonCustom::Functions10D',
|
||||
},
|
||||
},
|
||||
{
|
||||
Name => 'CustomFunctionsD30',
|
||||
Condition => '$self->{Model} =~ /EOS D30\b/',
|
||||
SubDirectory => {
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||||
TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30',
|
||||
},
|
||||
},
|
||||
{
|
||||
Name => 'CustomFunctionsD60',
|
||||
Condition => '$self->{Model} =~ /EOS D60\b/',
|
||||
SubDirectory => {
|
||||
# the stored size in the D60 apparently doesn't include the size word:
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size-2,$size)',
|
||||
# (D60 custom functions are basically the same as D30)
|
||||
TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30',
|
||||
},
|
||||
},
|
||||
{
|
||||
Name => 'CustomFunctionsUnknown',
|
||||
SubDirectory => {
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||||
TagTable => 'Image::ExifTool::CanonCustom::FuncsUnknown',
|
||||
},
|
||||
},
|
||||
],
|
||||
0x1038 => {
|
||||
Name => 'CanonAFInfo',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Canon::AFInfo' },
|
||||
},
|
||||
0x1093 => {
|
||||
Name => 'CanonFileInfo',
|
||||
SubDirectory => {
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||||
TagTable => 'Image::ExifTool::Canon::FileInfo',
|
||||
},
|
||||
},
|
||||
0x10a9 => {
|
||||
Name => 'ColorBalance',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorBalance' },
|
||||
},
|
||||
0x10b5 => { #PH
|
||||
Name => 'RawJpgInfo',
|
||||
SubDirectory => {
|
||||
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||||
TagTable => 'Image::ExifTool::CanonRaw::RawJpgInfo',
|
||||
},
|
||||
},
|
||||
0x10ae => {
|
||||
Name => 'ColorTemperature',
|
||||
Writable => 'int16u',
|
||||
},
|
||||
0x10b4 => {
|
||||
Name => 'ColorSpace',
|
||||
Writable => 'int16u',
|
||||
PrintConv => {
|
||||
1 => 'sRGB',
|
||||
2 => 'Adobe RGB',
|
||||
0xffff => 'Uncalibrated',
|
||||
},
|
||||
},
|
||||
0x1803 => { #3
|
||||
Name => 'ImageFormat',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::ImageFormat' },
|
||||
},
|
||||
0x1804 => { Name => 'RecordID', Writable => 'int32u' }, #3
|
||||
0x1806 => { #3
|
||||
Name => 'SelfTimerTime',
|
||||
Writable => 'int32u',
|
||||
ValueConv => '$val / 1000',
|
||||
ValueConvInv => '$val * 1000',
|
||||
PrintConv => '"$val s"',
|
||||
PrintConvInv => '$val=~s/\s*s.*//;$val',
|
||||
},
|
||||
0x1807 => {
|
||||
Name => 'TargetDistanceSetting',
|
||||
Format => 'float',
|
||||
PrintConv => '"$val mm"',
|
||||
PrintConvInv => '$val=~s/\s*mm$//;$val',
|
||||
},
|
||||
0x180b => [
|
||||
{
|
||||
# D30
|
||||
Name => 'SerialNumber',
|
||||
Condition => '$$self{Model} =~ /EOS D30\b/',
|
||||
Writable => 'int32u',
|
||||
PrintConv => 'sprintf("%x-%.5d",$val>>16,$val&0xffff)',
|
||||
PrintConvInv => '$val=~/(.*)-(\d+)/ ? (hex($1)<<16)+$2 : undef',
|
||||
},
|
||||
{
|
||||
# all EOS models (D30, 10D, 300D)
|
||||
Name => 'SerialNumber',
|
||||
Condition => '$$self{Model} =~ /EOS/',
|
||||
Writable => 'int32u',
|
||||
PrintConv => 'sprintf("%.10d",$val)',
|
||||
PrintConvInv => '$val',
|
||||
},
|
||||
{
|
||||
# this is not SerialNumber for PowerShot models (but what is it?) - PH
|
||||
Name => 'UnknownNumber',
|
||||
Unknown => 1,
|
||||
},
|
||||
],
|
||||
0x180e => {
|
||||
Name => 'TimeStamp',
|
||||
Writable => 0,
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::CanonRaw::TimeStamp',
|
||||
},
|
||||
},
|
||||
0x1810 => {
|
||||
Name => 'ImageInfo',
|
||||
Writable => 0,
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::CanonRaw::ImageInfo',
|
||||
},
|
||||
},
|
||||
0x1813 => { #3
|
||||
Name => 'FlashInfo',
|
||||
Writable => 0,
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::CanonRaw::FlashInfo',
|
||||
},
|
||||
},
|
||||
0x1814 => { #3
|
||||
Name => 'MeasuredEV',
|
||||
Notes => q{
|
||||
this is the Canon name for what could better be called MeasuredLV, and
|
||||
should be close to the calculated LightValue for a proper exposure with most
|
||||
models
|
||||
},
|
||||
Format => 'float',
|
||||
ValueConv => '$val + 5',
|
||||
ValueConvInv => '$val - 5',
|
||||
},
|
||||
0x1817 => {
|
||||
Name => 'FileNumber',
|
||||
Writable => 'int32u',
|
||||
Groups => { 2 => 'Image' },
|
||||
PrintConv => '$_=$val;s/(\d+)(\d{4})/$1-$2/;$_',
|
||||
PrintConvInv => '$_=$val;s/-//;$_',
|
||||
},
|
||||
0x1818 => { #3
|
||||
Name => 'ExposureInfo',
|
||||
Groups => { 1 => 'CIFF' }, # (only so CIFF shows up in group lists)
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::ExposureInfo' },
|
||||
},
|
||||
0x1834 => { #PH
|
||||
Name => 'CanonModelID',
|
||||
Writable => 'int32u',
|
||||
PrintHex => 1,
|
||||
Notes => q{
|
||||
this is the complete list of model ID numbers, but note that many of these
|
||||
models do not produce CRW images
|
||||
},
|
||||
SeparateTable => 'Canon CanonModelID',
|
||||
PrintConv => \%Image::ExifTool::Canon::canonModelID,
|
||||
},
|
||||
0x1835 => {
|
||||
Name => 'DecoderTable',
|
||||
Writable => 0,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::DecoderTable' },
|
||||
},
|
||||
0x183b => { #PH
|
||||
# display format for serial number
|
||||
Name => 'SerialNumberFormat',
|
||||
Writable => 'int32u',
|
||||
PrintHex => 1,
|
||||
PrintConv => {
|
||||
0x90000000 => 'Format 1',
|
||||
0xa0000000 => 'Format 2',
|
||||
},
|
||||
},
|
||||
0x2005 => {
|
||||
Name => 'RawData',
|
||||
Writable => 0,
|
||||
Binary => 1,
|
||||
},
|
||||
0x2007 => {
|
||||
Name => 'JpgFromRaw',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Writable => 'resize', # 'resize' allows this value to change size
|
||||
Permanent => 0,
|
||||
RawConv => '$self->ValidateImage(\$val,$tag)',
|
||||
},
|
||||
0x2008 => {
|
||||
Name => 'ThumbnailImage',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Writable => 'resize', # 'resize' allows this value to change size
|
||||
WriteCheck => '$self->CheckImage(\$val)',
|
||||
Permanent => 0,
|
||||
RawConv => '$self->ValidateImage(\$val,$tag)',
|
||||
},
|
||||
# the following entries are subdirectories
|
||||
# (any 0x28 and 0x30 tag types are handled automatically by the decoding logic)
|
||||
0x2804 => {
|
||||
Name => 'ImageDescription',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
0x2807 => { #3
|
||||
Name => 'CameraObject',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
0x3002 => { #3
|
||||
Name => 'ShootingRecord',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
0x3003 => { #3
|
||||
Name => 'MeasuredInfo',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
0x3004 => { #3
|
||||
Name => 'CameraSpecification',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
0x300a => { #3
|
||||
Name => 'ImageProps',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
0x300b => {
|
||||
Name => 'ExifInformation',
|
||||
SubDirectory => { },
|
||||
Writable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
# Canon binary data blocks
|
||||
%Image::ExifTool::CanonRaw::MakeModel = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
DATAMEMBER => [ 0, 6 ], # indices of data members to extract when writing
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'string',
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
# (can't specify a first entry because this isn't
|
||||
# a simple binary table with fixed offsets)
|
||||
0 => {
|
||||
Name => 'Make',
|
||||
Format => 'string[6]', # "Canon\0"
|
||||
DataMember => 'Make',
|
||||
RawConv => '$self->{Make} = $val',
|
||||
},
|
||||
6 => {
|
||||
Name => 'Model',
|
||||
Format => 'string', # no size = to the end of the data
|
||||
Description => 'Camera Model Name',
|
||||
DataMember => 'Model',
|
||||
RawConv => '$self->{Model} = $val',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::CanonRaw::TimeStamp = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'int32u',
|
||||
FIRST_ENTRY => 0,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Time' },
|
||||
0 => {
|
||||
Name => 'DateTimeOriginal',
|
||||
Description => 'Date/Time Original',
|
||||
Shift => 'Time',
|
||||
ValueConv => 'ConvertUnixTime($val)',
|
||||
ValueConvInv => 'GetUnixTime($val)',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
PrintConvInv => '$self->InverseDateTime($val)',
|
||||
},
|
||||
1 => { #3
|
||||
Name => 'TimeZoneCode',
|
||||
Format => 'int32s',
|
||||
ValueConv => '$val / 3600',
|
||||
ValueConvInv => '$val * 3600',
|
||||
},
|
||||
2 => { #3
|
||||
Name => 'TimeZoneInfo',
|
||||
Notes => 'set to 0x80000000 if TimeZoneCode is valid',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::CanonRaw::ImageFormat = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'int32u',
|
||||
FIRST_ENTRY => 0,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
0 => {
|
||||
Name => 'FileFormat',
|
||||
Flags => 'PrintHex',
|
||||
PrintConv => {
|
||||
0x00010000 => 'JPEG (lossy)',
|
||||
0x00010002 => 'JPEG (non-quantization)',
|
||||
0x00010003 => 'JPEG (lossy/non-quantization toggled)',
|
||||
0x00020001 => 'CRW',
|
||||
},
|
||||
},
|
||||
1 => {
|
||||
Name => 'TargetCompressionRatio',
|
||||
Format => 'float',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::CanonRaw::RawJpgInfo = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'int16u',
|
||||
FIRST_ENTRY => 1,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
# 0 => 'RawJpgInfoSize',
|
||||
1 => { #PH
|
||||
Name => 'RawJpgQuality',
|
||||
PrintConv => {
|
||||
1 => 'Economy',
|
||||
2 => 'Normal',
|
||||
3 => 'Fine',
|
||||
5 => 'Superfine',
|
||||
},
|
||||
},
|
||||
2 => { #PH
|
||||
Name => 'RawJpgSize',
|
||||
PrintConv => {
|
||||
0 => 'Large',
|
||||
1 => 'Medium',
|
||||
2 => 'Small',
|
||||
},
|
||||
},
|
||||
3 => 'RawJpgWidth', #PH
|
||||
4 => 'RawJpgHeight', #PH
|
||||
);
|
||||
|
||||
%Image::ExifTool::CanonRaw::FlashInfo = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'float',
|
||||
FIRST_ENTRY => 0,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
0 => 'FlashGuideNumber',
|
||||
1 => 'FlashThreshold',
|
||||
);
|
||||
|
||||
%Image::ExifTool::CanonRaw::ExposureInfo = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'float',
|
||||
FIRST_ENTRY => 0,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
0 => 'ExposureCompensation',
|
||||
1 => {
|
||||
Name => 'ShutterSpeedValue',
|
||||
ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
|
||||
ValueConvInv => '$val>0 ? -log($val)/log(2) : -100',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
|
||||
},
|
||||
2 => {
|
||||
Name => 'ApertureValue',
|
||||
ValueConv => '2 ** ($val / 2)',
|
||||
ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
|
||||
PrintConv => 'sprintf("%.1f",$val)',
|
||||
PrintConvInv => '$val',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::CanonRaw::ImageInfo = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
FORMAT => 'int32u',
|
||||
FIRST_ENTRY => 0,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||||
# Note: Don't make these writable (except rotation) because it confuses
|
||||
# Canon decoding software if the are changed
|
||||
0 => 'ImageWidth', #3
|
||||
1 => 'ImageHeight', #3
|
||||
2 => { #3
|
||||
Name => 'PixelAspectRatio',
|
||||
Format => 'float',
|
||||
},
|
||||
3 => {
|
||||
Name => 'Rotation',
|
||||
Format => 'int32s',
|
||||
Writable => 1,
|
||||
},
|
||||
4 => 'ComponentBitDepth', #3
|
||||
5 => 'ColorBitDepth', #3
|
||||
6 => 'ColorBW', #3
|
||||
);
|
||||
|
||||
# ref 4
|
||||
%Image::ExifTool::CanonRaw::DecoderTable = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
FORMAT => 'int32u',
|
||||
FIRST_ENTRY => 0,
|
||||
0 => 'DecoderTableNumber',
|
||||
2 => 'CompressedDataOffset',
|
||||
3 => 'CompressedDataLength',
|
||||
);
|
||||
|
||||
# ref 1/4
|
||||
%Image::ExifTool::CanonRaw::WhiteSample = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
FORMAT => 'int16u',
|
||||
FIRST_ENTRY => 1,
|
||||
1 => 'WhiteSampleWidth',
|
||||
2 => 'WhiteSampleHeight',
|
||||
3 => 'WhiteSampleLeftBorder',
|
||||
4 => 'WhiteSampleTopBorder',
|
||||
5 => 'WhiteSampleBits',
|
||||
# (followed by the encrypted white sample values, ref 1)
|
||||
# BlackLevels seem valid for D30 and D60, but not sure about PowerShot models
|
||||
0x37 => { Name => 'BlackLevels', Format => 'int16u[4]' }, #github387
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# AutoLoad our writer routines when necessary
|
||||
#
|
||||
sub AUTOLOAD
|
||||
{
|
||||
return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Raw file directory
|
||||
# Inputs: 0) ExifTool object reference
|
||||
# 1) directory information reference, 2) tag table reference
|
||||
# Returns: 1 on success
|
||||
sub ProcessCanonRaw($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $rawTagTable) = @_;
|
||||
my $blockStart = $$dirInfo{DirStart};
|
||||
my $blockSize = $$dirInfo{DirLen};
|
||||
my $raf = $$dirInfo{RAF} or return 0;
|
||||
my $buff;
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $buildMakerNotes = $et->Options('MakerNotes');
|
||||
|
||||
# 4 bytes at end of block give directory position within block
|
||||
$raf->Seek($blockStart+$blockSize-4, 0) or return 0;
|
||||
$raf->Read($buff, 4) == 4 or return 0;
|
||||
my $dirOffset = Get32u(\$buff,0) + $blockStart;
|
||||
# avoid infinite recursion
|
||||
$$et{ProcessedCanonRaw} or $$et{ProcessedCanonRaw} = { };
|
||||
if ($$et{ProcessedCanonRaw}{$dirOffset}) {
|
||||
$et->Warn("Not processing double-referenced $$dirInfo{DirName} directory");
|
||||
return 0;
|
||||
}
|
||||
$$et{ProcessedCanonRaw}{$dirOffset} = 1;
|
||||
$raf->Seek($dirOffset, 0) or return 0;
|
||||
$raf->Read($buff, 2) == 2 or return 0;
|
||||
my $entries = Get16u(\$buff,0); # get number of entries in directory
|
||||
# read the directory (10 bytes per entry)
|
||||
$raf->Read($buff, 10 * $entries) == 10 * $entries or return 0;
|
||||
|
||||
$verbose and $et->VerboseDir('CIFF', $entries);
|
||||
my $index;
|
||||
for ($index=0; $index<$entries; ++$index) {
|
||||
my $pt = 10 * $index;
|
||||
my $tag = Get16u(\$buff, $pt);
|
||||
my $size = Get32u(\$buff, $pt+2);
|
||||
my $valuePtr = Get32u(\$buff, $pt+6);
|
||||
my $ptr = $valuePtr + $blockStart; # all pointers relative to block start
|
||||
if ($tag & 0x8000) {
|
||||
$et->Warn('Bad CRW directory entry');
|
||||
return 1;
|
||||
}
|
||||
my $tagID = $tag & 0x3fff; # get tag ID
|
||||
my $tagType = ($tag >> 8) & 0x38; # get tag type
|
||||
my $valueInDir = ($tag & 0x4000); # flag for value in directory
|
||||
my $tagInfo = $et->GetTagInfo($rawTagTable, $tagID);
|
||||
if (($tagType==0x28 or $tagType==0x30) and not $valueInDir) {
|
||||
# this type of tag specifies a raw subdirectory
|
||||
my $name;
|
||||
$tagInfo and $name = $$tagInfo{Name};
|
||||
$name or $name = sprintf("CanonRaw_0x%.4x", $tag);
|
||||
my %subdirInfo = (
|
||||
DirName => $name,
|
||||
DataLen => 0,
|
||||
DirStart => $ptr,
|
||||
DirLen => $size,
|
||||
Nesting => $$dirInfo{Nesting} + 1,
|
||||
RAF => $raf,
|
||||
Parent => $$dirInfo{DirName},
|
||||
);
|
||||
if ($verbose) {
|
||||
my $fakeInfo = { Name => $name, SubDirectory => { } };
|
||||
$et->VerboseInfo($tagID, $fakeInfo,
|
||||
'Index' => $index,
|
||||
'Size' => $size,
|
||||
'Start' => $ptr,
|
||||
);
|
||||
}
|
||||
$et->ProcessDirectory(\%subdirInfo, $rawTagTable);
|
||||
next;
|
||||
}
|
||||
my ($valueDataPos, $count, $subdir);
|
||||
my $format = $crwTagFormat{$tagType};
|
||||
if ($tagInfo) {
|
||||
$subdir = $$tagInfo{SubDirectory};
|
||||
$format = $$tagInfo{Format} if $$tagInfo{Format};
|
||||
$count = $$tagInfo{Count};
|
||||
}
|
||||
# get value data
|
||||
my ($value, $delRawConv);
|
||||
if ($valueInDir) { # is the value data in the directory?
|
||||
# this type of tag stores the value in the 'size' and 'ptr' fields
|
||||
$valueDataPos = $dirOffset + $pt + 4; # (remember, +2 for the entry count)
|
||||
$size = 8;
|
||||
$value = substr($buff, $pt+2, $size);
|
||||
# set count to 1 by default for normal values in directory
|
||||
$count = 1 if not defined $count and $format and
|
||||
$format ne 'string' and not $subdir;
|
||||
} else {
|
||||
$valueDataPos = $ptr;
|
||||
# do hash of image data if requested
|
||||
if ($$et{ImageDataHash} and $tagID == 0x2005) {
|
||||
$raf->Seek($ptr, 0) and $et->ImageDataHash($raf, $size, 'raw');
|
||||
}
|
||||
if ($size <= 512 or ($verbose > 2 and $size <= 65536)
|
||||
or ($tagInfo and ($$tagInfo{SubDirectory}
|
||||
or grep(/^$$tagInfo{Name}$/i, $et->GetRequestedTags()) )))
|
||||
{
|
||||
# read value if size is small or specifically requested
|
||||
# or if this is a SubDirectory
|
||||
unless ($raf->Seek($ptr, 0) and $raf->Read($value, $size) == $size) {
|
||||
$et->Warn(sprintf("Error reading %d bytes from 0x%x",$size,$ptr));
|
||||
next;
|
||||
}
|
||||
} else {
|
||||
$value = "Binary data $size bytes";
|
||||
if ($tagInfo) {
|
||||
if ($et->Options('Binary') or $verbose) {
|
||||
# read the value anyway
|
||||
unless ($raf->Seek($ptr, 0) and $raf->Read($value, $size) == $size) {
|
||||
$et->Warn(sprintf("Error reading %d bytes from 0x%x",$size,$ptr));
|
||||
next;
|
||||
}
|
||||
}
|
||||
# force this to be a binary (scalar reference)
|
||||
$$tagInfo{RawConv} = '\$val';
|
||||
$delRawConv = 1;
|
||||
}
|
||||
$size = length $value;
|
||||
undef $format;
|
||||
}
|
||||
}
|
||||
# set count from tagInfo count if necessary
|
||||
if ($format and not $count) {
|
||||
# set count according to format and size
|
||||
my $fnum = $Image::ExifTool::Exif::formatNumber{$format};
|
||||
my $fsiz = $Image::ExifTool::Exif::formatSize[$fnum];
|
||||
$count = int($size / $fsiz);
|
||||
}
|
||||
if ($verbose) {
|
||||
my $val = $value;
|
||||
$format and $val = ReadValue(\$val, 0, $format, $count, $size);
|
||||
$et->VerboseInfo($tagID, $tagInfo,
|
||||
Table => $rawTagTable,
|
||||
Index => $index,
|
||||
Value => $val,
|
||||
DataPt => \$value,
|
||||
DataPos => $valueDataPos,
|
||||
Size => $size,
|
||||
Format => $format,
|
||||
Count => $count,
|
||||
);
|
||||
}
|
||||
if ($buildMakerNotes) {
|
||||
# build maker notes information if requested
|
||||
BuildMakerNotes($et, $tagID, $tagInfo, \$value, $format, $count);
|
||||
}
|
||||
next unless defined $tagInfo;
|
||||
|
||||
if ($subdir) {
|
||||
my $name = $$tagInfo{Name};
|
||||
my $newTagTable;
|
||||
if ($$subdir{TagTable}) {
|
||||
$newTagTable = GetTagTable($$subdir{TagTable});
|
||||
unless ($newTagTable) {
|
||||
warn "Unknown tag table $$subdir{TagTable}\n";
|
||||
next;
|
||||
}
|
||||
} else {
|
||||
warn "Must specify TagTable for SubDirectory $name\n";
|
||||
next;
|
||||
}
|
||||
my $subdirStart = 0;
|
||||
#### eval Start ()
|
||||
$subdirStart = eval $$subdir{Start} if $$subdir{Start};
|
||||
my $dirData = \$value;
|
||||
my %subdirInfo = (
|
||||
Name => $name,
|
||||
DataPt => $dirData,
|
||||
DataLen => $size,
|
||||
DataPos => $valueDataPos,
|
||||
DirStart => $subdirStart,
|
||||
DirLen => $size - $subdirStart,
|
||||
Nesting => $$dirInfo{Nesting} + 1,
|
||||
RAF => $raf,
|
||||
DirName => $name,
|
||||
Parent => $$dirInfo{DirName},
|
||||
);
|
||||
#### eval Validate ($dirData, $subdirStart, $size)
|
||||
if (defined $$subdir{Validate} and not eval $$subdir{Validate}) {
|
||||
$et->Warn("Invalid $name data");
|
||||
} else {
|
||||
$et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc});
|
||||
}
|
||||
} else {
|
||||
# convert to specified format if necessary
|
||||
$format and $value = ReadValue(\$value, 0, $format, $count, $size);
|
||||
# save the information
|
||||
$et->FoundTag($tagInfo, $value);
|
||||
delete $$tagInfo{RawConv} if $delRawConv;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# get information from raw file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 if this was a valid Canon RAW file
|
||||
sub ProcessCRW($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my ($buff, $sig);
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $buildMakerNotes = $et->Options('MakerNotes');
|
||||
|
||||
$raf->Read($buff,2) == 2 or return 0;
|
||||
SetByteOrder($buff) or return 0;
|
||||
$raf->Read($buff,4) == 4 or return 0;
|
||||
$raf->Read($sig,8) == 8 or return 0; # get file signature
|
||||
$sig =~ /^HEAP(CCDR|JPGM)/ or return 0; # validate signature
|
||||
my $hlen = Get32u(\$buff, 0);
|
||||
|
||||
$raf->Seek(0, 2) or return 0; # seek to end of file
|
||||
my $filesize = $raf->Tell() or return 0;
|
||||
|
||||
# initialize maker note data if building maker notes
|
||||
$buildMakerNotes and InitMakerNotes($et);
|
||||
|
||||
# set the FileType tag unless already done (eg. APP0 CIFF record in JPEG image)
|
||||
$et->SetFileType();
|
||||
|
||||
# build directory information for main raw directory
|
||||
my %dirInfo = (
|
||||
DataLen => 0,
|
||||
DirStart => $hlen,
|
||||
DirLen => $filesize - $hlen,
|
||||
Nesting => 0,
|
||||
RAF => $raf,
|
||||
Parent => 'CRW',
|
||||
);
|
||||
|
||||
# process the raw directory
|
||||
my $rawTagTable = GetTagTable('Image::ExifTool::CanonRaw::Main');
|
||||
my $oldIndent = $$et{INDENT};
|
||||
$$et{INDENT} .= '| ';
|
||||
unless (ProcessCanonRaw($et, \%dirInfo, $rawTagTable)) {
|
||||
$et->Warn('CRW file format error');
|
||||
}
|
||||
$$et{INDENT} = $oldIndent;
|
||||
|
||||
# finish building maker notes if necessary
|
||||
$buildMakerNotes and SaveMakerNotes($et);
|
||||
|
||||
# process trailers if they exist in CRW file (not in CIFF information!)
|
||||
if ($$et{FILE_TYPE} eq 'CRW') {
|
||||
my $trailInfo = $et->IdentifyTrailer($raf);
|
||||
$et->ProcessTrailers($trailInfo) if $trailInfo;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::CanonRaw - Read Canon RAW (CRW) meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to interpret
|
||||
meta information from Canon CRW raw files. These files are written directly
|
||||
by some Canon cameras, and contain meta information similar to that found in
|
||||
the EXIF Canon maker notes.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
The CR2 format written by some Canon cameras is very different the CRW
|
||||
format processed by this module. (CR2 is TIFF-based and uses standard EXIF
|
||||
tags.)
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.cybercom.net/~dcoffin/dcraw/>
|
||||
|
||||
=item L<http://www.wonderland.org/crw/>
|
||||
|
||||
=item L<http://xyrion.org/ciff/>
|
||||
|
||||
=item L<https://exiftool.org/canon_raw.html>
|
||||
|
||||
=back
|
||||
|
||||
=head1 ACKNOWLEDGEMENTS
|
||||
|
||||
Thanks to Dave Nicholson for decoding a number of new tags.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/CanonRaw Tags>,
|
||||
L<Image::ExifTool::Canon(3pm)|Image::ExifTool::Canon>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
2306
.resources/nix/bin/lib/Image/ExifTool/CanonVRD.pm
Normal file
2306
.resources/nix/bin/lib/Image/ExifTool/CanonVRD.pm
Normal file
File diff suppressed because it is too large
Load diff
235
.resources/nix/bin/lib/Image/ExifTool/CaptureOne.pm
Normal file
235
.resources/nix/bin/lib/Image/ExifTool/CaptureOne.pm
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: CaptureOne.pm
|
||||
#
|
||||
# Description: Read Capture One EIP and COS files
|
||||
#
|
||||
# Revisions: 2009/11/01 - P. Harvey Created
|
||||
#
|
||||
# Notes: The EIP format is a ZIP file containing an image (IIQ or TIFF)
|
||||
# and some settings files (COS). COS files are XML based.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::CaptureOne;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::XMP;
|
||||
use Image::ExifTool::ZIP;
|
||||
|
||||
$VERSION = '1.05';
|
||||
|
||||
# CaptureOne COS XML tags
|
||||
# - tags are added dynamically when encountered
|
||||
# - this table is not listed in tag name docs
|
||||
%Image::ExifTool::CaptureOne::Main = (
|
||||
GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Image' },
|
||||
PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP,
|
||||
VARS => { ID_FMT => 'none' },
|
||||
ColorCorrections => { ValueConv => '\$val', Hidden => 1 }, # (long list of floating point numbers)
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# We found an XMP property name/value
|
||||
# Inputs: 0) attribute list ref, 1) attr hash ref,
|
||||
# 2) property name ref, 3) property value ref
|
||||
# Returns: true if value was changed
|
||||
sub HandleCOSAttrs($$$$)
|
||||
{
|
||||
my ($attrList, $attrs, $prop, $valPt) = @_;
|
||||
my $changed;
|
||||
if (not length $$valPt and defined $$attrs{K} and defined $$attrs{V}) {
|
||||
$$prop = $$attrs{K};
|
||||
$$valPt = $$attrs{V};
|
||||
# remove these attributes from the list
|
||||
my @attrs = @$attrList;
|
||||
@$attrList = ( );
|
||||
my $a;
|
||||
foreach $a (@attrs) {
|
||||
if ($a eq 'K' or $a eq 'V') {
|
||||
delete $$attrs{$a};
|
||||
} else {
|
||||
push @$attrList, $a;
|
||||
}
|
||||
}
|
||||
$changed = 1;
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# We found a COS property name/value
|
||||
# Inputs: 0) ExifTool object ref, 1) tag table ref
|
||||
# 2) reference to array of XMP property names (last is current property)
|
||||
# 3) property value, 4) attribute hash ref (not used here)
|
||||
# Returns: 1 if valid tag was found
|
||||
sub FoundCOS($$$$;$)
|
||||
{
|
||||
my ($et, $tagTablePtr, $props, $val, $attrs) = @_;
|
||||
|
||||
my $tag = $$props[-1];
|
||||
unless ($$tagTablePtr{$tag}) {
|
||||
$et->VPrint(0, " | [adding $tag]\n");
|
||||
my $name = ucfirst $tag;
|
||||
$name =~ tr/-_a-zA-Z0-9//dc;
|
||||
return 0 unless length $tag;
|
||||
my %tagInfo = ( Name => $tag );
|
||||
# try formatting any tag with "Date" in the name as a date
|
||||
# (shouldn't affect non-date tags)
|
||||
if ($name =~ /Date(?![a-z])/) {
|
||||
$tagInfo{Groups} = { 2 => 'Time' };
|
||||
$tagInfo{ValueConv} = 'Image::ExifTool::XMP::ConvertXMPDate($val,1)';
|
||||
$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
|
||||
}
|
||||
AddTagToTable($tagTablePtr, $tag, \%tagInfo);
|
||||
}
|
||||
# convert from UTF8 to ExifTool Charset
|
||||
$val = $et->Decode($val, "UTF8");
|
||||
# un-escape XML character entities
|
||||
$val = Image::ExifTool::XMP::UnescapeXML($val);
|
||||
$et->HandleTag($tagTablePtr, $tag, $val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from a COS file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid XML file
|
||||
sub ProcessCOS($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
|
||||
# process using XMP module, but override handling of attributes and tags
|
||||
$$dirInfo{XMPParseOpts} = {
|
||||
AttrProc => \&HandleCOSAttrs,
|
||||
FoundProc => \&FoundCOS,
|
||||
};
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::CaptureOne::Main');
|
||||
my $success = $et->ProcessDirectory($dirInfo, $tagTablePtr);
|
||||
delete $$dirInfo{XMLParseArgs};
|
||||
return $success;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from a CaptureOne EIP file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1
|
||||
# Notes: Upon entry to this routine, the file type has already been verified
|
||||
# and the dirInfo hash contains a ZIP element unique to this process proc:
|
||||
# ZIP - reference to Archive::Zip object for this file
|
||||
sub ProcessEIP($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $zip = $$dirInfo{ZIP};
|
||||
my ($file, $buff, $status, $member, %parseFile);
|
||||
|
||||
$et->SetFileType('EIP');
|
||||
|
||||
# must catch all Archive::Zip warnings
|
||||
local $SIG{'__WARN__'} = \&Image::ExifTool::ZIP::WarnProc;
|
||||
# find all manifest files
|
||||
my @members = $zip->membersMatching('^manifest\d*.xml$');
|
||||
# and choose the one with the highest version number (any better ideas?)
|
||||
while (@members) {
|
||||
my $m = shift @members;
|
||||
my $f = $m->fileName();
|
||||
next if $file and $file gt $f;
|
||||
$member = $m;
|
||||
$file = $f;
|
||||
}
|
||||
# get file names from our chosen manifest file
|
||||
if ($member) {
|
||||
($buff, $status) = $zip->contents($member);
|
||||
if (not $status) {
|
||||
my $foundImage;
|
||||
while ($buff =~ m{<(RawPath|SettingsPath)>(.*?)</\1>}sg) {
|
||||
$file = $2;
|
||||
next unless $file =~ /\.(cos|iiq|jpe?g|tiff?)$/i;
|
||||
$parseFile{$file} = 1; # set flag to parse this file
|
||||
$foundImage = 1 unless $file =~ /\.cos$/i;
|
||||
}
|
||||
# ignore manifest unless it contained a valid image
|
||||
undef %parseFile unless $foundImage;
|
||||
}
|
||||
}
|
||||
# extract meta information from embedded files
|
||||
my $docNum = 0;
|
||||
@members = $zip->members(); # get all members
|
||||
foreach $member (@members) {
|
||||
# get filename of this ZIP member
|
||||
$file = $member->fileName();
|
||||
next unless defined $file;
|
||||
$et->VPrint(0, "File: $file\n");
|
||||
# set the document number and extract ZIP tags
|
||||
$$et{DOC_NUM} = ++$docNum;
|
||||
Image::ExifTool::ZIP::HandleMember($et, $member);
|
||||
if (%parseFile) {
|
||||
next unless $parseFile{$file};
|
||||
} else {
|
||||
# reading the manifest didn't work, so look for image files in the
|
||||
# root directory and .cos files in the CaptureOne directory
|
||||
next unless $file =~ m{^([^/]+\.(iiq|jpe?g|tiff?)|CaptureOne/.*\.cos)$}i;
|
||||
}
|
||||
# extract the contents of the file
|
||||
# Note: this could use a LOT of memory here for RAW images...
|
||||
($buff, $status) = $zip->contents($member);
|
||||
$status and $et->Warn("Error extracting $file"), next;
|
||||
if ($file =~ /\.cos$/i) {
|
||||
# process Capture One Settings files
|
||||
my %dirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirLen => length $buff,
|
||||
DataLen => length $buff,
|
||||
);
|
||||
ProcessCOS($et, \%dirInfo);
|
||||
} else {
|
||||
# set HtmlDump error if necessary because it doesn't work with embedded files
|
||||
if ($$et{HTML_DUMP}) {
|
||||
$$et{HTML_DUMP}{Error} = "Sorry, can't dump images embedded in ZIP files";
|
||||
}
|
||||
# process IIQ, JPEG and TIFF images
|
||||
$et->ExtractInfo(\$buff, { ReEntry => 1 });
|
||||
}
|
||||
undef $buff; # (free memory now)
|
||||
}
|
||||
delete $$et{DOC_NUM};
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::CaptureOne - Read Capture One EIP and COS files
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to extract meta
|
||||
information from Capture One EIP (Enhanced Image Package) and COS (Capture
|
||||
One Settings) files.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
The EIP format is a ZIP file containing an image (IIQ or TIFF) and some
|
||||
settings files (COS).
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/ZIP Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
2060
.resources/nix/bin/lib/Image/ExifTool/Casio.pm
Normal file
2060
.resources/nix/bin/lib/Image/ExifTool/Casio.pm
Normal file
File diff suppressed because it is too large
Load diff
434
.resources/nix/bin/lib/Image/ExifTool/Charset.pm
Normal file
434
.resources/nix/bin/lib/Image/ExifTool/Charset.pm
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Charset.pm
|
||||
#
|
||||
# Description: ExifTool character encoding routines
|
||||
#
|
||||
# Revisions: 2009/08/28 - P. Harvey created
|
||||
# 2010/01/20 - P. Harvey complete re-write
|
||||
# 2010/07/16 - P. Harvey added UTF-16 support
|
||||
#
|
||||
# Notes: Charset lookups are generated using my convertCharset script
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::Charset;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION %csType);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.11';
|
||||
|
||||
my %charsetTable; # character set tables we've loaded
|
||||
|
||||
# lookup for converting Unicode to 1-byte character sets
|
||||
my %unicode2byte = (
|
||||
Latin => { # pre-load Latin (cp1252) for speed
|
||||
0x20ac => 0x80, 0x0160 => 0x8a, 0x2013 => 0x96,
|
||||
0x201a => 0x82, 0x2039 => 0x8b, 0x2014 => 0x97,
|
||||
0x0192 => 0x83, 0x0152 => 0x8c, 0x02dc => 0x98,
|
||||
0x201e => 0x84, 0x017d => 0x8e, 0x2122 => 0x99,
|
||||
0x2026 => 0x85, 0x2018 => 0x91, 0x0161 => 0x9a,
|
||||
0x2020 => 0x86, 0x2019 => 0x92, 0x203a => 0x9b,
|
||||
0x2021 => 0x87, 0x201c => 0x93, 0x0153 => 0x9c,
|
||||
0x02c6 => 0x88, 0x201d => 0x94, 0x017e => 0x9e,
|
||||
0x2030 => 0x89, 0x2022 => 0x95, 0x0178 => 0x9f,
|
||||
},
|
||||
);
|
||||
|
||||
# bit flags for all supported character sets
|
||||
# (this number must be correct because it dictates the decoding algorithm!)
|
||||
# 0x001 = character set requires a translation module
|
||||
# 0x002 = inverse conversion not yet supported by Recompose()
|
||||
# 0x080 = some characters with codepoints in the range 0x00-0x7f are remapped
|
||||
# 0x100 = 1-byte fixed-width characters
|
||||
# 0x200 = 2-byte fixed-width characters
|
||||
# 0x400 = 4-byte fixed-width characters
|
||||
# 0x800 = 1- and 2-byte variable-width characters, or 1-byte
|
||||
# fixed-width characters that map into multiple codepoints
|
||||
# Note: In its public interface, ExifTool can currently only support type 0x101
|
||||
# and lower character sets because strings are only converted if they
|
||||
# contain characters above 0x7f and there is no provision for specifying
|
||||
# the byte order for input/output values
|
||||
%csType = (
|
||||
UTF8 => 0x100,
|
||||
ASCII => 0x100, # (treated like UTF8)
|
||||
Arabic => 0x101,
|
||||
Baltic => 0x101,
|
||||
Cyrillic => 0x101,
|
||||
Greek => 0x101,
|
||||
Hebrew => 0x101,
|
||||
Latin => 0x101,
|
||||
Latin2 => 0x101,
|
||||
DOSLatinUS => 0x101,
|
||||
DOSLatin1 => 0x101,
|
||||
DOSCyrillic => 0x101,
|
||||
MacCroatian => 0x101,
|
||||
MacCyrillic => 0x101,
|
||||
MacGreek => 0x101,
|
||||
MacIceland => 0x101,
|
||||
MacLatin2 => 0x101,
|
||||
MacRoman => 0x101,
|
||||
MacRomanian => 0x101,
|
||||
MacTurkish => 0x101,
|
||||
Thai => 0x101,
|
||||
Turkish => 0x101,
|
||||
Vietnam => 0x101,
|
||||
MacArabic => 0x103, # (directional characters not supported)
|
||||
PDFDoc => 0x181,
|
||||
Unicode => 0x200, # (UCS2)
|
||||
UCS2 => 0x200,
|
||||
UTF16 => 0x200,
|
||||
Symbol => 0x201,
|
||||
JIS => 0x201,
|
||||
UCS4 => 0x400,
|
||||
MacChineseCN => 0x803,
|
||||
MacChineseTW => 0x803,
|
||||
MacHebrew => 0x803, # (directional characters not supported)
|
||||
MacKorean => 0x803,
|
||||
MacRSymbol => 0x803,
|
||||
MacThai => 0x803,
|
||||
MacJapanese => 0x883,
|
||||
ShiftJIS => 0x883,
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Load character set module
|
||||
# Inputs: 0) Module name
|
||||
# Returns: Reference to lookup hash, or undef on error
|
||||
sub LoadCharset($)
|
||||
{
|
||||
my $charset = shift;
|
||||
my $conv = $charsetTable{$charset};
|
||||
unless ($conv) {
|
||||
# load translation module
|
||||
my $module = "Image::ExifTool::Charset::$charset";
|
||||
no strict 'refs';
|
||||
if (%$module or eval "require $module") {
|
||||
$conv = $charsetTable{$charset} = \%$module;
|
||||
}
|
||||
}
|
||||
return $conv;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Does an array contain valid UTF-16 characters?
|
||||
# Inputs: 0) array reference to list of UCS-2 values
|
||||
# Returns: 0=invalid UTF-16, 1=valid UTF-16 with no surrogates, 2=valid UTF-16 with surrogates
|
||||
sub IsUTF16($)
|
||||
{
|
||||
local $_;
|
||||
my $uni = shift;
|
||||
my $surrogate;
|
||||
foreach (@$uni) {
|
||||
my $hiBits = ($_ & 0xfc00);
|
||||
if ($hiBits == 0xfc00) {
|
||||
# check for invalid values in UTF-16
|
||||
return 0 if $_ == 0xffff or $_ == 0xfffe or ($_ >= 0xfdd0 and $_ <= 0xfdef);
|
||||
} elsif ($surrogate) {
|
||||
return 0 if $hiBits != 0xdc00;
|
||||
$surrogate = 0;
|
||||
} else {
|
||||
return 0 if $hiBits == 0xdc00;
|
||||
$surrogate = 1 if $hiBits == 0xd800;
|
||||
}
|
||||
}
|
||||
return 1 if not defined $surrogate;
|
||||
return 2 unless $surrogate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Decompose string with specified encoding into an array of integer code points
|
||||
# Inputs: 0) ExifTool object ref (or undef), 1) string, 2) character set name,
|
||||
# 3) optional byte order ('II','MM','Unknown' or undef to use ExifTool ordering)
|
||||
# Returns: Reference to array of Unicode values
|
||||
# Notes: Accepts any type of character set
|
||||
# - byte order only used for fixed-width 2-byte and 4-byte character sets
|
||||
# - byte order mark observed and then removed with UCS2 and UCS4
|
||||
# - no warnings are issued if ExifTool object is not provided
|
||||
# - sets ExifTool WrongByteOrder flag if byte order is Unknown and current order is wrong
|
||||
sub Decompose($$$;$)
|
||||
{
|
||||
local $_;
|
||||
my ($et, $val, $charset) = @_; # ($byteOrder assigned later if required)
|
||||
my $type = $csType{$charset};
|
||||
my (@uni, $conv);
|
||||
|
||||
if ($type & 0x001) {
|
||||
$conv = LoadCharset($charset);
|
||||
unless ($conv) {
|
||||
# (shouldn't happen)
|
||||
$et->Warn("Invalid character set $charset") if $et;
|
||||
return \@uni; # error!
|
||||
}
|
||||
} elsif ($type == 0x100) {
|
||||
# convert ASCII and UTF8 (treat ASCII as UTF8)
|
||||
if ($] < 5.006001) {
|
||||
# do it ourself
|
||||
@uni = Image::ExifTool::UnpackUTF8($val);
|
||||
} else {
|
||||
# handle warnings from malformed UTF-8
|
||||
undef $Image::ExifTool::evalWarning;
|
||||
local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
|
||||
# (somehow the meaning of "U0" was reversed in Perl 5.10.0!)
|
||||
@uni = unpack($] < 5.010000 ? 'U0U*' : 'C0U*', $val);
|
||||
# issue warning if we had errors
|
||||
if ($Image::ExifTool::evalWarning and $et and not $$et{WarnBadUTF8}) {
|
||||
$et->Warn('Malformed UTF-8 character(s)');
|
||||
$$et{WarnBadUTF8} = 1;
|
||||
}
|
||||
}
|
||||
return \@uni; # all done!
|
||||
}
|
||||
if ($type & 0x100) { # 1-byte fixed-width characters
|
||||
@uni = unpack('C*', $val);
|
||||
foreach (@uni) {
|
||||
$_ = $$conv{$_} if defined $$conv{$_};
|
||||
}
|
||||
} elsif ($type & 0x600) { # 2-byte or 4-byte fixed-width characters
|
||||
my $unknown;
|
||||
my $byteOrder = $_[3];
|
||||
if (not $byteOrder) {
|
||||
$byteOrder = GetByteOrder();
|
||||
} elsif ($byteOrder eq 'Unknown') {
|
||||
$byteOrder = GetByteOrder();
|
||||
$unknown = 1;
|
||||
}
|
||||
my $fmt = $byteOrder eq 'MM' ? 'n*' : 'v*';
|
||||
if ($type & 0x400) { # 4-byte
|
||||
$fmt = uc $fmt; # unpack as 'N*' or 'V*'
|
||||
# honour BOM if it exists
|
||||
$val =~ s/^(\0\0\xfe\xff|\xff\xfe\0\0)// and $fmt = $1 eq "\0\0\xfe\xff" ? 'N*' : 'V*';
|
||||
undef $unknown; # (byte order logic applies to 2-byte only)
|
||||
} elsif ($val =~ s/^(\xfe\xff|\xff\xfe)//) {
|
||||
$fmt = $1 eq "\xfe\xff" ? 'n*' : 'v*';
|
||||
undef $unknown;
|
||||
}
|
||||
# convert from UCS2 or UCS4
|
||||
@uni = unpack($fmt, $val);
|
||||
|
||||
if (not $conv) {
|
||||
# no translation necessary
|
||||
if ($unknown) {
|
||||
# check the byte order
|
||||
my (%bh, %bl);
|
||||
my ($zh, $zl) = (0, 0);
|
||||
foreach (@uni) {
|
||||
$bh{$_ >> 8} = 1;
|
||||
$bl{$_ & 0xff} = 1;
|
||||
++$zh unless $_ & 0xff00;
|
||||
++$zl unless $_ & 0x00ff;
|
||||
}
|
||||
# count the number of unique values in the hi and lo bytes
|
||||
my ($bh, $bl) = (scalar(keys %bh), scalar(keys %bl));
|
||||
# the byte with the greater number of unique values should be
|
||||
# the low-order byte, otherwise the byte which is zero more
|
||||
# often is likely the high-order byte
|
||||
if ($bh > $bl or ($bh == $bl and $zl > $zh)) {
|
||||
# we guessed wrong, so decode using the other byte order
|
||||
$fmt =~ tr/nvNV/vnVN/;
|
||||
@uni = unpack($fmt, $val);
|
||||
$$et{WrongByteOrder} = 1;
|
||||
}
|
||||
}
|
||||
# handle surrogate pairs of UTF-16
|
||||
if ($charset eq 'UTF16') {
|
||||
my $i;
|
||||
for ($i=0; $i<$#uni; ++$i) {
|
||||
next unless ($uni[$i] & 0xfc00) == 0xd800 and
|
||||
($uni[$i+1] & 0xfc00) == 0xdc00;
|
||||
my $cp = 0x10000 + (($uni[$i] & 0x3ff) << 10) + ($uni[$i+1] & 0x3ff);
|
||||
splice(@uni, $i, 2, $cp);
|
||||
}
|
||||
}
|
||||
} elsif ($unknown) {
|
||||
# count encoding errors as we do the translation
|
||||
my $e1 = 0;
|
||||
foreach (@uni) {
|
||||
defined $$conv{$_} and $_ = $$conv{$_}, next;
|
||||
++$e1;
|
||||
}
|
||||
# try the other byte order if we had any errors
|
||||
if ($e1) {
|
||||
$fmt = $byteOrder eq 'MM' ? 'v*' : 'n*'; #(reversed)
|
||||
my @try = unpack($fmt, $val);
|
||||
my $e2 = 0;
|
||||
foreach (@try) {
|
||||
defined $$conv{$_} and $_ = $$conv{$_}, next;
|
||||
++$e2;
|
||||
}
|
||||
# use this byte order if there are fewer errors
|
||||
if ($e2 < $e1) {
|
||||
$$et{WrongByteOrder} = 1;
|
||||
return \@try;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# translate any characters found in the lookup
|
||||
foreach (@uni) {
|
||||
$_ = $$conv{$_} if defined $$conv{$_};
|
||||
}
|
||||
}
|
||||
} else { # variable-width characters
|
||||
# unpack into bytes
|
||||
my @bytes = unpack('C*', $val);
|
||||
while (@bytes) {
|
||||
my $ch = shift @bytes;
|
||||
my $cv = $$conv{$ch};
|
||||
# pass straight through if no translation
|
||||
$cv or push(@uni, $ch), next;
|
||||
# byte translates into single Unicode character
|
||||
ref $cv or push(@uni, $cv), next;
|
||||
# byte maps into multiple Unicode characters
|
||||
ref $cv eq 'ARRAY' and push(@uni, @$cv), next;
|
||||
# handle 2-byte character codes
|
||||
$ch = shift @bytes;
|
||||
if (defined $ch) {
|
||||
if ($$cv{$ch}) {
|
||||
$cv = $$cv{$ch};
|
||||
ref $cv or push(@uni, $cv), next;
|
||||
push @uni, @$cv; # multiple Unicode characters
|
||||
} else {
|
||||
push @uni, ord('?'); # encoding error
|
||||
unshift @bytes, $ch;
|
||||
}
|
||||
} else {
|
||||
push @uni, ord('?'); # encoding error
|
||||
}
|
||||
}
|
||||
}
|
||||
return \@uni;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Convert array of code point integers into a string with specified encoding
|
||||
# Inputs: 0) ExifTool ref (or undef), 1) unicode character array ref,
|
||||
# 2) character set (note: not all types are supported)
|
||||
# 3) byte order ('MM' or 'II', multi-byte sets only, defaults to current byte order)
|
||||
# Returns: converted string (truncated at null character if it exists), empty on error
|
||||
# Notes: converts elements of input character array to new code points
|
||||
# - ExifTool ref may be undef provided $charset is defined
|
||||
sub Recompose($$;$$)
|
||||
{
|
||||
local $_;
|
||||
my ($et, $uni, $charset) = @_; # ($byteOrder assigned later if required)
|
||||
my ($outVal, $conv, $inv);
|
||||
$charset or $charset = $$et{OPTIONS}{Charset};
|
||||
my $csType = $csType{$charset};
|
||||
if ($csType == 0x100) { # UTF8 (also treat ASCII as UTF8)
|
||||
if ($] >= 5.006001) {
|
||||
# let Perl do it
|
||||
$outVal = pack('C0U*', @$uni);
|
||||
} else {
|
||||
# do it ourself
|
||||
$outVal = Image::ExifTool::PackUTF8(@$uni);
|
||||
}
|
||||
$outVal =~ s/\0.*//s; # truncate at null terminator
|
||||
return $outVal;
|
||||
}
|
||||
# get references to forward and inverse lookup tables
|
||||
if ($csType & 0x801) {
|
||||
$conv = LoadCharset($charset);
|
||||
unless ($conv) {
|
||||
$et->Warn("Missing charset $charset") if $et;
|
||||
return '';
|
||||
}
|
||||
$inv = $unicode2byte{$charset};
|
||||
# generate inverse lookup if necessary
|
||||
unless ($inv) {
|
||||
if (not $csType or $csType & 0x802) {
|
||||
$et->Warn("Invalid destination charset $charset") if $et;
|
||||
return '';
|
||||
}
|
||||
# prepare table to convert from Unicode to 1-byte characters
|
||||
my ($char, %inv);
|
||||
foreach $char (keys %$conv) {
|
||||
$inv{$$conv{$char}} = $char;
|
||||
}
|
||||
$inv = $unicode2byte{$charset} = \%inv;
|
||||
}
|
||||
}
|
||||
if ($csType & 0x100) { # 1-byte fixed-width
|
||||
# convert to specified character set
|
||||
foreach (@$uni) {
|
||||
next if $_ < 0x80;
|
||||
$$inv{$_} and $_ = $$inv{$_}, next;
|
||||
# our tables omit 1-byte characters with the same values as Unicode,
|
||||
# so pass them straight through after making sure there isn't a
|
||||
# different character with this byte value
|
||||
next if $_ < 0x100 and not $$conv{$_};
|
||||
$_ = ord('?'); # set invalid characters to '?'
|
||||
if ($et and not $$et{EncodingError}) {
|
||||
$et->Warn("Some character(s) could not be encoded in $charset");
|
||||
$$et{EncodingError} = 1;
|
||||
}
|
||||
}
|
||||
# repack as an 8-bit string and truncate at null
|
||||
$outVal = pack('C*', @$uni);
|
||||
$outVal =~ s/\0.*//s;
|
||||
} else { # 2-byte and 4-byte fixed-width
|
||||
# convert if required
|
||||
if ($inv) {
|
||||
$$inv{$_} and $_ = $$inv{$_} foreach @$uni;
|
||||
}
|
||||
# generate surrogate pairs of UTF-16
|
||||
if ($charset eq 'UTF16') {
|
||||
my $i;
|
||||
for ($i=0; $i<@$uni; ++$i) {
|
||||
next unless $$uni[$i] >= 0x10000 and $$uni[$i] < 0x10ffff;
|
||||
my $t = $$uni[$i] - 0x10000;
|
||||
my $w1 = 0xd800 + (($t >> 10) & 0x3ff);
|
||||
my $w2 = 0xdc00 + ($t & 0x3ff);
|
||||
splice(@$uni, $i, 1, $w1, $w2);
|
||||
++$i; # skip surrogate pair
|
||||
}
|
||||
}
|
||||
# pack as 2- or 4-byte integer in specified byte order
|
||||
my $byteOrder = $_[3] || GetByteOrder();
|
||||
my $fmt = $byteOrder eq 'MM' ? 'n*' : 'v*';
|
||||
$fmt = uc($fmt) if $csType & 0x400;
|
||||
$outVal = pack($fmt, @$uni);
|
||||
}
|
||||
return $outVal;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::Charset - ExifTool character encoding routines
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is required by Image::ExifTool.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains routines used by ExifTool to translate special
|
||||
character sets. Currently, the following character sets are supported:
|
||||
|
||||
UTF8, UTF16, UCS2, UCS4, Arabic, Baltic, Cyrillic, Greek, Hebrew, JIS,
|
||||
Latin, Latin2, DOSLatinUS, DOSLatin1, DOSCyrillic, MacArabic,
|
||||
MacChineseCN, MacChineseTW, MacCroatian, MacCyrillic, MacGreek, MacHebrew,
|
||||
MacIceland, MacJapanese, MacKorean, MacLatin2, MacRSymbol, MacRoman,
|
||||
MacRomanian, MacThai, MacTurkish, PDFDoc, RSymbol, ShiftJIS, Symbol, Thai,
|
||||
Turkish, Vietnam
|
||||
|
||||
However, only some of these character sets are available to the user via
|
||||
ExifTool options -- the multi-byte character sets are used only internally
|
||||
when decoding certain types of information.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
39
.resources/nix/bin/lib/Image/ExifTool/Charset/Arabic.pm
Normal file
39
.resources/nix/bin/lib/Image/ExifTool/Charset/Arabic.pm
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Arabic.pm
|
||||
#
|
||||
# Description: cp1256 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1256.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Arabic = (
|
||||
0x80 => 0x20ac, 0x81 => 0x067e, 0x82 => 0x201a, 0x83 => 0x0192,
|
||||
0x84 => 0x201e, 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021,
|
||||
0x88 => 0x02c6, 0x89 => 0x2030, 0x8a => 0x0679, 0x8b => 0x2039,
|
||||
0x8c => 0x0152, 0x8d => 0x0686, 0x8e => 0x0698, 0x8f => 0x0688,
|
||||
0x90 => 0x06af, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c,
|
||||
0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
||||
0x98 => 0x06a9, 0x99 => 0x2122, 0x9a => 0x0691, 0x9b => 0x203a,
|
||||
0x9c => 0x0153, 0x9d => 0x200c, 0x9e => 0x200d, 0x9f => 0x06ba,
|
||||
0xa1 => 0x060c, 0xaa => 0x06be, 0xba => 0x061b, 0xbf => 0x061f,
|
||||
0xc0 => 0x06c1, 0xc1 => 0x0621, 0xc2 => 0x0622, 0xc3 => 0x0623,
|
||||
0xc4 => 0x0624, 0xc5 => 0x0625, 0xc6 => 0x0626, 0xc7 => 0x0627,
|
||||
0xc8 => 0x0628, 0xc9 => 0x0629, 0xca => 0x062a, 0xcb => 0x062b,
|
||||
0xcc => 0x062c, 0xcd => 0x062d, 0xce => 0x062e, 0xcf => 0x062f,
|
||||
0xd0 => 0x0630, 0xd1 => 0x0631, 0xd2 => 0x0632, 0xd3 => 0x0633,
|
||||
0xd4 => 0x0634, 0xd5 => 0x0635, 0xd6 => 0x0636, 0xd8 => 0x0637,
|
||||
0xd9 => 0x0638, 0xda => 0x0639, 0xdb => 0x063a, 0xdc => 0x0640,
|
||||
0xdd => 0x0641, 0xde => 0x0642, 0xdf => 0x0643, 0xe1 => 0x0644,
|
||||
0xe3 => 0x0645, 0xe4 => 0x0646, 0xe5 => 0x0647, 0xe6 => 0x0648,
|
||||
0xec => 0x0649, 0xed => 0x064a, 0xf0 => 0x064b, 0xf1 => 0x064c,
|
||||
0xf2 => 0x064d, 0xf3 => 0x064e, 0xf5 => 0x064f, 0xf6 => 0x0650,
|
||||
0xf8 => 0x0651, 0xfa => 0x0652, 0xfd => 0x200e, 0xfe => 0x200f,
|
||||
0xff => 0x06d2,
|
||||
);
|
||||
|
||||
1; # end
|
||||
35
.resources/nix/bin/lib/Image/ExifTool/Charset/Baltic.pm
Normal file
35
.resources/nix/bin/lib/Image/ExifTool/Charset/Baltic.pm
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Baltic.pm
|
||||
#
|
||||
# Description: cp1257 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1257.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Baltic = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x84 => 0x201e, 0x85 => 0x2026,
|
||||
0x86 => 0x2020, 0x87 => 0x2021, 0x89 => 0x2030, 0x8b => 0x2039, 0x8d => 0xa8,
|
||||
0x8e => 0x02c7, 0x8f => 0xb8, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c,
|
||||
0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
||||
0x99 => 0x2122, 0x9b => 0x203a, 0x9d => 0xaf, 0x9e => 0x02db, 0xa8 => 0xd8,
|
||||
0xaa => 0x0156, 0xaf => 0xc6, 0xb8 => 0xf8, 0xba => 0x0157, 0xbf => 0xe6,
|
||||
0xc0 => 0x0104, 0xc1 => 0x012e, 0xc2 => 0x0100, 0xc3 => 0x0106,
|
||||
0xc6 => 0x0118, 0xc7 => 0x0112, 0xc8 => 0x010c, 0xca => 0x0179,
|
||||
0xcb => 0x0116, 0xcc => 0x0122, 0xcd => 0x0136, 0xce => 0x012a,
|
||||
0xcf => 0x013b, 0xd0 => 0x0160, 0xd1 => 0x0143, 0xd2 => 0x0145,
|
||||
0xd4 => 0x014c, 0xd8 => 0x0172, 0xd9 => 0x0141, 0xda => 0x015a,
|
||||
0xdb => 0x016a, 0xdd => 0x017b, 0xde => 0x017d, 0xe0 => 0x0105,
|
||||
0xe1 => 0x012f, 0xe2 => 0x0101, 0xe3 => 0x0107, 0xe6 => 0x0119,
|
||||
0xe7 => 0x0113, 0xe8 => 0x010d, 0xea => 0x017a, 0xeb => 0x0117,
|
||||
0xec => 0x0123, 0xed => 0x0137, 0xee => 0x012b, 0xef => 0x013c,
|
||||
0xf0 => 0x0161, 0xf1 => 0x0144, 0xf2 => 0x0146, 0xf4 => 0x014d,
|
||||
0xf8 => 0x0173, 0xf9 => 0x0142, 0xfa => 0x015b, 0xfb => 0x016b,
|
||||
0xfd => 0x017c, 0xfe => 0x017e, 0xff => 0x02d9,
|
||||
);
|
||||
|
||||
1; # end
|
||||
45
.resources/nix/bin/lib/Image/ExifTool/Charset/Cyrillic.pm
Normal file
45
.resources/nix/bin/lib/Image/ExifTool/Charset/Cyrillic.pm
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Cyrillic.pm
|
||||
#
|
||||
# Description: cp1251 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Cyrillic = (
|
||||
0x80 => 0x0402, 0x81 => 0x0403, 0x82 => 0x201a, 0x83 => 0x0453,
|
||||
0x84 => 0x201e, 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021,
|
||||
0x88 => 0x20ac, 0x89 => 0x2030, 0x8a => 0x0409, 0x8b => 0x2039,
|
||||
0x8c => 0x040a, 0x8d => 0x040c, 0x8e => 0x040b, 0x8f => 0x040f,
|
||||
0x90 => 0x0452, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c,
|
||||
0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
||||
0x99 => 0x2122, 0x9a => 0x0459, 0x9b => 0x203a, 0x9c => 0x045a,
|
||||
0x9d => 0x045c, 0x9e => 0x045b, 0x9f => 0x045f, 0xa1 => 0x040e,
|
||||
0xa2 => 0x045e, 0xa3 => 0x0408, 0xa5 => 0x0490, 0xa8 => 0x0401,
|
||||
0xaa => 0x0404, 0xaf => 0x0407, 0xb2 => 0x0406, 0xb3 => 0x0456,
|
||||
0xb4 => 0x0491, 0xb8 => 0x0451, 0xb9 => 0x2116, 0xba => 0x0454,
|
||||
0xbc => 0x0458, 0xbd => 0x0405, 0xbe => 0x0455, 0xbf => 0x0457,
|
||||
0xc0 => 0x0410, 0xc1 => 0x0411, 0xc2 => 0x0412, 0xc3 => 0x0413,
|
||||
0xc4 => 0x0414, 0xc5 => 0x0415, 0xc6 => 0x0416, 0xc7 => 0x0417,
|
||||
0xc8 => 0x0418, 0xc9 => 0x0419, 0xca => 0x041a, 0xcb => 0x041b,
|
||||
0xcc => 0x041c, 0xcd => 0x041d, 0xce => 0x041e, 0xcf => 0x041f,
|
||||
0xd0 => 0x0420, 0xd1 => 0x0421, 0xd2 => 0x0422, 0xd3 => 0x0423,
|
||||
0xd4 => 0x0424, 0xd5 => 0x0425, 0xd6 => 0x0426, 0xd7 => 0x0427,
|
||||
0xd8 => 0x0428, 0xd9 => 0x0429, 0xda => 0x042a, 0xdb => 0x042b,
|
||||
0xdc => 0x042c, 0xdd => 0x042d, 0xde => 0x042e, 0xdf => 0x042f,
|
||||
0xe0 => 0x0430, 0xe1 => 0x0431, 0xe2 => 0x0432, 0xe3 => 0x0433,
|
||||
0xe4 => 0x0434, 0xe5 => 0x0435, 0xe6 => 0x0436, 0xe7 => 0x0437,
|
||||
0xe8 => 0x0438, 0xe9 => 0x0439, 0xea => 0x043a, 0xeb => 0x043b,
|
||||
0xec => 0x043c, 0xed => 0x043d, 0xee => 0x043e, 0xef => 0x043f,
|
||||
0xf0 => 0x0440, 0xf1 => 0x0441, 0xf2 => 0x0442, 0xf3 => 0x0443,
|
||||
0xf4 => 0x0444, 0xf5 => 0x0445, 0xf6 => 0x0446, 0xf7 => 0x0447,
|
||||
0xf8 => 0x0448, 0xf9 => 0x0449, 0xfa => 0x044a, 0xfb => 0x044b,
|
||||
0xfc => 0x044c, 0xfd => 0x044d, 0xfe => 0x044e, 0xff => 0x044f,
|
||||
);
|
||||
|
||||
1; # end
|
||||
49
.resources/nix/bin/lib/Image/ExifTool/Charset/DOSCyrillic.pm
Normal file
49
.resources/nix/bin/lib/Image/ExifTool/Charset/DOSCyrillic.pm
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DOSCyrillic.pm
|
||||
#
|
||||
# Description: cp866 to Unicode
|
||||
#
|
||||
# Revisions: 2020/03/23 - P. Harvey created
|
||||
#
|
||||
# References: 1) https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP866.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::DOSCyrillic = (
|
||||
0x80 => 0x0410, 0x81 => 0x0411, 0x82 => 0x0412, 0x83 => 0x0413,
|
||||
0x84 => 0x0414, 0x85 => 0x0415, 0x86 => 0x0416, 0x87 => 0x0417,
|
||||
0x88 => 0x0418, 0x89 => 0x0419, 0x8a => 0x041a, 0x8b => 0x041b,
|
||||
0x8c => 0x041c, 0x8d => 0x041d, 0x8e => 0x041e, 0x8f => 0x041f,
|
||||
0x90 => 0x0420, 0x91 => 0x0421, 0x92 => 0x0422, 0x93 => 0x0423,
|
||||
0x94 => 0x0424, 0x95 => 0x0425, 0x96 => 0x0426, 0x97 => 0x0427,
|
||||
0x98 => 0x0428, 0x99 => 0x0429, 0x9a => 0x042a, 0x9b => 0x042b,
|
||||
0x9c => 0x042c, 0x9d => 0x042d, 0x9e => 0x042e, 0x9f => 0x042f,
|
||||
0xa0 => 0x0430, 0xa1 => 0x0431, 0xa2 => 0x0432, 0xa3 => 0x0433,
|
||||
0xa4 => 0x0434, 0xa5 => 0x0435, 0xa6 => 0x0436, 0xa7 => 0x0437,
|
||||
0xa8 => 0x0438, 0xa9 => 0x0439, 0xaa => 0x043a, 0xab => 0x043b,
|
||||
0xac => 0x043c, 0xad => 0x043d, 0xae => 0x043e, 0xaf => 0x043f,
|
||||
0xb0 => 0x2591, 0xb1 => 0x2592, 0xb2 => 0x2593, 0xb3 => 0x2502,
|
||||
0xb4 => 0x2524, 0xb5 => 0x2561, 0xb6 => 0x2562, 0xb7 => 0x2556,
|
||||
0xb8 => 0x2555, 0xb9 => 0x2563, 0xba => 0x2551, 0xbb => 0x2557,
|
||||
0xbc => 0x255d, 0xbd => 0x255c, 0xbe => 0x255b, 0xbf => 0x2510,
|
||||
0xc0 => 0x2514, 0xc1 => 0x2534, 0xc2 => 0x252c, 0xc3 => 0x251c,
|
||||
0xc4 => 0x2500, 0xc5 => 0x253c, 0xc6 => 0x255e, 0xc7 => 0x255f,
|
||||
0xc8 => 0x255a, 0xc9 => 0x2554, 0xca => 0x2569, 0xcb => 0x2566,
|
||||
0xcc => 0x2560, 0xcd => 0x2550, 0xce => 0x256c, 0xcf => 0x2567,
|
||||
0xd0 => 0x2568, 0xd1 => 0x2564, 0xd2 => 0x2565, 0xd3 => 0x2559,
|
||||
0xd4 => 0x2558, 0xd5 => 0x2552, 0xd6 => 0x2553, 0xd7 => 0x256b,
|
||||
0xd8 => 0x256a, 0xd9 => 0x2518, 0xda => 0x250c, 0xdb => 0x2588,
|
||||
0xdc => 0x2584, 0xdd => 0x258c, 0xde => 0x2590, 0xdf => 0x2580,
|
||||
0xe0 => 0x0440, 0xe1 => 0x0441, 0xe2 => 0x0442, 0xe3 => 0x0443,
|
||||
0xe4 => 0x0444, 0xe5 => 0x0445, 0xe6 => 0x0446, 0xe7 => 0x0447,
|
||||
0xe8 => 0x0448, 0xe9 => 0x0449, 0xea => 0x044a, 0xeb => 0x044b,
|
||||
0xec => 0x044c, 0xed => 0x044d, 0xee => 0x044e, 0xef => 0x044f,
|
||||
0xf0 => 0x0401, 0xf1 => 0x0451, 0xf2 => 0x0404, 0xf3 => 0x0454,
|
||||
0xf4 => 0x0407, 0xf5 => 0x0457, 0xf6 => 0x040e, 0xf7 => 0x045e,
|
||||
0xf8 => 0x00b0, 0xf9 => 0x2219, 0xfa => 0x00b7, 0xfb => 0x221a,
|
||||
0xfc => 0x2116, 0xfd => 0x00a4, 0xfe => 0x25a0, 0xff => 0x00a0,
|
||||
);
|
||||
|
||||
1; # end
|
||||
49
.resources/nix/bin/lib/Image/ExifTool/Charset/DOSLatin1.pm
Normal file
49
.resources/nix/bin/lib/Image/ExifTool/Charset/DOSLatin1.pm
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DOSLatin1.pm
|
||||
#
|
||||
# Description: cp850 to Unicode
|
||||
#
|
||||
# Revisions: 2017/10/31- P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP850.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::DOSLatin1 = (
|
||||
0x80 => 0x00c7, 0x81 => 0x00fc, 0x82 => 0x00e9, 0x83 => 0x00e2,
|
||||
0x84 => 0x00e4, 0x85 => 0x00e0, 0x86 => 0x00e5, 0x87 => 0x00e7,
|
||||
0x88 => 0x00ea, 0x89 => 0x00eb, 0x8a => 0x00e8, 0x8b => 0x00ef,
|
||||
0x8c => 0x00ee, 0x8d => 0x00ec, 0x8e => 0x00c4, 0x8f => 0x00c5,
|
||||
0x90 => 0x00c9, 0x91 => 0x00e6, 0x92 => 0x00c6, 0x93 => 0x00f4,
|
||||
0x94 => 0x00f6, 0x95 => 0x00f2, 0x96 => 0x00fb, 0x97 => 0x00f9,
|
||||
0x98 => 0x00ff, 0x99 => 0x00d6, 0x9a => 0x00dc, 0x9b => 0x00f8,
|
||||
0x9c => 0x00a3, 0x9d => 0x00d8, 0x9e => 0x00d7, 0x9f => 0x0192,
|
||||
0xa0 => 0x00e1, 0xa1 => 0x00ed, 0xa2 => 0x00f3, 0xa3 => 0x00fa,
|
||||
0xa4 => 0x00f1, 0xa5 => 0x00d1, 0xa6 => 0x00aa, 0xa7 => 0x00ba,
|
||||
0xa8 => 0x00bf, 0xa9 => 0x00ae, 0xaa => 0x00ac, 0xab => 0x00bd,
|
||||
0xac => 0x00bc, 0xad => 0x00a1, 0xae => 0x00ab, 0xaf => 0x00bb,
|
||||
0xb0 => 0x2591, 0xb1 => 0x2592, 0xb2 => 0x2593, 0xb3 => 0x2502,
|
||||
0xb4 => 0x2524, 0xb5 => 0x00c1, 0xb6 => 0x00c2, 0xb7 => 0x00c0,
|
||||
0xb8 => 0x00a9, 0xb9 => 0x2563, 0xba => 0x2551, 0xbb => 0x2557,
|
||||
0xbc => 0x255d, 0xbd => 0x00a2, 0xbe => 0x00a5, 0xbf => 0x2510,
|
||||
0xc0 => 0x2514, 0xc1 => 0x2534, 0xc2 => 0x252c, 0xc3 => 0x251c,
|
||||
0xc4 => 0x2500, 0xc5 => 0x253c, 0xc6 => 0x00e3, 0xc7 => 0x00c3,
|
||||
0xc8 => 0x255a, 0xc9 => 0x2554, 0xca => 0x2569, 0xcb => 0x2566,
|
||||
0xcc => 0x2560, 0xcd => 0x2550, 0xce => 0x256c, 0xcf => 0x00a4,
|
||||
0xd0 => 0x00f0, 0xd1 => 0x00d0, 0xd2 => 0x00ca, 0xd3 => 0x00cb,
|
||||
0xd4 => 0x00c8, 0xd5 => 0x0131, 0xd6 => 0x00cd, 0xd7 => 0x00ce,
|
||||
0xd8 => 0x00cf, 0xd9 => 0x2518, 0xda => 0x250c, 0xdb => 0x2588,
|
||||
0xdc => 0x2584, 0xdd => 0x00a6, 0xde => 0x00cc, 0xdf => 0x2580,
|
||||
0xe0 => 0x00d3, 0xe1 => 0x00df, 0xe2 => 0x00d4, 0xe3 => 0x00d2,
|
||||
0xe4 => 0x00f5, 0xe5 => 0x00d5, 0xe6 => 0x00b5, 0xe7 => 0x00fe,
|
||||
0xe8 => 0x00de, 0xe9 => 0x00da, 0xea => 0x00db, 0xeb => 0x00d9,
|
||||
0xec => 0x00fd, 0xed => 0x00dd, 0xee => 0x00af, 0xef => 0x00b4,
|
||||
0xf0 => 0x00ad, 0xf1 => 0x00b1, 0xf2 => 0x2017, 0xf3 => 0x00be,
|
||||
0xf4 => 0x00b6, 0xf5 => 0x00a7, 0xf6 => 0x00f7, 0xf7 => 0x00b8,
|
||||
0xf8 => 0x00b0, 0xf9 => 0x00a8, 0xfa => 0x00b7, 0xfb => 0x00b9,
|
||||
0xfc => 0x00b3, 0xfd => 0x00b2, 0xfe => 0x25a0, 0xff => 0x00a0,
|
||||
);
|
||||
|
||||
1; # end
|
||||
49
.resources/nix/bin/lib/Image/ExifTool/Charset/DOSLatinUS.pm
Normal file
49
.resources/nix/bin/lib/Image/ExifTool/Charset/DOSLatinUS.pm
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DOSLatinUS.pm
|
||||
#
|
||||
# Description: cp437 to Unicode
|
||||
#
|
||||
# Revisions: 2017/10/31- P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::DOSLatinUS = (
|
||||
0x80 => 0x00c7, 0x81 => 0x00fc, 0x82 => 0x00e9, 0x83 => 0x00e2,
|
||||
0x84 => 0x00e4, 0x85 => 0x00e0, 0x86 => 0x00e5, 0x87 => 0x00e7,
|
||||
0x88 => 0x00ea, 0x89 => 0x00eb, 0x8a => 0x00e8, 0x8b => 0x00ef,
|
||||
0x8c => 0x00ee, 0x8d => 0x00ec, 0x8e => 0x00c4, 0x8f => 0x00c5,
|
||||
0x90 => 0x00c9, 0x91 => 0x00e6, 0x92 => 0x00c6, 0x93 => 0x00f4,
|
||||
0x94 => 0x00f6, 0x95 => 0x00f2, 0x96 => 0x00fb, 0x97 => 0x00f9,
|
||||
0x98 => 0x00ff, 0x99 => 0x00d6, 0x9a => 0x00dc, 0x9b => 0x00a2,
|
||||
0x9c => 0x00a3, 0x9d => 0x00a5, 0x9e => 0x20a7, 0x9f => 0x0192,
|
||||
0xa0 => 0x00e1, 0xa1 => 0x00ed, 0xa2 => 0x00f3, 0xa3 => 0x00fa,
|
||||
0xa4 => 0x00f1, 0xa5 => 0x00d1, 0xa6 => 0x00aa, 0xa7 => 0x00ba,
|
||||
0xa8 => 0x00bf, 0xa9 => 0x2310, 0xaa => 0x00ac, 0xab => 0x00bd,
|
||||
0xac => 0x00bc, 0xad => 0x00a1, 0xae => 0x00ab, 0xaf => 0x00bb,
|
||||
0xb0 => 0x2591, 0xb1 => 0x2592, 0xb2 => 0x2593, 0xb3 => 0x2502,
|
||||
0xb4 => 0x2524, 0xb5 => 0x2561, 0xb6 => 0x2562, 0xb7 => 0x2556,
|
||||
0xb8 => 0x2555, 0xb9 => 0x2563, 0xba => 0x2551, 0xbb => 0x2557,
|
||||
0xbc => 0x255d, 0xbd => 0x255c, 0xbe => 0x255b, 0xbf => 0x2510,
|
||||
0xc0 => 0x2514, 0xc1 => 0x2534, 0xc2 => 0x252c, 0xc3 => 0x251c,
|
||||
0xc4 => 0x2500, 0xc5 => 0x253c, 0xc6 => 0x255e, 0xc7 => 0x255f,
|
||||
0xc8 => 0x255a, 0xc9 => 0x2554, 0xca => 0x2569, 0xcb => 0x2566,
|
||||
0xcc => 0x2560, 0xcd => 0x2550, 0xce => 0x256c, 0xcf => 0x2567,
|
||||
0xd0 => 0x2568, 0xd1 => 0x2564, 0xd2 => 0x2565, 0xd3 => 0x2559,
|
||||
0xd4 => 0x2558, 0xd5 => 0x2552, 0xd6 => 0x2553, 0xd7 => 0x256b,
|
||||
0xd8 => 0x256a, 0xd9 => 0x2518, 0xda => 0x250c, 0xdb => 0x2588,
|
||||
0xdc => 0x2584, 0xdd => 0x258c, 0xde => 0x2590, 0xdf => 0x2580,
|
||||
0xe0 => 0x03b1, 0xe1 => 0x00df, 0xe2 => 0x0393, 0xe3 => 0x03c0,
|
||||
0xe4 => 0x03a3, 0xe5 => 0x03c3, 0xe6 => 0x00b5, 0xe7 => 0x03c4,
|
||||
0xe8 => 0x03a6, 0xe9 => 0x0398, 0xea => 0x03a9, 0xeb => 0x03b4,
|
||||
0xec => 0x221e, 0xed => 0x03c6, 0xee => 0x03b5, 0xef => 0x2229,
|
||||
0xf0 => 0x2261, 0xf1 => 0x00b1, 0xf2 => 0x2265, 0xf3 => 0x2264,
|
||||
0xf4 => 0x2320, 0xf5 => 0x2321, 0xf6 => 0x00f7, 0xf7 => 0x2248,
|
||||
0xf8 => 0x00b0, 0xf9 => 0x2219, 0xfa => 0x00b7, 0xfb => 0x221a,
|
||||
0xfc => 0x207f, 0xfd => 0x00b2, 0xfe => 0x25a0, 0xff => 0x00a0,
|
||||
);
|
||||
|
||||
1; # end
|
||||
40
.resources/nix/bin/lib/Image/ExifTool/Charset/Greek.pm
Normal file
40
.resources/nix/bin/lib/Image/ExifTool/Charset/Greek.pm
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Greek.pm
|
||||
#
|
||||
# Description: cp1253 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1253.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Greek = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e,
|
||||
0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x89 => 0x2030,
|
||||
0x8b => 0x2039, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c,
|
||||
0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
||||
0x99 => 0x2122, 0x9b => 0x203a, 0xa1 => 0x0385, 0xa2 => 0x0386,
|
||||
0xaf => 0x2015, 0xb4 => 0x0384, 0xb8 => 0x0388, 0xb9 => 0x0389,
|
||||
0xba => 0x038a, 0xbc => 0x038c, 0xbe => 0x038e, 0xbf => 0x038f,
|
||||
0xc0 => 0x0390, 0xc1 => 0x0391, 0xc2 => 0x0392, 0xc3 => 0x0393,
|
||||
0xc4 => 0x0394, 0xc5 => 0x0395, 0xc6 => 0x0396, 0xc7 => 0x0397,
|
||||
0xc8 => 0x0398, 0xc9 => 0x0399, 0xca => 0x039a, 0xcb => 0x039b,
|
||||
0xcc => 0x039c, 0xcd => 0x039d, 0xce => 0x039e, 0xcf => 0x039f,
|
||||
0xd0 => 0x03a0, 0xd1 => 0x03a1, 0xd3 => 0x03a3, 0xd4 => 0x03a4,
|
||||
0xd5 => 0x03a5, 0xd6 => 0x03a6, 0xd7 => 0x03a7, 0xd8 => 0x03a8,
|
||||
0xd9 => 0x03a9, 0xda => 0x03aa, 0xdb => 0x03ab, 0xdc => 0x03ac,
|
||||
0xdd => 0x03ad, 0xde => 0x03ae, 0xdf => 0x03af, 0xe0 => 0x03b0,
|
||||
0xe1 => 0x03b1, 0xe2 => 0x03b2, 0xe3 => 0x03b3, 0xe4 => 0x03b4,
|
||||
0xe5 => 0x03b5, 0xe6 => 0x03b6, 0xe7 => 0x03b7, 0xe8 => 0x03b8,
|
||||
0xe9 => 0x03b9, 0xea => 0x03ba, 0xeb => 0x03bb, 0xec => 0x03bc,
|
||||
0xed => 0x03bd, 0xee => 0x03be, 0xef => 0x03bf, 0xf0 => 0x03c0,
|
||||
0xf1 => 0x03c1, 0xf2 => 0x03c2, 0xf3 => 0x03c3, 0xf4 => 0x03c4,
|
||||
0xf5 => 0x03c5, 0xf6 => 0x03c6, 0xf7 => 0x03c7, 0xf8 => 0x03c8,
|
||||
0xf9 => 0x03c9, 0xfa => 0x03ca, 0xfb => 0x03cb, 0xfc => 0x03cc,
|
||||
0xfd => 0x03cd, 0xfe => 0x03ce,
|
||||
);
|
||||
|
||||
1; # end
|
||||
36
.resources/nix/bin/lib/Image/ExifTool/Charset/Hebrew.pm
Normal file
36
.resources/nix/bin/lib/Image/ExifTool/Charset/Hebrew.pm
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Hebrew.pm
|
||||
#
|
||||
# Description: cp1255 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1255.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Hebrew = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e,
|
||||
0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6,
|
||||
0x89 => 0x2030, 0x8b => 0x2039, 0x91 => 0x2018, 0x92 => 0x2019,
|
||||
0x93 => 0x201c, 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013,
|
||||
0x97 => 0x2014, 0x98 => 0x02dc, 0x99 => 0x2122, 0x9b => 0x203a,
|
||||
0xa4 => 0x20aa, 0xaa => 0xd7, 0xba => 0xf7, 0xc0 => 0x05b0, 0xc1 => 0x05b1,
|
||||
0xc2 => 0x05b2, 0xc3 => 0x05b3, 0xc4 => 0x05b4, 0xc5 => 0x05b5,
|
||||
0xc6 => 0x05b6, 0xc7 => 0x05b7, 0xc8 => 0x05b8, 0xc9 => 0x05b9,
|
||||
0xcb => 0x05bb, 0xcc => 0x05bc, 0xcd => 0x05bd, 0xce => 0x05be,
|
||||
0xcf => 0x05bf, 0xd0 => 0x05c0, 0xd1 => 0x05c1, 0xd2 => 0x05c2,
|
||||
0xd3 => 0x05c3, 0xd4 => 0x05f0, 0xd5 => 0x05f1, 0xd6 => 0x05f2,
|
||||
0xd7 => 0x05f3, 0xd8 => 0x05f4, 0xe0 => 0x05d0, 0xe1 => 0x05d1,
|
||||
0xe2 => 0x05d2, 0xe3 => 0x05d3, 0xe4 => 0x05d4, 0xe5 => 0x05d5,
|
||||
0xe6 => 0x05d6, 0xe7 => 0x05d7, 0xe8 => 0x05d8, 0xe9 => 0x05d9,
|
||||
0xea => 0x05da, 0xeb => 0x05db, 0xec => 0x05dc, 0xed => 0x05dd,
|
||||
0xee => 0x05de, 0xef => 0x05df, 0xf0 => 0x05e0, 0xf1 => 0x05e1,
|
||||
0xf2 => 0x05e2, 0xf3 => 0x05e3, 0xf4 => 0x05e4, 0xf5 => 0x05e5,
|
||||
0xf6 => 0x05e6, 0xf7 => 0x05e7, 0xf8 => 0x05e8, 0xf9 => 0x05e9,
|
||||
0xfa => 0x05ea, 0xfd => 0x200e, 0xfe => 0x200f,
|
||||
);
|
||||
|
||||
1; # end
|
||||
1735
.resources/nix/bin/lib/Image/ExifTool/Charset/JIS.pm
Normal file
1735
.resources/nix/bin/lib/Image/ExifTool/Charset/JIS.pm
Normal file
File diff suppressed because it is too large
Load diff
24
.resources/nix/bin/lib/Image/ExifTool/Charset/Latin.pm
Normal file
24
.resources/nix/bin/lib/Image/ExifTool/Charset/Latin.pm
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Latin.pm
|
||||
#
|
||||
# Description: cp1252 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Latin = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e,
|
||||
0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6,
|
||||
0x89 => 0x2030, 0x8a => 0x0160, 0x8b => 0x2039, 0x8c => 0x0152,
|
||||
0x8e => 0x017d, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c,
|
||||
0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
||||
0x98 => 0x02dc, 0x99 => 0x2122, 0x9a => 0x0161, 0x9b => 0x203a,
|
||||
0x9c => 0x0153, 0x9e => 0x017e, 0x9f => 0x0178,
|
||||
);
|
||||
|
||||
1; # end
|
||||
36
.resources/nix/bin/lib/Image/ExifTool/Charset/Latin2.pm
Normal file
36
.resources/nix/bin/lib/Image/ExifTool/Charset/Latin2.pm
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Latin2.pm
|
||||
#
|
||||
# Description: cp1250 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1250.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Latin2 = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x84 => 0x201e, 0x85 => 0x2026,
|
||||
0x86 => 0x2020, 0x87 => 0x2021, 0x89 => 0x2030, 0x8a => 0x0160,
|
||||
0x8b => 0x2039, 0x8c => 0x015a, 0x8d => 0x0164, 0x8e => 0x017d,
|
||||
0x8f => 0x0179, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c,
|
||||
0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
||||
0x99 => 0x2122, 0x9a => 0x0161, 0x9b => 0x203a, 0x9c => 0x015b,
|
||||
0x9d => 0x0165, 0x9e => 0x017e, 0x9f => 0x017a, 0xa1 => 0x02c7,
|
||||
0xa2 => 0x02d8, 0xa3 => 0x0141, 0xa5 => 0x0104, 0xaa => 0x015e,
|
||||
0xaf => 0x017b, 0xb2 => 0x02db, 0xb3 => 0x0142, 0xb9 => 0x0105,
|
||||
0xba => 0x015f, 0xbc => 0x013d, 0xbd => 0x02dd, 0xbe => 0x013e,
|
||||
0xbf => 0x017c, 0xc0 => 0x0154, 0xc3 => 0x0102, 0xc5 => 0x0139,
|
||||
0xc6 => 0x0106, 0xc8 => 0x010c, 0xca => 0x0118, 0xcc => 0x011a,
|
||||
0xcf => 0x010e, 0xd0 => 0x0110, 0xd1 => 0x0143, 0xd2 => 0x0147,
|
||||
0xd5 => 0x0150, 0xd8 => 0x0158, 0xd9 => 0x016e, 0xdb => 0x0170,
|
||||
0xde => 0x0162, 0xe0 => 0x0155, 0xe3 => 0x0103, 0xe5 => 0x013a,
|
||||
0xe6 => 0x0107, 0xe8 => 0x010d, 0xea => 0x0119, 0xec => 0x011b,
|
||||
0xef => 0x010f, 0xf0 => 0x0111, 0xf1 => 0x0144, 0xf2 => 0x0148,
|
||||
0xf5 => 0x0151, 0xf8 => 0x0159, 0xf9 => 0x016f, 0xfb => 0x0171,
|
||||
0xfe => 0x0163, 0xff => 0x02d9,
|
||||
);
|
||||
|
||||
1; # end
|
||||
47
.resources/nix/bin/lib/Image/ExifTool/Charset/MacArabic.pm
Normal file
47
.resources/nix/bin/lib/Image/ExifTool/Charset/MacArabic.pm
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacArabic.pm
|
||||
#
|
||||
# Description: Mac Arabic to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ARABIC.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
# and directional characters are ignored
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacArabic = (
|
||||
0x80 => 0xc4, 0x81 => 0xa0, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0x06ba, 0x8c => 0xab, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0x2026,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xbb,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf7, 0x9c => 0xfa, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x20, 0xa1 => 0x21, 0xa2 => 0x22,
|
||||
0xa3 => 0x23, 0xa4 => 0x24, 0xa5 => 0x066a, 0xa6 => 0x26, 0xa7 => 0x27,
|
||||
0xa8 => 0x28, 0xa9 => 0x29, 0xaa => 0x2a, 0xab => 0x2b, 0xac => 0x060c,
|
||||
0xad => 0x2d, 0xae => 0x2e, 0xaf => 0x2f, 0xb0 => 0x0660, 0xb1 => 0x0661,
|
||||
0xb2 => 0x0662, 0xb3 => 0x0663, 0xb4 => 0x0664, 0xb5 => 0x0665,
|
||||
0xb6 => 0x0666, 0xb7 => 0x0667, 0xb8 => 0x0668, 0xb9 => 0x0669, 0xba => 0x3a,
|
||||
0xbb => 0x061b, 0xbc => 0x3c, 0xbd => 0x3d, 0xbe => 0x3e, 0xbf => 0x061f,
|
||||
0xc0 => 0x274a, 0xc1 => 0x0621, 0xc2 => 0x0622, 0xc3 => 0x0623,
|
||||
0xc4 => 0x0624, 0xc5 => 0x0625, 0xc6 => 0x0626, 0xc7 => 0x0627,
|
||||
0xc8 => 0x0628, 0xc9 => 0x0629, 0xca => 0x062a, 0xcb => 0x062b,
|
||||
0xcc => 0x062c, 0xcd => 0x062d, 0xce => 0x062e, 0xcf => 0x062f,
|
||||
0xd0 => 0x0630, 0xd1 => 0x0631, 0xd2 => 0x0632, 0xd3 => 0x0633,
|
||||
0xd4 => 0x0634, 0xd5 => 0x0635, 0xd6 => 0x0636, 0xd7 => 0x0637,
|
||||
0xd8 => 0x0638, 0xd9 => 0x0639, 0xda => 0x063a, 0xdb => 0x5b, 0xdc => 0x5c,
|
||||
0xdd => 0x5d, 0xde => 0x5e, 0xdf => 0x5f, 0xe0 => 0x0640, 0xe1 => 0x0641,
|
||||
0xe2 => 0x0642, 0xe3 => 0x0643, 0xe4 => 0x0644, 0xe5 => 0x0645,
|
||||
0xe6 => 0x0646, 0xe7 => 0x0647, 0xe8 => 0x0648, 0xe9 => 0x0649,
|
||||
0xea => 0x064a, 0xeb => 0x064b, 0xec => 0x064c, 0xed => 0x064d,
|
||||
0xee => 0x064e, 0xef => 0x064f, 0xf0 => 0x0650, 0xf1 => 0x0651,
|
||||
0xf2 => 0x0652, 0xf3 => 0x067e, 0xf4 => 0x0679, 0xf5 => 0x0686,
|
||||
0xf6 => 0x06d5, 0xf7 => 0x06a4, 0xf8 => 0x06af, 0xf9 => 0x0688,
|
||||
0xfa => 0x0691, 0xfb => 0x7b, 0xfc => 0x7c, 0xfd => 0x7d, 0xfe => 0x0698,
|
||||
0xff => 0x06d2,
|
||||
);
|
||||
|
||||
1; # end
|
||||
2088
.resources/nix/bin/lib/Image/ExifTool/Charset/MacChineseCN.pm
Normal file
2088
.resources/nix/bin/lib/Image/ExifTool/Charset/MacChineseCN.pm
Normal file
File diff suppressed because it is too large
Load diff
3623
.resources/nix/bin/lib/Image/ExifTool/Charset/MacChineseTW.pm
Normal file
3623
.resources/nix/bin/lib/Image/ExifTool/Charset/MacChineseTW.pm
Normal file
File diff suppressed because it is too large
Load diff
43
.resources/nix/bin/lib/Image/ExifTool/Charset/MacCroatian.pm
Normal file
43
.resources/nix/bin/lib/Image/ExifTool/Charset/MacCroatian.pm
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacCroatian.pm
|
||||
#
|
||||
# Description: Mac Croatian to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CROATIAN.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacCroatian = (
|
||||
0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7,
|
||||
0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xa9 => 0x0160,
|
||||
0xaa => 0x2122, 0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0x017d,
|
||||
0xaf => 0xd8, 0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0x2206,
|
||||
0xb6 => 0x2202, 0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x0161,
|
||||
0xba => 0x222b, 0xbb => 0xaa, 0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0x017e,
|
||||
0xbf => 0xf8, 0xc0 => 0xbf, 0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a,
|
||||
0xc4 => 0x0192, 0xc5 => 0x2248, 0xc6 => 0x0106, 0xc7 => 0xab, 0xc8 => 0x010c,
|
||||
0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5,
|
||||
0xce => 0x0152, 0xcf => 0x0153, 0xd0 => 0x0110, 0xd1 => 0x2014,
|
||||
0xd2 => 0x201c, 0xd3 => 0x201d, 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7,
|
||||
0xd7 => 0x25ca, 0xd8 => 0xf8ff, 0xd9 => 0xa9, 0xda => 0x2044, 0xdb => 0x20ac,
|
||||
0xdc => 0x2039, 0xdd => 0x203a, 0xde => 0xc6, 0xdf => 0xbb, 0xe0 => 0x2013,
|
||||
0xe1 => 0xb7, 0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2,
|
||||
0xe6 => 0x0107, 0xe7 => 0xc1, 0xe8 => 0x010d, 0xe9 => 0xc8, 0xea => 0xcd,
|
||||
0xeb => 0xce, 0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4,
|
||||
0xf0 => 0x0111, 0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9,
|
||||
0xf5 => 0x0131, 0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x03c0,
|
||||
0xfa => 0xcb, 0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0xca, 0xfe => 0xe6,
|
||||
0xff => 0x02c7,
|
||||
);
|
||||
|
||||
1; # end
|
||||
47
.resources/nix/bin/lib/Image/ExifTool/Charset/MacCyrillic.pm
Normal file
47
.resources/nix/bin/lib/Image/ExifTool/Charset/MacCyrillic.pm
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacCyrillic.pm
|
||||
#
|
||||
# Description: Mac Cyrillic to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CYRILLIC.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacCyrillic = (
|
||||
0x80 => 0x0410, 0x81 => 0x0411, 0x82 => 0x0412, 0x83 => 0x0413,
|
||||
0x84 => 0x0414, 0x85 => 0x0415, 0x86 => 0x0416, 0x87 => 0x0417,
|
||||
0x88 => 0x0418, 0x89 => 0x0419, 0x8a => 0x041a, 0x8b => 0x041b,
|
||||
0x8c => 0x041c, 0x8d => 0x041d, 0x8e => 0x041e, 0x8f => 0x041f,
|
||||
0x90 => 0x0420, 0x91 => 0x0421, 0x92 => 0x0422, 0x93 => 0x0423,
|
||||
0x94 => 0x0424, 0x95 => 0x0425, 0x96 => 0x0426, 0x97 => 0x0427,
|
||||
0x98 => 0x0428, 0x99 => 0x0429, 0x9a => 0x042a, 0x9b => 0x042b,
|
||||
0x9c => 0x042c, 0x9d => 0x042d, 0x9e => 0x042e, 0x9f => 0x042f,
|
||||
0xa0 => 0x2020, 0xa1 => 0xb0, 0xa2 => 0x0490, 0xa4 => 0xa7, 0xa5 => 0x2022,
|
||||
0xa6 => 0xb6, 0xa7 => 0x0406, 0xa8 => 0xae, 0xaa => 0x2122, 0xab => 0x0402,
|
||||
0xac => 0x0452, 0xad => 0x2260, 0xae => 0x0403, 0xaf => 0x0453,
|
||||
0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0x0456,
|
||||
0xb6 => 0x0491, 0xb7 => 0x0408, 0xb8 => 0x0404, 0xb9 => 0x0454,
|
||||
0xba => 0x0407, 0xbb => 0x0457, 0xbc => 0x0409, 0xbd => 0x0459,
|
||||
0xbe => 0x040a, 0xbf => 0x045a, 0xc0 => 0x0458, 0xc1 => 0x0405, 0xc2 => 0xac,
|
||||
0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248, 0xc6 => 0x2206, 0xc7 => 0xab,
|
||||
0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0x040b, 0xcc => 0x045b,
|
||||
0xcd => 0x040c, 0xce => 0x045c, 0xcf => 0x0455, 0xd0 => 0x2013,
|
||||
0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d, 0xd4 => 0x2018,
|
||||
0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x201e, 0xd8 => 0x040e, 0xd9 => 0x045e,
|
||||
0xda => 0x040f, 0xdb => 0x045f, 0xdc => 0x2116, 0xdd => 0x0401,
|
||||
0xde => 0x0451, 0xdf => 0x044f, 0xe0 => 0x0430, 0xe1 => 0x0431,
|
||||
0xe2 => 0x0432, 0xe3 => 0x0433, 0xe4 => 0x0434, 0xe5 => 0x0435,
|
||||
0xe6 => 0x0436, 0xe7 => 0x0437, 0xe8 => 0x0438, 0xe9 => 0x0439,
|
||||
0xea => 0x043a, 0xeb => 0x043b, 0xec => 0x043c, 0xed => 0x043d,
|
||||
0xee => 0x043e, 0xef => 0x043f, 0xf0 => 0x0440, 0xf1 => 0x0441,
|
||||
0xf2 => 0x0442, 0xf3 => 0x0443, 0xf4 => 0x0444, 0xf5 => 0x0445,
|
||||
0xf6 => 0x0446, 0xf7 => 0x0447, 0xf8 => 0x0448, 0xf9 => 0x0449,
|
||||
0xfa => 0x044a, 0xfb => 0x044b, 0xfc => 0x044c, 0xfd => 0x044d,
|
||||
0xfe => 0x044e, 0xff => 0x20ac,
|
||||
);
|
||||
|
||||
1; # end
|
||||
45
.resources/nix/bin/lib/Image/ExifTool/Charset/MacGreek.pm
Normal file
45
.resources/nix/bin/lib/Image/ExifTool/Charset/MacGreek.pm
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacGreek.pm
|
||||
#
|
||||
# Description: Mac Greek to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GREEK.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacGreek = (
|
||||
0x80 => 0xc4, 0x81 => 0xb9, 0x82 => 0xb2, 0x83 => 0xc9, 0x84 => 0xb3,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0x0385, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0x0384, 0x8c => 0xa8, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xa3, 0x93 => 0x2122,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0x2022, 0x97 => 0xbd, 0x98 => 0x2030,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xa6, 0x9c => 0x20ac, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0x0393, 0xa2 => 0x0394,
|
||||
0xa3 => 0x0398, 0xa4 => 0x039b, 0xa5 => 0x039e, 0xa6 => 0x03a0, 0xa7 => 0xdf,
|
||||
0xa8 => 0xae, 0xaa => 0x03a3, 0xab => 0x03aa, 0xac => 0xa7, 0xad => 0x2260,
|
||||
0xae => 0xb0, 0xaf => 0xb7, 0xb0 => 0x0391, 0xb2 => 0x2264, 0xb3 => 0x2265,
|
||||
0xb4 => 0xa5, 0xb5 => 0x0392, 0xb6 => 0x0395, 0xb7 => 0x0396, 0xb8 => 0x0397,
|
||||
0xb9 => 0x0399, 0xba => 0x039a, 0xbb => 0x039c, 0xbc => 0x03a6,
|
||||
0xbd => 0x03ab, 0xbe => 0x03a8, 0xbf => 0x03a9, 0xc0 => 0x03ac,
|
||||
0xc1 => 0x039d, 0xc2 => 0xac, 0xc3 => 0x039f, 0xc4 => 0x03a1, 0xc5 => 0x2248,
|
||||
0xc6 => 0x03a4, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0,
|
||||
0xcb => 0x03a5, 0xcc => 0x03a7, 0xcd => 0x0386, 0xce => 0x0388,
|
||||
0xcf => 0x0153, 0xd0 => 0x2013, 0xd1 => 0x2015, 0xd2 => 0x201c,
|
||||
0xd3 => 0x201d, 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x0389,
|
||||
0xd8 => 0x038a, 0xd9 => 0x038c, 0xda => 0x038e, 0xdb => 0x03ad,
|
||||
0xdc => 0x03ae, 0xdd => 0x03af, 0xde => 0x03cc, 0xdf => 0x038f,
|
||||
0xe0 => 0x03cd, 0xe1 => 0x03b1, 0xe2 => 0x03b2, 0xe3 => 0x03c8,
|
||||
0xe4 => 0x03b4, 0xe5 => 0x03b5, 0xe6 => 0x03c6, 0xe7 => 0x03b3,
|
||||
0xe8 => 0x03b7, 0xe9 => 0x03b9, 0xea => 0x03be, 0xeb => 0x03ba,
|
||||
0xec => 0x03bb, 0xed => 0x03bc, 0xee => 0x03bd, 0xef => 0x03bf,
|
||||
0xf0 => 0x03c0, 0xf1 => 0x03ce, 0xf2 => 0x03c1, 0xf3 => 0x03c3,
|
||||
0xf4 => 0x03c4, 0xf5 => 0x03b8, 0xf6 => 0x03c9, 0xf7 => 0x03c2,
|
||||
0xf8 => 0x03c7, 0xf9 => 0x03c5, 0xfa => 0x03b6, 0xfb => 0x03ca,
|
||||
0xfc => 0x03cb, 0xfd => 0x0390, 0xfe => 0x03b0, 0xff => 0xad,
|
||||
);
|
||||
|
||||
1; # end
|
||||
47
.resources/nix/bin/lib/Image/ExifTool/Charset/MacHebrew.pm
Normal file
47
.resources/nix/bin/lib/Image/ExifTool/Charset/MacHebrew.pm
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacHebrew.pm
|
||||
#
|
||||
# Description: Mac Hebrew to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/HEBREW.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
# and directional characters are ignored
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacHebrew = (
|
||||
0x80 => 0xc4, 0x81 => [0x05f2,0x05b7], 0x82 => 0xc7, 0x83 => 0xc9,
|
||||
0x84 => 0xd1, 0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0,
|
||||
0x89 => 0xe2, 0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7,
|
||||
0x8e => 0xe9, 0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed,
|
||||
0x93 => 0xec, 0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3,
|
||||
0x98 => 0xf2, 0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa,
|
||||
0x9d => 0xf9, 0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x20, 0xa1 => 0x21,
|
||||
0xa2 => 0x22, 0xa3 => 0x23, 0xa4 => 0x24, 0xa5 => 0x25, 0xa6 => 0x20aa,
|
||||
0xa7 => 0x27, 0xa8 => 0x29, 0xa9 => 0x28, 0xaa => 0x2a, 0xab => 0x2b,
|
||||
0xac => 0x2c, 0xad => 0x2d, 0xae => 0x2e, 0xaf => 0x2f, 0xb0 => 0x30,
|
||||
0xb1 => 0x31, 0xb2 => 0x32, 0xb3 => 0x33, 0xb4 => 0x34, 0xb5 => 0x35,
|
||||
0xb6 => 0x36, 0xb7 => 0x37, 0xb8 => 0x38, 0xb9 => 0x39, 0xba => 0x3a,
|
||||
0xbb => 0x3b, 0xbc => 0x3c, 0xbd => 0x3d, 0xbe => 0x3e, 0xbf => 0x3f,
|
||||
0xc0 => [0xf86a,0x05dc,0x05b9], 0xc1 => 0x201e, 0xc2 => 0xf89b,
|
||||
0xc3 => 0xf89c, 0xc4 => 0xf89d, 0xc5 => 0xf89e, 0xc6 => 0x05bc,
|
||||
0xc7 => 0xfb4b, 0xc8 => 0xfb35, 0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0x05b8,
|
||||
0xcc => 0x05b7, 0xcd => 0x05b5, 0xce => 0x05b6, 0xcf => 0x05b4,
|
||||
0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d,
|
||||
0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xfb2a, 0xd7 => 0xfb2b,
|
||||
0xd8 => 0x05bf, 0xd9 => 0x05b0, 0xda => 0x05b2, 0xdb => 0x05b1,
|
||||
0xdc => 0x05bb, 0xdd => 0x05b9, 0xde => [0x05b8,0xf87f], 0xdf => 0x05b3,
|
||||
0xe0 => 0x05d0, 0xe1 => 0x05d1, 0xe2 => 0x05d2, 0xe3 => 0x05d3,
|
||||
0xe4 => 0x05d4, 0xe5 => 0x05d5, 0xe6 => 0x05d6, 0xe7 => 0x05d7,
|
||||
0xe8 => 0x05d8, 0xe9 => 0x05d9, 0xea => 0x05da, 0xeb => 0x05db,
|
||||
0xec => 0x05dc, 0xed => 0x05dd, 0xee => 0x05de, 0xef => 0x05df,
|
||||
0xf0 => 0x05e0, 0xf1 => 0x05e1, 0xf2 => 0x05e2, 0xf3 => 0x05e3,
|
||||
0xf4 => 0x05e4, 0xf5 => 0x05e5, 0xf6 => 0x05e6, 0xf7 => 0x05e7,
|
||||
0xf8 => 0x05e8, 0xf9 => 0x05e9, 0xfa => 0x05ea, 0xfb => 0x7d, 0xfc => 0x5d,
|
||||
0xfd => 0x7b, 0xfe => 0x5b, 0xff => 0x7c,
|
||||
);
|
||||
|
||||
1; # end
|
||||
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacIceland.pm
Normal file
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacIceland.pm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacIceland.pm
|
||||
#
|
||||
# Description: Mac Icelandic to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ICELAND.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacIceland = (
|
||||
0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0xdd, 0xa1 => 0xb0, 0xa4 => 0xa7,
|
||||
0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122,
|
||||
0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0xc6, 0xaf => 0xd8,
|
||||
0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202,
|
||||
0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa,
|
||||
0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0xe6, 0xbf => 0xf8, 0xc0 => 0xbf,
|
||||
0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248,
|
||||
0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0,
|
||||
0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153,
|
||||
0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d,
|
||||
0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff,
|
||||
0xd9 => 0x0178, 0xda => 0x2044, 0xdb => 0x20ac, 0xdc => 0xd0, 0xdd => 0xf0,
|
||||
0xdf => 0xfe, 0xe0 => 0xfd, 0xe1 => 0xb7, 0xe2 => 0x201a, 0xe3 => 0x201e,
|
||||
0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca, 0xe7 => 0xc1, 0xe8 => 0xcb,
|
||||
0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce, 0xec => 0xcf, 0xed => 0xcc,
|
||||
0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff, 0xf1 => 0xd2, 0xf2 => 0xda,
|
||||
0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0x0131, 0xf6 => 0x02c6, 0xf7 => 0x02dc,
|
||||
0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9, 0xfb => 0x02da, 0xfc => 0xb8,
|
||||
0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7,
|
||||
);
|
||||
|
||||
1; # end
|
||||
1933
.resources/nix/bin/lib/Image/ExifTool/Charset/MacJapanese.pm
Normal file
1933
.resources/nix/bin/lib/Image/ExifTool/Charset/MacJapanese.pm
Normal file
File diff suppressed because it is too large
Load diff
2720
.resources/nix/bin/lib/Image/ExifTool/Charset/MacKorean.pm
Normal file
2720
.resources/nix/bin/lib/Image/ExifTool/Charset/MacKorean.pm
Normal file
File diff suppressed because it is too large
Load diff
44
.resources/nix/bin/lib/Image/ExifTool/Charset/MacLatin2.pm
Normal file
44
.resources/nix/bin/lib/Image/ExifTool/Charset/MacLatin2.pm
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacLatin2.pm
|
||||
#
|
||||
# Description: Mac Central European to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/CENTEURO.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacLatin2 = (
|
||||
0x80 => 0xc4, 0x81 => 0x0100, 0x82 => 0x0101, 0x83 => 0xc9, 0x84 => 0x0104,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0x0105, 0x89 => 0x010c,
|
||||
0x8a => 0xe4, 0x8b => 0x010d, 0x8c => 0x0106, 0x8d => 0x0107, 0x8e => 0xe9,
|
||||
0x8f => 0x0179, 0x90 => 0x017a, 0x91 => 0x010e, 0x92 => 0xed, 0x93 => 0x010f,
|
||||
0x94 => 0x0112, 0x95 => 0x0113, 0x96 => 0x0116, 0x97 => 0xf3, 0x98 => 0x0117,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0x011a,
|
||||
0x9e => 0x011b, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa2 => 0x0118,
|
||||
0xa4 => 0xa7, 0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae,
|
||||
0xaa => 0x2122, 0xab => 0x0119, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0x0123,
|
||||
0xaf => 0x012e, 0xb0 => 0x012f, 0xb1 => 0x012a, 0xb2 => 0x2264,
|
||||
0xb3 => 0x2265, 0xb4 => 0x012b, 0xb5 => 0x0136, 0xb6 => 0x2202,
|
||||
0xb7 => 0x2211, 0xb8 => 0x0142, 0xb9 => 0x013b, 0xba => 0x013c,
|
||||
0xbb => 0x013d, 0xbc => 0x013e, 0xbd => 0x0139, 0xbe => 0x013a,
|
||||
0xbf => 0x0145, 0xc0 => 0x0146, 0xc1 => 0x0143, 0xc2 => 0xac, 0xc3 => 0x221a,
|
||||
0xc4 => 0x0144, 0xc5 => 0x0147, 0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb,
|
||||
0xc9 => 0x2026, 0xca => 0xa0, 0xcb => 0x0148, 0xcc => 0x0150, 0xcd => 0xd5,
|
||||
0xce => 0x0151, 0xcf => 0x014c, 0xd0 => 0x2013, 0xd1 => 0x2014,
|
||||
0xd2 => 0x201c, 0xd3 => 0x201d, 0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7,
|
||||
0xd7 => 0x25ca, 0xd8 => 0x014d, 0xd9 => 0x0154, 0xda => 0x0155,
|
||||
0xdb => 0x0158, 0xdc => 0x2039, 0xdd => 0x203a, 0xde => 0x0159,
|
||||
0xdf => 0x0156, 0xe0 => 0x0157, 0xe1 => 0x0160, 0xe2 => 0x201a,
|
||||
0xe3 => 0x201e, 0xe4 => 0x0161, 0xe5 => 0x015a, 0xe6 => 0x015b, 0xe7 => 0xc1,
|
||||
0xe8 => 0x0164, 0xe9 => 0x0165, 0xea => 0xcd, 0xeb => 0x017d, 0xec => 0x017e,
|
||||
0xed => 0x016a, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0x016b, 0xf1 => 0x016e,
|
||||
0xf2 => 0xda, 0xf3 => 0x016f, 0xf4 => 0x0170, 0xf5 => 0x0171, 0xf6 => 0x0172,
|
||||
0xf7 => 0x0173, 0xf8 => 0xdd, 0xf9 => 0xfd, 0xfa => 0x0137, 0xfb => 0x017b,
|
||||
0xfc => 0x0141, 0xfd => 0x017c, 0xfe => 0x0122, 0xff => 0x02c7,
|
||||
);
|
||||
|
||||
1; # end
|
||||
2087
.resources/nix/bin/lib/Image/ExifTool/Charset/MacRSymbol.pm
Normal file
2087
.resources/nix/bin/lib/Image/ExifTool/Charset/MacRSymbol.pm
Normal file
File diff suppressed because it is too large
Load diff
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacRoman.pm
Normal file
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacRoman.pm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacRoman.pm
|
||||
#
|
||||
# Description: Mac Roman to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacRoman = (
|
||||
0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7,
|
||||
0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122,
|
||||
0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0xc6, 0xaf => 0xd8,
|
||||
0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202,
|
||||
0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa,
|
||||
0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0xe6, 0xbf => 0xf8, 0xc0 => 0xbf,
|
||||
0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248,
|
||||
0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0,
|
||||
0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153,
|
||||
0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d,
|
||||
0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff,
|
||||
0xd9 => 0x0178, 0xda => 0x2044, 0xdb => 0x20ac, 0xdc => 0x2039,
|
||||
0xdd => 0x203a, 0xde => 0xfb01, 0xdf => 0xfb02, 0xe0 => 0x2021, 0xe1 => 0xb7,
|
||||
0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca,
|
||||
0xe7 => 0xc1, 0xe8 => 0xcb, 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce,
|
||||
0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff,
|
||||
0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0x0131,
|
||||
0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9,
|
||||
0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7,
|
||||
);
|
||||
|
||||
1; # end
|
||||
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacRomanian.pm
Normal file
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacRomanian.pm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacRomanian.pm
|
||||
#
|
||||
# Description: Mac Romanian to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMANIAN.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacRomanian = (
|
||||
0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7,
|
||||
0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122,
|
||||
0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0x0102, 0xaf => 0x0218,
|
||||
0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202,
|
||||
0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa,
|
||||
0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0x0103, 0xbf => 0x0219, 0xc0 => 0xbf,
|
||||
0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248,
|
||||
0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0,
|
||||
0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153,
|
||||
0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d,
|
||||
0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff,
|
||||
0xd9 => 0x0178, 0xda => 0x2044, 0xdb => 0x20ac, 0xdc => 0x2039,
|
||||
0xdd => 0x203a, 0xde => 0x021a, 0xdf => 0x021b, 0xe0 => 0x2021, 0xe1 => 0xb7,
|
||||
0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca,
|
||||
0xe7 => 0xc1, 0xe8 => 0xcb, 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce,
|
||||
0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff,
|
||||
0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0x0131,
|
||||
0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9,
|
||||
0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7,
|
||||
);
|
||||
|
||||
1; # end
|
||||
49
.resources/nix/bin/lib/Image/ExifTool/Charset/MacThai.pm
Normal file
49
.resources/nix/bin/lib/Image/ExifTool/Charset/MacThai.pm
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacThai.pm
|
||||
#
|
||||
# Description: Mac Thai to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/THAI.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacThai = (
|
||||
0x80 => 0xab, 0x81 => 0xbb, 0x82 => 0x2026, 0x83 => [0x0e48,0xf875],
|
||||
0x84 => [0x0e49,0xf875], 0x85 => [0x0e4a,0xf875], 0x86 => [0x0e4b,0xf875],
|
||||
0x87 => [0x0e4c,0xf875], 0x88 => [0x0e48,0xf873], 0x89 => [0x0e49,0xf873],
|
||||
0x8a => [0x0e4a,0xf873], 0x8b => [0x0e4b,0xf873], 0x8c => [0x0e4c,0xf873],
|
||||
0x8d => 0x201c, 0x8e => 0x201d, 0x8f => [0x0e4d,0xf874], 0x91 => 0x2022,
|
||||
0x92 => [0x0e31,0xf874], 0x93 => [0x0e47,0xf874], 0x94 => [0x0e34,0xf874],
|
||||
0x95 => [0x0e35,0xf874], 0x96 => [0x0e36,0xf874], 0x97 => [0x0e37,0xf874],
|
||||
0x98 => [0x0e48,0xf874], 0x99 => [0x0e49,0xf874], 0x9a => [0x0e4a,0xf874],
|
||||
0x9b => [0x0e4b,0xf874], 0x9c => [0x0e4c,0xf874], 0x9d => 0x2018,
|
||||
0x9e => 0x2019, 0xa1 => 0x0e01, 0xa2 => 0x0e02, 0xa3 => 0x0e03,
|
||||
0xa4 => 0x0e04, 0xa5 => 0x0e05, 0xa6 => 0x0e06, 0xa7 => 0x0e07,
|
||||
0xa8 => 0x0e08, 0xa9 => 0x0e09, 0xaa => 0x0e0a, 0xab => 0x0e0b,
|
||||
0xac => 0x0e0c, 0xad => 0x0e0d, 0xae => 0x0e0e, 0xaf => 0x0e0f,
|
||||
0xb0 => 0x0e10, 0xb1 => 0x0e11, 0xb2 => 0x0e12, 0xb3 => 0x0e13,
|
||||
0xb4 => 0x0e14, 0xb5 => 0x0e15, 0xb6 => 0x0e16, 0xb7 => 0x0e17,
|
||||
0xb8 => 0x0e18, 0xb9 => 0x0e19, 0xba => 0x0e1a, 0xbb => 0x0e1b,
|
||||
0xbc => 0x0e1c, 0xbd => 0x0e1d, 0xbe => 0x0e1e, 0xbf => 0x0e1f,
|
||||
0xc0 => 0x0e20, 0xc1 => 0x0e21, 0xc2 => 0x0e22, 0xc3 => 0x0e23,
|
||||
0xc4 => 0x0e24, 0xc5 => 0x0e25, 0xc6 => 0x0e26, 0xc7 => 0x0e27,
|
||||
0xc8 => 0x0e28, 0xc9 => 0x0e29, 0xca => 0x0e2a, 0xcb => 0x0e2b,
|
||||
0xcc => 0x0e2c, 0xcd => 0x0e2d, 0xce => 0x0e2e, 0xcf => 0x0e2f,
|
||||
0xd0 => 0x0e30, 0xd1 => 0x0e31, 0xd2 => 0x0e32, 0xd3 => 0x0e33,
|
||||
0xd4 => 0x0e34, 0xd5 => 0x0e35, 0xd6 => 0x0e36, 0xd7 => 0x0e37,
|
||||
0xd8 => 0x0e38, 0xd9 => 0x0e39, 0xda => 0x0e3a, 0xdb => 0x2060,
|
||||
0xdc => 0x200b, 0xdd => 0x2013, 0xde => 0x2014, 0xdf => 0x0e3f,
|
||||
0xe0 => 0x0e40, 0xe1 => 0x0e41, 0xe2 => 0x0e42, 0xe3 => 0x0e43,
|
||||
0xe4 => 0x0e44, 0xe5 => 0x0e45, 0xe6 => 0x0e46, 0xe7 => 0x0e47,
|
||||
0xe8 => 0x0e48, 0xe9 => 0x0e49, 0xea => 0x0e4a, 0xeb => 0x0e4b,
|
||||
0xec => 0x0e4c, 0xed => 0x0e4d, 0xee => 0x2122, 0xef => 0x0e4f,
|
||||
0xf0 => 0x0e50, 0xf1 => 0x0e51, 0xf2 => 0x0e52, 0xf3 => 0x0e53,
|
||||
0xf4 => 0x0e54, 0xf5 => 0x0e55, 0xf6 => 0x0e56, 0xf7 => 0x0e57,
|
||||
0xf8 => 0x0e58, 0xf9 => 0x0e59, 0xfa => 0xae, 0xfb => 0xa9,
|
||||
);
|
||||
|
||||
1; # end
|
||||
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacTurkish.pm
Normal file
42
.resources/nix/bin/lib/Image/ExifTool/Charset/MacTurkish.pm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: MacTurkish.pm
|
||||
#
|
||||
# Description: Mac Turkish to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/TURKISH.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::MacTurkish = (
|
||||
0x80 => 0xc4, 0x81 => 0xc5, 0x82 => 0xc7, 0x83 => 0xc9, 0x84 => 0xd1,
|
||||
0x85 => 0xd6, 0x86 => 0xdc, 0x87 => 0xe1, 0x88 => 0xe0, 0x89 => 0xe2,
|
||||
0x8a => 0xe4, 0x8b => 0xe3, 0x8c => 0xe5, 0x8d => 0xe7, 0x8e => 0xe9,
|
||||
0x8f => 0xe8, 0x90 => 0xea, 0x91 => 0xeb, 0x92 => 0xed, 0x93 => 0xec,
|
||||
0x94 => 0xee, 0x95 => 0xef, 0x96 => 0xf1, 0x97 => 0xf3, 0x98 => 0xf2,
|
||||
0x99 => 0xf4, 0x9a => 0xf6, 0x9b => 0xf5, 0x9c => 0xfa, 0x9d => 0xf9,
|
||||
0x9e => 0xfb, 0x9f => 0xfc, 0xa0 => 0x2020, 0xa1 => 0xb0, 0xa4 => 0xa7,
|
||||
0xa5 => 0x2022, 0xa6 => 0xb6, 0xa7 => 0xdf, 0xa8 => 0xae, 0xaa => 0x2122,
|
||||
0xab => 0xb4, 0xac => 0xa8, 0xad => 0x2260, 0xae => 0xc6, 0xaf => 0xd8,
|
||||
0xb0 => 0x221e, 0xb2 => 0x2264, 0xb3 => 0x2265, 0xb4 => 0xa5, 0xb6 => 0x2202,
|
||||
0xb7 => 0x2211, 0xb8 => 0x220f, 0xb9 => 0x03c0, 0xba => 0x222b, 0xbb => 0xaa,
|
||||
0xbc => 0xba, 0xbd => 0x03a9, 0xbe => 0xe6, 0xbf => 0xf8, 0xc0 => 0xbf,
|
||||
0xc1 => 0xa1, 0xc2 => 0xac, 0xc3 => 0x221a, 0xc4 => 0x0192, 0xc5 => 0x2248,
|
||||
0xc6 => 0x2206, 0xc7 => 0xab, 0xc8 => 0xbb, 0xc9 => 0x2026, 0xca => 0xa0,
|
||||
0xcb => 0xc0, 0xcc => 0xc3, 0xcd => 0xd5, 0xce => 0x0152, 0xcf => 0x0153,
|
||||
0xd0 => 0x2013, 0xd1 => 0x2014, 0xd2 => 0x201c, 0xd3 => 0x201d,
|
||||
0xd4 => 0x2018, 0xd5 => 0x2019, 0xd6 => 0xf7, 0xd7 => 0x25ca, 0xd8 => 0xff,
|
||||
0xd9 => 0x0178, 0xda => 0x011e, 0xdb => 0x011f, 0xdc => 0x0130,
|
||||
0xdd => 0x0131, 0xde => 0x015e, 0xdf => 0x015f, 0xe0 => 0x2021, 0xe1 => 0xb7,
|
||||
0xe2 => 0x201a, 0xe3 => 0x201e, 0xe4 => 0x2030, 0xe5 => 0xc2, 0xe6 => 0xca,
|
||||
0xe7 => 0xc1, 0xe8 => 0xcb, 0xe9 => 0xc8, 0xea => 0xcd, 0xeb => 0xce,
|
||||
0xec => 0xcf, 0xed => 0xcc, 0xee => 0xd3, 0xef => 0xd4, 0xf0 => 0xf8ff,
|
||||
0xf1 => 0xd2, 0xf2 => 0xda, 0xf3 => 0xdb, 0xf4 => 0xd9, 0xf5 => 0xf8a0,
|
||||
0xf6 => 0x02c6, 0xf7 => 0x02dc, 0xf8 => 0xaf, 0xf9 => 0x02d8, 0xfa => 0x02d9,
|
||||
0xfb => 0x02da, 0xfc => 0xb8, 0xfd => 0x02dd, 0xfe => 0x02db, 0xff => 0x02c7,
|
||||
);
|
||||
|
||||
1; # end
|
||||
28
.resources/nix/bin/lib/Image/ExifTool/Charset/PDFDoc.pm
Normal file
28
.resources/nix/bin/lib/Image/ExifTool/Charset/PDFDoc.pm
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: PDFDoc.pm
|
||||
#
|
||||
# Description: PDFDocEncoding to Unicode
|
||||
#
|
||||
# Revisions: 2010/10/16 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://www.adobe.com/devnet/pdf/pdf_reference.html
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
# This set re-maps characters with codepoints less than 0x80
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::PDFDoc = (
|
||||
0x18 => 0x02d8, 0x82 => 0x2021, 0x8c => 0x201e, 0x96 => 0x0152,
|
||||
0x19 => 0x02c7, 0x83 => 0x2026, 0x8d => 0x201c, 0x97 => 0x0160,
|
||||
0x1a => 0x02c6, 0x84 => 0x2014, 0x8e => 0x201d, 0x98 => 0x0178,
|
||||
0x1b => 0x02d9, 0x85 => 0x2013, 0x8f => 0x2018, 0x99 => 0x017d,
|
||||
0x1c => 0x02dd, 0x86 => 0x0192, 0x90 => 0x2019, 0x9a => 0x0131,
|
||||
0x1d => 0x02db, 0x87 => 0x2044, 0x91 => 0x201a, 0x9b => 0x0142,
|
||||
0x1e => 0x02da, 0x88 => 0x2039, 0x92 => 0x2122, 0x9c => 0x0153,
|
||||
0x1f => 0x02dc, 0x89 => 0x203a, 0x93 => 0xfb01, 0x9d => 0x0161,
|
||||
0x80 => 0x2022, 0x8a => 0x2212, 0x94 => 0xfb02, 0x9e => 0x017e,
|
||||
0x81 => 0x2020, 0x8b => 0x2030, 0x95 => 0x0141, 0xa0 => 0x20ac,
|
||||
);
|
||||
|
||||
1; # end
|
||||
1835
.resources/nix/bin/lib/Image/ExifTool/Charset/ShiftJIS.pm
Normal file
1835
.resources/nix/bin/lib/Image/ExifTool/Charset/ShiftJIS.pm
Normal file
File diff suppressed because it is too large
Load diff
54
.resources/nix/bin/lib/Image/ExifTool/Charset/Symbol.pm
Normal file
54
.resources/nix/bin/lib/Image/ExifTool/Charset/Symbol.pm
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Symbol.pm
|
||||
#
|
||||
# Description: Symbol to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://blogs.msdn.com/michkap/archive/2005/11/08/490495.aspx
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode.
|
||||
# This set re-maps characters with codepoints less than 0x80
|
||||
# (Although all bytes >= 0x20 should be mapped according to the
|
||||
# reference, I didn't map chars below 0x80 because I have some
|
||||
# samples where these are regular ASCII characters, even though
|
||||
# I think the encoding is probably incorrect for these samples)
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Symbol = (
|
||||
0x80 => 0xf080, 0x81 => 0xf081, 0x82 => 0xf082, 0x83 => 0xf083,
|
||||
0x84 => 0xf084, 0x85 => 0xf085, 0x86 => 0xf086, 0x87 => 0xf087,
|
||||
0x88 => 0xf088, 0x89 => 0xf089, 0x8a => 0xf08a, 0x8b => 0xf08b,
|
||||
0x8c => 0xf08c, 0x8d => 0xf08d, 0x8e => 0xf08e, 0x8f => 0xf08f,
|
||||
0x90 => 0xf090, 0x91 => 0xf091, 0x92 => 0xf092, 0x93 => 0xf093,
|
||||
0x94 => 0xf094, 0x95 => 0xf095, 0x96 => 0xf096, 0x97 => 0xf097,
|
||||
0x98 => 0xf098, 0x99 => 0xf099, 0x9a => 0xf09a, 0x9b => 0xf09b,
|
||||
0x9c => 0xf09c, 0x9d => 0xf09d, 0x9e => 0xf09e, 0x9f => 0xf09f,
|
||||
0xa0 => 0xf0a0, 0xa1 => 0xf0a1, 0xa2 => 0xf0a2, 0xa3 => 0xf0a3,
|
||||
0xa4 => 0xf0a4, 0xa5 => 0xf0a5, 0xa6 => 0xf0a6, 0xa7 => 0xf0a7,
|
||||
0xa8 => 0xf0a8, 0xa9 => 0xf0a9, 0xaa => 0xf0aa, 0xab => 0xf0ab,
|
||||
0xac => 0xf0ac, 0xad => 0xf0ad, 0xae => 0xf0ae, 0xaf => 0xf0af,
|
||||
0xb0 => 0xf0b0, 0xb1 => 0xf0b1, 0xb2 => 0xf0b2, 0xb3 => 0xf0b3,
|
||||
0xb4 => 0xf0b4, 0xb5 => 0xf0b5, 0xb6 => 0xf0b6, 0xb7 => 0xf0b7,
|
||||
0xb8 => 0xf0b8, 0xb9 => 0xf0b9, 0xba => 0xf0ba, 0xbb => 0xf0bb,
|
||||
0xbc => 0xf0bc, 0xbd => 0xf0bd, 0xbe => 0xf0be, 0xbf => 0xf0bf,
|
||||
0xc0 => 0xf0c0, 0xc1 => 0xf0c1, 0xc2 => 0xf0c2, 0xc3 => 0xf0c3,
|
||||
0xc4 => 0xf0c4, 0xc5 => 0xf0c5, 0xc6 => 0xf0c6, 0xc7 => 0xf0c7,
|
||||
0xc8 => 0xf0c8, 0xc9 => 0xf0c9, 0xca => 0xf0ca, 0xcb => 0xf0cb,
|
||||
0xcc => 0xf0cc, 0xcd => 0xf0cd, 0xce => 0xf0ce, 0xcf => 0xf0cf,
|
||||
0xd0 => 0xf0d0, 0xd1 => 0xf0d1, 0xd2 => 0xf0d2, 0xd3 => 0xf0d3,
|
||||
0xd4 => 0xf0d4, 0xd5 => 0xf0d5, 0xd6 => 0xf0d6, 0xd7 => 0xf0d7,
|
||||
0xd8 => 0xf0d8, 0xd9 => 0xf0d9, 0xda => 0xf0da, 0xdb => 0xf0db,
|
||||
0xdc => 0xf0dc, 0xdd => 0xf0dd, 0xde => 0xf0de, 0xdf => 0xf0df,
|
||||
0xe0 => 0xf0e0, 0xe1 => 0xf0e1, 0xe2 => 0xf0e2, 0xe3 => 0xf0e3,
|
||||
0xe4 => 0xf0e4, 0xe5 => 0xf0e5, 0xe6 => 0xf0e6, 0xe7 => 0xf0e7,
|
||||
0xe8 => 0xf0e8, 0xe9 => 0xf0e9, 0xea => 0xf0ea, 0xeb => 0xf0eb,
|
||||
0xec => 0xf0ec, 0xed => 0xf0ed, 0xee => 0xf0ee, 0xef => 0xf0ef,
|
||||
0xf0 => 0xf0f0, 0xf1 => 0xf0f1, 0xf2 => 0xf0f2, 0xf3 => 0xf0f3,
|
||||
0xf4 => 0xf0f4, 0xf5 => 0xf0f5, 0xf6 => 0xf0f6, 0xf7 => 0xf0f7,
|
||||
0xf8 => 0xf0f8, 0xf9 => 0xf0f9, 0xfa => 0xf0fa, 0xfb => 0xf0fb,
|
||||
0xfc => 0xf0fc, 0xfd => 0xf0fd, 0xfe => 0xf0fe, 0xff => 0xf0ff,
|
||||
);
|
||||
|
||||
1; # end
|
||||
41
.resources/nix/bin/lib/Image/ExifTool/Charset/Thai.pm
Normal file
41
.resources/nix/bin/lib/Image/ExifTool/Charset/Thai.pm
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Thai.pm
|
||||
#
|
||||
# Description: cp874 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP874.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Thai = (
|
||||
0x80 => 0x20ac, 0x85 => 0x2026, 0x91 => 0x2018, 0x92 => 0x2019,
|
||||
0x93 => 0x201c, 0x94 => 0x201d, 0x95 => 0x2022, 0x96 => 0x2013,
|
||||
0x97 => 0x2014, 0xa1 => 0x0e01, 0xa2 => 0x0e02, 0xa3 => 0x0e03,
|
||||
0xa4 => 0x0e04, 0xa5 => 0x0e05, 0xa6 => 0x0e06, 0xa7 => 0x0e07,
|
||||
0xa8 => 0x0e08, 0xa9 => 0x0e09, 0xaa => 0x0e0a, 0xab => 0x0e0b,
|
||||
0xac => 0x0e0c, 0xad => 0x0e0d, 0xae => 0x0e0e, 0xaf => 0x0e0f,
|
||||
0xb0 => 0x0e10, 0xb1 => 0x0e11, 0xb2 => 0x0e12, 0xb3 => 0x0e13,
|
||||
0xb4 => 0x0e14, 0xb5 => 0x0e15, 0xb6 => 0x0e16, 0xb7 => 0x0e17,
|
||||
0xb8 => 0x0e18, 0xb9 => 0x0e19, 0xba => 0x0e1a, 0xbb => 0x0e1b,
|
||||
0xbc => 0x0e1c, 0xbd => 0x0e1d, 0xbe => 0x0e1e, 0xbf => 0x0e1f,
|
||||
0xc0 => 0x0e20, 0xc1 => 0x0e21, 0xc2 => 0x0e22, 0xc3 => 0x0e23,
|
||||
0xc4 => 0x0e24, 0xc5 => 0x0e25, 0xc6 => 0x0e26, 0xc7 => 0x0e27,
|
||||
0xc8 => 0x0e28, 0xc9 => 0x0e29, 0xca => 0x0e2a, 0xcb => 0x0e2b,
|
||||
0xcc => 0x0e2c, 0xcd => 0x0e2d, 0xce => 0x0e2e, 0xcf => 0x0e2f,
|
||||
0xd0 => 0x0e30, 0xd1 => 0x0e31, 0xd2 => 0x0e32, 0xd3 => 0x0e33,
|
||||
0xd4 => 0x0e34, 0xd5 => 0x0e35, 0xd6 => 0x0e36, 0xd7 => 0x0e37,
|
||||
0xd8 => 0x0e38, 0xd9 => 0x0e39, 0xda => 0x0e3a, 0xdf => 0x0e3f,
|
||||
0xe0 => 0x0e40, 0xe1 => 0x0e41, 0xe2 => 0x0e42, 0xe3 => 0x0e43,
|
||||
0xe4 => 0x0e44, 0xe5 => 0x0e45, 0xe6 => 0x0e46, 0xe7 => 0x0e47,
|
||||
0xe8 => 0x0e48, 0xe9 => 0x0e49, 0xea => 0x0e4a, 0xeb => 0x0e4b,
|
||||
0xec => 0x0e4c, 0xed => 0x0e4d, 0xee => 0x0e4e, 0xef => 0x0e4f,
|
||||
0xf0 => 0x0e50, 0xf1 => 0x0e51, 0xf2 => 0x0e52, 0xf3 => 0x0e53,
|
||||
0xf4 => 0x0e54, 0xf5 => 0x0e55, 0xf6 => 0x0e56, 0xf7 => 0x0e57,
|
||||
0xf8 => 0x0e58, 0xf9 => 0x0e59, 0xfa => 0x0e5a, 0xfb => 0x0e5b,
|
||||
);
|
||||
|
||||
1; # end
|
||||
25
.resources/nix/bin/lib/Image/ExifTool/Charset/Turkish.pm
Normal file
25
.resources/nix/bin/lib/Image/ExifTool/Charset/Turkish.pm
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Turkish.pm
|
||||
#
|
||||
# Description: cp1254 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1254.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Turkish = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e,
|
||||
0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6,
|
||||
0x89 => 0x2030, 0x8a => 0x0160, 0x8b => 0x2039, 0x8c => 0x0152,
|
||||
0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201c, 0x94 => 0x201d,
|
||||
0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, 0x98 => 0x02dc,
|
||||
0x99 => 0x2122, 0x9a => 0x0161, 0x9b => 0x203a, 0x9c => 0x0153,
|
||||
0x9f => 0x0178, 0xd0 => 0x011e, 0xdd => 0x0130, 0xde => 0x015e,
|
||||
0xf0 => 0x011f, 0xfd => 0x0131, 0xfe => 0x015f,
|
||||
);
|
||||
|
||||
1; # end
|
||||
27
.resources/nix/bin/lib/Image/ExifTool/Charset/Vietnam.pm
Normal file
27
.resources/nix/bin/lib/Image/ExifTool/Charset/Vietnam.pm
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Vietnam.pm
|
||||
#
|
||||
# Description: cp1258 to Unicode
|
||||
#
|
||||
# Revisions: 2010/01/20 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1258.TXT
|
||||
#
|
||||
# Notes: The table omits 1-byte characters with the same values as Unicode
|
||||
#------------------------------------------------------------------------------
|
||||
use strict;
|
||||
|
||||
%Image::ExifTool::Charset::Vietnam = (
|
||||
0x80 => 0x20ac, 0x82 => 0x201a, 0x83 => 0x0192, 0x84 => 0x201e,
|
||||
0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02c6,
|
||||
0x89 => 0x2030, 0x8b => 0x2039, 0x8c => 0x0152, 0x91 => 0x2018,
|
||||
0x92 => 0x2019, 0x93 => 0x201c, 0x94 => 0x201d, 0x95 => 0x2022,
|
||||
0x96 => 0x2013, 0x97 => 0x2014, 0x98 => 0x02dc, 0x99 => 0x2122,
|
||||
0x9b => 0x203a, 0x9c => 0x0153, 0x9f => 0x0178, 0xc3 => 0x0102,
|
||||
0xcc => 0x0300, 0xd0 => 0x0110, 0xd2 => 0x0309, 0xd5 => 0x01a0,
|
||||
0xdd => 0x01af, 0xde => 0x0303, 0xe3 => 0x0103, 0xec => 0x0301,
|
||||
0xf0 => 0x0111, 0xf2 => 0x0323, 0xf5 => 0x01a1, 0xfd => 0x01b0,
|
||||
0xfe => 0x20ab,
|
||||
);
|
||||
|
||||
1; # end
|
||||
3884
.resources/nix/bin/lib/Image/ExifTool/DICOM.pm
Normal file
3884
.resources/nix/bin/lib/Image/ExifTool/DICOM.pm
Normal file
File diff suppressed because it is too large
Load diff
893
.resources/nix/bin/lib/Image/ExifTool/DJI.pm
Normal file
893
.resources/nix/bin/lib/Image/ExifTool/DJI.pm
Normal file
|
|
@ -0,0 +1,893 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DJI.pm
|
||||
#
|
||||
# Description: DJI Phantom maker notes tags
|
||||
#
|
||||
# Revisions: 2016-07-25 - P. Harvey Created
|
||||
# 2017-06-23 - PH Added XMP tags
|
||||
# 2024-12-04 - PH Added protobuf tags
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DJI;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION %knownProtocol);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::Exif;
|
||||
use Image::ExifTool::XMP;
|
||||
use Image::ExifTool::GPS;
|
||||
use Image::ExifTool::Protobuf;
|
||||
|
||||
$VERSION = '1.16';
|
||||
|
||||
sub ProcessDJIInfo($$$);
|
||||
sub ProcessSettings($$$);
|
||||
|
||||
%knownProtocol = (
|
||||
'dvtm_ac203.proto' => 1, # Osmo Action 4
|
||||
'dvtm_ac204.proto' => 1, # Osmo Action 5
|
||||
'dvtm_AVATA2.proto' => 1, # Avata 2
|
||||
'dvtm_wm265e.proto' => 1, # Mavic 3
|
||||
'dvtm_pm320.proto' => 1, # Matrice 30
|
||||
'dvtm_Mini4_Pro.proto' => 1, # Mini 4
|
||||
'dvtm_dji_neo.proto' => 1, # Neo
|
||||
'dvtm_Air3.proto' => 1, # Air 3
|
||||
'dvtm_Air3s.proto' => 1, # Air 3s
|
||||
'dvtm_PP-101.proto' => 1, # Osmo Pocket 3
|
||||
'dvtm_oq101.proto' => 1, # Osmo 360
|
||||
'dvtm_wa345e.proto' => 1, # Matrice 4E
|
||||
'dvtm_wm261.proto' => 1, # Mavic Pro 3
|
||||
# dvtm_wm169.proto seems to be DJI O3 but the structure is unknown
|
||||
);
|
||||
|
||||
|
||||
my %convFloat2 = (
|
||||
PrintConv => 'sprintf("%+.2f", $val)',
|
||||
PrintConvInv => '$val',
|
||||
);
|
||||
|
||||
# DJI maker notes (ref PH, mostly educated guesses based on DJI QuickTime::UserData tags)
|
||||
%Image::ExifTool::DJI::Main = (
|
||||
WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
|
||||
CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
NOTES => q{
|
||||
This table lists tags found in the maker notes of images from some DJI
|
||||
Phantom drones.
|
||||
},
|
||||
0x01 => { Name => 'Make', Writable => 'string' },
|
||||
# 0x02 - int8u[4]: "1 0 0 0", "1 1 0 0"
|
||||
0x03 => { Name => 'SpeedX', Writable => 'float', %convFloat2 }, # (guess)
|
||||
0x04 => { Name => 'SpeedY', Writable => 'float', %convFloat2 }, # (guess)
|
||||
0x05 => { Name => 'SpeedZ', Writable => 'float', %convFloat2 }, # (guess)
|
||||
0x06 => { Name => 'Pitch', Writable => 'float', %convFloat2 },
|
||||
0x07 => { Name => 'Yaw', Writable => 'float', %convFloat2 },
|
||||
0x08 => { Name => 'Roll', Writable => 'float', %convFloat2 },
|
||||
0x09 => { Name => 'CameraPitch',Writable => 'float', %convFloat2 },
|
||||
0x0a => { Name => 'CameraYaw', Writable => 'float', %convFloat2 },
|
||||
0x0b => { Name => 'CameraRoll', Writable => 'float', %convFloat2 },
|
||||
);
|
||||
|
||||
# DJI debug maker notes
|
||||
%Image::ExifTool::DJI::Info = (
|
||||
PROCESS_PROC => \&ProcessDJIInfo,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
NOTES => 'Tags written by some DJI drones.',
|
||||
VARS => { LONG_TAGS => 2 },
|
||||
ae_dbg_info => { Name => 'AEDebugInfo' },
|
||||
ae_histogram_info => { Name => 'AEHistogramInfo' },
|
||||
ae_local_histogram => { Name => 'AELocalHistogram' },
|
||||
ae_liveview_histogram_info => { Name => 'AELiveViewHistogramInfo' },
|
||||
ae_liveview_local_histogram => { Name => 'AELiveViewLocalHistogram' },
|
||||
awb_dbg_info => { Name => 'AWBDebugInfo' },
|
||||
af_dbg_info => { Name => 'AFDebugInfo' },
|
||||
hiso => { Name => 'Histogram' },
|
||||
xidiri => { Name => 'Xidiri' },
|
||||
'GimbalDegree(Y,P,R)'=> { Name => 'GimbalDegree' },
|
||||
'FlightDegree(Y,P,R)'=> { Name => 'FlightDegree' },
|
||||
adj_dbg_info => { Name => 'ADJDebugInfo' },
|
||||
sensor_id => { Name => 'SensorID' },
|
||||
'FlightSpeed(X,Y,Z)' => { Name => 'FlightSpeed' },
|
||||
hyperlapse_dbg_info => { Name => 'HyperlapsDebugInfo' },
|
||||
);
|
||||
|
||||
# thermal parameters in APP4 of DJI ZH20T images (ref forum11401)
|
||||
%Image::ExifTool::DJI::ThermalParams = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'APP4', 2 => 'Image' },
|
||||
NOTES => 'Thermal parameters extracted from APP4 of DJI RJPEG files from the ZH20T.',
|
||||
# 0x00 - 0xaa551206 - temperature header magic number
|
||||
0x24 => { Name => 'K1', Format => 'float' },
|
||||
0x28 => { Name => 'K2', Format => 'float' },
|
||||
0x2c => { Name => 'K3', Format => 'float' },
|
||||
0x30 => { Name => 'K4', Format => 'float' },
|
||||
0x34 => { Name => 'KF', Format => 'float' },
|
||||
0x38 => { Name => 'B1', Format => 'float' },
|
||||
0x3c => { Name => 'B2', Format => 'float' },
|
||||
0x44 => { Name => 'ObjectDistance', Format => 'int16u' },
|
||||
0x46 => { Name => 'RelativeHumidity', Format => 'int16u' },
|
||||
0x48 => { Name => 'Emissivity', Format => 'int16u' },
|
||||
0x4a => { Name => 'Reflection', Format => 'int16u', },
|
||||
0x4c => { Name => 'AmbientTemperature', Format => 'int16u' }, # (aka D1)
|
||||
0x50 => { Name => 'D2', Format => 'int32s' },
|
||||
0x54 => { Name => 'KJ', Format => 'int16u' },
|
||||
0x56 => { Name => 'DB', Format => 'int16u' },
|
||||
0x58 => { Name => 'KK', Format => 'int16u' },
|
||||
# 0x500 - 0x55aa1206 - device header magic number
|
||||
# (nothing yet decoded from device header)
|
||||
);
|
||||
|
||||
# thermal parameters in APP4 of DJI M3T, H20N, M2EA and some M30T images (ref PH/forum11401)
|
||||
%Image::ExifTool::DJI::ThermalParams2 = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'APP4', 2 => 'Image' },
|
||||
NOTES => 'Thermal parameters extracted from APP4 of DJI M3T RJPEG files.',
|
||||
0x00 => { Name => 'AmbientTemperature', Format => 'float', PrintConv => 'sprintf("%.1f C",$val)' }, # (NC)
|
||||
0x04 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.1f m",$val)' },
|
||||
0x08 => { Name => 'Emissivity', Format => 'float', PrintConv => 'sprintf("%.2f",$val)' },
|
||||
0x0c => { Name => 'RelativeHumidity', Format => 'float', PrintConv => 'sprintf("%g %%",$val*100)' },
|
||||
0x10 => { Name => 'ReflectedTemperature',Format => 'float', PrintConv => 'sprintf("%.1f C",$val)' },
|
||||
0x65 => { Name => 'IDString', Format => 'string[16]' }, # (NC)
|
||||
);
|
||||
|
||||
# thermal parameters in APP4 of some DJI M30T images (ref PH)
|
||||
%Image::ExifTool::DJI::ThermalParams3 = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'APP4', 2 => 'Image' },
|
||||
NOTES => 'Thermal parameters extracted from APP4 of some DJI RJPEG files.',
|
||||
# 0x00 - 0xaa553800 - params3 magic number
|
||||
0x04 => { Name => 'RelativeHumidity', Format => 'int16u' },
|
||||
0x06 => { Name => 'ObjectDistance', Format => 'int16u', ValueConv => '$val / 10' },
|
||||
0x08 => { Name => 'Emissivity', Format => 'int16u', ValueConv => '$val / 100' },
|
||||
0x0a => { Name => 'ReflectedTemperature',Format => 'int16u', ValueConv => '$val / 10' },
|
||||
);
|
||||
|
||||
%Image::ExifTool::DJI::XMP = (
|
||||
%Image::ExifTool::XMP::xmpTableDefaults,
|
||||
GROUPS => { 0 => 'XMP', 1 => 'XMP-drone-dji', 2 => 'Location' },
|
||||
NAMESPACE => 'drone-dji',
|
||||
TABLE_DESC => 'XMP DJI',
|
||||
VARS => { ID_FMT => 'none' },
|
||||
NOTES => 'XMP tags used by DJI for images from drones.',
|
||||
AbsoluteAltitude => { Writable => 'real' },
|
||||
RelativeAltitude => { Writable => 'real' },
|
||||
GimbalRollDegree => { Writable => 'real' },
|
||||
GimbalYawDegree => { Writable => 'real' },
|
||||
GimbalPitchDegree => { Writable => 'real' },
|
||||
FlightRollDegree => { Writable => 'real' },
|
||||
FlightYawDegree => { Writable => 'real' },
|
||||
FlightPitchDegree => { Writable => 'real' },
|
||||
GpsLatitude => {
|
||||
Name => 'GPSLatitude',
|
||||
Writable => 'real',
|
||||
Avoid => 1,
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
|
||||
},
|
||||
GpsLongtitude => { # [sic] (misspelt in DJI original file)
|
||||
Name => 'GPSLongtitude',
|
||||
Writable => 'real',
|
||||
Avoid => 1, # (in case someone tries to write "GPSLong*")
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
|
||||
},
|
||||
GpsLongitude => { #PH (NC)
|
||||
Name => 'GPSLongitude',
|
||||
Writable => 'real',
|
||||
Avoid => 1,
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
|
||||
},
|
||||
FlightXSpeed => { Writable => 'real' },
|
||||
FlightYSpeed => { Writable => 'real' },
|
||||
FlightZSpeed => { Writable => 'real' },
|
||||
CamReverse => { }, # integer?
|
||||
GimbalReverse => { }, # integer?
|
||||
SelfData => { Groups => { 2 => 'Image' } },
|
||||
CalibratedFocalLength => { Writable => 'real', Groups => { 2 => 'Image' } },
|
||||
CalibratedOpticalCenterX => { Writable => 'real', Groups => { 2 => 'Image' } },
|
||||
CalibratedOpticalCenterY => { Writable => 'real', Groups => { 2 => 'Image' } },
|
||||
RtkFlag => { }, # integer?
|
||||
RtkStdLon => { Writable => 'real' },
|
||||
RtkStdLat => { Writable => 'real' },
|
||||
RtkStdHgt => { Writable => 'real' },
|
||||
DewarpData => { Groups => { 2 => 'Image' } },
|
||||
DewarpFlag => { Groups => { 2 => 'Image' } }, # integer?
|
||||
Latitude => {
|
||||
Name => 'Latitude',
|
||||
Writable => 'real',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
|
||||
},
|
||||
Longitude => {
|
||||
Name => 'Longitude',
|
||||
Writable => 'real',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::DJI::Glamour = (
|
||||
GROUPS => { 0 => 'QuickTime', 1 => 'DJI', 2 => 'Image' },
|
||||
PROCESS_PROC => \&Image::ExifTool::DJI::ProcessSettings,
|
||||
NOTES => 'Glamour settings used by some DJI models.',
|
||||
beauty_enable => 'BeautyEnable',
|
||||
smoother => 'Smoother',
|
||||
whitening => 'Whitening',
|
||||
face_slimming => 'FaceSlimming',
|
||||
eye_enlarge => 'EyeEnlarge',
|
||||
nose_slimming => 'NoseSlimming',
|
||||
mouth_beautify => 'MouthModify',
|
||||
teeth_whitening => 'TeethWhitening',
|
||||
leg_longer => 'LegLonger',
|
||||
head_shrinking => 'HeadShrinking',
|
||||
lipstick => 'Lipstick',
|
||||
blush => 'Blush',
|
||||
dark_circle => 'DarkCircle',
|
||||
acne_spot_removal=>'AcneSpotRemoval',
|
||||
eyebrows => 'Eyebrows',
|
||||
);
|
||||
|
||||
# metadata in protobuf format (djmd and dbgi meta types, ref PH)
|
||||
%Image::ExifTool::DJI::Protobuf = (
|
||||
GROUPS => { 0 => 'Protobuf', 1 => 'DJI', 2 => 'Camera' },
|
||||
TAG_PREFIX => '',
|
||||
PROCESS_PROC => \&Image::ExifTool::Protobuf::ProcessProtobuf,
|
||||
NOTES => q{
|
||||
Tags found in protobuf-format DJI djmd and dbgi timed metadata. The known
|
||||
tags listed below are extracted by default, but unknown djmd tags may be
|
||||
extracted as well by setting the Unknown option to 1, or 2 to also extract
|
||||
unknown dbgi debug tags. Tag ID's are composed of the corresponding .proto
|
||||
file name combined with the hierarchical protobuf field numbers.
|
||||
|
||||
ExifTool currently extracts timed GPS plus a few other tags from DJI devices
|
||||
which use the following protocols: dvtm_AVATA2.proto (Avata 2),
|
||||
dvtm_ac203.proto (Osmo Action 4), dvtm_ac204.proto (Osmo Action 5),
|
||||
dvtm_wm265e.proto (Mavic 3), dvtm_pm320.proto (Matrice 30),
|
||||
dvtm_Mini4_Pro.proto (Mini 4 Pro), dvtm_dji_neo.proto (DJI Neo),
|
||||
dvtm_Air3.proto (Air 3), dvtm_Air3s.proto (Air 3s), dvtm_PP-101.proto (Osmo
|
||||
Pocket 3), dvtm_oq101.proto (Osmo 360), dvtm_wa345e.proto (Matrice 4E) and
|
||||
dvtm_wm261.proto (Mavic Pro 3).
|
||||
|
||||
Note that with the protobuf format, numerical tags missing from the output
|
||||
for a given protocol should be considered to have the default value of 0.
|
||||
},
|
||||
Protocol => {
|
||||
Notes => "typically protobuf field 1-1-1, but ExifTool doesn't rely on this",
|
||||
RawConv => q{
|
||||
unless ($Image::ExifTool::DJI::knownProtocol{$val}) {
|
||||
$self->Warn("Unknown protocol $val (please submit sample for testing)");
|
||||
}
|
||||
return $val;
|
||||
},
|
||||
},
|
||||
#
|
||||
# Osmo Action 4
|
||||
#
|
||||
'dvtm_ac203_1-1-5' => { Name => 'SerialNumber', Notes => 'Osmo Action 4' }, # (NC)
|
||||
# dvtm_ac203_1-1-6 - some version number
|
||||
'dvtm_ac203_1-1-10' => 'Model',
|
||||
'dvtm_ac203_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_ac203_3-2-2-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_ac203_3-2-4-1' => { # (NC)
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_ac203_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' }, # (NC)
|
||||
# dvtm_ac203_3-2-9-1 - looks like Z accerometer measurement, but 2 and 3 don't look like other components
|
||||
'dvtm_ac203_3-2-10-2' => { Name => 'AccelerometerX', Format => 'float' } , # (NC) left/right
|
||||
'dvtm_ac203_3-2-10-3' => { Name => 'AccelerometerY', Format => 'float' } , # (NC) front/back
|
||||
'dvtm_ac203_3-2-10-4' => { Name => 'AccelerometerZ', Format => 'float' } , # (NC) up/down
|
||||
# dvtm_ac203_3-4-1-4 - model code?
|
||||
'dvtm_ac203_3-4-2-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_ac203_3-4-2-2' => {
|
||||
Name => 'GPSAltitude',
|
||||
Groups => { 2 => 'Location' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1000',
|
||||
},
|
||||
'dvtm_ac203_3-4-2-6-1' => {
|
||||
Name => 'GPSDateTime',
|
||||
Format => 'string',
|
||||
Groups => { 2 => 'Time' },
|
||||
RawConv => '$$self{GPSDateTime} = $val',
|
||||
ValueConv => '$val =~ tr/-/:/; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
#
|
||||
# Osmo Action 5
|
||||
#
|
||||
'dvtm_ac204_1-1-5' => { Name => 'SerialNumber', Notes => 'Osmo Action 5' }, # (NC)
|
||||
# dvtm_ac204_1-1-6 - some version number
|
||||
'dvtm_ac204_1-1-10' => 'Model',
|
||||
'dvtm_ac204_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_ac204_3-2-4-1' => { # (NC)
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_ac204_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' }, # (NC)
|
||||
'dvtm_ac204_3-2-10-2' => { Name => 'AccelerometerX', Format => 'float' } , # (NC) left/right
|
||||
'dvtm_ac204_3-2-10-3' => { Name => 'AccelerometerY', Format => 'float' } , # (NC) front/back
|
||||
'dvtm_ac204_3-2-10-4' => { Name => 'AccelerometerZ', Format => 'float' } , # (NC) up/down
|
||||
# dvtm_ac204_3-4-1-4 - model code?
|
||||
'dvtm_ac204_3-4-2-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_ac204_3-4-2-2' => {
|
||||
Name => 'GPSAltitude',
|
||||
Groups => { 2 => 'Location' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1000',
|
||||
},
|
||||
'dvtm_ac204_3-4-2-6-1' => {
|
||||
Name => 'GPSDateTime',
|
||||
Format => 'string',
|
||||
Groups => { 2 => 'Time' },
|
||||
RawConv => '$$self{GPSDateTime} = $val',
|
||||
ValueConv => '$val =~ tr/-/:/; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
#
|
||||
# Avata 2
|
||||
#
|
||||
# dvtm_AVATA2_1-1-2 - some version number
|
||||
# dvtm_AVATA2_1-1-3 - some version number
|
||||
'dvtm_AVATA2_1-1-5' => { Name => 'SerialNumber', Notes => 'Avata 2' }, # (NC)
|
||||
'dvtm_AVATA2_1-1-10' => 'Model',
|
||||
# dvtm_AVATA2_2-2-1-4 - model code?
|
||||
'dvtm_AVATA2_2-2-3-1' => 'SerialNumber2', # (NC)
|
||||
'dvtm_AVATA2_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
# dvtm_AVATA2_3-1-1 - frame number (starting at 1)
|
||||
'dvtm_AVATA2_3-1-2' => { # (also 3-2-1-6 and 3-4-1-6)
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
# milliseconds, but I don't know what the zero is
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
# dvtm_AVATA2_3-2-1-4 - model code?
|
||||
# dvtm_AVATA2_3-2-1-5 - frame rate?
|
||||
'dvtm_AVATA2_3-2-2-1' => { Name => 'ISO', Format => 'float' }, # (NC)
|
||||
'dvtm_AVATA2_3-2-4-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_AVATA2_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' }, # (NC)
|
||||
'dvtm_AVATA2_3-2-10-1' => { # (NC)
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
# dvtm_AVATA2_3-4-1-4 - model code?
|
||||
'dvtm_AVATA2_3-4-3' => { # (NC)
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_AVATA2_3-4-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_AVATA2_3-4-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' }, # (NC)
|
||||
'dvtm_AVATA2_3-4-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' }, # (NC)
|
||||
#
|
||||
# Mavic 3
|
||||
#
|
||||
'dvtm_wm265e_1-1-5' => { Name => 'SerialNumber', Notes => 'Mavic 3' }, # (confirmed)
|
||||
'dvtm_wm265e_1-1-10' => 'Model',
|
||||
'dvtm_wm265e_2-2' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
# dvtm_wm265e_3-2-1-4 - model code?
|
||||
'dvtm_wm265e_3-2-2-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_wm265e_3-2-3-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
# dvtm_wm265e_3-2-5-1 - unknown rational (xxxx / 1000)
|
||||
'dvtm_wm265e_3-2-6-1' => { Name => 'DigitalZoom', Format => 'float' },
|
||||
'dvtm_wm265e_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_wm265e_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_wm265e_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_wm265e_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' },
|
||||
'dvtm_wm265e_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
#
|
||||
# Matrice 30
|
||||
#
|
||||
'dvtm_pm320_1-1-5' => { Name => 'SerialNumber', Notes => 'Matrice 30' },
|
||||
'dvtm_pm320_1-1-10' => 'Model',
|
||||
'dvtm_pm320_2-2' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_pm320_3-2-2-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_pm320_3-2-3-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_pm320_3-2-4-1' => { # (NC)
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
'dvtm_pm320_3-2-6-1' => { Name => 'DigitalZoom', Format => 'float' },
|
||||
'dvtm_pm320_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_pm320_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_pm320_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_pm320_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' },
|
||||
'dvtm_pm320_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
#
|
||||
# Mini 4 Pro
|
||||
#
|
||||
'dvtm_Mini4_Pro_1-1-5' => { Name => 'SerialNumber', Notes => 'Mini 4 Pro' },
|
||||
'dvtm_Mini4_Pro_1-1-10' => 'Model',
|
||||
'dvtm_Mini4_Pro_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_Mini4_Pro_3-2-7-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_Mini4_Pro_3-2-10-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_Mini4_Pro_3-2-11-1' => {
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
'dvtm_Mini4_Pro_3-2-32-1' => { Name => 'ColorTemperature', Format => 'unsigned' },
|
||||
# dvtm_Mini4_Pro_3-2-37-1 - something to do with battery level or time remaining?
|
||||
'dvtm_Mini4_Pro_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_Mini4_Pro_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_Mini4_Pro_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_Mini4_Pro_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' }, # (NC)
|
||||
'dvtm_Mini4_Pro_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
#
|
||||
# DJI Neo (very similar to AVATA2)
|
||||
#
|
||||
# dvtm_dji_neo_1-1-2 - some version number
|
||||
# dvtm_dji_neo_1-1-3 - some version number
|
||||
'dvtm_dji_neo_1-1-5' => { Name => 'SerialNumber', Notes => 'DJI Neo' }, # (NC)
|
||||
'dvtm_dji_neo_1-1-10' => 'Model',
|
||||
# dvtm_dji_neo_2-2-1-4 - model code?
|
||||
# dvtm_dji_neo_2-2-2-1 - some firmware version?
|
||||
# dvtm_dji_neo_2-2-2-2 - some version number?
|
||||
'dvtm_dji_neo_2-2-3-1' => 'SerialNumber2', # (NC)
|
||||
'dvtm_dji_neo_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
# dvtm_dji_neo_3-1-1 - frame number (starting at 1)
|
||||
'dvtm_dji_neo_3-1-2' => { # (also 3-2-1-6 and 3-4-1-6)
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
# milliseconds, but I don't know what the zero is
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
# dvtm_dji_neo_3-2-1-4 - model code?
|
||||
# dvtm_dji_neo_3-2-1-5 - frame rate?
|
||||
'dvtm_dji_neo_3-2-2-1' => { Name => 'ISO', Format => 'float' }, # (NC)
|
||||
'dvtm_dji_neo_3-2-4-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_dji_neo_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' }, # (NC)
|
||||
'dvtm_dji_neo_3-2-10-1' => { # (NC)
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
# dvtm_dji_neo_3-4-1-4 - model code?
|
||||
'dvtm_dji_neo_3-4-3' => { # (NC)
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_dji_neo_3-4-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_dji_neo_3-4-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' }, # (NC)
|
||||
#
|
||||
# Air 3
|
||||
#
|
||||
'dvtm_Air3_1-1-5' => { Name => 'SerialNumber', Notes => 'Air 3' },
|
||||
'dvtm_Air3_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_Air3_3-1-2' => {
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
'dvtm_Air3_3-2-7-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_Air3_3-2-10-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_Air3_3-2-32-1' => { Name => 'ColorTemperature', Format => 'unsigned' },
|
||||
'dvtm_Air3_3-2-11-1' => {
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
'dvtm_Air3_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_Air3_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_Air3_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_Air3_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' },
|
||||
'dvtm_Air3_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
#
|
||||
# Air 3s (same structure as Air 3)
|
||||
#
|
||||
'dvtm_Air3s_1-1-5' => { Name => 'SerialNumber', Notes => 'Air 3s' },
|
||||
'dvtm_Air3s_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_Air3s_3-1-2' => {
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
'dvtm_Air3s_3-2-7-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_Air3s_3-2-10-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_Air3s_3-2-32-1' => { Name => 'ColorTemperature', Format => 'unsigned' },
|
||||
'dvtm_Air3s_3-2-11-1' => {
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
'dvtm_Air3s_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_Air3s_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_Air3s_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_Air3s_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' },
|
||||
'dvtm_Air3s_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
#
|
||||
# Osmo 360 (similar to Action 4/5)
|
||||
#
|
||||
'dvtm_oq101_1-1-5' => { Name => 'SerialNumber', Notes => 'Osmo 360' },
|
||||
'dvtm_oq101_1-1-10' => 'Model',
|
||||
'dvtm_oq101_3-1-2' => {
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
'dvtm_oq101_3-2-3-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_oq101_3-2-4-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_oq101_3-2-6-1' => { Name => 'ColorTemperature', Format => 'unsigned' },
|
||||
'dvtm_oq101_1-14-1' => {
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
'dvtm_oq101_3-4-2-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_oq101_3-4-2-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_oq101_3-4-2-6-1' => {
|
||||
Name => 'GPSDateTime',
|
||||
Format => 'string',
|
||||
Groups => { 2 => 'Time' },
|
||||
RawConv => '$$self{GPSDateTime} = $val',
|
||||
ValueConv => '$val =~ tr/-/:/; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
'dvtm_oq101_3-2-10-2' => { Name => 'AccelerometerX', Format => 'float' },
|
||||
'dvtm_oq101_3-2-10-3' => { Name => 'AccelerometerY', Format => 'float' },
|
||||
'dvtm_oq101_3-2-10-4' => { Name => 'AccelerometerZ', Format => 'float' },
|
||||
#
|
||||
# Osmo Pocket 3
|
||||
#
|
||||
'dvtm_PP-101_1-1-5' => { Name => 'SerialNumber', Notes => 'Osmo Pocket 3' },
|
||||
'dvtm_PP-101_1-1-10' => 'Model',
|
||||
'dvtm_PP-101_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_PP-101_3-1-2' => {
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
'dvtm_PP-101_3-2-9-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_PP-101_3-2-10-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_PP-101_3-2-24-1' => { Name => 'ColorTemperature', Format => 'unsigned' },
|
||||
#
|
||||
# Matrice 4E
|
||||
#
|
||||
'dvtm_wa345e_1-1-5' => { Name => 'SerialNumber', Notes => 'Matrice 4E' },
|
||||
'dvtm_wa345e_1-1-10' => 'Model',
|
||||
'dvtm_wa345e_2-2' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_wa345e_3-1-2' => {
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
'dvtm_wa345e_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_wa345e_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_wa345e_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_wa345e_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' },
|
||||
'dvtm_wa345e_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
'dvtm_wa345e_3-3-4-6-1' => {
|
||||
Name => 'GPSDateTime',
|
||||
Format => 'string',
|
||||
Groups => { 2 => 'Time' },
|
||||
RawConv => '$$self{GPSDateTime} = $val',
|
||||
ValueConv => '$val =~ tr/-/:/; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
#
|
||||
# Mavic Pro 3
|
||||
#
|
||||
'dvtm_wm261_1-1-5' => { Name => 'SerialNumber', Notes => 'Mavic Pro 3' },
|
||||
'dvtm_wm261_1-1-10' => 'Model',
|
||||
'dvtm_wm261_2-3' => {
|
||||
Name => 'FrameInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::FrameInfo' },
|
||||
},
|
||||
'dvtm_wm261_3-1-2' => {
|
||||
Name => 'TimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'unsigned',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
'dvtm_wm261_3-2-9-1' => { Name => 'ISO', Format => 'float' },
|
||||
'dvtm_wm261_3-2-10-1' => {
|
||||
Name => 'ShutterSpeed',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||||
},
|
||||
'dvtm_wm261_3-2-11-1'=> {
|
||||
Name => 'FNumber',
|
||||
Format => 'rational',
|
||||
PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
|
||||
},
|
||||
'dvtm_wm261_3-3-3' => {
|
||||
Name => 'DroneInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::DroneInfo' },
|
||||
},
|
||||
'dvtm_wm261_3-3-4-1' => {
|
||||
Name => 'GPSInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GPSInfo' },
|
||||
},
|
||||
'dvtm_wm261_3-3-4-2' => { Name => 'AbsoluteAltitude', Format => 'int64s', ValueConv => '$val / 1000' },
|
||||
'dvtm_wm261_3-3-5-1' => { Name => 'RelativeAltitude', Format => 'float', ValueConv => '$val / 1000' },
|
||||
'dvtm_wm261_3-4-3' => {
|
||||
Name => 'GimbalInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DJI::GimbalInfo' },
|
||||
},
|
||||
'dvtm_wm261_3-3-4-6-1' => {
|
||||
Name => 'GPSDateTime',
|
||||
Format => 'string',
|
||||
Groups => { 2 => 'Time' },
|
||||
RawConv => '$$self{GPSDateTime} = $val',
|
||||
ValueConv => '$val =~ tr/-/:/; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::DJI::DroneInfo = (
|
||||
GROUPS => { 0 => 'Protobuf', 1 => 'DJI', 2 => 'Camera' },
|
||||
PROCESS_PROC => \&Image::ExifTool::Protobuf::ProcessProtobuf,
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Field #' },
|
||||
1 => { Name => 'DroneRoll', Format => 'int64s', ValueConv => '$val / 10' },
|
||||
2 => { Name => 'DronePitch', Format => 'int64s', ValueConv => '$val / 10' },
|
||||
3 => { Name => 'DroneYaw', Format => 'int64s', ValueConv => '$val / 10' },
|
||||
);
|
||||
|
||||
%Image::ExifTool::DJI::GimbalInfo = (
|
||||
GROUPS => { 0 => 'Protobuf', 1 => 'DJI', 2 => 'Camera' },
|
||||
PROCESS_PROC => \&Image::ExifTool::Protobuf::ProcessProtobuf,
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Field #' },
|
||||
1 => { Name => 'GimbalPitch',Format => 'int64s', ValueConv => '$val / 10' },
|
||||
2 => { Name => 'GimbalRoll', Format => 'int64s', ValueConv => '$val / 10' }, # usually 0, so missing
|
||||
3 => { Name => 'GimbalYaw', Format => 'int64s', ValueConv => '$val / 10' },
|
||||
);
|
||||
|
||||
%Image::ExifTool::DJI::FrameInfo = (
|
||||
GROUPS => { 0 => 'Protobuf', 1 => 'DJI', 2 => 'Video' },
|
||||
PROCESS_PROC => \&Image::ExifTool::Protobuf::ProcessProtobuf,
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Field #' },
|
||||
1 => { Name => 'FrameWidth', Format => 'unsigned' },
|
||||
2 => { Name => 'FrameHeight', Format => 'unsigned' },
|
||||
3 => { Name => 'FrameRate', Format => 'float' },
|
||||
# 4-8: seen these values respectively for DJI Neo: 1,8,4,1,4
|
||||
);
|
||||
|
||||
%Image::ExifTool::DJI::GPSInfo = (
|
||||
GROUPS => { 0 => 'Protobuf', 1 => 'DJI', 2 => 'Location' },
|
||||
PROCESS_PROC => \&Image::ExifTool::Protobuf::ProcessProtobuf,
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Field #' },
|
||||
1 => {
|
||||
Name => 'CoordinateUnits',
|
||||
Format => 'unsigned',
|
||||
Notes => 'not extracted, but used internally to convert coordinates to degrees',
|
||||
# don't extract this -- just convert to degrees
|
||||
RawConv => '$$self{CoordUnits} = $val; undef',
|
||||
# PrintConv => { 0 => 'Radians', 1 => 'Degrees' },
|
||||
},
|
||||
2 => {
|
||||
Name => 'GPSLatitude',
|
||||
Format => 'double',
|
||||
# set ExifTool GPSLatitude/GPSLongitude members so GPSDateTime will be generated if necessary
|
||||
RawConv => '$$self{GPSLatitude} = $$self{CoordUnits} ? $val : $val * 180 / 3.141592653589793', # (NC)
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
3 => {
|
||||
Name => 'GPSLongitude',
|
||||
Format => 'double',
|
||||
RawConv => '$$self{GPSLongitude} = $$self{CoordUnits} ? $val : $val * 180 / 3.141592653589793', # (NC)
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process DJI beauty settings (ref PH)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessSettings($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTbl) = @_;
|
||||
$et->VerboseDir($$dirInfo{DirName}, undef, length(${$$dirInfo{DataPt}}));
|
||||
foreach (split /;/, ${$$dirInfo{DataPt}}) {
|
||||
my ($tag, $val) = split /=/;
|
||||
next unless defined $tag and defined $val;
|
||||
$et->HandleTag($tagTbl, $tag, $val, MakeTagInfo => 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process DJI info (ref PH)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessDJIInfo($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTbl) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirStart = $$dirInfo{DirStart} || 0;
|
||||
my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
|
||||
if ($dirStart) {
|
||||
my $buff = substr($$dataPt, $dirStart, $dirLen);
|
||||
$dataPt = \$buff;
|
||||
}
|
||||
$et->VerboseDir('DJIInfo', undef, length $$dataPt);
|
||||
while ($$dataPt =~ /\G\[(.*?)\](?=(\[|$))/sg) {
|
||||
my ($tag, $val) = split /:/, $1, 2;
|
||||
next unless defined $tag and defined $val;
|
||||
if ($val =~ /^([\x20-\x7e]+)\0*$/) {
|
||||
$val = $1;
|
||||
} else {
|
||||
my $buff = $val;
|
||||
$val = \$buff;
|
||||
}
|
||||
$et->HandleTag($tagTbl, $tag, $val, MakeTagInfo => 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DJI - DJI Phantom maker notes tags
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to interpret
|
||||
the maker notes in images from some DJI Phantom drones.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/DJI Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
871
.resources/nix/bin/lib/Image/ExifTool/DNG.pm
Normal file
871
.resources/nix/bin/lib/Image/ExifTool/DNG.pm
Normal file
|
|
@ -0,0 +1,871 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DNG.pm
|
||||
#
|
||||
# Description: Read DNG-specific information
|
||||
#
|
||||
# Revisions: 01/09/2006 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.adobe.com/products/dng/
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DNG;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::Exif;
|
||||
use Image::ExifTool::MakerNotes;
|
||||
use Image::ExifTool::CanonRaw;
|
||||
|
||||
$VERSION = '1.25';
|
||||
|
||||
sub ProcessOriginalRaw($$$);
|
||||
sub ProcessAdobeData($$$);
|
||||
sub ProcessAdobeMakN($$$);
|
||||
sub ProcessAdobeCRW($$$);
|
||||
sub ProcessAdobeRAF($$$);
|
||||
sub ProcessAdobeMRW($$$);
|
||||
sub ProcessAdobeSR2($$$);
|
||||
sub ProcessAdobeIFD($$$);
|
||||
sub WriteAdobeStuff($$$);
|
||||
|
||||
# data in OriginalRawFileData
|
||||
%Image::ExifTool::DNG::OriginalRaw = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
PROCESS_PROC => \&ProcessOriginalRaw,
|
||||
NOTES => q{
|
||||
This table defines tags extracted from the DNG OriginalRawFileData
|
||||
information.
|
||||
},
|
||||
0 => { Name => 'OriginalRawImage', Binary => 1 },
|
||||
1 => { Name => 'OriginalRawResource', Binary => 1 },
|
||||
2 => 'OriginalRawFileType',
|
||||
3 => 'OriginalRawCreator',
|
||||
4 => { Name => 'OriginalTHMImage', Binary => 1 },
|
||||
5 => { Name => 'OriginalTHMResource', Binary => 1 },
|
||||
6 => 'OriginalTHMFileType',
|
||||
7 => 'OriginalTHMCreator',
|
||||
);
|
||||
|
||||
%Image::ExifTool::DNG::AdobeData = ( #PH
|
||||
GROUPS => { 0 => 'MakerNotes', 1 => 'AdobeDNG', 2 => 'Image' },
|
||||
PROCESS_PROC => \&ProcessAdobeData,
|
||||
WRITE_PROC => \&WriteAdobeStuff,
|
||||
NOTES => q{
|
||||
This information is found in the "Adobe" DNGPrivateData.
|
||||
|
||||
The maker notes ('MakN') are processed by ExifTool, but some information may
|
||||
have been lost by the Adobe DNG Converter. This is because the Adobe DNG
|
||||
Converter (as of version 6.3) doesn't properly handle information referenced
|
||||
from inside the maker notes that lies outside the original maker notes
|
||||
block. This information is lost when only the maker note block is copied to
|
||||
the DNG image. While this doesn't effect all makes of cameras, it is a
|
||||
problem for some major brands such as Olympus and Sony.
|
||||
|
||||
Other entries in this table represent proprietary information that is
|
||||
extracted from the original RAW image and restructured to a different (but
|
||||
still proprietary) Adobe format.
|
||||
},
|
||||
MakN => [ ], # (filled in later)
|
||||
'CRW ' => {
|
||||
Name => 'AdobeCRW',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::CanonRaw::Main',
|
||||
ProcessProc => \&ProcessAdobeCRW,
|
||||
WriteProc => \&WriteAdobeStuff,
|
||||
},
|
||||
},
|
||||
'MRW ' => {
|
||||
Name => 'AdobeMRW',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::MinoltaRaw::Main',
|
||||
ProcessProc => \&ProcessAdobeMRW,
|
||||
WriteProc => \&WriteAdobeStuff,
|
||||
},
|
||||
},
|
||||
'SR2 ' => {
|
||||
Name => 'AdobeSR2',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Sony::SR2Private',
|
||||
ProcessProc => \&ProcessAdobeSR2,
|
||||
},
|
||||
},
|
||||
'RAF ' => {
|
||||
Name => 'AdobeRAF',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::FujiFilm::RAF',
|
||||
ProcessProc => \&ProcessAdobeRAF,
|
||||
},
|
||||
},
|
||||
'Pano' => {
|
||||
Name => 'AdobePano',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::PanasonicRaw::Main',
|
||||
ProcessProc => \&ProcessAdobeIFD,
|
||||
},
|
||||
},
|
||||
'Koda' => {
|
||||
Name => 'AdobeKoda',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Kodak::IFD',
|
||||
ProcessProc => \&ProcessAdobeIFD,
|
||||
},
|
||||
},
|
||||
'Leaf' => {
|
||||
Name => 'AdobeLeaf',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Leaf::SubIFD',
|
||||
ProcessProc => \&ProcessAdobeIFD,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# (DNG 1.7)
|
||||
%Image::ExifTool::DNG::ImageSeq = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
0 => { Name => 'SeqID', Format => 'var_string' },
|
||||
1 => { Name => 'SeqType', Format => 'var_string' },
|
||||
2 => { Name => 'SeqFrameInfo', Format => 'var_string' },
|
||||
3 => { Name => 'SeqIndex', Format => 'int32u' },
|
||||
7 => { Name => 'SeqCount', Format => 'int32u' },
|
||||
11 => { Name => 'SeqFinal', Format => 'int8u', PrintConv => { 0 => 'No', 1 => 'Yes' } },
|
||||
);
|
||||
|
||||
# (DNG 1.7)
|
||||
%Image::ExifTool::DNG::ProfileDynamicRange = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
0 => { Name => 'PDRVersion', Format => 'int16u' },
|
||||
2 => { Name => 'DynamicRange', Format => 'int16u', PrintConv => { 0 => 'Standard', 1 => 'High' } },
|
||||
4 => { Name => 'HintMaxOutputValue', Format => 'float' },
|
||||
);
|
||||
|
||||
# fill in maker notes
|
||||
{
|
||||
my $tagInfo;
|
||||
my $list = $Image::ExifTool::DNG::AdobeData{MakN};
|
||||
foreach $tagInfo (@Image::ExifTool::MakerNotes::Main) {
|
||||
unless (ref $tagInfo eq 'HASH') {
|
||||
push @$list, $tagInfo;
|
||||
next;
|
||||
}
|
||||
my %copy = %$tagInfo;
|
||||
delete $copy{Groups};
|
||||
delete $copy{GotGroups};
|
||||
delete $copy{Table};
|
||||
push @$list, \%copy;
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process DNG OriginalRawFileData information
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
sub ProcessOriginalRaw($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $start = $$dirInfo{DirStart};
|
||||
my $end = $start + $$dirInfo{DirLen};
|
||||
my $pos = $start;
|
||||
my ($index, $err);
|
||||
|
||||
SetByteOrder('MM'); # pointers are always big-endian in this structure
|
||||
for ($index=0; $index<8; ++$index) {
|
||||
last if $pos + 4 > $end;
|
||||
my $val = Get32u($dataPt, $pos);
|
||||
$val or $pos += 4, next; # ignore zero values
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $index);
|
||||
$tagInfo or $err = "Missing DNG tag $index", last;
|
||||
if ($index & 0x02) {
|
||||
# extract a simple file type (tags 2, 3, 6 and 7)
|
||||
$val = substr($$dataPt, $pos, 4);
|
||||
$pos += 4;
|
||||
} else {
|
||||
# extract a compressed data block (tags 0, 1, 4 and 5)
|
||||
my $n = int(($val + 65535) / 65536);
|
||||
my $hdrLen = 4 * ($n + 2);
|
||||
$pos + $hdrLen > $end and $err = '', last;
|
||||
my $tag = $$tagInfo{Name};
|
||||
# only extract this information if requested (because it takes time)
|
||||
my $lcTag = lc $tag;
|
||||
if (($$et{OPTIONS}{Binary} and not $$et{EXCL_TAG_LOOKUP}{$lcTag}) or
|
||||
$$et{REQ_TAG_LOOKUP}{$lcTag})
|
||||
{
|
||||
unless (eval { require Compress::Zlib }) {
|
||||
$err = 'Install Compress::Zlib to extract compressed images';
|
||||
last;
|
||||
}
|
||||
my $i;
|
||||
$val = '';
|
||||
my $p2 = $pos + Get32u($dataPt, $pos + 4);
|
||||
for ($i=0; $i<$n; ++$i) {
|
||||
# inflate this compressed block
|
||||
my $p1 = $p2;
|
||||
$p2 = $pos + Get32u($dataPt, $pos + ($i + 2) * 4);
|
||||
if ($p1 >= $p2 or $p2 > $end) {
|
||||
$err = 'Bad compressed RAW image';
|
||||
last;
|
||||
}
|
||||
my $buff = substr($$dataPt, $p1, $p2 - $p1);
|
||||
my ($v2, $stat);
|
||||
my $inflate = Compress::Zlib::inflateInit();
|
||||
$inflate and ($v2, $stat) = $inflate->inflate($buff);
|
||||
if ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) {
|
||||
$val .= $v2;
|
||||
} else {
|
||||
$err = 'Error inflating compressed RAW image';
|
||||
last;
|
||||
}
|
||||
}
|
||||
$pos = $p2;
|
||||
} else {
|
||||
$pos + $hdrLen > $end and $err = '', last;
|
||||
my $len = Get32u($dataPt, $pos + $hdrLen - 4);
|
||||
$pos + $len > $end and $err = '', last;
|
||||
$val = substr($$dataPt, $pos + $hdrLen, $len - $hdrLen);
|
||||
$val = "Binary data $len bytes";
|
||||
$pos += $len; # skip over this block
|
||||
}
|
||||
}
|
||||
$et->FoundTag($tagInfo, $val);
|
||||
}
|
||||
$et->Warn($err || 'Bad OriginalRawFileData') if defined $err;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe DNGPrivateData directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessAdobeData($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $pos = $$dirInfo{DirStart};
|
||||
my $end = $$dirInfo{DirLen} + $pos;
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $htmlDump = $et->Options('HtmlDump');
|
||||
|
||||
return 0 unless $$dataPt =~ /^Adobe\0/;
|
||||
unless ($outfile) {
|
||||
$et->VerboseDir($dirInfo);
|
||||
# don't parse makernotes if FastScan > 1
|
||||
my $fast = $et->Options('FastScan');
|
||||
return 1 if $fast and $fast > 1;
|
||||
}
|
||||
$htmlDump and $et->HDump($dataPos, 6, 'Adobe DNGPrivateData header');
|
||||
SetByteOrder('MM'); # always big endian
|
||||
$pos += 6;
|
||||
while ($pos + 8 <= $end) {
|
||||
my ($tag, $size) = unpack("x${pos}a4N", $$dataPt);
|
||||
$pos += 8;
|
||||
last if $pos + $size > $end;
|
||||
my $tagInfo = $$tagTablePtr{$tag};
|
||||
if ($htmlDump) {
|
||||
my $name = "Adobe$tag";
|
||||
$name =~ tr/ //d;
|
||||
$et->HDump($dataPos + $pos - 8, 8, "$name header", "Data Size: $size bytes");
|
||||
# dump non-EXIF format data
|
||||
unless ($tag =~ /^(MakN|SR2 )$/) {
|
||||
$et->HDump($dataPos + $pos, $size, "$name data");
|
||||
}
|
||||
}
|
||||
if ($verbose and not $outfile) {
|
||||
$tagInfo or $et->VPrint(0, "$$et{INDENT}Unsupported DNGAdobeData record: ($tag)\n");
|
||||
$et->VerboseInfo($tag,
|
||||
ref $tagInfo eq 'HASH' ? $tagInfo : undef,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => $pos,
|
||||
Size => $size,
|
||||
);
|
||||
}
|
||||
my $value;
|
||||
while ($tagInfo) {
|
||||
my ($subTable, $subName, $processProc);
|
||||
if (ref $tagInfo eq 'HASH') {
|
||||
unless ($$tagInfo{SubDirectory}) {
|
||||
if ($outfile) {
|
||||
# copy value across to outfile
|
||||
$value = substr($$dataPt, $pos, $size);
|
||||
} else {
|
||||
$et->HandleTag($tagTablePtr, $tag, substr($$dataPt, $pos, $size));
|
||||
}
|
||||
last;
|
||||
}
|
||||
$subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
|
||||
$subName = $$tagInfo{Name};
|
||||
$processProc = $tagInfo->{SubDirectory}->{ProcessProc};
|
||||
} else {
|
||||
$subTable = $tagTablePtr;
|
||||
$subName = 'AdobeMakN';
|
||||
$processProc = \&ProcessAdobeMakN;
|
||||
}
|
||||
my %dirInfo = (
|
||||
Base => $$dirInfo{Base},
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
DataLen => $$dirInfo{DataLen},
|
||||
DirStart => $pos,
|
||||
DirLen => $size,
|
||||
DirName => $subName,
|
||||
);
|
||||
if ($outfile) {
|
||||
$dirInfo{Proc} = $processProc; # WriteAdobeStuff() calls this to do the actual writing
|
||||
$value = $et->WriteDirectory(\%dirInfo, $subTable, \&WriteAdobeStuff);
|
||||
# use old directory if an error occurred
|
||||
defined $value or $value = substr($$dataPt, $pos, $size);
|
||||
} else {
|
||||
# override process proc for MakN
|
||||
$et->ProcessDirectory(\%dirInfo, $subTable, $processProc);
|
||||
}
|
||||
last;
|
||||
}
|
||||
if (defined $value and length $value) {
|
||||
# add "Adobe" header if necessary
|
||||
$$outfile = "Adobe\0" unless $$outfile and length $$outfile;
|
||||
$$outfile .= $tag . pack('N', length $value) . $value;
|
||||
$$outfile .= "\0" if length($value) & 0x01; # pad if necessary
|
||||
}
|
||||
$pos += $size;
|
||||
++$pos if $size & 0x01; # (darn padding)
|
||||
}
|
||||
$pos == $end or $et->Warn("$pos $end Adobe private data is corrupt");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe CRW directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
# Notes: data has 4 byte header (2 for byte order and 2 for entry count)
|
||||
# - this routine would be as simple as ProcessAdobeMRW() below if Adobe hadn't
|
||||
# pulled the bonehead move of reformatting the CRW information
|
||||
sub ProcessAdobeCRW($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $start = $$dirInfo{DirStart};
|
||||
my $end = $start + $$dirInfo{DirLen};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $buildMakerNotes = $et->Options('MakerNotes');
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
my ($newTags, $oldChanged);
|
||||
|
||||
SetByteOrder('MM'); # always big endian
|
||||
return 0 if $$dirInfo{DirLen} < 4;
|
||||
my $byteOrder = substr($$dataPt, $start, 2);
|
||||
return 0 unless $byteOrder =~ /^(II|MM)$/;
|
||||
|
||||
# initialize maker note data if building maker notes
|
||||
$buildMakerNotes and Image::ExifTool::CanonRaw::InitMakerNotes($et);
|
||||
|
||||
my $entries = Get16u($dataPt, $start + 2);
|
||||
my $pos = $start + 4;
|
||||
$et->VerboseDir($dirInfo, $entries) unless $outfile;
|
||||
if ($outfile) {
|
||||
# get hash of new tags
|
||||
$newTags = $et->GetNewTagInfoHash($tagTablePtr);
|
||||
$$outfile = substr($$dataPt, $start, 4);
|
||||
$oldChanged = $$et{CHANGED};
|
||||
}
|
||||
# loop through entries in Adobe CRW information
|
||||
my $index;
|
||||
for ($index=0; $index<$entries; ++$index) {
|
||||
last if $pos + 6 > $end;
|
||||
my $tag = Get16u($dataPt, $pos);
|
||||
my $size = Get32u($dataPt, $pos + 2);
|
||||
$pos += 6;
|
||||
last if $pos + $size > $end;
|
||||
my $value = substr($$dataPt, $pos, $size);
|
||||
my $tagID = $tag & 0x3fff;
|
||||
my $tagType = ($tag >> 8) & 0x38; # get tag type
|
||||
my $format = $Image::ExifTool::CanonRaw::crwTagFormat{$tagType};
|
||||
my $count;
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID, \$value);
|
||||
if ($tagInfo) {
|
||||
$format = $$tagInfo{Format} if $$tagInfo{Format};
|
||||
$count = $$tagInfo{Count};
|
||||
}
|
||||
# set count to 1 by default for values that were in the directory entry
|
||||
if (not defined $count and $tag & 0x4000 and $format and $format ne 'string') {
|
||||
$count = 1;
|
||||
}
|
||||
# set count from tagInfo count if necessary
|
||||
if ($format and not $count) {
|
||||
# set count according to format and size
|
||||
my $fnum = $Image::ExifTool::Exif::formatNumber{$format};
|
||||
my $fsiz = $Image::ExifTool::Exif::formatSize[$fnum];
|
||||
$count = int($size / $fsiz);
|
||||
}
|
||||
$format or $format = 'undef';
|
||||
SetByteOrder($byteOrder);
|
||||
my $val = ReadValue(\$value, 0, $format, $count, $size);
|
||||
if ($outfile) {
|
||||
if ($tagInfo) {
|
||||
my $subdir = $$tagInfo{SubDirectory};
|
||||
if ($subdir and $$subdir{TagTable}) {
|
||||
my $name = $$tagInfo{Name};
|
||||
my $newTagTable = GetTagTable($$subdir{TagTable});
|
||||
return 0 unless $newTagTable;
|
||||
my $subdirStart = 0;
|
||||
#### eval Start ()
|
||||
$subdirStart = eval $$subdir{Start} if $$subdir{Start};
|
||||
my $dirData = \$value;
|
||||
my %subdirInfo = (
|
||||
Name => $name,
|
||||
DataPt => $dirData,
|
||||
DataLen => $size,
|
||||
DirStart => $subdirStart,
|
||||
DirLen => $size - $subdirStart,
|
||||
Parent => $$dirInfo{DirName},
|
||||
);
|
||||
#### eval Validate ($dirData, $subdirStart, $size)
|
||||
if (defined $$subdir{Validate} and not eval $$subdir{Validate}) {
|
||||
$et->Warn("Invalid $name data");
|
||||
} else {
|
||||
$subdir = $et->WriteDirectory(\%subdirInfo, $newTagTable);
|
||||
if (defined $subdir and length $subdir) {
|
||||
if ($subdirStart) {
|
||||
# add header before data directory
|
||||
$value = substr($value, 0, $subdirStart) . $subdir;
|
||||
} else {
|
||||
$value = $subdir;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif ($$newTags{$tagID}) {
|
||||
my $nvHash = $et->GetNewValueHash($tagInfo);
|
||||
if ($et->IsOverwriting($nvHash, $val)) {
|
||||
my $newVal = $et->GetNewValue($nvHash);
|
||||
my $verboseVal;
|
||||
$verboseVal = $newVal if $verbose > 1;
|
||||
# convert to specified format if necessary
|
||||
if (defined $newVal and $format) {
|
||||
$newVal = WriteValue($newVal, $format, $count);
|
||||
}
|
||||
if (defined $newVal) {
|
||||
$et->VerboseValue("- CanonRaw:$$tagInfo{Name}", $value);
|
||||
$et->VerboseValue("+ CanonRaw:$$tagInfo{Name}", $verboseVal);
|
||||
$value = $newVal;
|
||||
++$$et{CHANGED};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# write out new value (always big-endian)
|
||||
SetByteOrder('MM');
|
||||
# (verified that there is no padding here)
|
||||
$$outfile .= Set16u($tag) . Set32u(length($value)) . $value;
|
||||
} else {
|
||||
$et->HandleTag($tagTablePtr, $tagID, $val,
|
||||
Index => $index,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $$dirInfo{DataPos},
|
||||
Start => $pos,
|
||||
Size => $size,
|
||||
TagInfo => $tagInfo,
|
||||
);
|
||||
if ($buildMakerNotes) {
|
||||
# build maker notes information if requested
|
||||
Image::ExifTool::CanonRaw::BuildMakerNotes($et, $tagID, $tagInfo,
|
||||
\$value, $format, $count);
|
||||
}
|
||||
}
|
||||
# (we lost the directory structure, but the second tag 0x0805
|
||||
# should be in the ImageDescription directory)
|
||||
$$et{DIR_NAME} = 'ImageDescription' if $tagID == 0x0805;
|
||||
SetByteOrder('MM');
|
||||
$pos += $size;
|
||||
}
|
||||
if ($outfile and (not defined $$outfile or $index != $entries or
|
||||
$$et{CHANGED} == $oldChanged))
|
||||
{
|
||||
$$et{CHANGED} = $oldChanged; # nothing changed
|
||||
undef $$outfile; # rewrite old directory
|
||||
}
|
||||
if ($index != $entries) {
|
||||
$et->Warn('Truncated CRW notes');
|
||||
} elsif ($pos < $end) {
|
||||
$et->Warn($end-$pos . ' extra bytes at end of CRW notes');
|
||||
}
|
||||
# finish building maker notes if necessary
|
||||
if ($buildMakerNotes) {
|
||||
SetByteOrder($byteOrder);
|
||||
Image::ExifTool::CanonRaw::SaveMakerNotes($et);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe MRW directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
# Notes: data has 4 byte header (2 for byte order and 2 for entry count)
|
||||
sub ProcessAdobeMRW($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dirLen = $$dirInfo{DirLen};
|
||||
my $dirStart = $$dirInfo{DirStart};
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
|
||||
# construct fake MRW file
|
||||
my $buff = "\0MRM" . pack('N', $dirLen - 4);
|
||||
# ignore leading byte order and directory count words
|
||||
$buff .= substr($$dataPt, $dirStart + 4, $dirLen - 4);
|
||||
my $raf = File::RandomAccess->new(\$buff);
|
||||
my %dirInfo = ( RAF => $raf, OutFile => $outfile );
|
||||
my $rtnVal = Image::ExifTool::MinoltaRaw::ProcessMRW($et, \%dirInfo);
|
||||
if ($outfile and defined $$outfile and length $$outfile) {
|
||||
# remove MRW header and add Adobe header
|
||||
$$outfile = substr($$dataPt, $dirStart, 4) . substr($$outfile, 8);
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe RAF directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
sub ProcessAdobeRAF($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
return 0 if $$dirInfo{OutFile}; # (can't write this yet)
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $pos = $$dirInfo{DirStart};
|
||||
my $dirEnd = $$dirInfo{DirLen} + $pos;
|
||||
my ($readIt, $warn);
|
||||
|
||||
# set byte order according to first 2 bytes of Adobe RAF data
|
||||
if ($pos + 2 <= $dirEnd and SetByteOrder(substr($$dataPt, $pos, 2))) {
|
||||
$pos += 2;
|
||||
} else {
|
||||
$et->Warn('Invalid DNG RAF data');
|
||||
return 0;
|
||||
}
|
||||
$et->VerboseDir($dirInfo);
|
||||
# make fake RAF object for processing (same acronym, different meaning)
|
||||
my $raf = File::RandomAccess->new($dataPt);
|
||||
my $num = '';
|
||||
# loop through all records in Adobe RAF data:
|
||||
# 0 - RAF table (not processed)
|
||||
# 1 - first RAF directory
|
||||
# 2 - second RAF directory (if available)
|
||||
for (;;) {
|
||||
last if $pos + 4 > $dirEnd;
|
||||
my $len = Get32u($dataPt, $pos);
|
||||
$pos += 4 + $len; # step to next entry in Adobe RAF record
|
||||
$len or last; # ends with an empty entry
|
||||
$readIt or $readIt = 1, next; # ignore first entry (RAF table)
|
||||
my %dirInfo = (
|
||||
RAF => $raf,
|
||||
DirStart => $pos - $len,
|
||||
);
|
||||
$$et{SET_GROUP1} = "RAF$num";
|
||||
$et->ProcessDirectory(\%dirInfo, $tagTablePtr) or $warn = 1;
|
||||
delete $$et{SET_GROUP1};
|
||||
$num = ($num || 1) + 1;
|
||||
}
|
||||
$warn and $et->Warn('Possibly corrupt RAF information');
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe SR2 directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
# Notes: data has 6 byte header (2 for byte order and 4 for original offset)
|
||||
sub ProcessAdobeSR2($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
return 0 if $$dirInfo{OutFile}; # (can't write this yet)
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $start = $$dirInfo{DirStart};
|
||||
my $len = $$dirInfo{DirLen};
|
||||
|
||||
return 0 if $len < 6;
|
||||
SetByteOrder('MM');
|
||||
my $originalPos = Get32u($dataPt, $start + 2);
|
||||
return 0 unless SetByteOrder(substr($$dataPt, $start, 2));
|
||||
|
||||
$et->VerboseDir($dirInfo);
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirStart = $start + 6; # pointer to maker note directory
|
||||
my $dirLen = $len - 6;
|
||||
|
||||
# initialize subdirectory information
|
||||
my $fix = $dataPos + $dirStart - $originalPos;
|
||||
my %subdirInfo = (
|
||||
DirName => 'AdobeSR2',
|
||||
Base => $$dirInfo{Base} + $fix,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos - $fix,
|
||||
DataLen => $$dirInfo{DataLen},
|
||||
DirStart => $dirStart,
|
||||
DirLen => $dirLen,
|
||||
Parent => $$dirInfo{DirName},
|
||||
);
|
||||
if ($et->Options('HtmlDump')) {
|
||||
$et->HDump($dataPos + $start, 6, 'Adobe SR2 data');
|
||||
}
|
||||
# parse the SR2 directory
|
||||
$et->ProcessDirectory(\%subdirInfo, $tagTablePtr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe-mutilated IFD directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
# Notes: data has 2 byte header (byte order of the data)
|
||||
sub ProcessAdobeIFD($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
return 0 if $$dirInfo{OutFile}; # (can't write this yet)
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $pos = $$dirInfo{DirStart};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
|
||||
return 0 if $$dirInfo{DirLen} < 4;
|
||||
my $dataOrder = substr($$dataPt, $pos, 2);
|
||||
return 0 unless SetByteOrder($dataOrder); # validate byte order of data
|
||||
|
||||
# parse the mutilated IFD. This is similar to a TIFF IFD, except:
|
||||
# - data follows directly after Count entry in IFD
|
||||
# - byte order of IFD entries is always big-endian, but byte order of data changes
|
||||
SetByteOrder('MM'); # IFD structure is always big-endian
|
||||
my $entries = Get16u($dataPt, $pos + 2);
|
||||
$et->VerboseDir($dirInfo, $entries);
|
||||
$pos += 4;
|
||||
|
||||
my $end = $pos + $$dirInfo{DirLen};
|
||||
my $index;
|
||||
for ($index=0; $index<$entries; ++$index) {
|
||||
last if $pos + 8 > $end;
|
||||
SetByteOrder('MM'); # directory entries always big-endian (doh!)
|
||||
my $tagID = Get16u($dataPt, $pos);
|
||||
my $format = Get16u($dataPt, $pos+2);
|
||||
my $count = Get32u($dataPt, $pos+4);
|
||||
if ($format < 1 or $format > 13) {
|
||||
# warn unless the IFD was just padded with zeros
|
||||
$format and $et->Warn(
|
||||
sprintf("Unknown format ($format) for $$dirInfo{DirName} tag 0x%x",$tagID));
|
||||
return 0; # must be corrupted
|
||||
}
|
||||
my $size = $Image::ExifTool::Exif::formatSize[$format] * $count;
|
||||
last if $pos + 8 + $size > $end;
|
||||
my $formatStr = $Image::ExifTool::Exif::formatName[$format];
|
||||
SetByteOrder($dataOrder); # data stored in native order
|
||||
my $val = ReadValue($dataPt, $pos + 8, $formatStr, $count, $size);
|
||||
$et->HandleTag($tagTablePtr, $tagID, $val,
|
||||
Index => $index,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => $pos + 8,
|
||||
Size => $size
|
||||
);
|
||||
$pos += 8 + $size;
|
||||
}
|
||||
if ($index < $entries) {
|
||||
$et->Warn("Truncated $$dirInfo{DirName} directory");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Adobe MakerNotes directory
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success, otherwise returns 0 and sets a Warning
|
||||
# Notes: data has 6 byte header (2 for byte order and 4 for original offset)
|
||||
# --> or 18 bytes for DNG converted from JPG by Adobe Camera Raw!
|
||||
sub ProcessAdobeMakN($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $start = $$dirInfo{DirStart};
|
||||
my $len = $$dirInfo{DirLen};
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
|
||||
return 0 if $len < 6;
|
||||
SetByteOrder('MM');
|
||||
my $originalPos = Get32u($dataPt, $start + 2);
|
||||
return 0 unless SetByteOrder(substr($$dataPt, $start, 2));
|
||||
|
||||
$et->VerboseDir($dirInfo) unless $outfile;
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $hdrLen = 6;
|
||||
|
||||
# 2018-09-27: hack for extra 12 bytes in MakN header of JPEG converted to DNG
|
||||
# by Adobe Camera Raw (4 bytes "00 00 00 01" followed by 8 unknown bytes)
|
||||
# - this is because CameraRaw copies the maker notes from the wrong location
|
||||
# in a JPG image (off by 12 bytes presumably due to the JPEG headers)
|
||||
# - this hack won't work in most cases because the extra bytes are not consistent
|
||||
# since they are just the data that existed in the JPG before the maker notes
|
||||
# - also, the last 12 bytes of the maker notes will be missing
|
||||
# - 2022-04-26: this bug still exists in Camera Raw 14.3
|
||||
$hdrLen += 12 if $len >= 18 and substr($$dataPt, $start+6, 4) eq "\0\0\0\x01";
|
||||
|
||||
my $dirStart = $start + $hdrLen; # pointer to maker note directory
|
||||
my $dirLen = $len - $hdrLen;
|
||||
|
||||
my $hdr = substr($$dataPt, $dirStart, $dirLen < 48 ? $dirLen : 48);
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, 'MakN', \$hdr);
|
||||
return 0 unless $tagInfo and $$tagInfo{SubDirectory};
|
||||
my $subdir = $$tagInfo{SubDirectory};
|
||||
my $subTable = GetTagTable($$subdir{TagTable});
|
||||
# initialize subdirectory information
|
||||
my %subdirInfo = (
|
||||
DirName => 'MakerNotes',
|
||||
Name => $$tagInfo{Name}, # needed for maker notes verbose dump
|
||||
Base => $$dirInfo{Base},
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
DataLen => $$dirInfo{DataLen},
|
||||
DirStart => $dirStart,
|
||||
DirLen => $dirLen,
|
||||
TagInfo => $tagInfo,
|
||||
FixBase => $$subdir{FixBase},
|
||||
EntryBased=> $$subdir{EntryBased},
|
||||
Parent => $$dirInfo{DirName},
|
||||
);
|
||||
# look for start of maker notes IFD
|
||||
my $loc = Image::ExifTool::MakerNotes::LocateIFD($et,\%subdirInfo);
|
||||
unless (defined $loc) {
|
||||
$et->Warn('Maker notes could not be parsed');
|
||||
return 0;
|
||||
}
|
||||
if ($et->Options('HtmlDump')) {
|
||||
$et->HDump($dataPos + $start, $hdrLen, 'Adobe MakN data');
|
||||
$et->HDump($dataPos + $dirStart, $loc, "$$tagInfo{Name} header") if $loc;
|
||||
}
|
||||
|
||||
my $fix = 0;
|
||||
unless ($$subdir{Base}) {
|
||||
# adjust base offset for current maker note position
|
||||
$fix = $dataPos + $dirStart - $originalPos;
|
||||
$subdirInfo{Base} += $fix;
|
||||
$subdirInfo{DataPos} -= $fix;
|
||||
}
|
||||
if ($outfile) {
|
||||
# rewrite the maker notes directory
|
||||
my $fixup = $subdirInfo{Fixup} = Image::ExifTool::Fixup->new;
|
||||
my $oldChanged = $$et{CHANGED};
|
||||
my $buff = $et->WriteDirectory(\%subdirInfo, $subTable);
|
||||
# nothing to do if error writing directory or nothing changed
|
||||
unless (defined $buff and $$et{CHANGED} != $oldChanged) {
|
||||
$$et{CHANGED} = $oldChanged;
|
||||
return 1;
|
||||
}
|
||||
# deleting maker notes if directory is empty
|
||||
unless (length $buff) {
|
||||
$$outfile = '';
|
||||
return 1;
|
||||
}
|
||||
# apply a one-time fixup to offsets
|
||||
if ($subdirInfo{Relative}) {
|
||||
# shift all offsets to be relative to new base
|
||||
my $baseShift = $dataPos + $dirStart + $$dirInfo{Base} - $subdirInfo{Base};
|
||||
$fixup->{Shift} += $baseShift;
|
||||
} else {
|
||||
# shift offsets to position of original maker notes
|
||||
$fixup->{Shift} += $originalPos;
|
||||
}
|
||||
# if we wrote the directory as a block the header is already included
|
||||
$loc = 0 if $subdirInfo{BlockWrite};
|
||||
$fixup->{Shift} += $loc; # adjust for makernotes header
|
||||
$fixup->ApplyFixup(\$buff); # fix up pointer offsets
|
||||
# get copy of original Adobe header (6 or 18) and makernotes header ($loc)
|
||||
my $header = substr($$dataPt, $start, $hdrLen + $loc);
|
||||
# add Adobe and makernotes headers to new directory
|
||||
$$outfile = $header . $buff;
|
||||
} else {
|
||||
# parse the maker notes directory
|
||||
$et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc});
|
||||
# extract maker notes as a block if specified
|
||||
if ($et->Options('MakerNotes') or
|
||||
$$et{REQ_TAG_LOOKUP}{lc($$tagInfo{Name})})
|
||||
{
|
||||
my $val;
|
||||
if ($$tagInfo{MakerNotes}) {
|
||||
$subdirInfo{Base} = $$dirInfo{Base} + $fix;
|
||||
$subdirInfo{DataPos} = $dataPos - $fix;
|
||||
$subdirInfo{DirStart} = $dirStart;
|
||||
$subdirInfo{DirLen} = $dirLen;
|
||||
# rebuild the maker notes to identify all offsets that require fixing up
|
||||
$val = Image::ExifTool::Exif::RebuildMakerNotes($et, \%subdirInfo, $subTable);
|
||||
if (not defined $val and $dirLen > 4) {
|
||||
$et->Warn('Error rebuilding maker notes (may be corrupt)');
|
||||
}
|
||||
} else {
|
||||
# extract this directory as a block if specified
|
||||
return 1 unless $$tagInfo{Writable};
|
||||
}
|
||||
$val = substr($$dataPt, 20) unless defined $val;
|
||||
my $key = $et->FoundTag($tagInfo, $val);
|
||||
if ($$et{MAKER_NOTE_FIXUP}) {
|
||||
$$et{TAG_EXTRA}{$key}{Fixup} = $$et{MAKER_NOTE_FIXUP};
|
||||
delete $$et{MAKER_NOTE_FIXUP};
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Write Adobe information (calls appropriate ProcessProc to do the actual work)
|
||||
# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref
|
||||
# Returns: new data block (may be empty if directory is deleted) or undef on error
|
||||
sub WriteAdobeStuff($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
$et or return 1; # allow dummy access
|
||||
my $proc = $$dirInfo{Proc} || \&ProcessAdobeData;
|
||||
my $buff;
|
||||
$$dirInfo{OutFile} = \$buff;
|
||||
&$proc($et, $dirInfo, $tagTablePtr) or undef $buff;
|
||||
return $buff;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DNG.pm - Read DNG-specific information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains routines required by Image::ExifTool to process
|
||||
information in DNG (Digital Negative) images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.adobe.com/products/dng/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/DNG Tags>,
|
||||
L<Image::ExifTool::TagNames/EXIF Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
247
.resources/nix/bin/lib/Image/ExifTool/DPX.pm
Normal file
247
.resources/nix/bin/lib/Image/ExifTool/DPX.pm
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DPX.pm
|
||||
#
|
||||
# Description: Read DPX meta information
|
||||
#
|
||||
# Revisions: 2013-09-19 - P. Harvey created
|
||||
#
|
||||
# References: 1) http://www.cineon.com/ff_draft.php
|
||||
# 2) Harry Mallon private communication
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DPX;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.07';
|
||||
|
||||
# DPX tags
|
||||
%Image::ExifTool::DPX::Main = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
NOTES => 'Tags extracted from DPX (Digital Picture Exchange) images.',
|
||||
0 => { Name => 'ByteOrder', Format => 'undef[4]', PrintConv => { SDPX => 'Big-endian', XPDS => 'Little-endian' } },
|
||||
8 => { Name => 'HeaderVersion', Format => 'string[8]' },
|
||||
# 24 => { Name => 'GenericHeaderSize', Format => 'int32u' }, # = 1664
|
||||
# 28 => { Name => 'IndustryStandardHeaderSize', Format => 'int32u' }, # = 384
|
||||
16 => { Name => 'DPXFileSize', Format => 'int32u' },
|
||||
20 => { Name => 'DittoKey', Format => 'int32u', PrintConv => { 0 => 'Same', 1 => 'New' } },
|
||||
36 => { Name => 'ImageFileName', Format => 'string[100]' },
|
||||
136 => {
|
||||
Name => 'CreateDate',
|
||||
Format => 'string[24]',
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => '$val =~ s/(\d{4}:\d{2}:\d{2}):/$1 /; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
160 => { Name => 'Creator', Format => 'string[100]', Groups => { 2 => 'Author' } },
|
||||
260 => { Name => 'Project', Format => 'string[200]' },
|
||||
460 => { Name => 'Copyright', Format => 'string[200]', Groups => { 2 => 'Author' } },
|
||||
660 => { Name => 'EncryptionKey', Format => 'int32u', PrintConv => 'sprintf("%.8x",$val)' },
|
||||
768 => {
|
||||
Name => 'Orientation',
|
||||
Format => 'int16u',
|
||||
PrintConv => {
|
||||
0 => 'Horizontal (normal)',
|
||||
1 => 'Mirror vertical',
|
||||
2 => 'Mirror horizontal',
|
||||
3 => 'Rotate 180',
|
||||
4 => 'Mirror horizontal and rotate 270 CW',
|
||||
5 => 'Rotate 90 CW',
|
||||
6 => 'Rotate 270 CW',
|
||||
7 => 'Mirror horizontal and rotate 90 CW',
|
||||
},
|
||||
},
|
||||
770 => { Name => 'ImageElements', Format => 'int16u' },
|
||||
772 => { Name => 'ImageWidth', Format => 'int32u' },
|
||||
776 => { Name => 'ImageHeight', Format => 'int32u' },
|
||||
780 => { Name => 'DataSign', Format => 'int32u', PrintConv => { 0 => 'Unsigned', 1 => 'Signed' } },
|
||||
800 => {
|
||||
Name => 'ComponentsConfiguration',
|
||||
Format => 'int8u',
|
||||
PrintConv => {
|
||||
0 => 'User-defined single component',
|
||||
1 => 'Red (R)',
|
||||
2 => 'Green (G)',
|
||||
3 => 'Blue (B)',
|
||||
4 => 'Alpha (matte)',
|
||||
6 => 'Luminance (Y)',
|
||||
7 => 'Chrominance (Cb, Cr, subsampled by two)',
|
||||
8 => 'Depth (Z)',
|
||||
9 => 'Composite video',
|
||||
50 => 'R, G, B',
|
||||
51 => 'R, G, B, Alpha',
|
||||
52 => 'Alpha, B, G, R',
|
||||
100 => 'Cb, Y, Cr, Y (4:2:2)',
|
||||
101 => 'Cb, Y, A, Cr, Y, A (4:2:2:4)',
|
||||
102 => 'Cb, Y, Cr (4:4:4)',
|
||||
103 => 'Cb, Y, Cr, A (4:4:4:4)',
|
||||
150 => 'User-defined 2 component element',
|
||||
151 => 'User-defined 3 component element',
|
||||
152 => 'User-defined 4 component element',
|
||||
153 => 'User-defined 5 component element',
|
||||
154 => 'User-defined 6 component element',
|
||||
155 => 'User-defined 7 component element',
|
||||
156 => 'User-defined 8 component element',
|
||||
},
|
||||
},
|
||||
801 => { #2
|
||||
Name => 'TransferCharacteristic',
|
||||
Format => 'int8u',
|
||||
PrintConv => {
|
||||
0 => 'User-defined',
|
||||
1 => 'Printing density',
|
||||
2 => 'Linear',
|
||||
3 => 'Logarithmic',
|
||||
4 => 'Unspecified video',
|
||||
5 => 'SMPTE 274M',
|
||||
6 => 'ITU-R 709-4',
|
||||
7 => 'ITU-R 601-5 system B or G (625)',
|
||||
8 => 'ITU-R 601-5 system M (525)',
|
||||
9 => 'Composite video (NTSC)',
|
||||
10 => 'Composite video (PAL)',
|
||||
11 => 'Z (depth) - linear',
|
||||
12 => 'Z (depth) - homogeneous',
|
||||
13 => 'SMPTE ADX',
|
||||
14 => 'ITU-R 2020 NCL',
|
||||
15 => 'ITU-R 2020 CL',
|
||||
16 => 'IEC 61966-2-4 xvYCC',
|
||||
17 => 'ITU-R 2100 NCL/PQ',
|
||||
18 => 'ITU-R 2100 ICtCp/PQ',
|
||||
19 => 'ITU-R 2100 NCL/HLG',
|
||||
20 => 'ITU-R 2100 ICtCp/HLG',
|
||||
21 => 'RP 431-2:2011 Gama 2.6',
|
||||
22 => 'IEC 61966-2-1 sRGB',
|
||||
},
|
||||
},
|
||||
802 => { #2
|
||||
Name => 'ColorimetricSpecification',
|
||||
Format => 'int8u',
|
||||
PrintConv => {
|
||||
0 => 'User-defined',
|
||||
1 => 'Printing density',
|
||||
4 => 'Unspecified video',
|
||||
5 => 'SMPTE 274M',
|
||||
6 => 'ITU-R 709-4',
|
||||
7 => 'ITU-R 601-5 system B or G (625)',
|
||||
8 => 'ITU-R 601-5 system M (525)',
|
||||
9 => 'Composite video (NTSC)',
|
||||
10 => 'Composite video (PAL)',
|
||||
13 => 'SMPTE ADX',
|
||||
14 => 'ITU-R 2020',
|
||||
15 => 'P3D65',
|
||||
16 => 'P3DCI',
|
||||
17 => 'P3D60',
|
||||
18 => 'ACES',
|
||||
},
|
||||
},
|
||||
803 => { Name => 'BitDepth', Format => 'int8u' },
|
||||
820 => { Name => 'ImageDescription', Format => 'string[32]' },
|
||||
892 => { Name => 'Image2Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
964 => { Name => 'Image3Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
1036=> { Name => 'Image4Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
1108=> { Name => 'Image5Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
1180=> { Name => 'Image6Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
1252=> { Name => 'Image7Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
1324=> { Name => 'Image8Description', Format => 'string[32]', RawConv => '$val=~/[^\xff]/ ? $val : undef' },
|
||||
# 1408=> { Name => 'XOffset', Format => 'int32u' },
|
||||
# 1412=> { Name => 'YOffset', Format => 'int32u' },
|
||||
# 1416=> { Name => 'XCenter', Format => 'float' },
|
||||
# 1420=> { Name => 'YCenter', Format => 'float' },
|
||||
# 1424=> { Name => 'XOriginalSize', Format => 'int32u' },
|
||||
# 1428=> { Name => 'YOriginalSize', Format => 'int32u' },
|
||||
1432=> { Name => 'SourceFileName', Format => 'string[100]' },
|
||||
1532=> { Name => 'SourceCreateDate', Format => 'string[24]' },
|
||||
1556=> { Name => 'InputDeviceName', Format => 'string[32]' },
|
||||
1588=> { Name => 'InputDeviceSerialNumber', Format => 'string[32]' },
|
||||
# 1620 => { Name => 'Border', Format => 'int16u[4]' },
|
||||
1628 => {
|
||||
Name => 'AspectRatio',
|
||||
Format => 'int32u[2]',
|
||||
RawConv => '$val =~ /4294967295/ ? undef : $val', # ignore undefined values
|
||||
PrintConv => q{
|
||||
return 'undef' if $val eq '0 0';
|
||||
return 'inf' if $val=~/ 0$/;
|
||||
my @a=split(' ',$val);
|
||||
return join(':', Rationalize($a[0]/$a[1]));
|
||||
},
|
||||
},
|
||||
1724 => { Name => 'OriginalFrameRate',Format => 'float' },
|
||||
1728 => { Name => 'ShutterAngle', Format => 'float', RawConv => '($val =~ /\d/ and $val !~ /nan/i) ? $val : undef' }, #2
|
||||
1732 => { Name => 'FrameID', Format => 'string[32]' },
|
||||
1764 => { Name => 'SlateInformation', Format => 'string[100]' },
|
||||
1920 => { Name => 'TimeCode', Format => 'int32u' }, #2
|
||||
1940 => { Name => 'FrameRate', Format => 'float', RawConv => '($val =~ /\d/ and $val !~ /nan/i) ? $val : undef' }, #2
|
||||
1972 => { Name => 'Reserved5', Format => 'string[76]', Unknown => 1 },
|
||||
2048 => { Name => 'UserID', Format => 'string[32]' },
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract EXIF information from a DPX image
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid DPX file
|
||||
sub ProcessDPX($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $buff;
|
||||
|
||||
# verify this is a valid DPX file
|
||||
return 0 unless $raf->Read($buff, 2080) == 2080;
|
||||
return 0 unless $buff =~ /^(SDPX|XPDS)/;
|
||||
SetByteOrder($1 eq 'SDPX' ? 'MM' : 'II');
|
||||
$et->SetFileType(); # set the FileType tag
|
||||
my $hdrLen = Get32u(\$buff,24) + Get32u(\$buff,28);
|
||||
$hdrLen == 2048 or $et->Warn("Unexpected DPX header length ($hdrLen)");
|
||||
my %dirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => length($buff),
|
||||
);
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::DPX::Main');
|
||||
$et->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DPX - Read DPX meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read
|
||||
metadata from DPX (Digital Picture Exchange) images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.cineon.com/ff_draft.php>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/DPX Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
138
.resources/nix/bin/lib/Image/ExifTool/DSF.pm
Normal file
138
.resources/nix/bin/lib/Image/ExifTool/DSF.pm
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DSF.pm
|
||||
#
|
||||
# Description: Read DSF meta information
|
||||
#
|
||||
# Revisions: 2025-09-24 - P. Harvey Created
|
||||
#
|
||||
# References: 1) https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DSF;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.00';
|
||||
|
||||
# DSF header format
|
||||
%Image::ExifTool::DSF::Main = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Audio' },
|
||||
FORMAT => 'int32u',
|
||||
NOTES => q{
|
||||
Tags read from the 'fmt ' header of a DSF (DSD Stream File) audio files. As
|
||||
well, ID3 metadata may also exist in these files. See
|
||||
L<https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf>
|
||||
for the specification.
|
||||
},
|
||||
3 => 'FormatVersion',
|
||||
4 => { Name => 'FormatID', PrintConv => { 0 => 'DSD Raw' }},
|
||||
5 => {
|
||||
Name => 'ChannelType',
|
||||
PrintConv => {
|
||||
1 => 'Mono',
|
||||
2 => 'Stereo (Left, Right)',
|
||||
3 => '3 Channels (Left, Right, Center)',
|
||||
4 => 'Quad (Left, Right, Back L, Back R)',
|
||||
5 => '4 Channels (Left, Right, Center, Bass)',
|
||||
6 => '5 Channels (Left, Right, Center, Back L, Back R)',
|
||||
7 => '5.1 Channels (Left, Right, Center, Bass, Back L, Back R)',
|
||||
},
|
||||
},
|
||||
6 => 'ChannelCount',
|
||||
7 => 'SampleRate',
|
||||
8 => 'BitsPerSample',
|
||||
9 => { Name => 'SampleCount', Format => 'int64u' },
|
||||
11 => 'BlockSize',
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract metadata from a DSF file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid DSF file
|
||||
sub ProcessDSF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $buf2, $tagTablePtr);
|
||||
|
||||
# verify this is a valid DSF file
|
||||
return 0 unless $raf->Read($buff, 40) == 40;
|
||||
return 0 unless $buff =~ /^DSD \x1c\0{7}.{16}fmt /s;
|
||||
$et->SetFileType(); # set the FileType tag
|
||||
my $tagTbl = GetTagTable('Image::ExifTool::DSF::Main');
|
||||
SetByteOrder('II');
|
||||
my $fmtLen = Get64u(\$buff,32);
|
||||
unless ($fmtLen > 12 and $fmtLen < 1000 and
|
||||
$raf->Read($buf2, $fmtLen - 12) == $fmtLen - 12)
|
||||
{
|
||||
$et->Warn('Error reading DSF fmt chunk');
|
||||
return 1;
|
||||
}
|
||||
my $fileSize = Get64u(\$buff, 12);
|
||||
my $metaPos = Get64u(\$buff, 20);
|
||||
$buff = substr($buff, 28) . $buf2;
|
||||
#
|
||||
# process the DSF 'fmt ' chunk
|
||||
#
|
||||
my %dirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => length($buff),
|
||||
);
|
||||
$et->ProcessBinaryData(\%dirInfo, $tagTbl);
|
||||
#
|
||||
# process ID3v2 if it exists
|
||||
#
|
||||
my $metaLen = $fileSize - $metaPos;
|
||||
if ($metaPos and $metaLen > 0 and $metaLen < 20000000 and
|
||||
$raf->Seek($metaPos, 0) and $raf->Read($buff, $metaLen) == $metaLen)
|
||||
{
|
||||
$dirInfo{DataPos} = $metaPos;
|
||||
$dirInfo{DirLen} = $metaLen;
|
||||
my $id3Tbl = GetTagTable('Image::ExifTool::ID3::Main');
|
||||
$et->ProcessDirectory(\%dirInfo, $id3Tbl);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DSF - Read DSF meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read DSF
|
||||
(DSD Stream File) audio files.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/DSF Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
315
.resources/nix/bin/lib/Image/ExifTool/DV.pm
Normal file
315
.resources/nix/bin/lib/Image/ExifTool/DV.pm
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DV.pm
|
||||
#
|
||||
# Description: Read DV meta information
|
||||
#
|
||||
# Revisions: 2010/12/24 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://www.ffmpeg.org/
|
||||
# 2) http://dvswitch.alioth.debian.org/wiki/DV_format/
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DV;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.02';
|
||||
|
||||
# DV profiles (ref 1)
|
||||
my @dvProfiles = (
|
||||
{
|
||||
DSF => 0,
|
||||
VideoSType => 0x0,
|
||||
FrameSize => 120000,
|
||||
VideoFormat => 'IEC 61834, SMPTE-314M - 525/60 (NTSC)',
|
||||
Colorimetry => '4:1:1',
|
||||
FrameRate => 30000/1001,
|
||||
ImageHeight => 480,
|
||||
ImageWidth => 720,
|
||||
},{
|
||||
DSF => 1,
|
||||
VideoSType => 0x0,
|
||||
FrameSize => 144000,
|
||||
VideoFormat => 'IEC 61834 - 625/50 (PAL)',
|
||||
Colorimetry => '4:2:0',
|
||||
FrameRate => 25/1,
|
||||
ImageHeight => 576,
|
||||
ImageWidth => 720,
|
||||
},{
|
||||
DSF => 1,
|
||||
VideoSType => 0x0,
|
||||
FrameSize => 144000,
|
||||
VideoFormat => 'SMPTE-314M - 625/50 (PAL)',
|
||||
Colorimetry => '4:1:1',
|
||||
FrameRate => 25/1,
|
||||
ImageHeight => 576,
|
||||
ImageWidth => 720,
|
||||
},{
|
||||
DSF => 0,
|
||||
VideoSType => 0x4,
|
||||
FrameSize => 240000,
|
||||
VideoFormat => 'DVCPRO50: SMPTE-314M - 525/60 (NTSC) 50 Mbps',
|
||||
Colorimetry => '4:2:2',
|
||||
FrameRate => 30000/1001,
|
||||
ImageHeight => 480,
|
||||
ImageWidth => 720,
|
||||
},{
|
||||
DSF => 1,
|
||||
VideoSType => 0x4,
|
||||
FrameSize => 288000,
|
||||
VideoFormat => 'DVCPRO50: SMPTE-314M - 625/50 (PAL) 50 Mbps',
|
||||
Colorimetry => '4:2:2',
|
||||
FrameRate => 25/1,
|
||||
ImageHeight => 576,
|
||||
ImageWidth => 720,
|
||||
},{
|
||||
DSF => 0,
|
||||
VideoSType => 0x14,
|
||||
FrameSize => 480000,
|
||||
VideoFormat => 'DVCPRO HD: SMPTE-370M - 1080i60 100 Mbps',
|
||||
Colorimetry => '4:2:2',
|
||||
FrameRate => 30000/1001,
|
||||
ImageHeight => 1080,
|
||||
ImageWidth => 1280,
|
||||
},{
|
||||
DSF => 1,
|
||||
VideoSType => 0x14,
|
||||
FrameSize => 576000,
|
||||
VideoFormat => 'DVCPRO HD: SMPTE-370M - 1080i50 100 Mbps',
|
||||
Colorimetry => '4:2:2',
|
||||
FrameRate => 25/1,
|
||||
ImageHeight => 1080,
|
||||
ImageWidth => 1440,
|
||||
},{
|
||||
DSF => 0,
|
||||
VideoSType => 0x18,
|
||||
FrameSize => 240000,
|
||||
VideoFormat => 'DVCPRO HD: SMPTE-370M - 720p60 100 Mbps',
|
||||
Colorimetry => '4:2:2',
|
||||
FrameRate => 60000/1001,
|
||||
ImageHeight => 720,
|
||||
ImageWidth => 960,
|
||||
},{
|
||||
DSF => 1,
|
||||
VideoSType => 0x18,
|
||||
FrameSize => 288000,
|
||||
VideoFormat => 'DVCPRO HD: SMPTE-370M - 720p50 100 Mbps',
|
||||
Colorimetry => '4:2:2',
|
||||
FrameRate => 50/1,
|
||||
ImageHeight => 720,
|
||||
ImageWidth => 960,
|
||||
},{
|
||||
DSF => 1,
|
||||
VideoSType => 0x1,
|
||||
FrameSize => 144000,
|
||||
VideoFormat => 'IEC 61883-5 - 625/50 (PAL)',
|
||||
Colorimetry => '4:2:0',
|
||||
FrameRate => 25/1,
|
||||
ImageHeight => 576,
|
||||
ImageWidth => 720,
|
||||
},
|
||||
);
|
||||
|
||||
# tags to extract, in the order we want to extract them
|
||||
my @dvTags = (
|
||||
'DateTimeOriginal', 'ImageWidth', 'ImageHeight', 'Duration',
|
||||
'TotalBitrate', 'VideoFormat', 'VideoScanType', 'FrameRate',
|
||||
'AspectRatio', 'Colorimetry', 'AudioChannels', 'AudioSampleRate',
|
||||
'AudioBitsPerSample',
|
||||
);
|
||||
|
||||
# DV tags
|
||||
%Image::ExifTool::DV::Main = (
|
||||
GROUPS => { 2 => 'Video' },
|
||||
VARS => { ID_FMT => 'none' },
|
||||
NOTES => 'The following tags are extracted from DV videos.',
|
||||
DateTimeOriginal => {
|
||||
Description => 'Date/Time Original',
|
||||
Groups => { 2 => 'Time' },
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
ImageWidth => { },
|
||||
ImageHeight => { },
|
||||
Duration => { PrintConv => 'ConvertDuration($val)' },
|
||||
TotalBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||||
VideoFormat => { },
|
||||
VideoScanType => { },
|
||||
FrameRate => { PrintConv => 'int($val * 1000 + 0.5) / 1000' },
|
||||
AspectRatio => { },
|
||||
Colorimetry => { },
|
||||
AudioChannels => { Groups => { 2 => 'Audio' } },
|
||||
AudioSampleRate => { Groups => { 2 => 'Audio' } },
|
||||
AudioBitsPerSample => { Groups => { 2 => 'Audio' } },
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information in a DV file (ref 1)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid DV file
|
||||
sub ProcessDV($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
local $_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $start, $profile, $tag, $i, $j);
|
||||
|
||||
$raf->Read($buff, 12000) or return 0;
|
||||
if ($buff =~ /\x1f\x07\0[\x3f\xbf]/sg) {
|
||||
$start = pos($buff) - 4;
|
||||
} else {
|
||||
while ($buff =~ /[\0\xff]\x3f\x07\0.{76}\xff\x3f\x07\x01/sg) {
|
||||
next if pos($buff) - 163 < 0;
|
||||
$start = pos($buff) - 163;
|
||||
last;
|
||||
}
|
||||
return 0 unless defined $start;
|
||||
}
|
||||
my $len = length $buff;
|
||||
# must at least have a full DIF header
|
||||
return 0 if $start + 80 * 6 > $len;
|
||||
|
||||
$et->SetFileType();
|
||||
|
||||
my $pos = $start;
|
||||
my $dsf = (Get8u(\$buff, $pos + 3) & 0x80) >> 7;
|
||||
my $stype = Get8u(\$buff, $pos + 80*5 + 48 + 3) & 0x1f;
|
||||
|
||||
# 576i50 25Mbps 4:1:1 is a special case
|
||||
if ($dsf == 1 && $stype == 0 && Get8u(\$buff, 4) & 0x07) {
|
||||
$profile = $dvProfiles[2];
|
||||
} else {
|
||||
foreach (@dvProfiles) {
|
||||
next unless $dsf == $$_{DSF} and $stype == $$_{VideoSType};
|
||||
$profile = $_;
|
||||
last;
|
||||
}
|
||||
$profile or $et->Warn("Unrecognized DV profile"), return 1;
|
||||
}
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::DV::Main');
|
||||
|
||||
# calculate total bit rate and duration
|
||||
my $byteRate = $$profile{FrameSize} * $$profile{FrameRate};
|
||||
my $fileSize = $$et{VALUE}{FileSize};
|
||||
$$profile{TotalBitrate} = 8 * $byteRate;
|
||||
$$profile{Duration} = $fileSize / $byteRate if defined $fileSize;
|
||||
|
||||
# read DVPack metadata from the VAUX DIF's to extract video tags
|
||||
delete $$profile{DateTimeOriginal};
|
||||
delete $$profile{AspectRatio};
|
||||
delete $$profile{VideoScanType};
|
||||
my ($date, $time, $is16_9, $interlace);
|
||||
for ($i=1; $i<6; ++$i) {
|
||||
$pos += 80;
|
||||
my $type = Get8u(\$buff, $pos);
|
||||
next unless ($type & 0xf0) == 0x50; # look for VAUX types
|
||||
for ($j=0; $j<15; ++$j) {
|
||||
my $p = $pos + $j * 5 + 3;
|
||||
$type = Get8u(\$buff, $p);
|
||||
if ($type == 0x61) { # video control
|
||||
my $apt = Get8u(\$buff, $start + 4) & 0x07;
|
||||
my $t = Get8u(\$buff, $p + 2);
|
||||
$is16_9 = (($t & 0x07) == 0x02 or (not $apt and ($t & 0x07) == 0x07));
|
||||
$interlace = Get8u(\$buff, $p + 3) & 0x10; # (ref 2)
|
||||
} elsif ($type == 0x62) { # date
|
||||
# mask off unused bits
|
||||
my @d = unpack('C*', substr($buff, $p + 1, 4));
|
||||
# (ignore timezone in byte 0 until we can test this properly - see ref 2)
|
||||
$date = sprintf('%.2x:%.2x:%.2x', $d[3], $d[2] & 0x1f, $d[1] & 0x3f);
|
||||
if ($date =~ /[a-f]/) {
|
||||
undef $date; # invalid date
|
||||
} else {
|
||||
# add century (this will work until 2089)
|
||||
$date = ($date lt '9' ? '20' : '19') . $date;
|
||||
}
|
||||
undef $time;
|
||||
} elsif ($type == 0x63 and $date) { # time
|
||||
# (ignore frames past second in byte 0 for now - see ref 2)
|
||||
my $val = Get32u(\$buff, $p + 1) & 0x007f7f3f;
|
||||
my @t = unpack('C*', substr($buff, $p + 1, 4));
|
||||
$time = sprintf('%.2x:%.2x:%.2x', $t[3] & 0x3f, $t[2] & 0x7f, $t[1] & 0x7f);
|
||||
last;
|
||||
} else {
|
||||
undef $time; # must be consecutive
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($date and $time) {
|
||||
$$profile{DateTimeOriginal} = "$date $time";
|
||||
if (defined $is16_9) {
|
||||
$$profile{AspectRatio} = $is16_9 ? '16:9' : '4:3';
|
||||
$$profile{VideoScanType} = $interlace ? 'Interlaced' : 'Progressive';
|
||||
}
|
||||
}
|
||||
|
||||
# read audio tags if available
|
||||
delete $$profile{AudioSampleRate};
|
||||
delete $$profile{AudioBitsPerSample};
|
||||
delete $$profile{AudioChannels};
|
||||
$pos = $start + 80*6 + 80*16*3 + 3;
|
||||
if ($pos + 4 < $len and Get8u(\$buff, $pos) == 0x50) {
|
||||
my $smpls = Get8u(\$buff, $pos + 1);
|
||||
my $freq = (Get8u(\$buff, $pos + 4) >> 3) & 0x07;
|
||||
my $stype = Get8u(\$buff, $pos + 3) & 0x1f;
|
||||
my $quant = Get8u(\$buff, $pos + 4) & 0x07;
|
||||
if ($freq < 3) {
|
||||
$$profile{AudioSampleRate} = {0=>48000, 1=>44100, 2=>32000}->{$freq};
|
||||
}
|
||||
if ($stype < 3) {
|
||||
$stype = 2 if $stype == 0 and $quant and $freq == 2;
|
||||
$$profile{AudioChannels} = {0=>2, 1=>0, 2=>4, 3=>8}->{$stype};
|
||||
}
|
||||
$$profile{AudioBitsPerSample} = $quant ? 12 : 16;
|
||||
}
|
||||
|
||||
# save our metadata
|
||||
foreach $tag (@dvTags) {
|
||||
next unless defined $$profile{$tag};
|
||||
$et->HandleTag($tagTablePtr, $tag, $$profile{$tag});
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DV - Read DV meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read meta
|
||||
information from DV (raw Digital Video) files.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://ffmpeg.org/>
|
||||
|
||||
=item L<http://dvswitch.alioth.debian.org/wiki/DV_format/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/DV Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
404
.resources/nix/bin/lib/Image/ExifTool/DarwinCore.pm
Normal file
404
.resources/nix/bin/lib/Image/ExifTool/DarwinCore.pm
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DarwinCore.pm
|
||||
#
|
||||
# Description: Darwin Core XMP tags
|
||||
#
|
||||
# Revisions: 2013-01-28 - P. Harvey Created
|
||||
# 2025-03-09 - Herb forum17172 changes dwc.tdwg.org 2023
|
||||
#
|
||||
# References: 1) http://rs.tdwg.org/dwc/index.htm
|
||||
# 2) https://exiftool.org/forum/index.php/topic,4442.0/all.html
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DarwinCore;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool::XMP;
|
||||
|
||||
$VERSION = '1.08';
|
||||
|
||||
my %dateTimeInfo = (
|
||||
# NOTE: Do NOT put "Groups" here because Groups hash must not be common!
|
||||
Writable => 'date',
|
||||
Shift => 'Time',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
PrintConvInv => '$self->InverseDateTime($val,undef,1)',
|
||||
);
|
||||
|
||||
my %materialSample = (
|
||||
STRUCT_NAME => 'DarwinCore MaterialSample',
|
||||
NAMESPACE => 'dwc',
|
||||
materialSampleID => { },
|
||||
);
|
||||
|
||||
my %event = (
|
||||
STRUCT_NAME => 'DarwinCore Event',
|
||||
NAMESPACE => 'dwc',
|
||||
day => { Writable => 'integer', Groups => { 2 => 'Time' } },
|
||||
earliestDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, # dwc.tdwg.org removed 2023
|
||||
endDayOfYear => { Writable => 'integer', Groups => { 2 => 'Time' } },
|
||||
eventDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
|
||||
eventID => { Avoid => 1, Notes => 'avoided in favor of XMP-iptcExt:EventID' },
|
||||
eventRemarks => { Writable => 'lang-alt' },
|
||||
eventTime => {
|
||||
Groups => { 2 => 'Time' },
|
||||
Writable => 'string', # (so we can format this ourself)
|
||||
Shift => 'Time',
|
||||
# (allow date/time or just time value)
|
||||
ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
ValueConvInv => 'Image::ExifTool::XMP::FormatXMPDate($val) or $val',
|
||||
PrintConvInv => q{
|
||||
my $v = $self->InverseDateTime($val,undef,1);
|
||||
undef $Image::ExifTool::evalWarning;
|
||||
return $v if $v;
|
||||
# allow time-only values by adding dummy date (thanks Herb)
|
||||
my $v = $self->InverseDateTime("2000:01:01 $val",undef,1);
|
||||
undef $Image::ExifTool::evalWarning;
|
||||
return $v if $v and $v =~ s/.* //; # strip off dummy date
|
||||
$Image::ExifTool::evalWarning = 'Invalid date/time or time-only value (use HH:MM:SS[.ss][+/-HH:MM|Z])';
|
||||
return undef;
|
||||
},
|
||||
},
|
||||
fieldNotes => { },
|
||||
fieldNumber => { },
|
||||
habitat => { },
|
||||
latestDate => { %dateTimeInfo, Groups => { 2 => 'Time' } }, # dwc.tdwg.org removed 2023
|
||||
month => { Writable => 'integer', Groups => { 2 => 'Time' } },
|
||||
parentEventID => { },
|
||||
samplingEffort => { },
|
||||
samplingProtocol => { },
|
||||
sampleSizeValue => { },
|
||||
sampleSizeUnit => { },
|
||||
startDayOfYear => { Writable => 'integer', Groups => { 2 => 'Time' } },
|
||||
verbatimEventDate => { Groups => { 2 => 'Time' } },
|
||||
year => { Writable => 'integer', Groups => { 2 => 'Time' } },
|
||||
eventType => { }, # dwc.tdwg.org added 2023
|
||||
);
|
||||
|
||||
# Darwin Core tags
|
||||
%Image::ExifTool::DarwinCore::Main = (
|
||||
GROUPS => { 0 => 'XMP', 1 => 'XMP-dwc', 2 => 'Other' },
|
||||
NAMESPACE => 'dwc',
|
||||
WRITABLE => 'string',
|
||||
NOTES => q{
|
||||
Tags defined in the Darwin Core (dwc) XMP namespace. See
|
||||
L<http://rs.tdwg.org/dwc/index.htm> for the official specification.
|
||||
},
|
||||
Event => {
|
||||
Name => 'DCEvent', # (avoid conflict with XMP-iptcExt:Event)
|
||||
FlatName => 'Event',
|
||||
Struct => \%event,
|
||||
},
|
||||
# tweak a few of the flattened tag names
|
||||
EventEventDate => { Name => 'EventDate', Flat => 1 },
|
||||
EventEventID => { Name => 'EventID', Flat => 1 },
|
||||
EventEventRemarks => { Name => 'EventRemarks', Flat => 1 },
|
||||
EventEventTime => { Name => 'EventTime', Flat => 1 },
|
||||
FossilSpecimen => { Struct => \%materialSample },
|
||||
GeologicalContext => {
|
||||
FlatName => '', # ('GeologicalContext' is too long)
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore GeologicalContext',
|
||||
NAMESPACE => 'dwc',
|
||||
bed => { },
|
||||
earliestAgeOrLowestStage => { },
|
||||
earliestEonOrLowestEonothem => { },
|
||||
earliestEpochOrLowestSeries => { },
|
||||
earliestEraOrLowestErathem => { },
|
||||
earliestPeriodOrLowestSystem=> { },
|
||||
formation => { },
|
||||
geologicalContextID => { },
|
||||
group => { },
|
||||
highestBiostratigraphicZone => { },
|
||||
latestAgeOrHighestStage => { },
|
||||
latestEonOrHighestEonothem => { },
|
||||
latestEpochOrHighestSeries => { },
|
||||
latestEraOrHighestErathem => { },
|
||||
latestPeriodOrHighestSystem => { },
|
||||
lithostratigraphicTerms => { },
|
||||
lowestBiostratigraphicZone => { },
|
||||
member => { },
|
||||
},
|
||||
},
|
||||
GeologicalContextBed => { Name => 'GeologicalContextBed', Flat => 1 },
|
||||
GeologicalContextFormation => { Name => 'GeologicalContextFormation', Flat => 1 },
|
||||
GeologicalContextGroup => { Name => 'GeologicalContextGroup', Flat => 1 },
|
||||
GeologicalContextMember => { Name => 'GeologicalContextMember', Flat => 1 },
|
||||
HumanObservation => { Struct => \%event },
|
||||
Identification => {
|
||||
FlatName => '', # ('Identification' is redundant)
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore Identification',
|
||||
NAMESPACE => 'dwc',
|
||||
dateIdentified => { %dateTimeInfo, Groups => { 2 => 'Time' } },
|
||||
identificationID => { },
|
||||
identificationQualifier => { },
|
||||
identificationReferences => { },
|
||||
identificationRemarks => { },
|
||||
identificationVerificationStatus => { },
|
||||
identifiedBy => { },
|
||||
typeStatus => { },
|
||||
# new, ref forum13707
|
||||
identifiedByID => { },
|
||||
verbatimIdentification => { },
|
||||
},
|
||||
},
|
||||
LivingSpecimen => { Struct => \%materialSample },
|
||||
MachineObservation => { Struct => \%event },
|
||||
MaterialSample => { Struct => \%materialSample },
|
||||
MaterialSampleMaterialSampleID => { Name => 'MaterialSampleID', Flat => 1 },
|
||||
MeasurementOrFact => {
|
||||
FlatName => '', # ('MeasurementOrFact' is redundant and too long)
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore MeasurementOrFact',
|
||||
NAMESPACE => 'dwc',
|
||||
measurementAccuracy => { Format => 'real' },
|
||||
measurementDeterminedBy => { },
|
||||
measurementDeterminedDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
|
||||
measurementID => { },
|
||||
measurementMethod => { },
|
||||
measurementRemarks => { },
|
||||
measurementType => { },
|
||||
measurementUnit => { },
|
||||
measurementValue => { },
|
||||
parentMeasurementID => { }, # dwc.tdwg.org added 2023
|
||||
},
|
||||
},
|
||||
Occurrence => {
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore Occurrence',
|
||||
NAMESPACE => 'dwc',
|
||||
associatedMedia => { },
|
||||
associatedOccurrences => { },
|
||||
associatedReferences => { },
|
||||
associatedSequences => { }, # dwc.tdwg.org removed 2023
|
||||
associatedTaxa => { },
|
||||
behavior => { },
|
||||
catalogNumber => { },
|
||||
disposition => { }, # dwc.tdwg.org removed 2023
|
||||
establishmentMeans => { },
|
||||
individualCount => { },
|
||||
individualID => { }, # dwc.tdwg.org removed 2023
|
||||
lifeStage => { },
|
||||
occurrenceDetails => { },
|
||||
occurrenceID => { }, # dwc.tdwg.org removed 2023
|
||||
occurrenceRemarks => { },
|
||||
occurrenceStatus => { },
|
||||
organismQuantity => { },
|
||||
organismQuantityType => { },
|
||||
otherCatalogNumbers => { },
|
||||
preparations => { },
|
||||
previousIdentifications => { }, # dwc.tdwg.org removed 2023
|
||||
recordedBy => { }, # dwc.tdwg.org removed 2023
|
||||
recordNumber => { },
|
||||
reproductiveCondition => { },
|
||||
sex => { },
|
||||
# new, ref forum13707
|
||||
degreeOfEstablishment => { },
|
||||
georeferenceVerificationStatus => { },
|
||||
pathway => { },
|
||||
recordedByID => { },
|
||||
caste => { }, # dwc.tdwg.org added 2023
|
||||
vitality => { }, # dwc.tdwg.org added 2023
|
||||
},
|
||||
},
|
||||
OccurrenceOccurrenceDetails => { Name => 'OccurrenceDetails', Flat => 1 },
|
||||
OccurrenceOccurrenceID => { Name => 'OccurrenceID', Flat => 1 },
|
||||
OccurrenceOccurrenceRemarks => { Name => 'OccurrenceRemarks', Flat => 1 },
|
||||
OccurrenceOccurrenceStatus => { Name => 'OccurrenceStatus', Flat => 1 },
|
||||
Organism => {
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore Organism',
|
||||
NAMESPACE => 'dwc',
|
||||
associatedOccurrences => { }, # dwc.tdwg.org removed 2023
|
||||
associatedOrganisms => { },
|
||||
organismID => { },
|
||||
organismName => { },
|
||||
organismRemarks => { },
|
||||
organismScope => { },
|
||||
previousIdentifications => { },
|
||||
},
|
||||
},
|
||||
OrganismOrganismID => { Name => 'OrganismID', Flat => 1 },
|
||||
OrganismOrganismName => { Name => 'OrganismName', Flat => 1 },
|
||||
OrganismOrganismRemarks => { Name => 'OrganismRemarks', Flat => 1 },
|
||||
OrganismOrganismScope => { Name => 'OrganismScope', Flat => 1 },
|
||||
PreservedSpecimen => { Struct => \%materialSample },
|
||||
Record => {
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore Record',
|
||||
NAMESPACE => 'dwc',
|
||||
basisOfRecord => { },
|
||||
collectionCode => { },
|
||||
collectionID => { },
|
||||
dataGeneralizations => { },
|
||||
datasetID => { },
|
||||
datasetName => { },
|
||||
dynamicProperties => { },
|
||||
informationWithheld => { },
|
||||
institutionCode => { },
|
||||
institutionID => { },
|
||||
ownerInstitutionCode => { },
|
||||
},
|
||||
},
|
||||
ResourceRelationship => {
|
||||
FlatName => '', # ('ResourceRelationship' is redundant and too long)
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore ResourceRelationship',
|
||||
NAMESPACE => 'dwc',
|
||||
relatedResourceID => { },
|
||||
relationshipAccordingTo => { },
|
||||
relationshipEstablishedDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
|
||||
relationshipOfResource => { },
|
||||
relationshipRemarks => { },
|
||||
resourceID => { },
|
||||
resourceRelationshipID => { },
|
||||
relationshipOfResourceID => { }, # new, ref forum13707
|
||||
},
|
||||
},
|
||||
Taxon => {
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore Taxon',
|
||||
NAMESPACE => 'dwc',
|
||||
acceptedNameUsage => { },
|
||||
acceptedNameUsageID => { },
|
||||
class => { },
|
||||
family => { },
|
||||
genus => { },
|
||||
higherClassification => { },
|
||||
infraspecificEpithet => { },
|
||||
cultivarEpithet => { }, # new, ref forum13707
|
||||
kingdom => { },
|
||||
nameAccordingTo => { },
|
||||
nameAccordingToID => { },
|
||||
namePublishedIn => { },
|
||||
namePublishedInID => { },
|
||||
namePublishedInYear => { },
|
||||
nomenclaturalCode => { },
|
||||
nomenclaturalStatus => { },
|
||||
order => { },
|
||||
originalNameUsage => { },
|
||||
originalNameUsageID => { },
|
||||
parentNameUsage => { },
|
||||
parentNameUsageID => { },
|
||||
phylum => { },
|
||||
scientificName => { },
|
||||
scientificNameAuthorship => { },
|
||||
scientificNameID => { },
|
||||
specificEpithet => { },
|
||||
subgenus => { },
|
||||
taxonConceptID => { },
|
||||
taxonID => { },
|
||||
taxonRank => { },
|
||||
taxonRemarks => { },
|
||||
taxonomicStatus => { },
|
||||
verbatimTaxonRank => { },
|
||||
vernacularName => { Writable => 'lang-alt' },
|
||||
superFamily => { }, # dwc.tdwg.org added 2023
|
||||
subFamily => { }, # dwc.tdwg.org added 2023
|
||||
tribe => { }, # dwc.tdwg.org added 2023
|
||||
subTribe => { }, # dwc.tdwg.org added 2023
|
||||
genericName => { }, # dwc.tdwg.org added 2023
|
||||
infragenericEpithet => { }, # dwc.tdwg.org added 2023
|
||||
},
|
||||
},
|
||||
TaxonTaxonConceptID => { Name => 'TaxonConceptID', Flat => 1 },
|
||||
TaxonTaxonID => { Name => 'TaxonID', Flat => 1 },
|
||||
TaxonTaxonRank => { Name => 'TaxonRank', Flat => 1 },
|
||||
TaxonTaxonRemarks => { Name => 'TaxonRemarks', Flat => 1 },
|
||||
dctermsLocation => {
|
||||
Name => 'DCTermsLocation',
|
||||
Groups => { 2 => 'Location' },
|
||||
FlatName => 'DC', # ('dctermsLocation' is too long)
|
||||
Struct => {
|
||||
STRUCT_NAME => 'DarwinCore DCTermsLocation',
|
||||
NAMESPACE => 'dwc',
|
||||
continent => { },
|
||||
coordinatePrecision => { },
|
||||
coordinateUncertaintyInMeters => { },
|
||||
country => { },
|
||||
countryCode => { },
|
||||
county => { },
|
||||
decimalLatitude => { },
|
||||
decimalLongitude => { },
|
||||
footprintSpatialFit => { },
|
||||
footprintSRS => { },
|
||||
footprintWKT => { },
|
||||
geodeticDatum => { },
|
||||
georeferencedBy => { },
|
||||
georeferencedDate => { },
|
||||
georeferenceProtocol => { },
|
||||
georeferenceRemarks => { },
|
||||
georeferenceSources => { },
|
||||
georeferenceVerificationStatus => { }, # dwc.tdwg.org removed 2023
|
||||
higherGeography => { },
|
||||
higherGeographyID => { },
|
||||
island => { },
|
||||
islandGroup => { },
|
||||
locality => { },
|
||||
locationAccordingTo => { },
|
||||
locationID => { },
|
||||
locationRemarks => { },
|
||||
maximumDepthInMeters => { },
|
||||
maximumDistanceAboveSurfaceInMeters => { },
|
||||
maximumElevationInMeters => { },
|
||||
minimumDepthInMeters => { },
|
||||
minimumDistanceAboveSurfaceInMeters => { },
|
||||
minimumElevationInMeters => { },
|
||||
municipality => { },
|
||||
pointRadiusSpatialFit => { },
|
||||
stateProvince => { },
|
||||
verbatimCoordinates => { },
|
||||
verbatimCoordinateSystem => { },
|
||||
verbatimDepth => { },
|
||||
verbatimElevation => { },
|
||||
verbatimLatitude => { },
|
||||
verbatimLocality => { },
|
||||
verbatimLongitude => { },
|
||||
verbatimSRS => { },
|
||||
waterBody => { },
|
||||
# new, ref forum13707
|
||||
verticalDatum => { },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
1; #end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DarwinCore - Darwin Core XMP tags
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This file contains tag definitions for the Darwin Core XMP namespace.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://rs.tdwg.org/dwc/index.htm>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/XMP Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
378
.resources/nix/bin/lib/Image/ExifTool/DjVu.pm
Normal file
378
.resources/nix/bin/lib/Image/ExifTool/DjVu.pm
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: DjVu.pm
|
||||
#
|
||||
# Description: Read DjVu archive meta information
|
||||
#
|
||||
# Revisions: 09/25/2008 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://djvu.sourceforge.net/ (DjVu v3 specification, Nov 2005)
|
||||
# 2) http://www.djvu.org/
|
||||
#
|
||||
# Notes: DjVu files are recognized and the IFF structure is processed
|
||||
# by Image::ExifTool::AIFF
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::DjVu;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.07';
|
||||
|
||||
sub ParseAnt($);
|
||||
sub ProcessAnt($$$);
|
||||
sub ProcessMeta($$$);
|
||||
sub ProcessBZZ($$$);
|
||||
|
||||
# DjVu chunks that we parse (ref 4)
|
||||
%Image::ExifTool::DjVu::Main = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => q{
|
||||
Information is extracted from the following chunks in DjVu images. See
|
||||
L<http://www.djvu.org/> for the DjVu specification.
|
||||
},
|
||||
INFO => {
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Info' },
|
||||
},
|
||||
FORM => {
|
||||
TypeOnly => 1, # extract chunk type only, then descend into chunk
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Form' },
|
||||
},
|
||||
ANTa => {
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Ant' },
|
||||
},
|
||||
ANTz => {
|
||||
Name => 'CompressedAnnotation',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::DjVu::Ant',
|
||||
ProcessProc => \&ProcessBZZ,
|
||||
}
|
||||
},
|
||||
INCL => 'IncludedFileID',
|
||||
);
|
||||
|
||||
# information in the DjVu INFO chunk
|
||||
%Image::ExifTool::DjVu::Info = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
FORMAT => 'int8u',
|
||||
PRIORITY => 0, # first INFO block takes priority
|
||||
0 => {
|
||||
Name => 'ImageWidth',
|
||||
Format => 'int16u',
|
||||
},
|
||||
2 => {
|
||||
Name => 'ImageHeight',
|
||||
Format => 'int16u',
|
||||
},
|
||||
4 => {
|
||||
Name => 'DjVuVersion',
|
||||
Description => 'DjVu Version',
|
||||
Format => 'int8u[2]',
|
||||
# (this may be just one byte as with version 0.16)
|
||||
ValueConv => '$val=~/(\d+) (\d+)/ ? "$2.$1" : "0.$val"',
|
||||
},
|
||||
6 => {
|
||||
Name => 'SpatialResolution',
|
||||
Format => 'int16u',
|
||||
ValueConv => '(($val & 0xff)<<8) + ($val>>8)', # (little-endian!)
|
||||
},
|
||||
8 => {
|
||||
Name => 'Gamma',
|
||||
ValueConv => '$val / 10',
|
||||
},
|
||||
9 => {
|
||||
Name => 'Orientation',
|
||||
Mask => 0x07, # (upper 5 bits reserved)
|
||||
PrintConv => {
|
||||
1 => 'Horizontal (normal)',
|
||||
2 => 'Rotate 180',
|
||||
5 => 'Rotate 90 CW',
|
||||
6 => 'Rotate 270 CW',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# information in the DjVu FORM chunk
|
||||
%Image::ExifTool::DjVu::Form = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
0 => {
|
||||
Name => 'SubfileType',
|
||||
Format => 'undef[4]',
|
||||
Priority => 0,
|
||||
PrintConv => {
|
||||
DJVU => 'Single-page image',
|
||||
DJVM => 'Multi-page document',
|
||||
PM44 => 'Color IW44',
|
||||
BM44 => 'Grayscale IW44',
|
||||
DJVI => 'Shared component',
|
||||
THUM => 'Thumbnail image',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# tags found in the DjVu annotation chunk (ANTz or ANTa)
|
||||
%Image::ExifTool::DjVu::Ant = (
|
||||
PROCESS_PROC => \&Image::ExifTool::DjVu::ProcessAnt,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => 'Information extracted from annotation chunks.',
|
||||
# Note: For speed, ProcessAnt() pre-scans for known tag ID's, so if any
|
||||
# new tags are added here they must also be added to the pre-scan check
|
||||
metadata => {
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Meta' }
|
||||
},
|
||||
xmp => {
|
||||
Name => 'XMP',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }
|
||||
},
|
||||
);
|
||||
|
||||
# tags found in the DjVu annotation metadata
|
||||
%Image::ExifTool::DjVu::Meta = (
|
||||
PROCESS_PROC => \&Image::ExifTool::DjVu::ProcessMeta,
|
||||
GROUPS => { 1 => 'DjVu-Meta', 2 => 'Image' },
|
||||
NOTES => q{
|
||||
This table lists the standard DjVu metadata tags, but ExifTool will extract
|
||||
any tags that exist even if they don't appear here. The DjVu v3
|
||||
documentation endorses tags borrowed from two standards: 1) BibTeX
|
||||
bibliography system tags (all lowercase Tag ID's in the table below), and 2)
|
||||
PDF DocInfo tags (capitalized Tag ID's).
|
||||
},
|
||||
# BibTeX tags (ref http://en.wikipedia.org/wiki/BibTeX)
|
||||
address => { Groups => { 2 => 'Location' } },
|
||||
annote => { Name => 'Annotation' },
|
||||
author => { Groups => { 2 => 'Author' } },
|
||||
booktitle => { Name => 'BookTitle' },
|
||||
chapter => { },
|
||||
crossref => { Name => 'CrossRef' },
|
||||
edition => { },
|
||||
eprint => { Name => 'EPrint' },
|
||||
howpublished=> { Name => 'HowPublished' },
|
||||
institution => { },
|
||||
journal => { },
|
||||
key => { },
|
||||
month => { Groups => { 2 => 'Time' } },
|
||||
note => { },
|
||||
number => { },
|
||||
organization=> { },
|
||||
pages => { },
|
||||
publisher => { },
|
||||
school => { },
|
||||
series => { },
|
||||
title => { },
|
||||
type => { },
|
||||
url => { Name => 'URL' },
|
||||
volume => { },
|
||||
year => { Groups => { 2 => 'Time' } },
|
||||
# PDF tags (same as Image::ExifTool::PDF::Info)
|
||||
Title => { },
|
||||
Author => { Groups => { 2 => 'Author' } },
|
||||
Subject => { },
|
||||
Keywords => { },
|
||||
Creator => { },
|
||||
Producer => { },
|
||||
CreationDate => {
|
||||
Name => 'CreateDate',
|
||||
Groups => { 2 => 'Time' },
|
||||
# RFC 3339 date/time format
|
||||
ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
ModDate => {
|
||||
Name => 'ModifyDate',
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
Trapped => {
|
||||
# remove leading '/' from '/True' or '/False'
|
||||
ValueConv => '$val=~s{^/}{}; $val',
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Parse DjVu annotation "s-expression" syntax (recursively)
|
||||
# Inputs: 0) data ref (with pos($$dataPt) set to start of annotation)
|
||||
# Returns: reference to list of tokens/references, or undef if no tokens,
|
||||
# and the position in $$dataPt is set to end of last token
|
||||
# Notes: The DjVu annotation syntax is not well documented, so I make
|
||||
# a number of assumptions here!
|
||||
sub ParseAnt($)
|
||||
{
|
||||
my $dataPt = shift;
|
||||
my (@toks, $tok, $more);
|
||||
# (the DjVu annotation syntax really sucks, and requires that every
|
||||
# single token be parsed in order to properly scan through the items)
|
||||
Tok: for (;;) {
|
||||
# find the next token
|
||||
last unless $$dataPt =~ /(\S)/sg; # get next non-space character
|
||||
if ($1 eq '(') { # start of list
|
||||
$tok = ParseAnt($dataPt);
|
||||
} elsif ($1 eq ')') { # end of list
|
||||
$more = 1;
|
||||
last;
|
||||
} elsif ($1 eq '"') { # quoted string
|
||||
$tok = '';
|
||||
for (;;) {
|
||||
# get string up to the next quotation mark
|
||||
# this doesn't work in perl 5.6.2! grrrr
|
||||
# last Tok unless $$dataPt =~ /(.*?)"/sg;
|
||||
# $tok .= $1;
|
||||
my $pos = pos($$dataPt);
|
||||
last Tok unless $$dataPt =~ /"/sg;
|
||||
$tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
|
||||
# we're good unless quote was escaped by odd number of backslashes
|
||||
last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
|
||||
$tok .= '"'; # quote is part of the string
|
||||
}
|
||||
# convert C escape sequences, allowed in quoted text
|
||||
# (note: this only converts a few of them!)
|
||||
my %esc = ( a => "\a", b => "\b", f => "\f", n => "\n",
|
||||
r => "\r", t => "\t", '"' => '"', '\\' => '\\' );
|
||||
$tok =~ s/\\(.)/$esc{$1}||'\\'.$1/egs;
|
||||
} else { # key name
|
||||
pos($$dataPt) = pos($$dataPt) - 1;
|
||||
# allow anything in key but whitespace, braces and double quotes
|
||||
# (this is one of those assumptions I mentioned)
|
||||
$tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef;
|
||||
}
|
||||
push @toks, $tok if defined $tok;
|
||||
}
|
||||
# prevent further parsing unless more after this
|
||||
pos($$dataPt) = length $$dataPt unless $more;
|
||||
return @toks ? \@toks : undef;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process DjVu annotation chunk (ANTa or decoded ANTz)
|
||||
# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessAnt($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
|
||||
# quick pre-scan to check for metadata or XMP
|
||||
return 1 unless $$dataPt =~ /\(\s*(metadata|xmp)[\s("]/s;
|
||||
|
||||
# parse annotations into a tree structure
|
||||
pos($$dataPt) = 0;
|
||||
my $toks = ParseAnt($dataPt) or return 0;
|
||||
|
||||
# process annotations individually
|
||||
my $ant;
|
||||
foreach $ant (@$toks) {
|
||||
next unless ref $ant eq 'ARRAY' and @$ant >= 2;
|
||||
my $tag = shift @$ant;
|
||||
next if ref $tag or not defined $$tagTablePtr{$tag};
|
||||
if ($tag eq 'metadata') {
|
||||
# ProcessMeta() takes array reference
|
||||
$et->HandleTag($tagTablePtr, $tag, $ant);
|
||||
} else {
|
||||
next if ref $$ant[0]; # only process simple values
|
||||
$et->HandleTag($tagTablePtr, $tag, $$ant[0]);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process DjVu metadata
|
||||
# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
# Notes: input dirInfo DataPt is a reference to a list of pre-parsed metadata entries
|
||||
sub ProcessMeta($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
return 0 unless ref $$dataPt eq 'ARRAY';
|
||||
$et->VerboseDir('Metadata', scalar @$$dataPt);
|
||||
my ($item, $err);
|
||||
foreach $item (@$$dataPt) {
|
||||
# make sure item is a simple tag/value pair
|
||||
$err=1, next unless ref $item eq 'ARRAY' and @$item >= 2 and
|
||||
not ref $$item[0] and not ref $$item[1];
|
||||
# add any new tags to the table
|
||||
unless ($$tagTablePtr{$$item[0]}) {
|
||||
my $name = $$item[0];
|
||||
$name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters
|
||||
length $name or $err = 1, next;
|
||||
AddTagToTable($tagTablePtr, $$item[0], { Name => ucfirst($name) });
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $$item[0], $$item[1]);
|
||||
}
|
||||
$err and $et->Warn('Ignored invalid metadata entry(s)');
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process BZZ-compressed data (in DjVu images)
|
||||
# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessBZZ($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
require Image::ExifTool::BZZ;
|
||||
my $buff = Image::ExifTool::BZZ::Decode($$dirInfo{DataPt});
|
||||
unless (defined $buff) {
|
||||
$et->Warn("Error decoding $$dirInfo{DirName}");
|
||||
return 0;
|
||||
}
|
||||
my $verbose = $et->Options('Verbose');
|
||||
if ($verbose >= 3) {
|
||||
# dump the decoded data in very verbose mode
|
||||
$et->VerboseDir("Decoded $$dirInfo{DirName}", 0, length $buff);
|
||||
$et->VerboseDump(\$buff);
|
||||
}
|
||||
$$dirInfo{DataPt} = \$buff;
|
||||
$$dirInfo{DataLen} = $$dirInfo{DirLen} = length $buff;
|
||||
# process the data using the default process proc for this table
|
||||
my $processProc = $$tagTablePtr{PROCESS_PROC} or return 0;
|
||||
return &$processProc($et, $dirInfo, $tagTablePtr);
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::DjVu - Read DjVu meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to extract meta
|
||||
information from DjVu images. Parsing of the DjVu IFF structure is done by
|
||||
Image::ExifTool::AIFF.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://djvu.sourceforge.net/>
|
||||
|
||||
=item L<http://www.djvu.org/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/DjVu Tags>,
|
||||
L<Image::ExifTool::AIFF(3pm)|Image::ExifTool::AIFF>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
1589
.resources/nix/bin/lib/Image/ExifTool/EXE.pm
Normal file
1589
.resources/nix/bin/lib/Image/ExifTool/EXE.pm
Normal file
File diff suppressed because it is too large
Load diff
7231
.resources/nix/bin/lib/Image/ExifTool/Exif.pm
Normal file
7231
.resources/nix/bin/lib/Image/ExifTool/Exif.pm
Normal file
File diff suppressed because it is too large
Load diff
159
.resources/nix/bin/lib/Image/ExifTool/FITS.pm
Normal file
159
.resources/nix/bin/lib/Image/ExifTool/FITS.pm
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: FITS.pm
|
||||
#
|
||||
# Description: Read Flexible Image Transport System metadata
|
||||
#
|
||||
# Revisions: 2018/03/07 - P. Harvey Created
|
||||
#
|
||||
# References: 1) https://fits.gsfc.nasa.gov/fits_standard.html
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::FITS;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.02';
|
||||
|
||||
# FITS tags (ref 1)
|
||||
%Image::ExifTool::FITS::Main = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => q{
|
||||
This table lists some standard Flexible Image Transport System (FITS) tags,
|
||||
but ExifTool will extract any other tags found. See
|
||||
L<https://fits.gsfc.nasa.gov/fits_standard.html> for the specification.
|
||||
},
|
||||
TELESCOP => 'Telescope',
|
||||
BACKGRND => 'Background',
|
||||
INSTRUME => 'Instrument',
|
||||
OBJECT => 'Object',
|
||||
OBSERVER => 'Observer',
|
||||
DATE => { Name => 'CreateDate', Groups => { 2 => 'Time' } },
|
||||
AUTHOR => { Name => 'Author', Groups => { 2 => 'Author' } },
|
||||
REFERENC => 'Reference',
|
||||
'DATE-OBS'=> { Name => 'ObservationDate', Groups => { 2 => 'Time' } },
|
||||
'TIME-OBS'=> { Name => 'ObservationTime', Groups => { 2 => 'Time' } },
|
||||
'DATE-END'=> { Name => 'ObservationDateEnd', Groups => { 2 => 'Time' } },
|
||||
'TIME-END'=> { Name => 'ObservationTimeEnd', Groups => { 2 => 'Time' } },
|
||||
COMMENT => { Name => 'Comment', PrintConv => '$val =~ s/^ +//; $val',
|
||||
Notes => 'leading spaces are removed if L<PrintConv|../ExifTool.html#PrintConv> is enabled' },
|
||||
HISTORY => { Name => 'History', PrintConv => '$val =~ s/^ +//; $val',
|
||||
Notes => 'leading spaces are removed if L<PrintConv|../ExifTool.html#PrintConv> is enabled' },
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information in a FITS document
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid FITS file
|
||||
sub ProcessFITS($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $tag, $continue);
|
||||
|
||||
return 0 unless $raf->Read($buff, 80) == 80 and $buff =~ /^SIMPLE = {20}T/;
|
||||
$et->SetFileType();
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::FITS::Main');
|
||||
|
||||
for (;;) {
|
||||
$raf->Read($buff, 80) == 80 or $et->Warn('Truncated FITS header'), last;
|
||||
my $key = substr($buff, 0, 8);
|
||||
$key =~ s/ +$//; # remove trailing space from key
|
||||
if ($key eq 'CONTINUE') {
|
||||
defined $continue or $et->Warn('Unexpected FITS CONTINUE keyword'), next;
|
||||
} else {
|
||||
if (defined $continue) {
|
||||
# the previous value wasn't continued, so store with the trailing '&'
|
||||
$et->HandleTag($tagTablePtr, $tag, $continue . '&');
|
||||
undef $continue;
|
||||
}
|
||||
last if $key eq 'END';
|
||||
# make sure the key is valid
|
||||
$key =~ /^[-_A-Z0-9]*$/ or $et->Warn('Format error in FITS header'), last;
|
||||
if ($key eq 'COMMENT' or $key eq 'HISTORY') {
|
||||
my $val = substr($buff, 8); # comments start in column 9
|
||||
$val =~ s/ +$//; # remove trailing spaces
|
||||
$et->HandleTag($tagTablePtr, $key, $val);
|
||||
next;
|
||||
}
|
||||
# ignore other lines that aren't tags
|
||||
next unless substr($buff,8,2) eq '= ';
|
||||
# save tag name (avoiding potential conflict with ExifTool variables)
|
||||
$tag = $Image::ExifTool::specialTags{$key} ? "_$key" : $key;
|
||||
# add to tag table if necessary
|
||||
unless ($$tagTablePtr{$tag}) {
|
||||
my $name = ucfirst lc $tag; # make tag name lower case with leading capital
|
||||
$name =~ s/_(.)/\U$1/g; # remove all '_' and capitalize subsequent letter
|
||||
AddTagToTable($tagTablePtr, $tag, { Name => $name });
|
||||
}
|
||||
}
|
||||
my $val = substr($buff, 10);
|
||||
# parse quoted values
|
||||
if ($val =~ /^'(.*?)'(.*)/) {
|
||||
($val, $buff) = ($1, $2);
|
||||
while ($buff =~ /^('.*?)'(.*)/) { # handle escaped quotes
|
||||
$val .= $1;
|
||||
$buff = $2;
|
||||
}
|
||||
$val =~ s/ +$//; # remove trailing spaces
|
||||
if (defined $continue) {
|
||||
$val = $continue . $val;
|
||||
undef $continue;
|
||||
}
|
||||
# check for possible continuation, removing trailing '&'
|
||||
$val =~ s/\&$// and $continue = $val, next;
|
||||
} elsif (defined $continue) {
|
||||
$et->Warn('Invalid FITS CONTINUE value');
|
||||
next;
|
||||
} else {
|
||||
$val =~ s/ *(\/.*)?$//; # remove trailing spaces and comment
|
||||
next unless length $val; # ignore undefined values
|
||||
$val =~ s/^ +//; # remove leading spaces
|
||||
# re-format floating-point values to use 'e'
|
||||
$val =~ tr/DE/e/ if $val =~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?([ED]([+-]?\d+))?$/;
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, $val);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::FITS - Read Flexible Image Transport System metadata
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read meta
|
||||
information from FITS (Flexible Image Transport System) images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://fits.gsfc.nasa.gov/fits_standard.html>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/FITS Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
321
.resources/nix/bin/lib/Image/ExifTool/FLAC.pm
Normal file
321
.resources/nix/bin/lib/Image/ExifTool/FLAC.pm
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: FLAC.pm
|
||||
#
|
||||
# Description: Read Free Lossless Audio Codec information
|
||||
#
|
||||
# Revisions: 11/13/2006 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://flac.sourceforge.net/
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::FLAC;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.09';
|
||||
|
||||
sub ProcessBitStream($$$);
|
||||
|
||||
# FLAC metadata blocks
|
||||
%Image::ExifTool::FLAC::Main = (
|
||||
NOTES => q{
|
||||
Free Lossless Audio Codec (FLAC) meta information. ExifTool also extracts
|
||||
ID3 information from these files.
|
||||
},
|
||||
0 => {
|
||||
Name => 'StreamInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::FLAC::StreamInfo' },
|
||||
},
|
||||
1 => { Name => 'Padding', Binary => 1, Unknown => 1 },
|
||||
2 => [{ # (see forum14064)
|
||||
Name => 'Application_riff',
|
||||
Condition => '$$valPt =~ /^riff(?!RIFF)/', # (all "riff" blocks but header)
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::RIFF::Main',
|
||||
ByteOrder => 'LittleEndian',
|
||||
Start => 4,
|
||||
},
|
||||
},{
|
||||
Name => 'ApplicationUnknown',
|
||||
Binary => 1,
|
||||
Unknown => 1,
|
||||
}],
|
||||
3 => { Name => 'SeekTable', Binary => 1, Unknown => 1 },
|
||||
4 => {
|
||||
Name => 'VorbisComment',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Comments' },
|
||||
},
|
||||
5 => { Name => 'CueSheet', Binary => 1, Unknown => 1 },
|
||||
6 => {
|
||||
Name => 'Picture',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::FLAC::Picture' },
|
||||
},
|
||||
# 7-126 - Reserved
|
||||
# 127 - Invalid
|
||||
);
|
||||
|
||||
%Image::ExifTool::FLAC::StreamInfo = (
|
||||
PROCESS_PROC => \&ProcessBitStream,
|
||||
NOTES => 'FLAC is big-endian, so bit 0 is the high-order bit in this table.',
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
'Bit000-015' => 'BlockSizeMin',
|
||||
'Bit016-031' => 'BlockSizeMax',
|
||||
'Bit032-055' => 'FrameSizeMin',
|
||||
'Bit056-079' => 'FrameSizeMax',
|
||||
'Bit080-099' => 'SampleRate',
|
||||
'Bit100-102' => {
|
||||
Name => 'Channels',
|
||||
ValueConv => '$val + 1',
|
||||
},
|
||||
'Bit103-107' => {
|
||||
Name => 'BitsPerSample',
|
||||
ValueConv => '$val + 1',
|
||||
},
|
||||
'Bit108-143' => 'TotalSamples',
|
||||
'Bit144-271' => { #Tim Eliseo
|
||||
Name => 'MD5Signature',
|
||||
Format => 'undef',
|
||||
ValueConv => 'unpack("H*",$val)',
|
||||
},
|
||||
);
|
||||
|
||||
%Image::ExifTool::FLAC::Picture = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
FORMAT => 'int32u',
|
||||
0 => {
|
||||
Name => 'PictureType',
|
||||
PrintConv => { # (Note: Duplicated in ID3, ASF and FLAC modules!)
|
||||
0 => 'Other',
|
||||
1 => '32x32 PNG Icon',
|
||||
2 => 'Other Icon',
|
||||
3 => 'Front Cover',
|
||||
4 => 'Back Cover',
|
||||
5 => 'Leaflet',
|
||||
6 => 'Media',
|
||||
7 => 'Lead Artist',
|
||||
8 => 'Artist',
|
||||
9 => 'Conductor',
|
||||
10 => 'Band',
|
||||
11 => 'Composer',
|
||||
12 => 'Lyricist',
|
||||
13 => 'Recording Studio or Location',
|
||||
14 => 'Recording Session',
|
||||
15 => 'Performance',
|
||||
16 => 'Capture from Movie or Video',
|
||||
17 => 'Bright(ly) Colored Fish',
|
||||
18 => 'Illustration',
|
||||
19 => 'Band Logo',
|
||||
20 => 'Publisher Logo',
|
||||
},
|
||||
},
|
||||
1 => {
|
||||
Name => 'PictureMIMEType',
|
||||
Format => 'var_pstr32',
|
||||
},
|
||||
2 => {
|
||||
Name => 'PictureDescription',
|
||||
Format => 'var_pstr32',
|
||||
ValueConv => '$self->Decode($val, "UTF8")',
|
||||
},
|
||||
3 => 'PictureWidth',
|
||||
4 => 'PictureHeight',
|
||||
5 => 'PictureBitsPerPixel',
|
||||
6 => 'PictureIndexedColors',
|
||||
7 => 'PictureLength',
|
||||
8 => {
|
||||
Name => 'Picture',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Format => 'undef[$val{7}]',
|
||||
Binary => 1,
|
||||
},
|
||||
);
|
||||
|
||||
# FLAC composite tags
|
||||
%Image::ExifTool::FLAC::Composite = (
|
||||
Duration => {
|
||||
Require => {
|
||||
0 => 'FLAC:SampleRate',
|
||||
1 => 'FLAC:TotalSamples',
|
||||
},
|
||||
ValueConv => '($val[0] and $val[1]) ? $val[1] / $val[0] : undef',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
);
|
||||
|
||||
# add our composite tags
|
||||
Image::ExifTool::AddCompositeTags('Image::ExifTool::FLAC');
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process information in a bit stream
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Notes: Byte order is used to determine the ordering of bits in the stream:
|
||||
# 'MM' = bit 0 is most significant, 'II' = bit 0 is least significant
|
||||
# - can handle arbitrarily wide values (eg. 8-byte or larger integers)
|
||||
sub ProcessBitStream($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirStart = $$dirInfo{DirStart} || 0;
|
||||
my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $byteOrder = GetByteOrder();
|
||||
my $tag;
|
||||
|
||||
if ($verbose) {
|
||||
$et->VPrint(0, " + [BitStream directory, $dirLen bytes, '${byteOrder}' order]\n");
|
||||
}
|
||||
foreach $tag (sort keys %$tagTablePtr) {
|
||||
next unless $tag =~ /^Bit(\d+)-?(\d+)?/;
|
||||
my ($b1, $b2) = ($1, $2 || $1); # start/end bit numbers in stream
|
||||
my ($i1, $i2) = (int($b1 / 8), int($b2 / 8)); # start/end byte numbers
|
||||
my ($f1, $f2) = ($b1 % 8, $b2 % 8); # start/end bit numbers within each byte
|
||||
last if $i2 >= $dirLen;
|
||||
my ($val, $extra);
|
||||
# if Format is unspecified, convert the specified number of bits to an unsigned integer,
|
||||
# otherwise allow HandleTag to convert whole bytes the normal way (via undefined $val)
|
||||
if (ref $$tagTablePtr{$tag} ne 'HASH' or not $$tagTablePtr{$tag}{Format}) {
|
||||
my ($i, $mask);
|
||||
$val = 0;
|
||||
$extra = ', Mask=0x' if $verbose and ($f1 != 0 or $f2 != 7);
|
||||
if ($byteOrder eq 'MM') {
|
||||
# loop from high byte to low byte
|
||||
for ($i=$i1; $i<=$i2; ++$i) {
|
||||
$mask = 0xff;
|
||||
if ($i == $i1 and $f1) {
|
||||
# mask off high bits in first word (0 is high bit)
|
||||
foreach ((8-$f1) .. 7) { $mask ^= (1 << $_) }
|
||||
}
|
||||
if ($i == $i2 and $f2 < 7) {
|
||||
# mask off low bits in last word (7 is low bit)
|
||||
foreach (0 .. (6-$f2)) { $mask ^= (1 << $_) }
|
||||
}
|
||||
$val = $val * 256 + ($mask & Get8u($dataPt, $i + $dirStart));
|
||||
$extra .= sprintf('%.2x', $mask) if $extra;
|
||||
}
|
||||
} else {
|
||||
# (FLAC is big-endian, but support little-endian bit streams
|
||||
# so this routine can be used by other modules)
|
||||
# loop from high byte to low byte
|
||||
for ($i=$i2; $i>=$i1; --$i) {
|
||||
$mask = 0xff;
|
||||
if ($i == $i1 and $f1) {
|
||||
# mask off low bits in first word (0 is low bit)
|
||||
foreach (0 .. ($f1-1)) { $mask ^= (1 << $_) }
|
||||
}
|
||||
if ($i == $i2 and $f2 < 7) {
|
||||
# mask off high bits in last word (7 is high bit)
|
||||
foreach (($f2+1) .. 7) { $mask ^= (1 << $_) }
|
||||
}
|
||||
$val = $val * 256 + ($mask & Get8u($dataPt, $i + $dirStart));
|
||||
$extra .= sprintf('%.2x', $mask) if $extra;
|
||||
}
|
||||
}
|
||||
# shift word down until low bit is in position 0
|
||||
until ($mask & 0x01) {
|
||||
$val /= 2;
|
||||
$mask >>= 1;
|
||||
}
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => $dirStart + $i1,
|
||||
Size => $i2 - $i1 + 1,
|
||||
Extra => $extra,
|
||||
);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from an Ogg FLAC file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid Ogg FLAC file
|
||||
sub ProcessFLAC($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
|
||||
# must first check for leading/trailing ID3 information
|
||||
unless ($$et{DoneID3}) {
|
||||
require Image::ExifTool::ID3;
|
||||
Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1;
|
||||
}
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $out = $et->Options('TextOut');
|
||||
my ($buff, $err);
|
||||
|
||||
# check FLAC signature
|
||||
$raf->Read($buff, 4) == 4 and $buff eq 'fLaC' or return 0;
|
||||
$et->SetFileType();
|
||||
SetByteOrder('MM');
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::FLAC::Main');
|
||||
for (;;) {
|
||||
# read next metadata block header
|
||||
$raf->Read($buff, 4) == 4 or last;
|
||||
my $flag = unpack('C', $buff);
|
||||
my $size = unpack('N', $buff) & 0x00ffffff;
|
||||
$raf->Read($buff, $size) == $size or $err = 1, last;
|
||||
my $last = $flag & 0x80; # last-metadata-block flag
|
||||
my $tag = $flag & 0x7f; # tag bits
|
||||
if ($verbose) {
|
||||
print $out "FLAC metadata block, type $tag:\n";
|
||||
$et->VerboseDump(\$buff, DataPos => $raf->Tell() - $size);
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, $buff,
|
||||
DataPt => \$buff,
|
||||
DataPos => $raf->Tell() - $size,
|
||||
Start => 0,
|
||||
Size => $size,
|
||||
);
|
||||
last if $last; # all done if is set
|
||||
}
|
||||
$err and $et->Warn('Format error in FLAC file');
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::FLAC - Read Free Lossless Audio Codec information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to extract meta
|
||||
information from Free Lossless Audio Codec (FLAC) audio files.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://flac.sourceforge.net/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/FLAC Tags>,
|
||||
L<Image::ExifTool::TagNames/Ogg Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
353
.resources/nix/bin/lib/Image/ExifTool/FLIF.pm
Normal file
353
.resources/nix/bin/lib/Image/ExifTool/FLIF.pm
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: FLIF.pm
|
||||
#
|
||||
# Description: Read/write FLIF meta information
|
||||
#
|
||||
# Revisions: 2016/10/11 - P. Harvey Created
|
||||
# 2016/10/14 - PH Added write support
|
||||
#
|
||||
# References: 1) http://flif.info/
|
||||
# 2) https://github.com/FLIF-hub/FLIF/blob/master/doc/metadata
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::FLIF;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.02';
|
||||
|
||||
my %flifMap = (
|
||||
EXIF => 'FLIF',
|
||||
XMP => 'FLIF',
|
||||
ICC_Profile => 'FLIF',
|
||||
IFD0 => 'EXIF',
|
||||
IFD1 => 'IFD0',
|
||||
ExifIFD => 'IFD0',
|
||||
GPS => 'IFD0',
|
||||
SubIFD => 'IFD0',
|
||||
GlobParamIFD => 'IFD0',
|
||||
PrintIM => 'IFD0',
|
||||
InteropIFD => 'ExifIFD',
|
||||
MakerNotes => 'ExifIFD',
|
||||
);
|
||||
|
||||
# FLIF tags
|
||||
%Image::ExifTool::FLIF::Main = (
|
||||
GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Image' },
|
||||
VARS => { ID_FMT => 'dec' },
|
||||
NOTES => q{
|
||||
Information extracted from Free Lossless Image Format files. See
|
||||
L<http://flif.info/> for more information.
|
||||
},
|
||||
#
|
||||
# header information
|
||||
#
|
||||
0 => {
|
||||
Name => 'ImageType',
|
||||
PrintConv => {
|
||||
'1' => 'Grayscale (non-interlaced)',
|
||||
'3' => 'RGB (non-interlaced)',
|
||||
'4' => 'RGBA (non-interlaced)',
|
||||
'A' => 'Grayscale (interlaced)',
|
||||
'C' => 'RGB (interlaced)',
|
||||
'D' => 'RGBA (interlaced)',
|
||||
'Q' => 'Grayscale Animation (non-interlaced)',
|
||||
'S' => 'RGB Animation (non-interlaced)',
|
||||
'T' => 'RGBA Animation (non-interlaced)',
|
||||
'a' => 'Grayscale Animation (interlaced)',
|
||||
'c' => 'RGB Animation (interlaced)',
|
||||
'd' => 'RGBA Animation (interlaced)',
|
||||
},
|
||||
},
|
||||
1 => {
|
||||
Name => 'BitDepth',
|
||||
PrintConv => {
|
||||
'0' => 'Custom',
|
||||
'1' => 8,
|
||||
'2' => 16,
|
||||
},
|
||||
},
|
||||
2 => 'ImageWidth',
|
||||
3 => 'ImageHeight',
|
||||
4 => 'AnimationFrames',
|
||||
5 => {
|
||||
Name => 'Encoding',
|
||||
PrintConv => {
|
||||
0 => 'FLIF16',
|
||||
},
|
||||
},
|
||||
#
|
||||
# metadata chunks
|
||||
#
|
||||
iCCP => {
|
||||
Name => 'ICC_Profile',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::ICC_Profile::Main',
|
||||
},
|
||||
},
|
||||
eXif => {
|
||||
Name => 'EXIF',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Exif::Main',
|
||||
ProcessProc => \&Image::ExifTool::ProcessTIFF,
|
||||
WriteProc => \&Image::ExifTool::WriteTIFF,
|
||||
Start => 6, # (skip "Exif\0\0" header)
|
||||
Header => "Exif\0\0",
|
||||
},
|
||||
},
|
||||
eXmp => {
|
||||
Name => 'XMP',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::XMP::Main',
|
||||
},
|
||||
},
|
||||
# tRko - list of truncation offsets
|
||||
# \0 - FLIF16-format image data
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read variable-length FLIF integer
|
||||
# Inputs: 0) raf reference, 1) number to add to returned value
|
||||
# Returns: integer, or undef on EOF
|
||||
sub GetVarInt($;$)
|
||||
{
|
||||
my ($raf, $add) = @_;
|
||||
my ($val, $buff);
|
||||
for ($val=0; ; $val<<=7) {
|
||||
$raf->Read($buff, 1) or return undef;
|
||||
my $byte = ord($buff);
|
||||
$val |= ($byte & 0x7f);
|
||||
last unless $byte & 0x80;
|
||||
}
|
||||
return $val + ($add || 0);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Construct variable-length FLIF integer
|
||||
# Inputs: 0) integer
|
||||
# Returns: FLIF variable-length integer byte stream
|
||||
sub SetVarInt($)
|
||||
{
|
||||
my $val = shift;
|
||||
my $buff = '';
|
||||
my $high = 0;
|
||||
for (;;) {
|
||||
$buff = chr(($val & 0x7f) | $high) . $buff;
|
||||
last unless $val >>= 7;
|
||||
$high = 0x80;
|
||||
}
|
||||
return $buff;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read FLIF header
|
||||
# Inputs: 0) RAF ref
|
||||
# Returns: Scalar context: binary header block
|
||||
# List context: header values (4 or 5 elements: type,depth,width,height[,frames])
|
||||
# or undef if this isn't a valid FLIF file header
|
||||
sub ReadFLIFHeader($)
|
||||
{
|
||||
my $raf = shift;
|
||||
my ($buff, @vals);
|
||||
|
||||
# verify this is a valid FLIF file
|
||||
return () unless $raf->Read($buff, 6) == 6 and $buff =~ /^FLIF([0-\x6f])([0-2])/;
|
||||
|
||||
# decode header information ("FLIF" chunk)
|
||||
push @vals, $1, $2; # type, depth
|
||||
push @vals, GetVarInt($raf,+1), GetVarInt($raf,+1); # width, height (+1 each)
|
||||
push @vals, GetVarInt($raf,+2) if $vals[0] gt 'H'; # frames (+2)
|
||||
|
||||
return () unless defined $vals[-1];
|
||||
return @vals if wantarray; # return the decoded header values
|
||||
|
||||
# return the binary header block
|
||||
my $hdrLen = $raf->Tell();
|
||||
return () unless $raf->Seek(0,0) and $raf->Read($buff, $hdrLen) == $hdrLen;
|
||||
return $buff;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# WriteFLIF : Write FLIF image
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid FLIF file, or -1 if
|
||||
# an output file was specified and a write error occurred
|
||||
sub WriteFLIF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $soi, @addTags, %doneTag);
|
||||
|
||||
# verify FLIF header and copy it to the output file
|
||||
$buff = ReadFLIFHeader($raf) or return 0;
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
Write($outfile, $buff) or return -1;
|
||||
|
||||
$et->InitWriteDirs(\%flifMap);
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::FLIF::Main');
|
||||
|
||||
# loop through the FLIF chunks
|
||||
for (;;) {
|
||||
my ($tag, $size, $inflated);
|
||||
# read new tag (or soi) unless we already hit the soi (start of image)
|
||||
if (not defined $soi) {
|
||||
$raf->Read($buff, 4) == 4 or $et->Error('Unexpected EOF'), last;
|
||||
if ($buff lt ' ') {
|
||||
$soi = $buff; # we have hit the start of image (no more metadata)
|
||||
# make list of new tags to add
|
||||
foreach $tag ('eXif', 'eXmp', 'iCCP') {
|
||||
push @addTags, $tag if $$et{ADD_DIRS}{$$tagTablePtr{$tag}{Name}} and not $doneTag{$tag};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (not defined $soi) {
|
||||
$tag = $buff;
|
||||
$size = GetVarInt($raf); # read the data size
|
||||
} elsif (@addTags) {
|
||||
$tag = shift @addTags;
|
||||
($buff, $size) = ('', 0); # create metadata from scratch
|
||||
} else {
|
||||
# finish copying file (no more metadata to add)
|
||||
Write($outfile, $soi) or return -1;
|
||||
Write($outfile, $buff) or return -1 while $raf->Read($buff, 65536);
|
||||
last; # all done!
|
||||
}
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
if ($tagInfo and $$tagInfo{SubDirectory} and $$et{EDIT_DIRS}{$$tagInfo{Name}}) {
|
||||
$doneTag{$tag} = 1; # prevent adding this back again later
|
||||
unless (defined $soi) {
|
||||
$raf->Read($buff, $size) == $size or $et->Error("Truncated FLIF $tag chunk"), last;
|
||||
}
|
||||
# rewrite the compressed data
|
||||
if (eval { require IO::Uncompress::RawInflate } and eval { require IO::Compress::RawDeflate } ) {
|
||||
if (length $buff == 0) {
|
||||
$inflated = $buff; # (creating from scratch, so no need to inflate)
|
||||
} elsif (not IO::Uncompress::RawInflate::rawinflate(\$buff => \$inflated)) {
|
||||
$et->Error("Error inflating FLIF $tag chunk"), last;
|
||||
}
|
||||
my $subdir = $$tagInfo{SubDirectory};
|
||||
my %subdirInfo = (
|
||||
DirName => $$tagInfo{Name},
|
||||
DataPt => \$inflated,
|
||||
DirStart => length($inflated) ? $$subdir{Start} : undef,
|
||||
ReadOnly => 1, # (used only by WriteXMP)
|
||||
);
|
||||
my $subTable = GetTagTable($$subdir{TagTable});
|
||||
$inflated = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
|
||||
if (defined $inflated) {
|
||||
next unless length $inflated; # (delete directory if length is zero)
|
||||
$inflated = $$subdir{Header} . $inflated if $$subdir{Header}; # (add back header if necessary)
|
||||
unless (IO::Compress::RawDeflate::rawdeflate(\$inflated => \$buff)) {
|
||||
$et->Error("Error deflating FLIF $tag chunk"), last;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$et->Warn('Install IO::Compress::RawDeflate to write FLIF metadata');
|
||||
}
|
||||
Write($outfile, $tag, SetVarInt(length $buff), $buff) or return -1;
|
||||
} elsif (not defined $soi) {
|
||||
Write($outfile, $tag, SetVarInt($size)) or return -1;
|
||||
Image::ExifTool::CopyBlock($raf, $outfile, $size) or return -1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Extract information from an FLIF file
|
||||
# Inputs: 0) ExifTool object reference, 1) DirInfo reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid FLIF file
|
||||
sub ProcessFLIF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $tag, $inflated);
|
||||
|
||||
# verify this is a valid FLIF file and read the header
|
||||
my @vals = ReadFLIFHeader($raf) or return 0;
|
||||
|
||||
$et->SetFileType();
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::FLIF::Main');
|
||||
my $verbose = $et->Options('Verbose');
|
||||
|
||||
# save the header information
|
||||
$et->VPrint(0, "FLIF header:\n") if $verbose;
|
||||
for ($tag=0; defined $vals[$tag]; ++$tag) {
|
||||
$et->HandleTag($tagTablePtr, $tag, $vals[$tag]);
|
||||
}
|
||||
|
||||
# loop through the FLIF chunks
|
||||
for (;;) {
|
||||
$raf->Read($tag, 4) == 4 or $et->Warn('Unexpected EOF'), last;
|
||||
my $byte = ord substr($tag, 0, 1);
|
||||
# all done if we arrived at the image chunk
|
||||
$byte < 32 and $et->HandleTag($tagTablePtr, 5, $byte), last;
|
||||
my $size = GetVarInt($raf);
|
||||
$et->VPrint(0, "FLIF $tag ($size bytes):\n") if $verbose;
|
||||
if ($$tagTablePtr{$tag}) {
|
||||
$raf->Read($buff, $size) == $size or $et->Warn("Truncated FLIF $tag chunk"), last;
|
||||
$et->VerboseDump(\$buff, Addr => $raf->Tell() - $size) if $verbose > 2;
|
||||
# inflate the compressed data
|
||||
if (eval { require IO::Uncompress::RawInflate }) {
|
||||
if (IO::Uncompress::RawInflate::rawinflate(\$buff => \$inflated)) {
|
||||
$et->HandleTag($tagTablePtr, $tag, $inflated,
|
||||
DataPt => \$inflated,
|
||||
Size => length $inflated,
|
||||
Extra => ' inflated',
|
||||
);
|
||||
} else {
|
||||
$et->Warn("Error inflating FLIF $tag chunk");
|
||||
}
|
||||
} else {
|
||||
$et->Warn('Install IO::Uncompress::RawInflate to decode FLIF metadata');
|
||||
}
|
||||
} else {
|
||||
$raf->Seek($size, 1) or $et->Warn('Seek error'), last;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::FLIF - Read/write FLIF meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains routines required by Image::ExifTool to read and write
|
||||
meta information in FLIF (Free Lossless Image Format) images.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://flif.info/>
|
||||
|
||||
=item L<https://github.com/FLIF-hub/FLIF/blob/master/doc/metadata>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/FLIF Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
1669
.resources/nix/bin/lib/Image/ExifTool/FLIR.pm
Normal file
1669
.resources/nix/bin/lib/Image/ExifTool/FLIR.pm
Normal file
File diff suppressed because it is too large
Load diff
366
.resources/nix/bin/lib/Image/ExifTool/Fixup.pm
Normal file
366
.resources/nix/bin/lib/Image/ExifTool/Fixup.pm
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Fixup.pm
|
||||
#
|
||||
# Description: Utility to handle pointer fixups
|
||||
#
|
||||
# Revisions: 01/19/2005 - P. Harvey Created
|
||||
# 04/11/2005 - P. Harvey Allow fixups to be tagged with a marker,
|
||||
# and add new marker-related routines
|
||||
# 06/21/2006 - P. Harvey Patch to work with negative offsets
|
||||
# 07/07/2006 - P. Harvey Added support for 16-bit pointers
|
||||
# 02/19/2013 - P. Harvey Added IsEmpty()
|
||||
#
|
||||
# Data Members:
|
||||
#
|
||||
# Start - Position in data where a zero pointer points to.
|
||||
# Shift - Amount to shift offsets (relative to Start).
|
||||
# Fixups - List of Fixup object references to to shift relative to this Fixup.
|
||||
# Pointers - Hash of references to fixup pointer arrays, keyed by ByteOrder
|
||||
# string (with "2" added if pointer is 16-bit [default is 32-bit],
|
||||
# plus "_$marker" suffix if tagged with a marker name).
|
||||
#
|
||||
# Procedure:
|
||||
#
|
||||
# 1. Create a Fixup object for each data block containing pointers
|
||||
# 2. Call AddFixup with the offset of each pointer in the block
|
||||
# - pointer is assumed int32u with the current byte order
|
||||
# - may also be called with a fixup reference for contained blocks
|
||||
# 3. Add the necessary pointer offset to $$fixup{Shift}
|
||||
# 4. Add data size to $$fixup{Start} if data is added before the block
|
||||
# - automatically also shifts pointers by this amount
|
||||
# 5. Call ApplyFixup to apply the fixup to all pointers
|
||||
# - resets Shift and Start to 0 after applying fixup
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::Fixup;
|
||||
|
||||
use strict;
|
||||
use Image::ExifTool qw(GetByteOrder SetByteOrder Get32u Get32s Set32u
|
||||
Get16u Get16s Set16u);
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '1.06';
|
||||
|
||||
sub AddFixup($$;$$);
|
||||
sub ApplyFixup($$);
|
||||
sub Dump($;$);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# New - create new Fixup object
|
||||
# Inputs: 0) reference to Fixup object or Fixup class name
|
||||
sub new
|
||||
{
|
||||
local $_;
|
||||
my $that = shift;
|
||||
my $class = ref($that) || $that || 'Image::ExifTool::Fixup';
|
||||
my $self = bless {}, $class;
|
||||
|
||||
# initialize required members
|
||||
$self->{Start} = 0;
|
||||
$self->{Shift} = 0;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Clone this object
|
||||
# Inputs: 0) reference to Fixup object or Fixup class name
|
||||
# Returns: reference to new Fixup object
|
||||
sub Clone($)
|
||||
{
|
||||
my $self = shift;
|
||||
my $clone = Image::ExifTool::Fixup->new;
|
||||
$clone->{Start} = $self->{Start};
|
||||
$clone->{Shift} = $self->{Shift};
|
||||
my $phash = $self->{Pointers};
|
||||
if ($phash) {
|
||||
$clone->{Pointers} = { };
|
||||
my $byteOrder;
|
||||
foreach $byteOrder (keys %$phash) {
|
||||
my @pointers = @{$phash->{$byteOrder}};
|
||||
$clone->{Pointers}->{$byteOrder} = \@pointers;
|
||||
}
|
||||
}
|
||||
if ($self->{Fixups}) {
|
||||
$clone->{Fixups} = [ ];
|
||||
my $subFixup;
|
||||
foreach $subFixup (@{$self->{Fixups}}) {
|
||||
push @{$clone->{Fixups}}, $subFixup->Clone();
|
||||
}
|
||||
}
|
||||
return $clone;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Add fixup pointer or another fixup object below this one
|
||||
# Inputs: 0) Fixup object reference
|
||||
# 1) Scalar for pointer offset, or reference to Fixup object
|
||||
# 2) Optional marker name for the pointer
|
||||
# 3) Optional pointer format ('int16u' or 'int32u', defaults to 'int32u')
|
||||
# Notes: Byte ordering must be set properly for the pointer being added (must keep
|
||||
# track of the byte order of each offset since MakerNotes may have different byte order!)
|
||||
sub AddFixup($$;$$)
|
||||
{
|
||||
my ($self, $pointer, $marker, $format) = @_;
|
||||
if (ref $pointer) {
|
||||
$self->{Fixups} or $self->{Fixups} = [ ];
|
||||
push @{$self->{Fixups}}, $pointer;
|
||||
} else {
|
||||
my $byteOrder = GetByteOrder();
|
||||
if (defined $format) {
|
||||
if ($format eq 'int16u') {
|
||||
$byteOrder .= '2';
|
||||
} elsif ($format ne 'int32u') {
|
||||
warn "Bad Fixup pointer format $format\n";
|
||||
}
|
||||
}
|
||||
$byteOrder .= "_$marker" if defined $marker;
|
||||
my $phash = $self->{Pointers};
|
||||
$phash or $phash = $self->{Pointers} = { };
|
||||
$phash->{$byteOrder} or $phash->{$byteOrder} = [ ];
|
||||
push @{$phash->{$byteOrder}}, $pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# fix up pointer offsets
|
||||
# Inputs: 0) Fixup object reference, 1) data reference
|
||||
# Outputs: Collapses fixup hierarchy into linear lists of fixup pointers
|
||||
sub ApplyFixup($$)
|
||||
{
|
||||
my ($self, $dataPt) = @_;
|
||||
|
||||
my $start = $self->{Start};
|
||||
my $shift = $self->{Shift} + $start; # make shift relative to start
|
||||
my $phash = $self->{Pointers};
|
||||
|
||||
# fix up pointers in this fixup
|
||||
if ($phash and ($start or $shift)) {
|
||||
my $saveOrder = GetByteOrder(); # save original byte ordering
|
||||
my ($byteOrder, $ptr);
|
||||
foreach $byteOrder (keys %$phash) {
|
||||
SetByteOrder(substr($byteOrder,0,2));
|
||||
# apply the fixup offset shift (must get as signed integer
|
||||
# to avoid overflow in case it was negative before)
|
||||
my ($get, $set) = ($byteOrder =~ /^(II2|MM2)/) ?
|
||||
(\&Get16s, \&Set16u) : (\&Get32s, \&Set32u);
|
||||
foreach $ptr (@{$phash->{$byteOrder}}) {
|
||||
$ptr += $start; # update pointer to new start location
|
||||
next unless $shift;
|
||||
&$set(&$get($dataPt, $ptr) + $shift, $dataPt, $ptr);
|
||||
}
|
||||
}
|
||||
SetByteOrder($saveOrder); # restore original byte ordering
|
||||
}
|
||||
# recurse into contained fixups
|
||||
if ($self->{Fixups}) {
|
||||
# create our pointer hash if it doesn't exist
|
||||
$phash or $phash = $self->{Pointers} = { };
|
||||
# loop through all contained fixups
|
||||
my $subFixup;
|
||||
foreach $subFixup (@{$self->{Fixups}}) {
|
||||
# adjust the subfixup start and shift
|
||||
$subFixup->{Start} += $start;
|
||||
$subFixup->{Shift} += $shift - $start;
|
||||
# recursively apply contained fixups
|
||||
ApplyFixup($subFixup, $dataPt);
|
||||
my $shash = $subFixup->{Pointers} or next;
|
||||
# add all pointers to our collapsed lists
|
||||
my $byteOrder;
|
||||
foreach $byteOrder (keys %$shash) {
|
||||
$phash->{$byteOrder} or $phash->{$byteOrder} = [ ];
|
||||
push @{$phash->{$byteOrder}}, @{$shash->{$byteOrder}};
|
||||
delete $shash->{$byteOrder};
|
||||
}
|
||||
delete $subFixup->{Pointers};
|
||||
}
|
||||
delete $self->{Fixups}; # remove our contained fixups
|
||||
}
|
||||
# reset our Start/Shift for the collapsed fixup
|
||||
$self->{Start} = $self->{Shift} = 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Is this Fixup empty?
|
||||
# Inputs: 0) Fixup object ref
|
||||
# Returns: True if there are no offsets to fix
|
||||
sub IsEmpty($)
|
||||
{
|
||||
my $self = shift;
|
||||
my $phash = $self->{Pointers};
|
||||
if ($phash) {
|
||||
my $key;
|
||||
foreach $key (keys %$phash) {
|
||||
next unless ref $$phash{$key} eq 'ARRAY';
|
||||
return 0 if @{$$phash{$key}};
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Does specified marker exist?
|
||||
# Inputs: 0) Fixup object reference, 1) marker name
|
||||
# Returns: True if fixup contains specified marker name
|
||||
sub HasMarker($$)
|
||||
{
|
||||
my ($self, $marker) = @_;
|
||||
my $phash = $self->{Pointers};
|
||||
return 0 unless $phash;
|
||||
return 1 if grep /_$marker$/, keys %$phash;
|
||||
return 0 unless $self->{Fixups};
|
||||
my $subFixup;
|
||||
foreach $subFixup (@{$self->{Fixups}}) {
|
||||
return 1 if $subFixup->HasMarker($marker);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Set all marker pointers to specified value
|
||||
# Inputs: 0) Fixup object reference, 1) data reference
|
||||
# 2) marker name, 3) pointer value, 4) offset to start of data
|
||||
sub SetMarkerPointers($$$$;$)
|
||||
{
|
||||
my ($self, $dataPt, $marker, $value, $startOffset) = @_;
|
||||
my $start = $self->{Start} + ($startOffset || 0);
|
||||
my $phash = $self->{Pointers};
|
||||
|
||||
if ($phash) {
|
||||
my $saveOrder = GetByteOrder(); # save original byte ordering
|
||||
my ($byteOrder, $ptr);
|
||||
foreach $byteOrder (keys %$phash) {
|
||||
next unless $byteOrder =~ /^(II|MM)(2?)_$marker$/;
|
||||
SetByteOrder($1);
|
||||
my $set = $2 ? \&Set16u : \&Set32u;
|
||||
foreach $ptr (@{$phash->{$byteOrder}}) {
|
||||
&$set($value, $dataPt, $ptr + $start);
|
||||
}
|
||||
}
|
||||
SetByteOrder($saveOrder); # restore original byte ordering
|
||||
}
|
||||
if ($self->{Fixups}) {
|
||||
my $subFixup;
|
||||
foreach $subFixup (@{$self->{Fixups}}) {
|
||||
$subFixup->SetMarkerPointers($dataPt, $marker, $value, $start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Get pointer values for specified marker
|
||||
# Inputs: 0) Fixup object reference, 1) data reference,
|
||||
# 2) marker name, 3) offset to start of data
|
||||
# Returns: List of marker pointers in list context, or first marker pointer otherwise
|
||||
sub GetMarkerPointers($$$;$)
|
||||
{
|
||||
my ($self, $dataPt, $marker, $startOffset) = @_;
|
||||
my $start = $self->{Start} + ($startOffset || 0);
|
||||
my $phash = $self->{Pointers};
|
||||
my @pointers;
|
||||
|
||||
if ($phash) {
|
||||
my $saveOrder = GetByteOrder();
|
||||
my ($byteOrder, $ptr);
|
||||
foreach $byteOrder (grep /_$marker$/, keys %$phash) {
|
||||
SetByteOrder(substr($byteOrder,0,2));
|
||||
my $get = ($byteOrder =~ /^(II2|MM2)/) ? \&Get16u : \&Get32u;
|
||||
foreach $ptr (@{$phash->{$byteOrder}}) {
|
||||
push @pointers, &$get($dataPt, $ptr + $start);
|
||||
}
|
||||
}
|
||||
SetByteOrder($saveOrder); # restore original byte ordering
|
||||
}
|
||||
if ($self->{Fixups}) {
|
||||
my $subFixup;
|
||||
foreach $subFixup (@{$self->{Fixups}}) {
|
||||
push @pointers, $subFixup->GetMarkerPointers($dataPt, $marker, $start);
|
||||
}
|
||||
}
|
||||
return @pointers if wantarray;
|
||||
return $pointers[0];
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Dump fixup to console for debugging
|
||||
# Inputs: 0) Fixup object reference, 1) optional initial indent string
|
||||
sub Dump($;$)
|
||||
{
|
||||
my ($self, $indent) = @_;
|
||||
$indent or $indent = '';
|
||||
printf "${indent}Fixup start=0x%x shift=0x%x\n", $self->{Start}, $self->{Shift};
|
||||
my $phash = $self->{Pointers};
|
||||
if ($phash) {
|
||||
my $byteOrder;
|
||||
foreach $byteOrder (sort keys %$phash) {
|
||||
print "$indent $byteOrder: ", join(' ',@{$phash->{$byteOrder}}),"\n";
|
||||
}
|
||||
}
|
||||
if ($self->{Fixups}) {
|
||||
my $subFixup;
|
||||
foreach $subFixup (@{$self->{Fixups}}) {
|
||||
Dump($subFixup, $indent . ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::Fixup - Utility to handle pointer fixups
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Image::ExifTool::Fixup;
|
||||
|
||||
$fixup = Image::ExifTool::Fixup->new;
|
||||
|
||||
# add a new fixup to a pointer at the specified offset in data
|
||||
$fixup->AddFixup($offset);
|
||||
|
||||
# add a new Fixup object to the tree
|
||||
$fixup->AddFixup($subFixup);
|
||||
|
||||
$fixup->{Start} += $shift1; # shift pointer offsets and values
|
||||
|
||||
$fixup->{Shift} += $shift2; # shift pointer values only
|
||||
|
||||
# recursively apply fixups to the specified data
|
||||
$fixup->ApplyFixups(\$data);
|
||||
|
||||
$fixup->Dump(); # dump debugging information
|
||||
|
||||
$fixup->IsEmpty(); # return true if no offsets to fix
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains the code to keep track of pointers in memory and to
|
||||
shift these pointers as required. It is used by ExifTool to maintain the
|
||||
pointers in image file directories (IFD's).
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
Keeps track of pointers with different byte ordering, and relies on
|
||||
Image::ExifTool::GetByteOrder() to determine the current byte ordering
|
||||
when adding new pointers to a fixup.
|
||||
|
||||
Maintains a hierarchical list of fixups so that the whole hierarchy can
|
||||
be shifted by a simple shift at the base. Hierarchy is collapsed to a
|
||||
linear list when ApplyFixups() is called.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
755
.resources/nix/bin/lib/Image/ExifTool/Flash.pm
Normal file
755
.resources/nix/bin/lib/Image/ExifTool/Flash.pm
Normal file
|
|
@ -0,0 +1,755 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Flash.pm
|
||||
#
|
||||
# Description: Read Shockwave Flash meta information
|
||||
#
|
||||
# Revisions: 05/16/2006 - P. Harvey Created
|
||||
# 06/07/2007 - PH Added support for FLV (Flash Video) files
|
||||
# 10/23/2008 - PH Added support for XMP in FLV and SWF
|
||||
#
|
||||
# References: 1) http://www.the-labs.com/MacromediaFlash/SWF-Spec/SWFfileformat.html
|
||||
# 2) http://sswf.sourceforge.net/SWFalexref.html
|
||||
# 3) http://osflash.org/flv/
|
||||
# 4) http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/flv_8h.html
|
||||
# 5) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
|
||||
# 6) http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v9.pdf
|
||||
# 7) http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6.html
|
||||
# 8) http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf
|
||||
#
|
||||
# Notes: I'll add AMF3 support if someone sends me a FLV with AMF3 data
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::Flash;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::FLAC;
|
||||
|
||||
$VERSION = '1.12';
|
||||
|
||||
sub ProcessMeta($$$;$);
|
||||
|
||||
# Meta packets that we process
|
||||
my %processMetaPacket = ( onMetaData => 1, onXMPData => 1 );
|
||||
|
||||
# information extracted from SWF header
|
||||
%Image::ExifTool::Flash::Main = (
|
||||
GROUPS => { 2 => 'Video' },
|
||||
VARS => { ALPHA_FIRST => 1 },
|
||||
NOTES => q{
|
||||
The information below is extracted from SWF (Shockwave Flash) files. Tags
|
||||
with string ID's represent information extracted from the file header.
|
||||
},
|
||||
FlashVersion => { },
|
||||
Compressed => { PrintConv => { 0 => 'False', 1 => 'True' } },
|
||||
ImageWidth => { },
|
||||
ImageHeight => { },
|
||||
FrameRate => { },
|
||||
FrameCount => { },
|
||||
Duration => {
|
||||
Notes => 'calculated from FrameRate and FrameCount',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
69 => {
|
||||
Name => 'FlashAttributes',
|
||||
PrintConv => { BITMASK => {
|
||||
0 => 'UseNetwork',
|
||||
3 => 'ActionScript3',
|
||||
4 => 'HasMetadata',
|
||||
} },
|
||||
},
|
||||
77 => {
|
||||
Name => 'XMP',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
|
||||
},
|
||||
);
|
||||
|
||||
# packets in Flash Video files
|
||||
%Image::ExifTool::Flash::FLV = (
|
||||
NOTES => q{
|
||||
Information is extracted from the following packets in FLV (Flash Video)
|
||||
files.
|
||||
},
|
||||
0x08 => {
|
||||
Name => 'Audio',
|
||||
BitMask => 0x04,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Flash::Audio' },
|
||||
},
|
||||
0x09 => {
|
||||
Name => 'Video',
|
||||
BitMask => 0x01,
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Flash::Video' },
|
||||
},
|
||||
0x12 => {
|
||||
Name => 'Meta',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Flash::Meta' },
|
||||
},
|
||||
);
|
||||
|
||||
# tags in Flash Video packet header
|
||||
%Image::ExifTool::Flash::Audio = (
|
||||
PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream,
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
NOTES => 'Information extracted from the Flash Audio header.',
|
||||
'Bit0-3' => {
|
||||
Name => 'AudioEncoding',
|
||||
PrintConv => {
|
||||
0 => 'PCM-BE (uncompressed)', # PCM-BE according to ref 4
|
||||
1 => 'ADPCM',
|
||||
2 => 'MP3',
|
||||
3 => 'PCM-LE (uncompressed)', #4
|
||||
4 => 'Nellymoser 16kHz Mono', #8
|
||||
5 => 'Nellymoser 8kHz Mono',
|
||||
6 => 'Nellymoser',
|
||||
7 => 'G.711 A-law logarithmic PCM', #8
|
||||
8 => 'G.711 mu-law logarithmic PCM', #8
|
||||
# (9 is reserved, ref 8)
|
||||
10 => 'AAC', #8
|
||||
11 => 'Speex', #8
|
||||
13 => 'MP3 8-Khz', #8
|
||||
15 => 'Device-specific sound', #8
|
||||
},
|
||||
},
|
||||
'Bit4-5' => {
|
||||
Name => 'AudioSampleRate',
|
||||
ValueConv => {
|
||||
0 => 5512,
|
||||
1 => 11025,
|
||||
2 => 22050,
|
||||
3 => 44100,
|
||||
},
|
||||
},
|
||||
'Bit6' => {
|
||||
Name => 'AudioBitsPerSample',
|
||||
ValueConv => '8 * ($val + 1)',
|
||||
},
|
||||
'Bit7' => {
|
||||
Name => 'AudioChannels',
|
||||
ValueConv => '$val + 1',
|
||||
PrintConv => {
|
||||
1 => '1 (mono)',
|
||||
2 => '2 (stereo)',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# tags in Flash Video packet header
|
||||
%Image::ExifTool::Flash::Video = (
|
||||
PROCESS_PROC => \&Image::ExifTool::FLAC::ProcessBitStream,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
NOTES => 'Information extracted from the Flash Video header.',
|
||||
'Bit4-7' => {
|
||||
Name => 'VideoEncoding',
|
||||
PrintConv => {
|
||||
1 => 'JPEG', #8
|
||||
2 => 'Sorensen H.263',
|
||||
3 => 'Screen Video',
|
||||
4 => 'On2 VP6',
|
||||
5 => 'On2 VP6 Alpha', #3
|
||||
6 => 'Screen Video 2', #3
|
||||
7 => 'H.264', #7 (called "AVC" by ref 8)
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# tags in Flash META packet (in ActionScript Message Format)
|
||||
%Image::ExifTool::Flash::Meta = (
|
||||
PROCESS_PROC => \&ProcessMeta,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
NOTES => q{
|
||||
Below are a few observed FLV Meta tags, but ExifTool will attempt to extract
|
||||
information from any tag found.
|
||||
},
|
||||
'audiocodecid' => { Name => 'AudioCodecID', Groups => { 2 => 'Audio' } },
|
||||
'audiodatarate' => {
|
||||
Name => 'AudioBitrate',
|
||||
Groups => { 2 => 'Audio' },
|
||||
ValueConv => '$val * 1000',
|
||||
PrintConv => 'ConvertBitrate($val)',
|
||||
},
|
||||
'audiodelay' => { Name => 'AudioDelay', Groups => { 2 => 'Audio' } },
|
||||
'audiosamplerate'=>{ Name => 'AudioSampleRate', Groups => { 2 => 'Audio' } },
|
||||
'audiosamplesize'=>{ Name => 'AudioSampleSize', Groups => { 2 => 'Audio' } },
|
||||
'audiosize' => { Name => 'AudioSize', Groups => { 2 => 'Audio' } },
|
||||
'bytelength' => 'ByteLength', # (youtube)
|
||||
'canseekontime' => 'CanSeekOnTime', # (youtube)
|
||||
'canSeekToEnd' => 'CanSeekToEnd',
|
||||
'creationdate' => {
|
||||
# (not an AMF date type in my sample)
|
||||
Name => 'CreateDate',
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => '$val=~s/\s+$//; $val', # trim trailing whitespace
|
||||
},
|
||||
'createdby' => 'CreatedBy', #7
|
||||
'cuePoints' => {
|
||||
Name => 'CuePoint',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Flash::CuePoint' },
|
||||
},
|
||||
'datasize' => 'DataSize',
|
||||
'duration' => {
|
||||
Name => 'Duration',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
'filesize' => 'FileSizeBytes',
|
||||
'framerate' => {
|
||||
Name => 'FrameRate',
|
||||
PrintConv => 'int($val * 1000 + 0.5) / 1000',
|
||||
},
|
||||
'hasAudio' => { Name => 'HasAudio', Groups => { 2 => 'Audio' } },
|
||||
'hasCuePoints' => 'HasCuePoints',
|
||||
'hasKeyframes' => 'HasKeyFrames',
|
||||
'hasMetadata' => 'HasMetadata',
|
||||
'hasVideo' => 'HasVideo',
|
||||
'height' => 'ImageHeight',
|
||||
'httphostheader'=> 'HTTPHostHeader', # (youtube)
|
||||
'keyframesTimes'=> 'KeyFramesTimes',
|
||||
'keyframesFilepositions' => 'KeyFramePositions',
|
||||
'lasttimestamp' => 'LastTimeStamp',
|
||||
'lastkeyframetimestamp' => 'LastKeyFrameTime',
|
||||
'metadatacreator'=>'MetadataCreator',
|
||||
'metadatadate' => {
|
||||
Name => 'MetadataDate',
|
||||
Groups => { 2 => 'Time' },
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
'purl' => 'URL', # (youtube) (what does P mean?)
|
||||
'pmsg' => 'Message', # (youtube) (what does P mean?)
|
||||
'sourcedata' => 'SourceData', # (youtube)
|
||||
'starttime' => { # (youtube)
|
||||
Name => 'StartTime',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
'stereo' => { Name => 'Stereo', Groups => { 2 => 'Audio' } },
|
||||
'totalduration' => { # (youtube)
|
||||
Name => 'TotalDuration',
|
||||
PrintConv => 'ConvertDuration($val)',
|
||||
},
|
||||
'totaldatarate' => { # (youtube)
|
||||
Name => 'TotalDataRate',
|
||||
ValueConv => '$val * 1000',
|
||||
PrintConv => 'int($val + 0.5)',
|
||||
},
|
||||
'totalduration' => 'TotalDuration',
|
||||
'videocodecid' => 'VideoCodecID',
|
||||
'videodatarate' => {
|
||||
Name => 'VideoBitrate',
|
||||
ValueConv => '$val * 1000',
|
||||
PrintConv => 'ConvertBitrate($val)',
|
||||
},
|
||||
'videosize' => 'VideoSize',
|
||||
'width' => 'ImageWidth',
|
||||
# tags in 'onXMPData' packets
|
||||
'liveXML' => { #5
|
||||
Name => 'XMP',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
|
||||
},
|
||||
);
|
||||
|
||||
# tags in Flash META CuePoint structure
|
||||
%Image::ExifTool::Flash::CuePoint = (
|
||||
PROCESS_PROC => \&ProcessMeta,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
NOTES => q{
|
||||
These tag names are added to the CuePoint name to generate complete tag
|
||||
names like "CuePoint0Name".
|
||||
},
|
||||
'name' => 'Name',
|
||||
'type' => 'Type',
|
||||
'time' => 'Time',
|
||||
'parameters' => {
|
||||
Name => 'Parameter',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Flash::Parameter' },
|
||||
},
|
||||
);
|
||||
|
||||
# tags in Flash META CuePoint Parameter structure
|
||||
%Image::ExifTool::Flash::Parameter = (
|
||||
PROCESS_PROC => \&ProcessMeta,
|
||||
GROUPS => { 2 => 'Video' },
|
||||
NOTES => q{
|
||||
There are no pre-defined parameter tags, but ExifTool will extract any
|
||||
existing parameters, with tag names like "CuePoint0ParameterXxx".
|
||||
},
|
||||
);
|
||||
|
||||
# name lookup for known AMF data types
|
||||
my @amfType = qw(double boolean string object movieClip null undefined reference
|
||||
mixedArray objectEnd array date longString unsupported recordSet
|
||||
XML typedObject AMF3data);
|
||||
|
||||
# test for AMF structure types (object, mixed array or typed object)
|
||||
my %isStruct = ( 0x03 => 1, 0x08 => 1, 0x10 => 1 );
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process Flash Video AMF Meta packet (ref 3)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# 3) Set to extract single type/value only
|
||||
# Returns: 1 on success, (or type/value if extracting single value)
|
||||
# Notes: Updates DataPos in dirInfo if extracting single value
|
||||
sub ProcessMeta($$$;$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr, $single) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirLen = $$dirInfo{DirLen} || length($$dataPt);
|
||||
my $pos = $$dirInfo{Pos} || 0;
|
||||
my ($type, $val, $rec);
|
||||
|
||||
$et->VerboseDir('Meta') unless $single;
|
||||
|
||||
Record: for ($rec=0; ; ++$rec) {
|
||||
last if $pos >= $dirLen;
|
||||
$type = ord(substr($$dataPt, $pos));
|
||||
++$pos;
|
||||
if ($type == 0x00 or $type == 0x0b) { # double or date
|
||||
last if $pos + 8 > $dirLen;
|
||||
$val = GetDouble($dataPt, $pos);
|
||||
$pos += 8;
|
||||
if ($type == 0x0b) { # date
|
||||
$val /= 1000; # convert to seconds
|
||||
my $frac = $val - int($val); # fractional seconds
|
||||
# get time zone
|
||||
last if $pos + 2 > $dirLen;
|
||||
my $tz = Get16s($dataPt, $pos);
|
||||
$pos += 2;
|
||||
# construct date/time string
|
||||
$val = Image::ExifTool::ConvertUnixTime(int($val));
|
||||
if ($frac) {
|
||||
$frac = sprintf('%.6f', $frac);
|
||||
$frac =~ s/(^0|0+$)//g;
|
||||
$val .= $frac;
|
||||
}
|
||||
# add timezone
|
||||
if ($tz < 0) {
|
||||
$val .= '-';
|
||||
$tz *= -1;
|
||||
} else {
|
||||
$val .= '+';
|
||||
}
|
||||
$val .= sprintf('%.2d:%.2d', int($tz/60), $tz%60);
|
||||
}
|
||||
} elsif ($type == 0x01) { # boolean
|
||||
last if $pos + 1 > $dirLen;
|
||||
$val = Get8u($dataPt, $pos);
|
||||
$val = { 0 => 'No', 1 => 'Yes' }->{$val} if $val < 2;
|
||||
++$pos;
|
||||
} elsif ($type == 0x02) { # string
|
||||
last if $pos + 2 > $dirLen;
|
||||
my $len = Get16u($dataPt, $pos);
|
||||
last if $pos + 2 + $len > $dirLen;
|
||||
$val = substr($$dataPt, $pos + 2, $len);
|
||||
$pos += 2 + $len;
|
||||
} elsif ($isStruct{$type}) { # object, mixed array or typed object
|
||||
$et->VPrint(1, " + [$amfType[$type]]\n");
|
||||
my $getName;
|
||||
$val = ''; # dummy value
|
||||
if ($type == 0x08) { # mixed array
|
||||
# skip last array index for mixed array
|
||||
last if $pos + 4 > $dirLen;
|
||||
$pos += 4;
|
||||
} elsif ($type == 0x10) { # typed object
|
||||
$getName = 1;
|
||||
}
|
||||
for (;;) {
|
||||
# get tag ID (or typed object name)
|
||||
last Record if $pos + 2 > $dirLen;
|
||||
my $len = Get16u($dataPt, $pos);
|
||||
if ($pos + 2 + $len > $dirLen) {
|
||||
$et->Warn("Truncated $amfType[$type] record");
|
||||
last Record;
|
||||
}
|
||||
my $tag = substr($$dataPt, $pos + 2, $len);
|
||||
$pos += 2 + $len;
|
||||
# first string of a typed object is the object name
|
||||
if ($getName) {
|
||||
$et->VPrint(1," | (object name '${tag}')\n");
|
||||
undef $getName;
|
||||
next; # (ignore name for now)
|
||||
}
|
||||
my $subTablePtr = $tagTablePtr;
|
||||
my $tagInfo = $$subTablePtr{$tag};
|
||||
# switch to subdirectory table if necessary
|
||||
if ($tagInfo and $$tagInfo{SubDirectory}) {
|
||||
my $subTable = $tagInfo->{SubDirectory}->{TagTable};
|
||||
# descend into Flash SubDirectory
|
||||
if ($subTable =~ /^Image::ExifTool::Flash::/) {
|
||||
$tag = $$tagInfo{Name}; # use our name for the tag
|
||||
$subTablePtr = GetTagTable($subTable);
|
||||
}
|
||||
}
|
||||
# get object value
|
||||
my $valPos = $pos + 1;
|
||||
$$dirInfo{Pos} = $pos;
|
||||
my $structName = $$dirInfo{StructName};
|
||||
# add structure name to start of tag name
|
||||
$tag = $structName . ucfirst($tag) if defined $structName;
|
||||
$$dirInfo{StructName} = $tag; # set new structure name
|
||||
my ($t, $v) = ProcessMeta($et, $dirInfo, $subTablePtr, 1);
|
||||
$$dirInfo{StructName} = $structName;# restore original structure name
|
||||
$pos = $$dirInfo{Pos}; # update to new position in packet
|
||||
# all done if this value contained tags
|
||||
last Record unless defined $t and defined $v;
|
||||
next if $isStruct{$t}; # already handled tags in sub-structures
|
||||
next if ref($v) eq 'ARRAY' and not @$v; # ignore empty arrays
|
||||
last if $t == 0x09; # (end of object)
|
||||
if (not $$subTablePtr{$tag} and $tag =~ /^\w+$/) {
|
||||
AddTagToTable($subTablePtr, $tag, { Name => ucfirst($tag) });
|
||||
$et->VPrint(1, " | (adding $tag)\n");
|
||||
}
|
||||
$et->HandleTag($subTablePtr, $tag, $v,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => $valPos,
|
||||
Size => $pos - $valPos,
|
||||
Format => $amfType[$t] || sprintf('0x%x',$t),
|
||||
);
|
||||
}
|
||||
# } elsif ($type == 0x04) { # movie clip (not supported)
|
||||
} elsif ($type == 0x05 or $type == 0x06 or $type == 0x09 or $type == 0x0d) {
|
||||
# null, undefined, dirLen of object, or unsupported
|
||||
$val = '';
|
||||
} elsif ($type == 0x07) { # reference
|
||||
last if $pos + 2 > $dirLen;
|
||||
$val = Get16u($dataPt, $pos);
|
||||
$pos += 2;
|
||||
} elsif ($type == 0x0a) { # array
|
||||
last if $pos + 4 > $dirLen;
|
||||
my $num = Get32u($dataPt, $pos);
|
||||
$$dirInfo{Pos} = $pos + 4;
|
||||
my ($i, @vals);
|
||||
# add array index to compound tag name
|
||||
my $structName = $$dirInfo{StructName};
|
||||
for ($i=0; $i<$num; ++$i) {
|
||||
$$dirInfo{StructName} = $structName . $i if defined $structName;
|
||||
my ($t, $v) = ProcessMeta($et, $dirInfo, $tagTablePtr, 1);
|
||||
last Record unless defined $v;
|
||||
# save value unless contained in a sub-structure
|
||||
push @vals, $v unless $isStruct{$t};
|
||||
}
|
||||
$$dirInfo{StructName} = $structName;
|
||||
$pos = $$dirInfo{Pos};
|
||||
$val = \@vals;
|
||||
} elsif ($type == 0x0c or $type == 0x0f) { # long string or XML
|
||||
last if $pos + 4 > $dirLen;
|
||||
my $len = Get32u($dataPt, $pos);
|
||||
last if $pos + 4 + $len > $dirLen;
|
||||
$val = substr($$dataPt, $pos + 4, $len);
|
||||
$pos += 4 + $len;
|
||||
# } elsif ($type == 0x0e) { # record set (not supported)
|
||||
# } elsif ($type == 0x11) { # AMF3 data (can't add support for this without a test sample)
|
||||
} else {
|
||||
my $t = $amfType[$type] || sprintf('type 0x%x',$type);
|
||||
$et->Warn("AMF $t record not yet supported");
|
||||
undef $type; # (so we don't print another warning)
|
||||
last; # can't continue
|
||||
}
|
||||
last if $single; # all done if extracting single value
|
||||
unless ($isStruct{$type}) {
|
||||
# only process certain Meta packets
|
||||
if ($type == 0x02 and not $rec) {
|
||||
my $verb = $processMetaPacket{$val} ? 'processing' : 'ignoring';
|
||||
$et->VPrint(0, " | ($verb $val information)\n");
|
||||
last unless $processMetaPacket{$val};
|
||||
} else {
|
||||
# give verbose indication if we ignore a lone value
|
||||
my $t = $amfType[$type] || sprintf('type 0x%x',$type);
|
||||
$et->VPrint(1, " | (ignored lone $t value '${val}')\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (not defined $val and defined $type) {
|
||||
$et->Warn(sprintf("Truncated AMF record 0x%x",$type));
|
||||
}
|
||||
return 1 unless $single; # all done
|
||||
$$dirInfo{Pos} = $pos; # update position
|
||||
return($type,$val); # return single type/value pair
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information frame a Flash Video file
|
||||
# Inputs: 0) ExifTool object reference, 1) Directory information reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid Flash Video file
|
||||
sub ProcessFLV($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $buff;
|
||||
|
||||
$raf->Read($buff, 9) == 9 or return 0;
|
||||
$buff =~ /^FLV\x01/ or return 0;
|
||||
SetByteOrder('MM');
|
||||
$et->SetFileType();
|
||||
my ($flags, $offset) = unpack('x4CN', $buff);
|
||||
$raf->Seek($offset-9, 1) or return 1 if $offset > 9;
|
||||
$flags &= 0x05; # only look for audio/video
|
||||
my $found = 0;
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Flash::FLV');
|
||||
for (;;) {
|
||||
$raf->Read($buff, 15) == 15 or last;
|
||||
my $len = unpack('x4N', $buff);
|
||||
my $type = $len >> 24;
|
||||
$len &= 0x00ffffff;
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $type);
|
||||
if ($verbose > 1) {
|
||||
my $name = $tagInfo ? $$tagInfo{Name} : "type $type";
|
||||
$et->VPrint(1, "FLV $name packet, len $len\n");
|
||||
}
|
||||
undef $buff;
|
||||
if ($tagInfo and $$tagInfo{SubDirectory}) {
|
||||
my $mask = $$tagInfo{BitMask};
|
||||
if ($mask) {
|
||||
# handle audio or video packet
|
||||
unless ($found & $mask) {
|
||||
$found |= $mask;
|
||||
$flags &= ~$mask;
|
||||
if ($len>=1 and $raf->Read($buff, 1) == 1) {
|
||||
$len -= 1;
|
||||
} else {
|
||||
$et->Warn("Bad $$tagInfo{Name} packet");
|
||||
last;
|
||||
}
|
||||
}
|
||||
} elsif ($raf->Read($buff, $len) == $len) {
|
||||
$len = 0;
|
||||
} else {
|
||||
$et->Warn('Truncated Meta packet');
|
||||
last;
|
||||
}
|
||||
}
|
||||
if (defined $buff) {
|
||||
$et->HandleTag($tagTablePtr, $type, undef,
|
||||
DataPt => \$buff,
|
||||
DataPos => $raf->Tell() - length($buff),
|
||||
);
|
||||
}
|
||||
last unless $flags;
|
||||
$raf->Seek($len, 1) or last if $len;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Found a Flash tag
|
||||
# Inputs: 0) ExifTool object ref, 1) tag name, 2) tag value
|
||||
sub FoundFlashTag($$$)
|
||||
{
|
||||
my ($et, $tag, $val) = @_;
|
||||
$et->HandleTag(\%Image::ExifTool::Flash::Main, $tag, $val);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read data from possibly compressed file
|
||||
# Inputs: 0) RAF reference, 1) data buffer, 2) bytes to read, 2) compressed flag
|
||||
# Returns: number of bytes read (may be greater than requested bytes if compressed)
|
||||
# - concatenates data to current buffer
|
||||
# - updates compressed flag with reference to inflate object for future calls
|
||||
# (or sets to error message and returns zero on error)
|
||||
sub ReadCompressed($$$$)
|
||||
{
|
||||
my ($raf, $len, $inflate) = ($_[0], $_[2], $_[3]);
|
||||
my $buff;
|
||||
unless ($raf->Read($buff, $len)) {
|
||||
$_[3] = 'Error reading file';
|
||||
return 0;
|
||||
}
|
||||
# uncompress if necessary
|
||||
if ($inflate) {
|
||||
unless (ref $inflate) {
|
||||
unless (eval { require Compress::Zlib }) {
|
||||
$_[3] = 'Install Compress::Zlib to extract compressed information';
|
||||
return 0;
|
||||
}
|
||||
$inflate = Compress::Zlib::inflateInit();
|
||||
unless ($inflate) {
|
||||
$_[3] = 'Error initializing inflate for Flash data';
|
||||
return 0;
|
||||
}
|
||||
$_[3] = $inflate; # pass inflate object back to caller
|
||||
}
|
||||
my $tmp = $buff;
|
||||
$buff = '';
|
||||
# read 64 more bytes at a time and inflate until we get enough uncompressed data
|
||||
for (;;) {
|
||||
my ($dat, $stat) = $inflate->inflate($tmp);
|
||||
if ($stat == Compress::Zlib::Z_STREAM_END() or
|
||||
$stat == Compress::Zlib::Z_OK())
|
||||
{
|
||||
$buff .= $dat; # add inflated data to buffer
|
||||
last if length $buff >= $len or $stat == Compress::Zlib::Z_STREAM_END();
|
||||
$raf->Read($tmp,64) or last; # must read a bit more data
|
||||
} else {
|
||||
$buff = '';
|
||||
last;
|
||||
}
|
||||
}
|
||||
$_[3] = 'Error inflating compressed Flash data' unless length $buff;
|
||||
}
|
||||
$_[1] = defined $_[1] ? $_[1] . $buff : $buff;
|
||||
return length $buff;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information frame a Flash file
|
||||
# Inputs: 0) ExifTool object reference, 1) Directory information reference
|
||||
# Returns: 1 on success, 0 if this wasn't a valid Flash file
|
||||
sub ProcessSWF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $hasMeta);
|
||||
|
||||
$raf->Read($buff, 8) == 8 or return 0;
|
||||
$buff =~ /^(F|C)WS([^\0])/ or return 0;
|
||||
my ($compressed, $vers) = ($1 eq 'C' ? 1 : 0, ord($2));
|
||||
|
||||
SetByteOrder('II');
|
||||
$et->SetFileType();
|
||||
GetTagTable('Image::ExifTool::Flash::Main'); # make sure table is initialized
|
||||
|
||||
FoundFlashTag($et, FlashVersion => $vers);
|
||||
FoundFlashTag($et, Compressed => $compressed);
|
||||
|
||||
# read the next 64 bytes of the file (and inflate if necessary)
|
||||
$buff = '';
|
||||
unless (ReadCompressed($raf, $buff, 64, $compressed)) {
|
||||
$et->Warn($compressed) if $compressed;
|
||||
return 1;
|
||||
}
|
||||
|
||||
# unpack elements of bit-packed Flash Rect structure
|
||||
my $nBits = unpack('C', $buff) >> 3; # bits in x1,x2,y1,y2 elements
|
||||
my $totBits = 5 + $nBits * 4; # total bits in Rect structure
|
||||
my $nBytes = int(($totBits + 7) / 8); # byte length of Rect structure
|
||||
if (length $buff < $nBytes + 4) {
|
||||
$et->Warn('Truncated Flash file');
|
||||
return 1;
|
||||
}
|
||||
my $bits = unpack("B$totBits", $buff);
|
||||
# isolate Rect elements and convert from ASCII bit strings to integers
|
||||
my @vals = unpack('x5' . "a$nBits" x 4, $bits);
|
||||
# (do conversion the hard way because oct("0b$val") requires Perl 5.6)
|
||||
map { $_ = unpack('N', pack('B32', '0' x (32 - length $_) . $_)) } @vals;
|
||||
|
||||
# calculate and store ImageWidth/Height
|
||||
FoundFlashTag($et, ImageWidth => ($vals[1] - $vals[0]) / 20);
|
||||
FoundFlashTag($et, ImageHeight => ($vals[3] - $vals[2]) / 20);
|
||||
|
||||
# get frame rate and count
|
||||
@vals = unpack("x${nBytes}v2", $buff);
|
||||
FoundFlashTag($et, FrameRate => $vals[0] / 256);
|
||||
FoundFlashTag($et, FrameCount => $vals[1]);
|
||||
FoundFlashTag($et, Duration => $vals[1] * 256 / $vals[0]) if $vals[0];
|
||||
|
||||
# scan through the tags to find FlashAttributes and XMP
|
||||
$buff = substr($buff, $nBytes + 4);
|
||||
for (;;) {
|
||||
my $buffLen = length $buff;
|
||||
last if $buffLen < 2;
|
||||
my $code = Get16u(\$buff, 0);
|
||||
my $pos = 2;
|
||||
my $tag = $code >> 6;
|
||||
my $size = $code & 0x3f;
|
||||
$et->VPrint(1, "SWF tag $tag ($size bytes):\n");
|
||||
last unless $tag == 69 or $tag == 77 or $hasMeta;
|
||||
# read enough to get a complete short record
|
||||
if ($pos + $size > $buffLen) {
|
||||
# (read 2 extra bytes if available to get next tag word)
|
||||
unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) {
|
||||
$et->Warn($compressed) if $compressed;
|
||||
return 1;
|
||||
}
|
||||
$buffLen = length $buff;
|
||||
last if $pos + $size > $buffLen;
|
||||
}
|
||||
# read extended record if necessary
|
||||
if ($size == 0x3f) {
|
||||
last if $pos + 4 > $buffLen;
|
||||
$size = Get32u(\$buff, $pos);
|
||||
$pos += 4;
|
||||
last if $size > 1000000; # don't read anything huge
|
||||
if ($pos + $size > $buffLen) {
|
||||
unless (ReadCompressed($raf, $buff, $size + 2, $compressed)) {
|
||||
$et->Warn($compressed) if $compressed;
|
||||
return 1;
|
||||
}
|
||||
$buffLen = length $buff;
|
||||
last if $pos + $size > $buffLen;
|
||||
}
|
||||
$et->VPrint(1, " [extended size $size bytes]\n");
|
||||
}
|
||||
if ($tag == 69) { # FlashAttributes
|
||||
last unless $size;
|
||||
my $flags = Get8u(\$buff, $pos);
|
||||
FoundFlashTag($et, $tag => $flags);
|
||||
last unless $flags & 0x10; # only continue if we have metadata (XMP)
|
||||
$hasMeta = 1;
|
||||
} elsif ($tag == 77) { # Metadata
|
||||
my $val = substr($buff, $pos, $size);
|
||||
FoundFlashTag($et, $tag => $val);
|
||||
last;
|
||||
}
|
||||
last if $pos + 2 > $buffLen;
|
||||
$buff = substr($buff, $pos); # remove everything before the next tag
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::Flash - Read Shockwave Flash meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read SWF
|
||||
(Shockwave Flash) and FLV (Flash Video) files.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
Flash Video AMF3 support has not yet been added because I haven't yet found
|
||||
a FLV file containing AMF3 information. If someone sends me a sample then I
|
||||
will add AMF3 support.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.the-labs.com/MacromediaFlash/SWF-Spec/SWFfileformat.html>
|
||||
|
||||
=item L<http://sswf.sourceforge.net/SWFalexref.html>
|
||||
|
||||
=item L<http://osflash.org/flv/>
|
||||
|
||||
=item L<http://www.irisa.fr/texmex/people/dufouil/ffmpegdoxy/flv_8h.html>
|
||||
|
||||
=item L<http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6.html>
|
||||
|
||||
=item L<http://www.adobe.com/devnet/swf/pdf/swf_file_format_spec_v9.pdf>
|
||||
|
||||
=item L<http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/Flash Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
2431
.resources/nix/bin/lib/Image/ExifTool/FlashPix.pm
Normal file
2431
.resources/nix/bin/lib/Image/ExifTool/FlashPix.pm
Normal file
File diff suppressed because it is too large
Load diff
932
.resources/nix/bin/lib/Image/ExifTool/Font.pm
Normal file
932
.resources/nix/bin/lib/Image/ExifTool/Font.pm
Normal file
|
|
@ -0,0 +1,932 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: Font.pm
|
||||
#
|
||||
# Description: Read meta information from font files
|
||||
#
|
||||
# Revisions: 2010/01/15 - P. Harvey Created
|
||||
#
|
||||
# References: 1) http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html
|
||||
# 2) http://www.microsoft.com/typography/otspec/otff.htm
|
||||
# 3) http://partners.adobe.com/public/developer/opentype/index_font_file.html
|
||||
# 4) http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf
|
||||
# 5) http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/compiler/src/java/flex2/compiler/util/MimeMappings.java
|
||||
# 6) http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf
|
||||
# 7) https://www.w3.org/TR/WOFF/
|
||||
# 8) https://www.w3.org/TR/WOFF2/
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::Font;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION %ttLang);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::XMP;
|
||||
|
||||
$VERSION = '1.12';
|
||||
|
||||
sub ProcessOTF($$);
|
||||
|
||||
# OTF tags to process (skip all others)
|
||||
my %processTag = ( name => 1, C2PA => 1 );
|
||||
|
||||
# TrueType 'name' platform codes
|
||||
my %ttPlatform = (
|
||||
0 => 'Unicode',
|
||||
1 => 'Macintosh',
|
||||
2 => 'ISO',
|
||||
3 => 'Windows',
|
||||
4 => 'Custom',
|
||||
);
|
||||
|
||||
# convert TrueType 'name' character encoding to ExifTool Charset (ref 1/2)
|
||||
my %ttCharset = (
|
||||
Macintosh => {
|
||||
0 => 'MacRoman', 17 => 'MacMalayalam',
|
||||
1 => 'MacJapanese', 18 => 'MacSinhalese',
|
||||
2 => 'MacChineseTW', 19 => 'MacBurmese',
|
||||
3 => 'MacKorean', 20 => 'MacKhmer',
|
||||
4 => 'MacArabic', 21 => 'MacThai',
|
||||
5 => 'MacHebrew', 22 => 'MacLaotian',
|
||||
6 => 'MacGreek', 23 => 'MacGeorgian',
|
||||
7 => 'MacCyrillic', 24 => 'MacArmenian', # 7=Russian
|
||||
8 => 'MacRSymbol', 25 => 'MacChineseCN',
|
||||
9 => 'MacDevanagari', 26 => 'MacTibetan',
|
||||
10 => 'MacGurmukhi', 27 => 'MacMongolian',
|
||||
11 => 'MacGujarati', 28 => 'MacGeez',
|
||||
12 => 'MacOriya', 29 => 'MacCyrillic', # 29=Slavic
|
||||
13 => 'MacBengali', 30 => 'MacVietnam',
|
||||
14 => 'MacTamil', 31 => 'MacSindhi',
|
||||
15 => 'MacTelugu', 32 => '', # 32=uninterpreted
|
||||
16 => 'MacKannada',
|
||||
},
|
||||
Windows => {
|
||||
0 => 'Symbol', 4 => 'Big5',
|
||||
1 => 'UCS2', 5 => 'Wansung',
|
||||
2 => 'ShiftJIS', 6 => 'Johab',
|
||||
3 => 'PRC', 10 => 'UCS4',
|
||||
},
|
||||
Unicode => {
|
||||
# (we don't currently handle the various Unicode flavours)
|
||||
0 => 'UCS2', # Unicode 1.0 semantics
|
||||
1 => 'UCS2', # Unicode 1.1 semantics
|
||||
2 => 'UCS2', # ISO 10646 semantics
|
||||
3 => 'UCS2', # Unicode 2.0 and onwards semantics, Unicode BMP only.
|
||||
4 => 'UCS2', # Unicode 2.0 and onwards semantics, Unicode full repertoire.
|
||||
# 5 => Unicode Variation Sequences (not used in Naming table)
|
||||
},
|
||||
ISO => { # (deprecated)
|
||||
0 => 'UTF8', # (7-bit ASCII)
|
||||
1 => 'UCS2', # ISO 10646
|
||||
2 => 'Latin', # ISO 8859-1
|
||||
},
|
||||
Custom => { },
|
||||
);
|
||||
|
||||
# convert TrueType 'name' language code to ExifTool language code
|
||||
%ttLang = (
|
||||
# Macintosh language codes (also used by QuickTime.pm)
|
||||
# oddities:
|
||||
# 49 - Cyrillic version 83 - Roman
|
||||
# 50 - Arabic version 84 - Arabic
|
||||
# 146 - with dot above
|
||||
Macintosh => {
|
||||
0 => 'en', 24 => 'lt', 48 => 'kk', 72 => 'ml', 129 => 'eu',
|
||||
1 => 'fr', 25 => 'pl', 49 => 'az', 73 => 'kn', 130 => 'ca',
|
||||
2 => 'de', 26 => 'hu', 50 => 'az', 74 => 'ta', 131 => 'la',
|
||||
3 => 'it', 27 => 'et', 51 => 'hy', 75 => 'te', 132 => 'qu',
|
||||
4 => 'nl-NL', 28 => 'lv', 52 => 'ka', 76 => 'si', 133 => 'gn',
|
||||
5 => 'sv', 29 => 'smi', 53 => 'ro', 77 => 'my', 134 => 'ay',
|
||||
6 => 'es', 30 => 'fo', 54 => 'ky', 78 => 'km', 135 => 'tt',
|
||||
7 => 'da', 31 => 'fa', 55 => 'tg', 79 => 'lo', 136 => 'ug',
|
||||
8 => 'pt', 32 => 'ru', 56 => 'tk', 80 => 'vi', 137 => 'dz',
|
||||
9 => 'no', 33 => 'zh-CN', 57 => 'mn-MN', 81 => 'id', 138 => 'jv',
|
||||
10 => 'he', 34 => 'nl-BE', 58 => 'mn-CN', 82 => 'tl', 139 => 'su',
|
||||
11 => 'ja', 35 => 'ga', 59 => 'ps', 83 => 'ms-MY', 140 => 'gl',
|
||||
12 => 'ar', 36 => 'sq', 60 => 'ku', 84 => 'ms-BN', 141 => 'af',
|
||||
13 => 'fi', 37 => 'ro', 61 => 'ks', 85 => 'am', 142 => 'br',
|
||||
14 => 'el', 38 => 'cs', 62 => 'sd', 86 => 'ti', 144 => 'gd',
|
||||
15 => 'is', 39 => 'sk', 63 => 'bo', 87 => 'om', 145 => 'gv',
|
||||
16 => 'mt', 40 => 'sl', 64 => 'ne', 88 => 'so', 146 => 'ga',
|
||||
17 => 'tr', 41 => 'yi', 65 => 'sa', 89 => 'sw', 147 => 'to',
|
||||
18 => 'hr', 42 => 'sr', 66 => 'mr', 90 => 'rw', 148 => 'el',
|
||||
19 => 'zh-TW', 43 => 'mk', 67 => 'bn', 91 => 'rn', 149 => 'kl',
|
||||
20 => 'ur', 44 => 'bg', 68 => 'as', 92 => 'ny', 150 => 'az',
|
||||
21 => 'hi', 45 => 'uk', 69 => 'gu', 93 => 'mg',
|
||||
22 => 'th', 46 => 'be', 70 => 'pa', 94 => 'eo',
|
||||
23 => 'ko', 47 => 'uz', 71 => 'or', 128 => 'cy',
|
||||
},
|
||||
# Windows language codes (http://msdn.microsoft.com/en-us/library/0h88fahh(VS.85).aspx)
|
||||
# Notes: This isn't an exact science. The reference above gives language codes
|
||||
# which are different from some ISO 639-1 numbers. Also, some Windows language
|
||||
# codes don't appear to have ISO 639-1 equivalents.
|
||||
# 0x0428 - fa by ref above
|
||||
# 0x048c - no ISO equivalent
|
||||
# 0x081a/0x83c - sr-SP
|
||||
# 0x0c0a - modern?
|
||||
# 0x2409 - Caribbean country code not found in ISO 3166-1
|
||||
Windows => {
|
||||
0x0401 => 'ar-SA', 0x0438 => 'fo', 0x0481 => 'mi', 0x1409 => 'en-NZ',
|
||||
0x0402 => 'bg', 0x0439 => 'hi', 0x0482 => 'oc', 0x140a => 'es-CR',
|
||||
0x0403 => 'ca', 0x043a => 'mt', 0x0483 => 'co', 0x140c => 'fr-LU',
|
||||
0x0404 => 'zh-TW', 0x043b => 'se-NO', 0x0484 => 'gsw', 0x141a => 'bs-BA',
|
||||
0x0405 => 'cs', 0x043c => 'gd', 0x0485 => 'sah', 0x143b => 'smj-SE',
|
||||
0x0406 => 'da', 0x043d => 'yi', 0x0486 => 'ny', 0x1801 => 'ar-MA',
|
||||
0x0407 => 'de-DE', 0x043e => 'ms-MY', 0x0487 => 'rw', 0x1809 => 'en-IE',
|
||||
0x0408 => 'el', 0x043f => 'kk', 0x048c => 'Dari', 0x180a => 'es-PA',
|
||||
0x0409 => 'en-US', 0x0440 => 'ky', 0x0801 => 'ar-IQ', 0x180c => 'fr-MC',
|
||||
0x040a => 'es-ES', 0x0441 => 'sw', 0x0804 => 'zh-CN', 0x181a => 'sr-BA',
|
||||
0x040b => 'fi', 0x0442 => 'tk', 0x0807 => 'de-CH', 0x183b => 'sma-NO',
|
||||
0x040c => 'fr-FR', 0x0443 => 'uz-UZ', 0x0809 => 'en-GB', 0x1c01 => 'ar-TN',
|
||||
0x040d => 'he', 0x0444 => 'tt', 0x080a => 'es-MX', 0x1c09 => 'en-ZA',
|
||||
0x040e => 'hu', 0x0445 => 'bn-IN', 0x080c => 'fr-BE', 0x1c0a => 'es-DO',
|
||||
0x040f => 'is', 0x0446 => 'pa', 0x0810 => 'it-CH', 0x1c1a => 'sr-BA',
|
||||
0x0410 => 'it-IT', 0x0447 => 'gu', 0x0813 => 'nl-BE', 0x1c3b => 'sma-SE',
|
||||
0x0411 => 'ja', 0x0448 => 'wo', 0x0814 => 'nn', 0x2001 => 'ar-OM',
|
||||
0x0412 => 'ko', 0x0449 => 'ta', 0x0816 => 'pt-PT', 0x2009 => 'en-JM',
|
||||
0x0413 => 'nl-NL', 0x044a => 'te', 0x0818 => 'ro-MO', 0x200a => 'es-VE',
|
||||
0x0414 => 'no-NO', 0x044b => 'kn', 0x0819 => 'ru-MO', 0x201a => 'bs-BA',
|
||||
0x0415 => 'pl', 0x044c => 'ml', 0x081a => 'sr-RS', 0x203b => 'sms',
|
||||
0x0416 => 'pt-BR', 0x044d => 'as', 0x081d => 'sv-FI', 0x2401 => 'ar-YE',
|
||||
0x0417 => 'rm', 0x044e => 'mr', 0x082c => 'az-AZ', 0x2409 => 'en-CB',
|
||||
0x0418 => 'ro', 0x044f => 'sa', 0x082e => 'dsb', 0x240a => 'es-CO',
|
||||
0x0419 => 'ru', 0x0450 => 'mn-MN', 0x083b => 'se-SE', 0x243b => 'smn',
|
||||
0x041a => 'hr', 0x0451 => 'bo', 0x083c => 'ga', 0x2801 => 'ar-SY',
|
||||
0x041b => 'sk', 0x0452 => 'cy', 0x083e => 'ms-BN', 0x2809 => 'en-BZ',
|
||||
0x041c => 'sq', 0x0453 => 'km', 0x0843 => 'uz-UZ', 0x280a => 'es-PE',
|
||||
0x041d => 'sv-SE', 0x0454 => 'lo', 0x0845 => 'bn-BD', 0x2c01 => 'ar-JO',
|
||||
0x041e => 'th', 0x0456 => 'gl', 0x0850 => 'mn-CN', 0x2c09 => 'en-TT',
|
||||
0x041f => 'tr', 0x0457 => 'kok', 0x085d => 'iu-CA', 0x2c0a => 'es-AR',
|
||||
0x0420 => 'ur', 0x045a => 'syr', 0x085f => 'tmh', 0x3001 => 'ar-LB',
|
||||
0x0421 => 'id', 0x045b => 'si', 0x086b => 'qu-EC', 0x3009 => 'en-ZW',
|
||||
0x0422 => 'uk', 0x045d => 'iu-CA', 0x0c01 => 'ar-EG', 0x300a => 'es-EC',
|
||||
0x0423 => 'be', 0x045e => 'am', 0x0c04 => 'zh-HK', 0x3401 => 'ar-KW',
|
||||
0x0424 => 'sl', 0x0461 => 'ne', 0x0c07 => 'de-AT', 0x3409 => 'en-PH',
|
||||
0x0425 => 'et', 0x0462 => 'fy', 0x0c09 => 'en-AU', 0x340a => 'es-CL',
|
||||
0x0426 => 'lv', 0x0463 => 'ps', 0x0c0a => 'es-ES', 0x3801 => 'ar-AE',
|
||||
0x0427 => 'lt', 0x0464 => 'fil', 0x0c0c => 'fr-CA', 0x380a => 'es-UY',
|
||||
0x0428 => 'tg', 0x0465 => 'dv', 0x0c1a => 'sr-RS', 0x3c01 => 'ar-BH',
|
||||
0x042a => 'vi', 0x0468 => 'ha', 0x0c3b => 'se-FI', 0x3c0a => 'es-PY',
|
||||
0x042b => 'hy', 0x046a => 'yo', 0x0c6b => 'qu-PE', 0x4001 => 'ar-QA',
|
||||
0x042c => 'az-AZ', 0x046b => 'qu-BO', 0x1001 => 'ar-LY', 0x4009 => 'en-IN',
|
||||
0x042d => 'eu', 0x046c => 'st', 0x1004 => 'zh-SG', 0x400a => 'es-BO',
|
||||
0x042e => 'hsb', 0x046d => 'ba', 0x1007 => 'de-LU', 0x4409 => 'en-MY',
|
||||
0x042f => 'mk', 0x046e => 'lb', 0x1009 => 'en-CA', 0x440a => 'es-SV',
|
||||
0x0430 => 'st', 0x046f => 'kl', 0x100a => 'es-GT', 0x4809 => 'en-SG',
|
||||
0x0431 => 'ts', 0x0470 => 'ig', 0x100c => 'fr-CH', 0x480a => 'es-HN',
|
||||
0x0432 => 'tn', 0x0478 => 'yi', 0x101a => 'hr-BA', 0x4c0a => 'es-NI',
|
||||
0x0434 => 'xh', 0x047a => 'arn', 0x103b => 'smj-NO',0x500a => 'es-PR',
|
||||
0x0435 => 'zu', 0x047c => 'moh', 0x1401 => 'ar-DZ', 0x540a => 'es-US',
|
||||
0x0436 => 'af', 0x047e => 'br', 0x1404 => 'zh-MO',
|
||||
0x0437 => 'ka', 0x0480 => 'ug', 0x1407 => 'de-LI',
|
||||
},
|
||||
Unicode => { },
|
||||
ISO => { },
|
||||
Custom => { },
|
||||
);
|
||||
|
||||
# the 63 known WOFF2 tags
|
||||
my @knownTags = (
|
||||
'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post', 'cvt',
|
||||
'fpgm', 'glyf', 'loca', 'prep', 'CFF', 'VORG', 'EBDT', 'EBLC', 'gasp',
|
||||
'hdmx', 'kern', 'LTSH', 'PCLT', 'VDMX', 'vhea', 'vmtx', 'BASE', 'GDEF',
|
||||
'GPOS', 'GSUB', 'EBSC', 'JSTF', 'MATH', 'CBDT', 'CBLC', 'COLR', 'CPAL',
|
||||
'SVG', 'sbix', 'acnt', 'avar', 'bdat', 'bloc', 'bsln', 'cvar', 'fdsc',
|
||||
'feat', 'fmtx', 'fvar', 'gvar', 'hsty', 'just', 'lcar', 'mort', 'morx',
|
||||
'opbd', 'prop', 'trak', 'Zapf', 'Silf', 'Glat', 'Gloc', 'Feat', 'Sill',
|
||||
);
|
||||
|
||||
# eclectic table of tags for various format font files
|
||||
%Image::ExifTool::Font::Main = (
|
||||
GROUPS => { 2 => 'Document' },
|
||||
NOTES => q{
|
||||
This table contains a collection of tags found in font files of various
|
||||
formats. ExifTool current recognizes OTF, TTF, TTC, DFONT, PFA, PFB, PFM,
|
||||
AFM, ACFM, AMFM, WOFF and WOFF2 font files.
|
||||
},
|
||||
name => {
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Font::Name' },
|
||||
},
|
||||
C2PA => {
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main', Start => 20 },
|
||||
},
|
||||
PFM => {
|
||||
Name => 'PFMHeader',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Font::PFM' },
|
||||
},
|
||||
PSInfo => {
|
||||
Name => 'PSFontInfo',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Font::PSInfo' },
|
||||
},
|
||||
AFM => {
|
||||
Name => 'AFM',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Font::AFM' },
|
||||
},
|
||||
numfonts => 'NumFonts',
|
||||
fontname => 'FontName',
|
||||
postfont => {
|
||||
Name => 'PostScriptFontName',
|
||||
Description => 'PostScript Font Name',
|
||||
},
|
||||
# for WOFF files
|
||||
WOFFVersion => { },
|
||||
XML => {
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Font::XML',
|
||||
IgnoreProp => { metadata => 1 },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# TrueType name tags (ref 1/2)
|
||||
%Image::ExifTool::Font::Name = (
|
||||
GROUPS => { 2 => 'Document' },
|
||||
NOTES => q{
|
||||
The following tags are extracted from the TrueType font "name" table found
|
||||
in OTF, TTF, TTC, DFONT, WOFF and WOFF2 files. These tags support localized
|
||||
languages by adding a hyphen followed by a language code to the end of the
|
||||
tag name (eg. "Copyright-fr" or "License-en-US"). Tags with no language
|
||||
code use the default language of "en".
|
||||
},
|
||||
0 => { Name => 'Copyright', Groups => { 2 => 'Author' } },
|
||||
1 => 'FontFamily',
|
||||
2 => 'FontSubfamily',
|
||||
3 => 'FontSubfamilyID',
|
||||
4 => 'FontName', # full name
|
||||
5 => 'NameTableVersion',
|
||||
6 => { Name => 'PostScriptFontName', Description => 'PostScript Font Name' },
|
||||
7 => 'Trademark',
|
||||
8 => 'Manufacturer',
|
||||
9 => 'Designer',
|
||||
10 => 'Description',
|
||||
11 => 'VendorURL',
|
||||
12 => 'DesignerURL',
|
||||
13 => 'License',
|
||||
14 => 'LicenseInfoURL',
|
||||
16 => 'PreferredFamily',
|
||||
17 => 'PreferredSubfamily',
|
||||
18 => 'CompatibleFontName',
|
||||
19 => 'SampleText',
|
||||
20 => {
|
||||
Name => 'PostScriptFontName',
|
||||
Description => 'PostScript Font Name',
|
||||
},
|
||||
21 => 'WWSFamilyName',
|
||||
22 => 'WWSSubfamilyName',
|
||||
);
|
||||
|
||||
# PostScript Font Metric file header (ref 4)
|
||||
%Image::ExifTool::Font::PFM = (
|
||||
GROUPS => { 2 => 'Document' },
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
NOTES => 'Tags extracted from the PFM file header.',
|
||||
0 => {
|
||||
Name => 'PFMVersion',
|
||||
Format => 'int16u',
|
||||
PrintConv => 'sprintf("%x.%.2x",$val>>8,$val&0xff)',
|
||||
},
|
||||
6 => { Name => 'Copyright', Format => 'string[60]', Groups => { 2 => 'Author' } },
|
||||
66 => { Name => 'FontType', Format => 'int16u' },
|
||||
68 => { Name => 'PointSize', Format => 'int16u' },
|
||||
70 => { Name => 'YResolution', Format => 'int16u' },
|
||||
72 => { Name => 'XResolution', Format => 'int16u' },
|
||||
74 => { Name => 'Ascent', Format => 'int16u' },
|
||||
76 => { Name => 'InternalLeading', Format => 'int16u' },
|
||||
78 => { Name => 'ExternalLeading', Format => 'int16u' },
|
||||
80 => { Name => 'Italic' },
|
||||
81 => { Name => 'Underline' },
|
||||
82 => { Name => 'Strikeout' },
|
||||
83 => { Name => 'Weight', Format => 'int16u' },
|
||||
85 => { Name => 'CharacterSet' },
|
||||
86 => { Name => 'PixWidth', Format => 'int16u' },
|
||||
88 => { Name => 'PixHeight', Format => 'int16u' },
|
||||
90 => { Name => 'PitchAndFamily' },
|
||||
91 => { Name => 'AvgWidth', Format => 'int16u' },
|
||||
93 => { Name => 'MaxWidth', Format => 'int16u' },
|
||||
95 => { Name => 'FirstChar' },
|
||||
96 => { Name => 'LastChar' },
|
||||
97 => { Name => 'DefaultChar' },
|
||||
98 => { Name => 'BreakChar' },
|
||||
99 => { Name => 'WidthBytes', Format => 'int16u' },
|
||||
# 101 => { Name => 'DeviceTypeOffset', Format => 'int32u' },
|
||||
# 105 => { Name => 'FontNameOffset', Format => 'int32u' },
|
||||
# 109 => { Name => 'BitsPointer', Format => 'int32u' },
|
||||
# 113 => { Name => 'BitsOffset', Format => 'int32u' },
|
||||
);
|
||||
|
||||
# PostScript FontInfo attributes (PFA, PFB) (ref PH)
|
||||
%Image::ExifTool::Font::PSInfo = (
|
||||
GROUPS => { 2 => 'Document' },
|
||||
NOTES => 'Tags extracted from PostScript font files (PFA and PFB).',
|
||||
FullName => { },
|
||||
FamilyName => { Name => 'FontFamily' },
|
||||
Weight => { },
|
||||
ItalicAngle => { },
|
||||
isFixedPitch=> { },
|
||||
UnderlinePosition => { },
|
||||
UnderlineThickness => { },
|
||||
Copyright => { Groups => { 2 => 'Author' } },
|
||||
Notice => { Groups => { 2 => 'Author' } },
|
||||
version => { },
|
||||
FontName => { },
|
||||
FontType => { },
|
||||
FSType => { },
|
||||
);
|
||||
|
||||
# Adobe Font Metrics tags (AFM) (ref 6)
|
||||
%Image::ExifTool::Font::AFM = (
|
||||
GROUPS => { 2 => 'Document' },
|
||||
NOTES => 'Tags extracted from Adobe Font Metrics files (AFM, ACFM and AMFM).',
|
||||
'Creation Date' => { Name => 'CreateDate', Groups => { 2 => 'Time' } },
|
||||
FontName => { },
|
||||
FullName => { },
|
||||
FamilyName => { Name => 'FontFamily' },
|
||||
Weight => { },
|
||||
Version => { },
|
||||
Notice => { Groups => { 2 => 'Author' } },
|
||||
EncodingScheme => { },
|
||||
MappingScheme => { },
|
||||
EscChar => { },
|
||||
CharacterSet=> { },
|
||||
Characters => { },
|
||||
IsBaseFont => { },
|
||||
# VVector => { },
|
||||
IsFixedV => { },
|
||||
CapHeight => { },
|
||||
XHeight => { },
|
||||
Ascender => { },
|
||||
Descender => { },
|
||||
);
|
||||
|
||||
# WOFF XML
|
||||
%Image::ExifTool::Font::XML = (
|
||||
GROUPS => { 1 => 'XML', 2 => 'Document' },
|
||||
PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP,
|
||||
NOTES => 'Tags found in WOFF and WOFF2 XML metadata.',
|
||||
version => { },
|
||||
uniqueidId => { Name => 'UniqueID' },
|
||||
vendorName => { },
|
||||
vendorUrl => { Name => 'VendorURL' },
|
||||
vendorDir => { },
|
||||
vendorClass => { },
|
||||
creditsCreditName => { Name => 'CreditName' },
|
||||
creditsCreditUrl => { Name => 'CreditURL' },
|
||||
creditsCreditRole => { Name => 'CreditRole' },
|
||||
creditsCreditDir => { Name => 'CreditDir' },
|
||||
creditsCreditClass => { Name => 'CreditClass' },
|
||||
descriptionUrl => { },
|
||||
descriptionText => { Name => 'Description' },
|
||||
licenseUrl => { Name => 'LicenseURL' },
|
||||
licenseId => { Name => 'LicenseID' },
|
||||
licenseText => { Name => 'License' },
|
||||
copyrightText => { Name => 'Copyright', Groups => { 2 => 'Author' } },
|
||||
trademarkText => { Name => 'Trademark' },
|
||||
licenseeDir => { },
|
||||
licenseeName => { },
|
||||
licenseeClass => { },
|
||||
extensionId => { Name => 'ExtensionID' },
|
||||
extensionName => { },
|
||||
extensionItemId => { Name => 'ExtensionItemID' },
|
||||
extensionItemName => { },
|
||||
extensionItemValue => { },
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from a TrueType font collection (TTC) (refs 2,3)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid TrueType font collection
|
||||
sub ProcessTTC($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $i);
|
||||
|
||||
return 0 unless $raf->Read($buff, 12) == 12;
|
||||
return 0 unless $buff =~ /^ttcf\0[\x01\x02]\0\0/;
|
||||
SetByteOrder('MM');
|
||||
my $num = Get32u(\$buff, 8);
|
||||
# might as well put a limit on the number of fonts we will parse (< 256)
|
||||
return 0 unless $num < 0x100 and $raf->Read($buff, $num * 4) == $num * 4;
|
||||
$et->SetFileType('TTC');
|
||||
return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main');
|
||||
$et->HandleTag($tagTablePtr, 'numfonts', $num);
|
||||
# loop through all fonts in the collection
|
||||
for ($i=0; $i<$num; ++$i) {
|
||||
my $n = $i + 1;
|
||||
$et->VPrint(0, "Font $n:\n");
|
||||
$$et{SET_GROUP1} = "+$n";
|
||||
my $offset = Get32u(\$buff, $i * 4);
|
||||
$raf->Seek($offset, 0) or last;
|
||||
ProcessOTF($et, $dirInfo) or last;
|
||||
}
|
||||
delete $$et{SET_GROUP1};
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process an OTF tag table entry (refs 1,2)
|
||||
# Inputs: 0) ExifTool ref, 1) entry index, 2) tag, 3) data ref,
|
||||
# 4) offset if uncompressed, 5) true to skip because data is transformed
|
||||
# Returns: undef on success, 0 to stop processing table, or error string otherwise
|
||||
sub ProcessTableEntry($$$$;$$)
|
||||
{
|
||||
my ($et, $idx, $tag, $dataPt, $offset, $transformed) = @_;
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $size = length $$dataPt;
|
||||
|
||||
$offset or $offset = 0;
|
||||
if ($verbose) {
|
||||
$tag =~ s/([\0-\x1f\x7f-\xff])/sprintf('\x%.2x',ord $1)/ge;
|
||||
my $str = sprintf("%s%d) Tag '%s' (offset 0x%.4x, %d bytes)\n",
|
||||
$$et{INDENT}, $idx, $tag, $offset, $size);
|
||||
$et->VPrint(0, $str);
|
||||
$et->VerboseDump($dataPt, Addr => $offset) if $verbose > 2;
|
||||
return undef unless $processTag{$tag};
|
||||
}
|
||||
return undef if $transformed or $size < 8;
|
||||
unless ($tag eq 'name') {
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main');
|
||||
$et->HandleTag($tagTablePtr, $tag, undef, DataPt => $dataPt, Size => length($$dataPt));
|
||||
return undef;
|
||||
}
|
||||
# process the 'name' tag
|
||||
my $entries = Get16u($dataPt, 2);
|
||||
my $recEnd = 6 + $entries * 12;
|
||||
if ($recEnd > $size) {
|
||||
$et->Warn('Truncated name record');
|
||||
return 0;
|
||||
}
|
||||
my $strStart = Get16u($dataPt, 4);
|
||||
if ($strStart < $recEnd or $strStart > $size) {
|
||||
$et->Warn('Invalid string offset');
|
||||
return 0;
|
||||
}
|
||||
# parse language-tag record (in format 1 Naming table only) (ref 2)
|
||||
my ($i, %langTag);
|
||||
if (Get16u($dataPt, 0) == 1 and $recEnd + 2 <= $size) {
|
||||
my $langTags = Get16u($dataPt, $recEnd);
|
||||
if ($langTags and $recEnd + 2 + $langTags * 4 < $size) {
|
||||
for ($i=0; $i<$langTags; ++$i) {
|
||||
my $pt = $recEnd + 2 + $i * 4;
|
||||
my $langLen = Get16u($dataPt, $pt);
|
||||
# make sure the language string length is reasonable (UTF-16BE)
|
||||
last if $langLen == 0 or $langLen & 0x01 or $langLen > 40;
|
||||
my $langPt = Get16u($dataPt, $pt + 2) + $strStart;
|
||||
last if $langPt + $langLen > $size;
|
||||
my $lang = substr($$dataPt, $langPt, $langLen);
|
||||
$lang = $et->Decode($lang,'UCS2','MM','UTF8');
|
||||
$lang =~ tr/-_a-zA-Z0-9//dc; # remove naughty characters
|
||||
$langTag{$i + 0x8000} = $lang;
|
||||
}
|
||||
}
|
||||
}
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Name');
|
||||
my $oldIndent = $$et{INDENT};
|
||||
$$et{INDENT} .= '| ';
|
||||
$et->VerboseDir('Name', $entries) if $verbose;
|
||||
for ($i=0; $i<$entries; ++$i) {
|
||||
my $pt = 6 + $i * 12;
|
||||
my $platform = Get16u($dataPt, $pt);
|
||||
my $encoding = Get16u($dataPt, $pt + 2);
|
||||
my $langID = Get16u($dataPt, $pt + 4);
|
||||
my $nameID = Get16u($dataPt, $pt + 6);
|
||||
my $strLen = Get16u($dataPt, $pt + 8);
|
||||
my $strPt = Get16u($dataPt, $pt + 10) + $strStart;
|
||||
if ($strPt + $strLen <= $size) {
|
||||
my $val = substr($$dataPt, $strPt, $strLen);
|
||||
my ($lang, $charset, $extra);
|
||||
my $sys = $ttPlatform{$platform};
|
||||
# translate from specified encoding
|
||||
if ($sys) {
|
||||
$lang = $ttLang{$sys}{$langID} || $langTag{$langID};
|
||||
$charset = $ttCharset{$sys}{$encoding};
|
||||
if (not $charset) {
|
||||
if (not defined $charset and not $$et{FontWarn}) {
|
||||
$et->Warn("Unknown $sys character set ($encoding)");
|
||||
$$et{FontWarn} = 1;
|
||||
}
|
||||
} else {
|
||||
# translate to ExifTool character set
|
||||
$val = $et->Decode($val, $charset);
|
||||
}
|
||||
} else {
|
||||
$et->Warn("Unknown platform ($platform) for name $nameID");
|
||||
}
|
||||
# get the tagInfo for our specific language (use 'en' for default)
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $nameID);
|
||||
if ($tagInfo and $lang and $lang ne 'en') {
|
||||
my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang);
|
||||
$tagInfo = $langInfo if $langInfo;
|
||||
}
|
||||
if ($verbose) {
|
||||
$langID > 0x400 and $langID = sprintf('0x%x', $langID);
|
||||
$extra = ", Plat=$platform/" . ($sys || 'Unknown') . ', ' .
|
||||
"Enc=$encoding/" . ($charset || 'Unknown') . ', ' .
|
||||
"Lang=$langID/" . ($lang || 'Unknown');
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $nameID, $val,
|
||||
TagInfo => $tagInfo,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $offset,
|
||||
Start => $strPt,
|
||||
Size => $strLen,
|
||||
Index => $i,
|
||||
Extra => $extra,
|
||||
);
|
||||
}
|
||||
}
|
||||
$$et{INDENT} = $oldIndent;
|
||||
return $verbose ? undef : 0;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from a TrueType font file (OTF or TTF) (refs 1,2)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid TrueType font file
|
||||
sub ProcessOTF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($tbl, $buff, $pos, $i);
|
||||
my $base = $$dirInfo{Base} || 0;
|
||||
|
||||
return 0 unless $raf->Read($buff, 12) == 12;
|
||||
return 0 unless $buff =~ /^(\0\x01\0\0|OTTO|true|typ1|\xa5(kbd|lst))[\0\x01]/;
|
||||
|
||||
$et->SetFileType($1 eq 'OTTO' ? 'OTF' : 'TTF');
|
||||
return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
|
||||
SetByteOrder('MM');
|
||||
my $numTables = Get16u(\$buff, 4);
|
||||
return 0 unless $numTables > 0 and $numTables < 0x200;
|
||||
my $len = $numTables * 16;
|
||||
return 0 unless $raf->Read($tbl, $len) == $len;
|
||||
|
||||
my $verbose = $et->Options('Verbose');
|
||||
|
||||
for ($pos=0, $i=0; $pos<$len; $pos+=16, ++$i) {
|
||||
# look for tags to process
|
||||
my $tag = substr($tbl, $pos, 4);
|
||||
next unless $processTag{$tag} or $verbose;
|
||||
my $offset = Get32u(\$tbl, $pos + 8);
|
||||
my $size = Get32u(\$tbl, $pos + 12);
|
||||
unless ($raf->Seek($offset+$base, 0) and $raf->Read($buff, $size) == $size) {
|
||||
$et->Warn("Error reading '${tag}' data");
|
||||
next;
|
||||
}
|
||||
my $err = ProcessTableEntry($et, $i, $tag, \$buff, $offset);
|
||||
$err and $et->Warn($err), last;
|
||||
last if defined $err;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from an Adobe Font Metrics file (AFM, ACFM, AMFM) (ref 6)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a recognized AFM-type file
|
||||
sub ProcessAFM($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $comment);
|
||||
|
||||
require Image::ExifTool::PostScript;
|
||||
local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf);
|
||||
$raf->ReadLine($buff);
|
||||
return 0 unless $buff =~ /^Start(Comp|Master)?FontMetrics\s+\d+/;
|
||||
my $ftyp = $1 ? ($1 eq 'Comp' ? 'ACFM' : 'AMFM') : 'AFM';
|
||||
$et->SetFileType($ftyp, 'application/x-font-afm');
|
||||
return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Font::AFM');
|
||||
|
||||
for (;;) {
|
||||
$raf->ReadLine($buff) or last;
|
||||
if (defined $comment and $buff !~ /^Comment\s/) {
|
||||
$et->FoundTag('Comment', $comment);
|
||||
undef $comment;
|
||||
}
|
||||
$buff =~ /^(\w+)\s+(.*?)[\x0d\x0a]/ or next;
|
||||
my ($tag, $val) = ($1, $2);
|
||||
if ($tag eq 'Comment' and $val =~ /^(Creation Date):\s+(.*)/) {
|
||||
($tag, $val) = ($1, $2);
|
||||
}
|
||||
$val =~ s/^\((.*)\)$/$1/; # (some values may be in brackets)
|
||||
if ($tag eq 'Comment') {
|
||||
# concatinate all comments into a single value
|
||||
$comment = defined($comment) ? "$comment\n$val" : $val;
|
||||
next;
|
||||
}
|
||||
unless ($et->HandleTag($tagTablePtr, $tag, $val)) {
|
||||
# end parsing if we start any subsection
|
||||
last if $tag =~ /^Start/ and $tag ne 'StartDirection';
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read WOFF2 255UInt16 integer (ref 8)
|
||||
# Inputs: 0) raf ref
|
||||
# Returns: value, or undef on error
|
||||
sub Read255UInt16($)
|
||||
{
|
||||
my $raf = shift;
|
||||
my $buff;
|
||||
return undef unless $raf->Read($buff, 1);
|
||||
my $val = unpack('C', $buff);
|
||||
if ($val == 253) {
|
||||
return undef unless $raf->Read($buff, 2) == 2;
|
||||
$val = unpack('n', $buff);
|
||||
} elsif ($val == 254) {
|
||||
return undef unless $raf->Read($buff, 1);
|
||||
$val = unpack('C', $buff) + 253 * 2;
|
||||
} elsif ($val == 255) {
|
||||
return undef unless $raf->Read($buff, 1);
|
||||
$val = unpack('C', $buff) + 253;
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read WOFF2 UIntBase128 integer (ref 8)
|
||||
# Inputs: 0) raf ref
|
||||
# Returns: value, or undef on error
|
||||
sub ReadUIntBase128($)
|
||||
{
|
||||
my $raf = shift;
|
||||
my $buff;
|
||||
my $val = 0;
|
||||
foreach (0..4) {
|
||||
return undef unless $raf->Read($buff, 1);
|
||||
my $byte = unpack('C', $buff);
|
||||
return undef if not $_ and $byte == 0x80;
|
||||
return undef if $val & 0xfe000000;
|
||||
$val = ($val << 7) | ($byte & 0x7f);
|
||||
return $val unless $byte & 0x80;
|
||||
}
|
||||
return undef; # can't be longer than 5 bytes
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Uncompress data
|
||||
# Inputs: 0) ExifTool ref, 1) data ref
|
||||
# Returns: true on success
|
||||
sub Uncompress($$)
|
||||
{
|
||||
my ($et, $dataPt) = @_;
|
||||
my $stat;
|
||||
unless (eval { require Compress::Zlib }) {
|
||||
$et->Warn('Install Compress::Zlib to read compressed metadata');
|
||||
return 0;
|
||||
}
|
||||
my $inflate = Compress::Zlib::inflateInit();
|
||||
$inflate and ($$dataPt, $stat) = $inflate->inflate($$dataPt);
|
||||
unless ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) {
|
||||
$et->Warn('Error uncompressing metadata');
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Brotli uncompress data
|
||||
# Inputs: 0) ExifTool ref, 1) data ref
|
||||
# Returns: true on success
|
||||
sub Unbrotli($$)
|
||||
{
|
||||
my ($et, $dataPt) = @_;
|
||||
unless (eval { require IO::Uncompress::Brotli }) {
|
||||
$et->Warn('Install IO::Compress::Brotli to decode Brotli-compressed metadata');
|
||||
return 0;
|
||||
}
|
||||
eval { $$dataPt = IO::Uncompress::Brotli::unbro($$dataPt, 100000000) };
|
||||
if ($@) {
|
||||
$et->Warn('Error decoding metadata');
|
||||
$et->Warn('Try updating to IO::Uncompress::Brotli 0.004 or later');
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from WOFF/WOFF2 font files
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a recognized WOFF file
|
||||
sub ProcessWOFF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $tbl, $i);
|
||||
$raf->Seek(0,0) and $raf->Read($buff,48) == 48 or return 0;
|
||||
$buff =~ /^(wOF[F2])/ or return 0;
|
||||
my ($type, $off) = $1 eq 'wOFF' ? ('woff' , 20) : ('woff2', 24);
|
||||
$et->SetFileType(uc($type), "font/$type");
|
||||
SetByteOrder('MM');
|
||||
my $flavor = substr($buff, 4, 4);
|
||||
my $numTables = Get16u(\$buff, 12);
|
||||
my ($vh, $vl, $metaPos, $metaLen) = unpack("x${off}nnNN", $buff);
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main');
|
||||
$et->HandleTag($tagTablePtr, WOFFVersion => "$vh.$vl");
|
||||
#
|
||||
# read font table
|
||||
#
|
||||
if ($type eq 'woff') {
|
||||
unless ($raf->Seek($off+24,0) and $raf->Read($tbl,$numTables*20)==$numTables*20) {
|
||||
$et->Warn('Error reading font table');
|
||||
return 1;
|
||||
}
|
||||
for ($i=0; $i<$numTables; ++$i) {
|
||||
my $pt = $i * 20;
|
||||
my ($tag, $pos, $compLen, $len) = unpack("x${pt}a4N3", $tbl);
|
||||
next unless $processTag{$tag} or $verbose;
|
||||
$raf->Seek($pos,0) and $raf->Read($buff,$compLen)==$compLen or $et->Warn('Bad font table entry'), return 1;
|
||||
my $dataPos;
|
||||
if ($compLen eq $len) {
|
||||
$dataPos = $pos;
|
||||
} else {
|
||||
next unless Uncompress($et, \$buff);
|
||||
}
|
||||
my $err = ProcessTableEntry($et, $i, $tag, \$buff, $dataPos);
|
||||
$err and $et->Warn($err), return 1;
|
||||
last if defined $err;
|
||||
}
|
||||
} else { # WOFF2
|
||||
my $compSize = Get32u(\$buff, 20);
|
||||
my ($err, @entry, $entry);
|
||||
$raf->Seek($off+24,0) or $et->Warn('Error seeking to font table'), return 1;
|
||||
for ($i=0; $i<$numTables; ++$i) {
|
||||
$raf->Read($buff, 1) or $err = 1, last;
|
||||
my $flags = unpack('C', $buff);
|
||||
my $tag = $knownTags[$flags & 0x3f];
|
||||
$tag or $raf->Read($tag, 4) == 4 or $err = 1, last;
|
||||
my $len = ReadUIntBase128($raf);
|
||||
defined $len or $err = 1, last;
|
||||
my $transformed;
|
||||
if (($tag eq 'glyf' or $tag eq 'loca') xor $flags & 0xc0) {
|
||||
# a non-null transform was used
|
||||
$len = ReadUIntBase128($raf);
|
||||
$transformed = 1;
|
||||
}
|
||||
# save information about this entry for later
|
||||
push @entry, [ $i, $tag, $len, $transformed ];
|
||||
}
|
||||
$err and $et->Warn('Error reading font table'), return 1;
|
||||
# skip the collection table if necessary
|
||||
if ($flavor eq 'ttcf') {
|
||||
$raf->Seek(4, 1) or $et->Warn('Seek error'), return 1;
|
||||
my $n = Read255UInt16($raf);
|
||||
defined $n and $raf->Seek(4,1) or $et->Warn('Error reading collection table'), return 1;
|
||||
$raf->Seek(4, 1) or $err = 1, last;
|
||||
for ($i=0; $i<$n; ++$i) {
|
||||
defined Read255UInt16($raf) or $err = 1, last;
|
||||
}
|
||||
$err and $et->Warn('Error reading collection directory'), return 1;
|
||||
}
|
||||
$raf->Read($buff,$compSize) == $compSize or $et->Warn('Error reading font data'), return 1;
|
||||
return 1 unless Unbrotli($et, \$buff);
|
||||
# after all that exhausting and frankly unnecessary work (poor file design),
|
||||
# we finally have the uncompressed font data so we can process the table entries
|
||||
my $pos = 0;
|
||||
foreach $entry (@entry) {
|
||||
my ($i, $tag, $len, $transformed) = @$entry;
|
||||
if ($processTag{$tag} or $verbose) {
|
||||
my $dat = substr($buff, $pos, $len);
|
||||
my $err = ProcessTableEntry($et, $i, $tag, \$dat, undef, $transformed);
|
||||
$err and $et->Warn($err), return 1;
|
||||
last if defined $err;
|
||||
}
|
||||
$pos += $len;
|
||||
}
|
||||
}
|
||||
#
|
||||
# read compressed XML-format metadata (NC)
|
||||
#
|
||||
if ($metaLen) {
|
||||
unless ($raf->Seek($metaPos,0) and $raf->Read($buff,$metaLen)==$metaLen) {
|
||||
$et->Warn('Error reading metadata');
|
||||
return 1;
|
||||
}
|
||||
if ($type eq 'woff') {
|
||||
return 1 unless Uncompress($et, \$buff);
|
||||
} else { # WOFF2
|
||||
return 1 unless Unbrotli($et, \$buff);
|
||||
}
|
||||
# (we don't properly support XML structures)
|
||||
my $oldStruct = $et->Options('Struct');
|
||||
$et->Options(Struct => 0);
|
||||
$et->HandleTag($tagTablePtr, 'XML', $buff);
|
||||
$et->Options(Struct => $oldStruct);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information from various format font files
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a recognized Font file
|
||||
sub ProcessFont($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my ($buff, $buf2, $rtnVal);
|
||||
return 0 unless $raf->Read($buff, 24) and $raf->Seek(0,0);
|
||||
if ($buff =~ /^(\0\x01\0\0|OTTO|true|typ1)[\0\x01]/) { # OTF, TTF
|
||||
$rtnVal = ProcessOTF($et, $dirInfo);
|
||||
} elsif ($buff =~ /^ttcf\0[\x01\x02]\0\0/) { # TTC
|
||||
$rtnVal = ProcessTTC($et, $dirInfo);
|
||||
} elsif ($buff =~ /^Start(Comp|Master)?FontMetrics\s+\d+/s) { # AFM
|
||||
$rtnVal = ProcessAFM($et, $dirInfo);
|
||||
} elsif ($buff =~ /^(.{6})?%!(PS-(AdobeFont-|Bitstream )|FontType1-)/s) {# PFA, PFB
|
||||
$raf->Seek(6,0) and $et->SetFileType('PFB') if $1;
|
||||
require Image::ExifTool::PostScript;
|
||||
$rtnVal = Image::ExifTool::PostScript::ProcessPS($et, $dirInfo);
|
||||
} elsif ($buff =~ /^\0[\x01\x02]/ and $raf->Seek(0, 2) and # PFM
|
||||
# validate file size
|
||||
$raf->Tell() > 117 and $raf->Tell() == unpack('x2V',$buff) and
|
||||
# read PFM header
|
||||
$raf->Seek(0,0) and $raf->Read($buff,117) == 117 and
|
||||
# validate "DeviceType" string (must be "PostScript\0")
|
||||
SetByteOrder('II') and $raf->Seek(Get32u(\$buff, 101), 0) and
|
||||
# the DeviceType should be "PostScript\0", but FontForge
|
||||
# incorrectly writes "Postscript\0", so ignore case
|
||||
$raf->Read($buf2, 11) == 11 and lc($buf2) eq "postscript\0")
|
||||
{
|
||||
$et->SetFileType('PFM');
|
||||
return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
|
||||
SetByteOrder('II');
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main');
|
||||
# process the PFM header
|
||||
$et->HandleTag($tagTablePtr, 'PFM', $buff);
|
||||
# extract the font names
|
||||
my $nameOff = Get32u(\$buff, 105);
|
||||
if ($raf->Seek($nameOff, 0) and $raf->Read($buff, 256) and
|
||||
$buff =~ /^([\x20-\xff]+)\0([\x20-\xff]+)\0/)
|
||||
{
|
||||
$et->HandleTag($tagTablePtr, 'fontname', $1);
|
||||
$et->HandleTag($tagTablePtr, 'postfont', $2);
|
||||
}
|
||||
$rtnVal = 1;
|
||||
} elsif ($buff =~ /^(wOF[F2])/) {
|
||||
$rtnVal = ProcessWOFF($et, $dirInfo);
|
||||
} else {
|
||||
$rtnVal = 0;
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::Font - Read meta information from font files
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains the routines required by Image::ExifTool to read meta
|
||||
information from various format font files. Currently recognized font file
|
||||
types are OTF, TTF, TTC, DFONT, PFA, PFB, PFM, AFM, ACFM, AMFM, WOFF and
|
||||
WOFF2.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html>
|
||||
|
||||
=item L<http://www.microsoft.com/typography/otspec/otff.htm>
|
||||
|
||||
=item L<http://partners.adobe.com/public/developer/opentype/index_font_file.html>
|
||||
|
||||
=item L<http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf>
|
||||
|
||||
=item L<http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/compiler/src/java/flex2/compiler/util/MimeMappings.java>
|
||||
|
||||
=item L<http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf>
|
||||
|
||||
=item L<https://www.w3.org/TR/WOFF/>
|
||||
|
||||
=item L<https://www.w3.org/TR/WOFF2/>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/Font Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
261
.resources/nix/bin/lib/Image/ExifTool/FotoStation.pm
Normal file
261
.resources/nix/bin/lib/Image/ExifTool/FotoStation.pm
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: FotoStation.pm
|
||||
#
|
||||
# Description: Read/write FotoWare FotoStation trailer
|
||||
#
|
||||
# Revisions: 10/28/2006 - P. Harvey Created
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::FotoStation;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.04';
|
||||
|
||||
sub ProcessFotoStation($$);
|
||||
|
||||
%Image::ExifTool::FotoStation::Main = (
|
||||
PROCESS_PROC => \&ProcessFotoStation,
|
||||
WRITE_PROC => \&ProcessFotoStation,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => q{
|
||||
The following tables define information found in the FotoWare FotoStation
|
||||
trailer.
|
||||
},
|
||||
0x01 => {
|
||||
Name => 'IPTC',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::IPTC::Main',
|
||||
},
|
||||
},
|
||||
0x02 => {
|
||||
Name => 'SoftEdit',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::FotoStation::SoftEdit',
|
||||
},
|
||||
},
|
||||
0x03 => {
|
||||
Name => 'ThumbnailImage',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Writable => 1,
|
||||
RawConv => '$self->ValidateImage(\$val,$tag)',
|
||||
},
|
||||
0x04 => {
|
||||
Name => 'PreviewImage',
|
||||
Groups => { 2 => 'Preview' },
|
||||
Writable => 1,
|
||||
RawConv => '$self->ValidateImage(\$val,$tag)',
|
||||
},
|
||||
);
|
||||
|
||||
# crop coordinate conversions
|
||||
my %cropConv = (
|
||||
ValueConv => '$val / 1000',
|
||||
ValueConvInv => '$val * 1000',
|
||||
PrintConv => '"$val%"',
|
||||
PrintConvInv => '$val=~tr/ %//d; $val',
|
||||
);
|
||||
|
||||
# soft crop record
|
||||
%Image::ExifTool::FotoStation::SoftEdit = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||||
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||||
WRITABLE => 1,
|
||||
FORMAT => 'int32s',
|
||||
FIRST_ENTRY => 0,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
0 => {
|
||||
Name => 'OriginalImageWidth',
|
||||
},
|
||||
1 => 'OriginalImageHeight',
|
||||
2 => 'ColorPlanes',
|
||||
3 => {
|
||||
Name => 'XYResolution',
|
||||
ValueConv => '$val / 1000',
|
||||
ValueConvInv => '$val * 1000',
|
||||
},
|
||||
4 => {
|
||||
Name => 'Rotation',
|
||||
Notes => q{
|
||||
rotations are stored as degrees CCW * 100, but converted to degrees CW by
|
||||
ExifTool
|
||||
},
|
||||
# raw value is 0, 9000, 18000 or 27000
|
||||
ValueConv => '$val ? 360 - $val / 100 : 0',
|
||||
ValueConvInv => '$val ? (360 - $val) * 100 : 0',
|
||||
},
|
||||
# 5 Validity Check (0x11222211)
|
||||
6 => {
|
||||
Name => 'CropLeft',
|
||||
%cropConv,
|
||||
},
|
||||
7 => {
|
||||
Name => 'CropTop',
|
||||
%cropConv,
|
||||
},
|
||||
8 => {
|
||||
Name => 'CropRight',
|
||||
%cropConv,
|
||||
},
|
||||
9 => {
|
||||
Name => 'CropBottom',
|
||||
%cropConv,
|
||||
},
|
||||
11 => {
|
||||
Name => 'CropRotation',
|
||||
# raw value in the range -4500 to 4500
|
||||
ValueConv => '-$val / 100',
|
||||
ValueConvInv => '-$val * 100',
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read/write FotoStation information in a file
|
||||
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||||
# Returns: 1 on success, 0 if this file didn't contain FotoStation information
|
||||
# - updates DataPos to point to start of FotoStation information
|
||||
# - updates DirLen to trailer length
|
||||
sub ProcessFotoStation($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
$et or return 1; # allow dummy access to autoload this package
|
||||
my ($buff, $footer, $dirBuff, $tagTablePtr);
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
my $offset = $$dirInfo{Offset} || 0;
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $out = $et->Options('TextOut');
|
||||
my $rtnVal = 0;
|
||||
|
||||
$$dirInfo{DirLen} = 0; # initialize returned trailer length
|
||||
$raf->Seek(-$offset, 2); # seek to specified offset from end of file
|
||||
|
||||
# loop through FotoStation records
|
||||
for (;;) {
|
||||
# look for trailer signature
|
||||
last unless $raf->Seek(-10, 1) and $raf->Read($footer, 10) == 10;
|
||||
my ($tag, $size, $sig) = unpack('nNN', $footer);
|
||||
last unless $sig == 0xa1b2c3d4 and $size >= 10 and $raf->Seek(-$size, 1);
|
||||
$size -= 10; # size of data only
|
||||
last unless $raf->Read($buff, $size) == $size;
|
||||
$raf->Seek(-$size, 1);
|
||||
# set variables returned in dirInfo hash
|
||||
$$dirInfo{DataPos} = $raf->Tell();
|
||||
$$dirInfo{DirLen} += $size + 10;
|
||||
|
||||
unless ($tagTablePtr) {
|
||||
$tagTablePtr = GetTagTable('Image::ExifTool::FotoStation::Main');
|
||||
SetByteOrder('MM'); # necessary for the binary data
|
||||
$rtnVal = 1; # we found a valid FotoStation trailer
|
||||
}
|
||||
unless ($outfile) {
|
||||
# print verbose trailer information
|
||||
if ($verbose or $$et{HTML_DUMP}) {
|
||||
$et->DumpTrailer({
|
||||
RAF => $raf,
|
||||
DataPos => $$dirInfo{DataPos},
|
||||
DirLen => $size + 10,
|
||||
DirName => "FotoStation_$tag",
|
||||
});
|
||||
}
|
||||
# extract information for this tag
|
||||
$et->HandleTag($tagTablePtr, $tag, $buff,
|
||||
DataPt => \$buff,
|
||||
Start => 0,
|
||||
Size => $size,
|
||||
DataPos => $$dirInfo{DataPos},
|
||||
);
|
||||
next;
|
||||
}
|
||||
if ($$et{DEL_GROUP}{FotoStation}) {
|
||||
$verbose and print $out " Deleting FotoStation trailer\n";
|
||||
$verbose = 0; # no more verbose messages after this
|
||||
++$$et{CHANGED};
|
||||
next;
|
||||
}
|
||||
# rewrite this information
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
if ($tagInfo) {
|
||||
my $newVal;
|
||||
my $tagName = $$tagInfo{Name};
|
||||
if ($$tagInfo{SubDirectory}) {
|
||||
my %subdirInfo = (
|
||||
DataPt => \$buff,
|
||||
DirStart => 0,
|
||||
DirLen => $size,
|
||||
DataPos => $$dirInfo{DataPos},
|
||||
DirName => $tagName,
|
||||
Parent => 'FotoStation',
|
||||
);
|
||||
my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
|
||||
$newVal = $et->WriteDirectory(\%subdirInfo, $subTable);
|
||||
} else {
|
||||
my $nvHash = $et->GetNewValueHash($tagInfo);
|
||||
if ($et->IsOverwriting($nvHash) > 0) {
|
||||
$newVal = $et->GetNewValue($nvHash);
|
||||
$newVal = '' unless defined $newVal;
|
||||
if ($verbose > 1) {
|
||||
my $n = length $newVal;
|
||||
print $out " - FotoStation:$tagName ($size bytes)\n" if $size;
|
||||
print $out " + FotoStation:$tagName ($n bytes)\n" if $n;
|
||||
}
|
||||
++$$et{CHANGED};
|
||||
}
|
||||
}
|
||||
if (defined $newVal) {
|
||||
# note: length may be 0 here, but we write the empty record anyway
|
||||
$buff = $newVal;
|
||||
$size = length($newVal) + 10;
|
||||
$footer = pack('nNN', $tag, $size, $sig);
|
||||
}
|
||||
}
|
||||
if (defined $dirBuff) {
|
||||
# maintain original record order
|
||||
$dirBuff = $buff . $footer . $dirBuff;
|
||||
} else {
|
||||
$dirBuff = $buff . $footer;
|
||||
}
|
||||
}
|
||||
# write the modified FotoStation trailer
|
||||
Write($outfile, $dirBuff) or $rtnVal = -1 if $dirBuff;
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::FotoStation - Read/write FotoWare FotoStation trailer
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read and
|
||||
write information from the FotoWare FotoStation trailer.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 ACKNOWLEDGEMENTS
|
||||
|
||||
Thanks to Mark Tate for information about the FotoStation data format.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/FotoStation Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
2061
.resources/nix/bin/lib/Image/ExifTool/FujiFilm.pm
Normal file
2061
.resources/nix/bin/lib/Image/ExifTool/FujiFilm.pm
Normal file
File diff suppressed because it is too large
Load diff
81
.resources/nix/bin/lib/Image/ExifTool/GE.pm
Normal file
81
.resources/nix/bin/lib/Image/ExifTool/GE.pm
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: GE.pm
|
||||
#
|
||||
# Description: General Imaging maker notes tags
|
||||
#
|
||||
# Revisions: 2010-12-14 - P. Harvey Created
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::GE;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::Exif;
|
||||
|
||||
$VERSION = '1.00';
|
||||
|
||||
sub ProcessGE2($$$);
|
||||
|
||||
# GE type 1 maker notes (ref PH)
|
||||
# (similar to Kodak::Type11 and Ricoh::Type2)
|
||||
%Image::ExifTool::GE::Main = (
|
||||
WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
|
||||
CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
|
||||
WRITABLE => 1,
|
||||
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||||
NOTES => q{
|
||||
This table lists tags found in the maker notes of some General Imaging
|
||||
camera models.
|
||||
},
|
||||
# 0x0104 - int32u
|
||||
# 0x0200 - int32u[3] (with invalid offset of 0)
|
||||
0x0202 => {
|
||||
Name => 'Macro',
|
||||
Writable => 'int16u',
|
||||
PrintConv => { 0 => 'Off', 1 => 'On' },
|
||||
},
|
||||
# 0x0203 - int16u: 0
|
||||
# 0x0204 - rational64u: 10/10
|
||||
# 0x0205 - rational64u: 7.249,7.34,9.47 (changes with camera model)
|
||||
# 0x0206 - int16u[6] (with invalid offset of 0)
|
||||
0x0207 => {
|
||||
Name => 'GEModel',
|
||||
Format => 'string',
|
||||
},
|
||||
0x0300 => {
|
||||
Name => 'GEMake',
|
||||
Format => 'string',
|
||||
},
|
||||
# 0x0500 - int16u: 0
|
||||
# 0x0600 - int32u: 0
|
||||
);
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::GE - General Imaging maker notes tags
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to interpret
|
||||
General Imaging maker notes.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/GE Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
624
.resources/nix/bin/lib/Image/ExifTool/GIF.pm
Normal file
624
.resources/nix/bin/lib/Image/ExifTool/GIF.pm
Normal file
|
|
@ -0,0 +1,624 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: GIF.pm
|
||||
#
|
||||
# Description: Read and write GIF meta information
|
||||
#
|
||||
# Revisions: 10/18/2005 - P. Harvey Separated from ExifTool.pm
|
||||
# 05/23/2008 - P. Harvey Added ability to read/write XMP
|
||||
# 10/28/2011 - P. Harvey Added ability to read/write ICC_Profile
|
||||
#
|
||||
# References: 1) http://www.w3.org/Graphics/GIF/spec-gif89a.txt
|
||||
# 2) http://www.adobe.com/devnet/xmp/
|
||||
# 3) http://graphcomp.com/info/specs/ani_gif.html
|
||||
# 4) http://www.color.org/icc_specs2.html
|
||||
# 5) http://www.midiox.com/mmgif.htm
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::GIF;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.21';
|
||||
|
||||
# road map of directory locations in GIF images
|
||||
my %gifMap = (
|
||||
XMP => 'GIF',
|
||||
ICC_Profile => 'GIF',
|
||||
);
|
||||
|
||||
# application extensions that we can write, and the order they are written
|
||||
my @appExtensions = ( 'XMP Data/XMP', 'ICCRGBG1/012' );
|
||||
|
||||
%Image::ExifTool::GIF::Main = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
VARS => { ID_FMT => 'none' },
|
||||
NOTES => q{
|
||||
This table lists information extracted from GIF images. See
|
||||
L<http://www.w3.org/Graphics/GIF/spec-gif89a.txt> for the official GIF89a
|
||||
specification.
|
||||
},
|
||||
GIFVersion => { },
|
||||
FrameCount => { Notes => 'number of animated images' },
|
||||
Text => { Notes => 'text displayed in image' },
|
||||
Comment => {
|
||||
# for documentation only -- flag as writable for the docs, but
|
||||
# it won't appear in the TagLookup because there is no WRITE_PROC
|
||||
Writable => 2,
|
||||
},
|
||||
Duration => {
|
||||
Notes => 'duration of a single animation iteration',
|
||||
PrintConv => 'sprintf("%.2f s",$val)',
|
||||
},
|
||||
ScreenDescriptor => {
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GIF::Screen' },
|
||||
},
|
||||
Extensions => { # (for documentation only)
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GIF::Extensions' },
|
||||
},
|
||||
TransparentColor => { },
|
||||
);
|
||||
|
||||
# GIF89a application extensions:
|
||||
%Image::ExifTool::GIF::Extensions = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => 'Tags extracted from GIF89a application extensions.',
|
||||
WRITE_PROC => sub { return 1 }, # (dummy proc to facilitate writable directories)
|
||||
'NETSCAPE/2.0' => { #3
|
||||
Name => 'Animation',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GIF::Animation' },
|
||||
},
|
||||
'XMP Data/XMP' => { #2
|
||||
Name => 'XMP',
|
||||
# IncludeLengthBytes indicates the length bytes are part of the data value...
|
||||
# undef = data may contain nulls and is split into 255-byte blocks
|
||||
# 1 = data may not contain nulls and is not split; NULL padding is added as necessary
|
||||
# 2 = data is not split and may be edited in place; 257-byte landing zone is added
|
||||
# (Terminator may be specified for a value of 1 above, but must be specified for 2)
|
||||
IncludeLengthBytes => 2,
|
||||
Terminator => q(<\\?xpacket end=['"][wr]['"]\\?>), # (regex to match end of valid data)
|
||||
Writable => 2, # (writable directory!)
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
|
||||
},
|
||||
'ICCRGBG1/012' => { #4
|
||||
Name => 'ICC_Profile',
|
||||
Writable => 2, # (writable directory!)
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
|
||||
},
|
||||
'MIDICTRL/Jon' => { #5
|
||||
Name => 'MIDIControl',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GIF::MIDIControl' },
|
||||
},
|
||||
'MIDISONG/Dm7' => { #5
|
||||
Name => 'MIDISong',
|
||||
Groups => { 2 => 'Audio' },
|
||||
Binary => 1,
|
||||
},
|
||||
'C2PA_GIF/' => { #https://c2pa.org/specifications/ (NC) (authentication code is 0x010000 binary, so removed from tag ID)
|
||||
Name => 'JUMBF',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main' },
|
||||
},
|
||||
);
|
||||
|
||||
# GIF locical screen descriptor
|
||||
%Image::ExifTool::GIF::Screen = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => 'Information extracted from the GIF logical screen descriptor.',
|
||||
0 => {
|
||||
Name => 'ImageWidth',
|
||||
Format => 'int16u',
|
||||
},
|
||||
2 => {
|
||||
Name => 'ImageHeight',
|
||||
Format => 'int16u',
|
||||
},
|
||||
4.1 => {
|
||||
Name => 'HasColorMap',
|
||||
Mask => 0x80,
|
||||
PrintConv => { 0 => 'No', 1 => 'Yes' },
|
||||
},
|
||||
4.2 => {
|
||||
Name => 'ColorResolutionDepth',
|
||||
Mask => 0x70,
|
||||
ValueConv => '$val + 1',
|
||||
},
|
||||
4.3 => {
|
||||
Name => 'BitsPerPixel',
|
||||
Mask => 0x07,
|
||||
ValueConv => '$val + 1',
|
||||
},
|
||||
5 => 'BackgroundColor',
|
||||
6 => {
|
||||
Name => 'PixelAspectRatio',
|
||||
RawConv => '$val ? $val : undef',
|
||||
ValueConv => '($val + 15) / 64',
|
||||
},
|
||||
);
|
||||
|
||||
# GIF Netscape 2.0 animation extension (ref 3)
|
||||
%Image::ExifTool::GIF::Animation = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Image' },
|
||||
NOTES => 'Information extracted from the "NETSCAPE2.0" animation extension.',
|
||||
1 => {
|
||||
Name => 'AnimationIterations',
|
||||
Format => 'int16u',
|
||||
PrintConv => '$val ? $val : "Infinite"',
|
||||
},
|
||||
);
|
||||
|
||||
# GIF MIDICTRL extension (ref 5)
|
||||
%Image::ExifTool::GIF::MIDIControl = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
GROUPS => { 2 => 'Audio' },
|
||||
NOTES => 'Information extracted from the MIDI control block extension.',
|
||||
0 => 'MIDIControlVersion',
|
||||
1 => 'SequenceNumber',
|
||||
2 => 'MelodicPolyphony',
|
||||
3 => 'PercussivePolyphony',
|
||||
4 => {
|
||||
Name => 'ChannelUsage',
|
||||
Format => 'int16u',
|
||||
PrintConv => 'sprintf("0x%.4x", $val)',
|
||||
},
|
||||
6 => {
|
||||
Name => 'DelayTime',
|
||||
Format => 'int16u',
|
||||
ValueConv => '$val / 100',
|
||||
PrintConv => '$val . " s"',
|
||||
},
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read/write meta information in GIF image
|
||||
# Inputs: 0) ExifTool object reference, 1) Directory information ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid GIF file, or -1 if
|
||||
# an output file was specified and a write error occurred
|
||||
sub ProcessGIF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $outfile = $$dirInfo{OutFile};
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $out = $et->Options('TextOut');
|
||||
my ($a, $s, $ch, $length, $buff);
|
||||
my ($err, $newComment, $setComment, $nvComment, $newExt);
|
||||
my ($addDirs, %doneDir);
|
||||
my ($frameCount, $delayTime) = (0, 0);
|
||||
|
||||
# verify this is a valid GIF file
|
||||
return 0 unless $raf->Read($buff, 6) == 6
|
||||
and $buff =~ /^GIF(8[79]a)$/
|
||||
and $raf->Read($s, 7) == 7;
|
||||
|
||||
my $ver = $1;
|
||||
my $rtnVal = 0;
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::GIF::Main');
|
||||
my $extTable = GetTagTable('Image::ExifTool::GIF::Extensions');
|
||||
SetByteOrder('II');
|
||||
|
||||
if ($outfile) {
|
||||
# add any user-defined writable app extensions to the list
|
||||
my $ext;
|
||||
foreach $ext (sort keys %$extTable) {
|
||||
next unless ref $$extTable{$ext} eq 'HASH';
|
||||
my $extInfo = $$extTable{$ext};
|
||||
next unless $$extInfo{SubDirectory} and $$extInfo{Writable} and not $gifMap{$$extInfo{Name}};
|
||||
$gifMap{$$extInfo{Name}} = 'GIF';
|
||||
push @appExtensions, $ext;
|
||||
}
|
||||
$et->InitWriteDirs(\%gifMap, 'XMP'); # make XMP the preferred group for GIF
|
||||
$addDirs = $$et{ADD_DIRS};
|
||||
# determine if we are editing the File:Comment tag
|
||||
my $delGroup = $$et{DEL_GROUP};
|
||||
$newComment = $et->GetNewValue('Comment', \$nvComment);
|
||||
$setComment = 1 if $nvComment or $$delGroup{File};
|
||||
# change to GIF 89a if adding comment, XMP or ICC_Profile
|
||||
$buff = 'GIF89a' if %$addDirs or defined $newComment;
|
||||
Write($outfile, $buff, $s) or $err = 1;
|
||||
$newExt = $et->GetNewTagInfoHash($extTable);
|
||||
} else {
|
||||
$et->SetFileType(); # set file type
|
||||
$et->HandleTag($tagTablePtr, 'GIFVersion', $ver);
|
||||
$et->HandleTag($tagTablePtr, 'ScreenDescriptor', $s);
|
||||
}
|
||||
my $flags = Get8u(\$s, 4);
|
||||
if ($flags & 0x80) { # does this image contain a color table?
|
||||
# calculate color table size
|
||||
$length = 3 * (2 << ($flags & 0x07));
|
||||
$raf->Read($buff, $length) == $length or return 0; # skip color table
|
||||
Write($outfile, $buff) or $err = 1 if $outfile;
|
||||
}
|
||||
#
|
||||
# loop through GIF blocks
|
||||
#
|
||||
Block:
|
||||
for (;;) {
|
||||
last unless $raf->Read($ch, 1);
|
||||
# write out any new metadata now if this isn't an extension block
|
||||
if ($outfile and ord($ch) != 0x21) {
|
||||
# write the comment first if necessary
|
||||
if (defined $newComment and $$nvComment{IsCreating}) {
|
||||
# write comment marker
|
||||
Write($outfile, "\x21\xfe") or $err = 1;
|
||||
$verbose and print $out " + Comment = $newComment\n";
|
||||
my $len = length($newComment);
|
||||
# write out the comment in 255-byte chunks, each
|
||||
# chunk beginning with a length byte
|
||||
my $n;
|
||||
for ($n=0; $n<$len; $n+=255) {
|
||||
my $size = $len - $n;
|
||||
$size > 255 and $size = 255;
|
||||
my $str = substr($newComment,$n,$size);
|
||||
Write($outfile, pack('C',$size), $str) or $err = 1;
|
||||
}
|
||||
Write($outfile, "\0") or $err = 1; # empty chunk as terminator
|
||||
undef $newComment;
|
||||
undef $nvComment; # delete any other extraneous comments
|
||||
++$$et{CHANGED}; # increment file changed flag
|
||||
}
|
||||
# add application extensions if necessary
|
||||
my $ext;
|
||||
my @new = sort keys %$newExt;
|
||||
foreach $ext (@appExtensions, @new) {
|
||||
my $extInfo = $$extTable{$ext};
|
||||
my $name = $$extInfo{Name};
|
||||
if ($$newExt{$ext}) {
|
||||
delete $$newExt{$ext};
|
||||
$doneDir{$name} = 1; # (we wrote this as a block instead)
|
||||
$buff = $et->GetNewValue($extInfo);
|
||||
$et->VerboseValue("+ GIF:$name", $buff);
|
||||
} elsif (exists $$addDirs{$name} and not defined $doneDir{$name}) {
|
||||
$doneDir{$name} = 1;
|
||||
my $tbl = GetTagTable($$extInfo{SubDirectory}{TagTable});
|
||||
my %dirInfo = ( Parent => 'GIF' );
|
||||
$verbose and print $out "Creating $name application extension block:\n";
|
||||
$buff = $et->WriteDirectory(\%dirInfo, $tbl);
|
||||
} else {
|
||||
next;
|
||||
}
|
||||
if (defined $buff and length $buff) {
|
||||
++$$et{CHANGED};
|
||||
Write($outfile, "\x21\xff\x0b", substr($ext,0,8), substr($ext,9,3)) or $err = 1;
|
||||
my $pos = 0;
|
||||
if (not $$extTable{$ext}{IncludeLengthBytes}) {
|
||||
my $len = length $buff;
|
||||
while ($pos < length $buff) {
|
||||
my $n = length($buff) - $pos;
|
||||
$n = 255 if $n > 255;
|
||||
Write($outfile, chr($n), substr($buff, $pos, $n)) or $err = 1;
|
||||
$pos += $n;
|
||||
}
|
||||
Write($outfile, "\0") or $err = 1; # write null terminator
|
||||
} elsif ($$extTable{$ext}{IncludeLengthBytes} < 2) {
|
||||
$pos += ord(substr($buff,$pos,1)) + 1 while $pos < length $buff;
|
||||
# write data, null padding and terminator
|
||||
Write($outfile, $buff, "\0" x ($pos - length($buff) + 1)) or $err = 1;
|
||||
} else {
|
||||
# write data, landing zone and null terminator
|
||||
Write($outfile, $buff, pack('C*',1,reverse(0..255),0)) or $err = 1;
|
||||
}
|
||||
++$doneDir{$name}; # set to 2 to indicate we added it
|
||||
} else {
|
||||
$verbose and print $out " -> no $name to add\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ord($ch) == 0x2c) {
|
||||
++$frameCount;
|
||||
Write($outfile, $ch) or $err = 1 if $outfile;
|
||||
# image descriptor
|
||||
last unless $raf->Read($buff, 8) == 8 and $raf->Read($ch, 1);
|
||||
Write($outfile, $buff, $ch) or $err = 1 if $outfile;
|
||||
if ($verbose and not $outfile) {
|
||||
my ($left, $top, $w, $h) = unpack('v*', $buff);
|
||||
print $out "Image: left=$left top=$top width=$w height=$h\n";
|
||||
}
|
||||
if (ord($ch) & 0x80) { # does color table exist?
|
||||
$length = 3 * (2 << (ord($ch) & 0x07));
|
||||
# skip the color table
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
Write($outfile, $buff) or $err = 1 if $outfile;
|
||||
}
|
||||
# skip "LZW Minimum Code Size" byte
|
||||
last unless $raf->Read($buff, 1);
|
||||
Write($outfile,$buff) or $err = 1 if $outfile;
|
||||
# skip image blocks
|
||||
for (;;) {
|
||||
last unless $raf->Read($ch, 1);
|
||||
Write($outfile, $ch) or $err = 1 if $outfile;
|
||||
last unless ord($ch);
|
||||
last unless $raf->Read($buff, ord($ch));
|
||||
Write($outfile,$buff) or $err = 1 if $outfile;
|
||||
}
|
||||
next; # continue with next field
|
||||
}
|
||||
# last if ord($ch) == 0x3b; # normal end of GIF marker
|
||||
unless (ord($ch) == 0x21) {
|
||||
if ($outfile) {
|
||||
Write($outfile, $ch) or $err = 1;
|
||||
# copy the rest of the file
|
||||
while ($raf->Read($buff, 65536)) {
|
||||
Write($outfile, $buff) or $err = 1;
|
||||
}
|
||||
}
|
||||
$rtnVal = 1;
|
||||
last;
|
||||
}
|
||||
# get extension block type/size
|
||||
last unless $raf->Read($s, 2) == 2;
|
||||
# get marker and block size
|
||||
($a,$length) = unpack("C"x2, $s);
|
||||
|
||||
if ($a == 0xfe) { # comment extension
|
||||
|
||||
my $comment = '';
|
||||
while ($length) {
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
$et->VerboseDump(\$buff) unless $outfile;
|
||||
# add buffer to comment string
|
||||
$comment .= $buff;
|
||||
last unless $raf->Read($ch, 1); # read next block header
|
||||
$length = ord($ch); # get next block size
|
||||
}
|
||||
last if $length; # was a read error if length isn't zero
|
||||
if ($outfile) {
|
||||
my $isOverwriting;
|
||||
if ($setComment) {
|
||||
if ($nvComment) {
|
||||
$isOverwriting = $et->IsOverwriting($nvComment,$comment);
|
||||
# get new comment again (may have been shifted)
|
||||
$newComment = $et->GetNewValue($nvComment) if defined $newComment;
|
||||
} else {
|
||||
# group delete, or deleting additional comments after writing one
|
||||
$isOverwriting = 1;
|
||||
}
|
||||
}
|
||||
if ($isOverwriting) {
|
||||
++$$et{CHANGED}; # increment file changed flag
|
||||
$et->VerboseValue('- GIF:Comment', $comment);
|
||||
$comment = $newComment;
|
||||
$et->VerboseValue('+ GIF:Comment', $comment) if defined $comment;
|
||||
undef $nvComment; # just delete remaining comments
|
||||
} else {
|
||||
undef $setComment; # leave remaining comments alone
|
||||
}
|
||||
if (defined $comment) {
|
||||
# write comment marker
|
||||
Write($outfile, "\x21\xfe") or $err = 1;
|
||||
my $len = length($comment);
|
||||
# write out the comment in 255-byte chunks, each
|
||||
# chunk beginning with a length byte
|
||||
my $n;
|
||||
for ($n=0; $n<$len; $n+=255) {
|
||||
my $size = $len - $n;
|
||||
$size > 255 and $size = 255;
|
||||
my $str = substr($comment,$n,$size);
|
||||
Write($outfile, pack('C',$size), $str) or $err = 1;
|
||||
}
|
||||
Write($outfile, "\0") or $err = 1; # empty chunk as terminator
|
||||
}
|
||||
undef $newComment; # don't write the new comment again
|
||||
} else {
|
||||
$rtnVal = 1;
|
||||
$et->FoundTag('Comment', $comment) if $comment;
|
||||
undef $comment;
|
||||
# assume no more than one comment in FastScan mode
|
||||
last if $et->Options('FastScan');
|
||||
}
|
||||
next;
|
||||
|
||||
} elsif ($a == 0xff and $length == 0x0b) { # application extension
|
||||
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
my $hdr = "$ch$s$buff";
|
||||
# add "/" for readability
|
||||
my $tag = substr($buff, 0, 8) . '/' . substr($buff, 8);
|
||||
$tag =~ tr/\0-\x1f//d; # remove nulls and control characters
|
||||
$verbose and print $out "Application Extension: $tag\n";
|
||||
|
||||
my $extInfo = $$extTable{$tag};
|
||||
my ($subdir, $inclLen, $justCopy, $name);
|
||||
if ($extInfo) {
|
||||
if ($outfile and $$newExt{$$extInfo{TagID}}) {
|
||||
delete $$newExt{$$extInfo{TagID}}; # don't create again
|
||||
# (write as a block -- don't define $subdir)
|
||||
} else {
|
||||
$subdir = $$extInfo{SubDirectory};
|
||||
}
|
||||
$inclLen = $$extInfo{IncludeLengthBytes};
|
||||
$name = $$extInfo{Name};
|
||||
# rewrite as-is unless this is a writable
|
||||
$justCopy = 1 if $outfile and not $$extInfo{Writable};
|
||||
} else {
|
||||
$justCopy = 1 if $outfile;
|
||||
}
|
||||
Write($outfile, $hdr) or $err = 1 if $justCopy;
|
||||
|
||||
# read the extension data
|
||||
my $dat = '';
|
||||
for (;;) {
|
||||
$raf->Read($ch, 1) or last Block; # read next block header
|
||||
$length = ord($ch) or last; # get next block size
|
||||
$raf->Read($buff, $length) == $length or last Block;
|
||||
Write($outfile, $ch, $buff) or $err = 1 if $justCopy;
|
||||
$dat .= $inclLen ? $ch . $buff : $buff;
|
||||
}
|
||||
if ($justCopy) {
|
||||
Write($outfile, "\0") or $err = 1;
|
||||
next;
|
||||
} elsif ($inclLen) {
|
||||
# remove landing zone or padding
|
||||
if ($$extInfo{Terminator} and $dat =~ /$$extInfo{Terminator}/g) {
|
||||
$dat = substr($dat, 0, pos($dat));
|
||||
} elsif ($dat =~ /\0/g) {
|
||||
$dat = substr($dat, 0, pos($dat) - 1);
|
||||
}
|
||||
}
|
||||
if ($subdir) {
|
||||
my %dirInfo = (
|
||||
DataPt => \$dat,
|
||||
DataLen => length $dat,
|
||||
DirLen => length $dat,
|
||||
DirName => $name,
|
||||
Parent => 'GIF',
|
||||
);
|
||||
my $subTable = GetTagTable($$subdir{TagTable});
|
||||
unless ($outfile) {
|
||||
$et->ProcessDirectory(\%dirInfo, $subTable);
|
||||
next;
|
||||
}
|
||||
next if $justCopy;
|
||||
if ($doneDir{$name} and $doneDir{$name} > 1) {
|
||||
$et->Warn("Duplicate $name block created");
|
||||
}
|
||||
$buff = $et->WriteDirectory(\%dirInfo, $subTable);
|
||||
if (defined $buff) {
|
||||
next unless length $buff; # delete this extension if length is zero
|
||||
$dat = $buff;
|
||||
}
|
||||
$doneDir{$name} = 1;
|
||||
} elsif ($outfile and not $justCopy) {
|
||||
my $nvHash = $et->GetNewValueHash($extInfo);
|
||||
if ($nvHash and $et->IsOverwriting($nvHash, $dat)) {
|
||||
++$$et{CHANGED};
|
||||
my $val = $et->GetNewValue($extInfo);
|
||||
$et->VerboseValue("- GIF:$name", $dat);
|
||||
next unless defined $val and length $val;
|
||||
$dat = $val;
|
||||
$et->VerboseValue("+ GIF:$name", $dat);
|
||||
$doneDir{$name} = 1; # (possibly wrote dir as a block)
|
||||
}
|
||||
} elsif (not $outfile) {
|
||||
$et->HandleTag($extTable, $tag, $dat);
|
||||
next;
|
||||
}
|
||||
Write($outfile, $hdr) or $err = 1; # write extension header
|
||||
if ($inclLen) {
|
||||
# check for null just to be safe
|
||||
$et->Error("$name contained NULL character") if $inclLen and $dat =~ /\0/;
|
||||
if ($inclLen > 1) {
|
||||
# add landing zone (without terminator, which will be added later)
|
||||
$dat .= pack('C*',1,reverse(0..255)) if $inclLen;
|
||||
} else {
|
||||
# pad with nulls as required
|
||||
my $pos = 0;
|
||||
$pos += ord(substr($dat,$pos,1)) + 1 while $pos < length $dat;
|
||||
$dat .= "\0" x ($pos - length($dat));
|
||||
}
|
||||
# write data and landing zone
|
||||
Write($outfile, $dat) or $err = 1;
|
||||
} else {
|
||||
# write as sub-blocks
|
||||
my $pos = 0;
|
||||
my $len = length $dat;
|
||||
while ($pos < $len) {
|
||||
my $n = $len - $pos;
|
||||
$n = 255 if $n > 255;
|
||||
Write($outfile, chr($n), substr($dat, $pos, $n)) or $err = 1;
|
||||
$pos += $n;
|
||||
}
|
||||
}
|
||||
Write($outfile, "\0") or $err = 1; # write null terminator
|
||||
next;
|
||||
|
||||
} elsif ($a == 0xf9 and $length == 4) { # graphic control extension
|
||||
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
# sum the individual delay times
|
||||
my $delay = Get16u(\$buff, 1);
|
||||
$delayTime += $delay;
|
||||
$verbose and printf $out "Graphic Control: delay=%.2f\n", $delay / 100;
|
||||
# get transparent colour
|
||||
my $bits = Get8u(\$buff, 0);
|
||||
$et->HandleTag($tagTablePtr, 'TransparentColor', Get8u(\$buff,3)) if $bits & 0x01;
|
||||
$raf->Seek(-$length, 1) or last;
|
||||
|
||||
} elsif ($a == 0x01 and $length == 12) { # plain text extension
|
||||
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
Write($outfile, $ch, $s, $buff) or $err = 1 if $outfile;
|
||||
if ($verbose and not $outfile) {
|
||||
my ($left, $top, $w, $h) = unpack('v4', $buff);
|
||||
print $out "Text: left=$left top=$top width=$w height=$h\n";
|
||||
}
|
||||
my $text = '';
|
||||
for (;;) {
|
||||
last unless $raf->Read($ch, 1);
|
||||
$length = ord($ch) or last;
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
Write($outfile, $ch, $buff) or $err = 1 if $outfile; # write block
|
||||
$text .= $buff;
|
||||
}
|
||||
Write($outfile, "\0") or $err = 1 if $outfile; # write terminator block
|
||||
$et->HandleTag($tagTablePtr, 'Text', $text);
|
||||
next;
|
||||
}
|
||||
Write($outfile, $ch, $s) or $err = 1 if $outfile;
|
||||
# skip the block
|
||||
while ($length) {
|
||||
last unless $raf->Read($buff, $length) == $length;
|
||||
Write($outfile, $buff) or $err = 1 if $outfile;
|
||||
last unless $raf->Read($ch, 1); # read next block header
|
||||
Write($outfile, $ch) or $err = 1 if $outfile;
|
||||
$length = ord($ch); # get next block size
|
||||
}
|
||||
}
|
||||
unless ($outfile) {
|
||||
$et->HandleTag($tagTablePtr, 'FrameCount', $frameCount) if $frameCount > 1;
|
||||
$et->HandleTag($tagTablePtr, 'Duration', $delayTime/100) if $delayTime;
|
||||
}
|
||||
|
||||
# set return value to -1 if we only had a write error
|
||||
$rtnVal = -1 if $rtnVal and $err;
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
|
||||
1; #end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::GIF - Read and write GIF meta information
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read and
|
||||
write GIF meta information.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<http://www.w3.org/Graphics/GIF/spec-gif89a.txt>
|
||||
|
||||
=item L<http://www.adobe.com/devnet/xmp/>
|
||||
|
||||
=item L<http://graphcomp.com/info/specs/ani_gif.html>
|
||||
|
||||
=item L<http://www.color.org/icc_specs2.html>
|
||||
|
||||
=item L<http://www.midiox.com/mmgif.htm>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
294
.resources/nix/bin/lib/Image/ExifTool/GIMP.pm
Normal file
294
.resources/nix/bin/lib/Image/ExifTool/GIMP.pm
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: GIMP.pm
|
||||
#
|
||||
# Description: Read meta information from GIMP XCF images
|
||||
#
|
||||
# Revisions: 2010/10/05 - P. Harvey Created
|
||||
# 2018/08/21 - PH Updated to current XCF specification (v013)
|
||||
#
|
||||
# References: 1) GIMP source code
|
||||
# 2) https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/xcf.txt
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::GIMP;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
|
||||
$VERSION = '1.03';
|
||||
|
||||
sub ProcessParasites($$$);
|
||||
|
||||
# GIMP XCF properties (ref 2)
|
||||
%Image::ExifTool::GIMP::Main = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
VARS => { ALPHA_FIRST => 1 },
|
||||
NOTES => q{
|
||||
The GNU Image Manipulation Program (GIMP) writes these tags in its native
|
||||
XCF (eXperimental Computing Facilty) images.
|
||||
},
|
||||
header => { SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Header' } },
|
||||
# recognized properties
|
||||
# 1 - ColorMap
|
||||
# 17 - SamplePoints? (doc says 17 is also "PROP_SAMPLE_POINTS"??)
|
||||
17 => {
|
||||
Name => 'Compression',
|
||||
Format => 'int8u',
|
||||
PrintConv => {
|
||||
0 => 'None',
|
||||
1 => 'RLE Encoding',
|
||||
2 => 'Zlib',
|
||||
3 => 'Fractal',
|
||||
},
|
||||
},
|
||||
# 18 - Guides
|
||||
19 => {
|
||||
Name => 'Resolution',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Resolution' },
|
||||
},
|
||||
20 => {
|
||||
Name => 'Tattoo',
|
||||
Format => 'int32u',
|
||||
},
|
||||
21 => {
|
||||
Name => 'Parasites',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GIMP::Parasite' },
|
||||
},
|
||||
22 => {
|
||||
Name => 'Units',
|
||||
Format => 'int32u',
|
||||
PrintConv => {
|
||||
1 => 'Inches',
|
||||
2 => 'mm',
|
||||
3 => 'Points',
|
||||
4 => 'Picas',
|
||||
},
|
||||
},
|
||||
# 23 Paths
|
||||
# 24 UserUnit
|
||||
# 25 Vectors
|
||||
);
|
||||
|
||||
# information extracted from the XCF file header (ref 2)
|
||||
%Image::ExifTool::GIMP::Header = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
9 => {
|
||||
Name => 'XCFVersion',
|
||||
Format => 'string[5]',
|
||||
DataMember => 'XCFVersion',
|
||||
RawConv => '$$self{XCFVersion} = $val',
|
||||
PrintConv => {
|
||||
'file' => '0',
|
||||
'v001' => '1',
|
||||
'v002' => '2',
|
||||
OTHER => sub { my $val = shift; $val =~ s/^v0*//; return $val },
|
||||
},
|
||||
},
|
||||
14 => { Name => 'ImageWidth', Format => 'int32u' },
|
||||
18 => { Name => 'ImageHeight', Format => 'int32u' },
|
||||
22 => {
|
||||
Name => 'ColorMode',
|
||||
Format => 'int32u',
|
||||
PrintConv => {
|
||||
0 => 'RGB Color',
|
||||
1 => 'Grayscale',
|
||||
2 => 'Indexed Color',
|
||||
},
|
||||
},
|
||||
# 26 - [XCF 4 or later] Precision
|
||||
);
|
||||
|
||||
# XCF resolution data (property type 19) (ref 2)
|
||||
%Image::ExifTool::GIMP::Resolution = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
FORMAT => 'float',
|
||||
0 => 'XResolution',
|
||||
1 => 'YResolution',
|
||||
);
|
||||
|
||||
# XCF "Parasite" data (property type 21) (ref 1/PH)
|
||||
%Image::ExifTool::GIMP::Parasite = (
|
||||
GROUPS => { 2 => 'Image' },
|
||||
PROCESS_PROC => \&ProcessParasites,
|
||||
'gimp-comment' => {
|
||||
Name => 'Comment',
|
||||
Format => 'string',
|
||||
},
|
||||
'exif-data' => {
|
||||
Name => 'ExifData',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Exif::Main',
|
||||
ProcessProc => \&Image::ExifTool::ProcessTIFF,
|
||||
Start => 6, # starts after "Exif\0\0" header
|
||||
},
|
||||
},
|
||||
'jpeg-exif-data' => { # (deprecated, untested)
|
||||
Name => 'JPEGExifData',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::Exif::Main',
|
||||
ProcessProc => \&Image::ExifTool::ProcessTIFF,
|
||||
Start => 6,
|
||||
},
|
||||
},
|
||||
'iptc-data' => { # (untested)
|
||||
Name => 'IPTCData',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' },
|
||||
},
|
||||
'icc-profile' => {
|
||||
Name => 'ICC_Profile',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
|
||||
},
|
||||
'icc-profile-name' => {
|
||||
Name => 'ICCProfileName',
|
||||
Format => 'string',
|
||||
},
|
||||
'gimp-metadata' => {
|
||||
Name => 'XMP',
|
||||
SubDirectory => {
|
||||
TagTable => 'Image::ExifTool::XMP::Main',
|
||||
Start => 10, # starts after "GIMP_XMP_1" header
|
||||
},
|
||||
},
|
||||
'gimp-image-metadata' => {
|
||||
Name => 'XML',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
|
||||
},
|
||||
# Seen, but not yet decoded:
|
||||
# gimp-image-grid
|
||||
# jpeg-settings
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information in a GIMP XCF parasite data (ref PH)
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessParasites($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $unknown = $et->Options('Unknown') || $et->Options('Verbose');
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $pos = $$dirInfo{DirStart} || 0;
|
||||
my $end = length $$dataPt;
|
||||
$et->VerboseDir('Parasites', undef, $end);
|
||||
for (;;) {
|
||||
last if $pos + 4 > $end;
|
||||
my $size = Get32u($dataPt, $pos); # length of tag string
|
||||
$pos += 4;
|
||||
last if $pos + $size + 8 > $end;
|
||||
my $tag = substr($$dataPt, $pos, $size);
|
||||
$pos += $size;
|
||||
$tag =~ s/\0.*//s; # trim at null terminator
|
||||
# my $flags = Get32u($dataPt, $pos); (ignore flags)
|
||||
$size = Get32u($dataPt, $pos + 4); # length of data
|
||||
$pos += 8;
|
||||
last if $pos + $size > $end;
|
||||
if (not $$tagTablePtr{$tag} and $unknown) {
|
||||
my $name = $tag;
|
||||
$name =~ tr/-_A-Za-z0-9//dc;
|
||||
$name =~ s/^gimp-//;
|
||||
next unless length $name;
|
||||
$name = ucfirst $name;
|
||||
$name =~ s/([a-z])-([a-z])/$1\u$2/g;
|
||||
$name = "GIMP-$name" unless length($name) > 1;
|
||||
AddTagToTable($tagTablePtr, $tag, { Name => $name, Unknown => 1 });
|
||||
}
|
||||
$et->HandleTag($tagTablePtr, $tag, undef,
|
||||
DataPt => $dataPt,
|
||||
Start => $pos,
|
||||
Size => $size,
|
||||
);
|
||||
$pos += $size;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Read information in a GIMP XCF document
|
||||
# Inputs: 0) ExifTool ref, 1) dirInfo ref
|
||||
# Returns: 1 on success, 0 if this wasn't a valid XCF file
|
||||
sub ProcessXCF($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $buff;
|
||||
|
||||
return 0 unless $raf->Read($buff, 26) == 26;
|
||||
return 0 unless $buff =~ /^gimp xcf /;
|
||||
|
||||
my $tagTablePtr = GetTagTable('Image::ExifTool::GIMP::Main');
|
||||
my $verbose = $et->Options('Verbose');
|
||||
$et->SetFileType();
|
||||
SetByteOrder('MM');
|
||||
|
||||
# process the XCF header
|
||||
$et->HandleTag($tagTablePtr, 'header', $buff);
|
||||
|
||||
# skip over precision for XCV version 4 or later
|
||||
$raf->Seek(4, 1) if $$et{XCFVersion} =~ /^v0*(\d+)/ and $1 >= 4;
|
||||
|
||||
# loop through image properties
|
||||
for (;;) {
|
||||
$raf->Read($buff, 8) == 8 or last;
|
||||
my $tag = Get32u(\$buff, 0) or last;
|
||||
my $size = Get32u(\$buff, 4);
|
||||
$verbose and $et->VPrint(0, "XCF property $tag ($size bytes):\n");
|
||||
unless ($$tagTablePtr{$tag}) {
|
||||
$raf->Seek($size, 1);
|
||||
next;
|
||||
}
|
||||
$raf->Read($buff, $size) == $size or last;
|
||||
$et->HandleTag($tagTablePtr, $tag, undef,
|
||||
DataPt => \$buff,
|
||||
DataPos => $raf->Tell() - $size,
|
||||
Size => $size,
|
||||
);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::GIMP - Read meta information from GIMP XCF images
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read meta
|
||||
information from GIMP (GNU Image Manipulation Program) XCF (eXperimental
|
||||
Computing Facility) images. This is the native image format used by the
|
||||
GIMP software.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://developer.gimp.org/core/setup/git/>
|
||||
|
||||
=item L<http://svn.gnome.org/viewvc/gimp/trunk/devel-docs/xcf.txt?view=markup>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/GIMP Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
552
.resources/nix/bin/lib/Image/ExifTool/GM.pm
Normal file
552
.resources/nix/bin/lib/Image/ExifTool/GM.pm
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: GM.pm
|
||||
#
|
||||
# Description: Read GM PDR metadata from automobile videos
|
||||
#
|
||||
# Revisions: 2024-04-01 - P. Harvey Created
|
||||
#
|
||||
# References: 1) https://exiftool.org/forum/index.php?topic=11335
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::GM;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::GPS;
|
||||
|
||||
$VERSION = '1.02';
|
||||
|
||||
sub Process_marl($$$);
|
||||
sub Process_mrld($$$);
|
||||
sub Process_mrlv($$$);
|
||||
sub PrintCSV($;$);
|
||||
|
||||
# rename some units strings
|
||||
my %convertUnits = (
|
||||
"\xc2\xb0" => 'deg',
|
||||
"\xc2\xb0C" => 'C',
|
||||
"\xc2\xb0/sec" => 'deg/sec',
|
||||
ltr => 'L',
|
||||
);
|
||||
|
||||
my $pi = 3.141592653589793;
|
||||
|
||||
# offsets and scaling factors to convert to reasonable units
|
||||
my %changeOffset = (
|
||||
C => -273.15, # K to C
|
||||
);
|
||||
my %changeScale = (
|
||||
G => 1 / 9.80665, # m/s2 to G
|
||||
kph => 3.6, # m/s to km/h
|
||||
deg => 180 / $pi, # radians to degrees
|
||||
'deg/sec' => 180 / $pi, # rad/s to deg/s
|
||||
'%' => 100, # decimal to %
|
||||
kPa => 1/1000, # Pa to kPa
|
||||
rpm => 10, # ? (arbitrary factor of 10)
|
||||
km => 1/1000, # m to km
|
||||
L => 1000, # m3 to L
|
||||
mm => 1000, # m to mm
|
||||
);
|
||||
|
||||
# default print conversions for various types of units
|
||||
my %printConv = (
|
||||
rpm => 'sprintf("%.2f rpm", $val)',
|
||||
'%' => 'sprintf("%.2f %%", $val)',
|
||||
kPa => 'sprintf("%.2f kPa", $val)',
|
||||
G => 'sprintf("%.3f G", $val)',
|
||||
km => 'sprintf("%.3f km", $val)',
|
||||
kph => 'sprintf("%.2f km/h", $val)',
|
||||
deg => 'sprintf("%.2f deg", $val)',
|
||||
'deg/sec' => 'sprintf("%.2f deg/sec", $val)',
|
||||
);
|
||||
|
||||
# channel parameters extracted from marl dictionary
|
||||
my @channel = qw(
|
||||
ID Type Num Units Flags Interval Min Max DispMin DispMax Multiplier Offset
|
||||
Name Description
|
||||
);
|
||||
my %channelStruct = (
|
||||
STRUCT_NAME => 'GM Channel',
|
||||
NOTES => 'Information stored for each channel in the Marlin dictionary.',
|
||||
SORT_ORDER => \@channel,
|
||||
ID => { Writable => 0, Notes => 'channel ID number' },
|
||||
Type => { Writable => 0, Notes => 'measurement type' },
|
||||
Num => { Writable => 0, Notes => 'units ID number' },
|
||||
Units => { Writable => 0, Notes => 'units string' },
|
||||
Flags => { Writable => 0, Notes => 'channel flags' },
|
||||
Interval=> { Writable => 0, Notes => 'measurement interval', ValueConv => '$val / 1e7', PrintConv => '"$val s"' },
|
||||
Min => { Writable => 0, Notes => 'raw value minimum' },
|
||||
Max => { Writable => 0, Notes => 'raw value maximum' },
|
||||
DispMin => { Writable => 0, Notes => 'displayed value minimum' },
|
||||
DispMax => { Writable => 0, Notes => 'displayed value maximum' },
|
||||
Multiplier=>{Writable => 0, Notes => 'multiplier for raw value' },
|
||||
Offset => { Writable => 0, Notes => 'offset for scaled value' },
|
||||
Name => { Writable => 0, Notes => 'channel name' },
|
||||
Description=>{Writable=> 0, Notes => 'channel description' },
|
||||
);
|
||||
|
||||
# tags found in the 'mrlh' (marl header) atom
|
||||
%Image::ExifTool::GM::mrlh = (
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
NOTES => 'The Marlin PDR header.',
|
||||
0 => { Name => 'MarlinDataVersion', Format => 'int16u[2]', PrintConv => '$val =~ tr/ /./; $val' },
|
||||
);
|
||||
|
||||
# tags found in the 'mrlv' (Marlin values) atom
|
||||
%Image::ExifTool::GM::mrlv = (
|
||||
PROCESS_PROC => \&Process_mrlv,
|
||||
FORMAT => 'string',
|
||||
NOTES => q{Tags found in the 'mrlv' (Marlin values) box.},
|
||||
'time'=> { Name => 'Time1', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
|
||||
date => { Name => 'Date1', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
|
||||
ltim => { Name => 'Time2', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
|
||||
ldat => { Name => 'Date2', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
|
||||
tstm => {
|
||||
Name => 'StartTime',
|
||||
Groups => { 2 => 'Time' },
|
||||
Format => 'int64u',
|
||||
RawConv => '$$self{GMStartTime} = $val / 1e7',
|
||||
ValueConv => 'ConvertUnixTime($val, undef, 6)', # (likely UTC, but not sure so don't add time zone)
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
zone => { Name => 'TimeZone', Groups => { 2 => 'Time' } },
|
||||
lang => 'Language',
|
||||
unit => { Name => 'Units', PrintConv => { usim => 'U.S. Imperial' } },
|
||||
swvs => 'SoftwareVersion',
|
||||
# id ? ""
|
||||
# cntr ? ""
|
||||
# flap ? ""
|
||||
);
|
||||
|
||||
# tags found in the 'mrld' (Marlin dictionary) atom
|
||||
%Image::ExifTool::GM::mrld = (
|
||||
PROCESS_PROC => \&Process_mrld,
|
||||
VARS => { ADD_FLATTENED => 1 },
|
||||
WRITABLE => 0,
|
||||
NOTES => q{
|
||||
The Marlin dictionary. Only one channel is listed but all available
|
||||
channels are extracted. Use the -struct (L<API Struct|../ExifTool.html#Struct>) option to extract the
|
||||
channel information as structures.
|
||||
},
|
||||
Channel01 => { Struct => \%channelStruct },
|
||||
);
|
||||
|
||||
# tags found in 'marl' ctbx timed metadata
|
||||
%Image::ExifTool::GM::marl = (
|
||||
PROCESS_PROC => \&Process_marl,
|
||||
GROUPS => { 2 => 'Other' },
|
||||
VARS => { ID_FMT => 'none', NO_LOOKUP => 1 },
|
||||
NOTES => q{
|
||||
Tags extracted from the 'ctbx' 'marl' (Marlin) box of timed PDR metadata
|
||||
from GM cars. Use the -ee (L<API ExtractEmbedded|../ExifTool.html#ExtractEmbedded>) option to extract this
|
||||
information, or the L<API PrintCSV|../ExifTool.html#PrintCSV> option to output in CSV format.
|
||||
},
|
||||
TimeStamp => { # (the marl timestamp)
|
||||
Groups => { 2 => 'Time' },
|
||||
Notes => q{
|
||||
the numerical value is seconds since start of video, but the print
|
||||
conversion adds StartTime to provide a date/time value. Extracted as
|
||||
GPSDateTime if requested
|
||||
},
|
||||
ValueConv => '$val / 1e7',
|
||||
PrintConv => q{
|
||||
return "$val s" unless $$self{GMStartTime};
|
||||
return $self->ConvertDateTime(ConvertUnixTime($val+$$self{GMStartTime},undef,6));
|
||||
},
|
||||
},
|
||||
GPSDateTime => { # (alternative for TimeStamp)
|
||||
Groups => { 2 => 'Time' },
|
||||
Notes => 'generated from the TimeStamp only if specifically requested',
|
||||
RawConv => '$$self{GMStartTime} ? $val : undef',
|
||||
ValueConv => 'ConvertUnixTime($val / 1e7 + $$self{GMStartTime}) . "Z"',
|
||||
PrintConv => '$self->ConvertDateTime($val,undef,6)',
|
||||
},
|
||||
Latitude => {
|
||||
Name => 'GPSLatitude',
|
||||
Description => 'GPS Latitude', # (need description so we don't set it from the mrld)
|
||||
Groups => { 2 => 'Location' },
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
Longitude => {
|
||||
Name => 'GPSLongitude',
|
||||
Description => 'GPS Longitude',
|
||||
Groups => { 2 => 'Location' },
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
Altitude => {
|
||||
Name => 'GPSAltitude',
|
||||
Description => 'GPS Altitude',
|
||||
Groups => { 2 => 'Location' },
|
||||
},
|
||||
Heading => {
|
||||
Name => 'GPSTrack',
|
||||
Description => 'GPS Track',
|
||||
Groups => { 2 => 'Location' },
|
||||
PrintConv => '$val > 360 ? "n/a" : sprintf("%.2f",$val)', # (seen 655.35)
|
||||
},
|
||||
ABSActive => { },
|
||||
AccelPos => { },
|
||||
BatteryVoltage => { },
|
||||
Beacon => { },
|
||||
BoostPressureInd => { },
|
||||
BrakePos => { },
|
||||
ClutchPos => { },
|
||||
CoolantTemp => { },
|
||||
CornerExitSetting => { },
|
||||
CPUFree => { },
|
||||
CPUIO => { },
|
||||
CPUIRQ => { },
|
||||
CPUSystem => { },
|
||||
CPUUser => { },
|
||||
DiskReadOperations => { },
|
||||
DiskReadRate => { },
|
||||
DiskReadTime => { },
|
||||
DiskWriteOperations => { },
|
||||
DiskWriteRate => { },
|
||||
DiskWriteTime => { },
|
||||
Distance => { },
|
||||
DriverPerformanceMode => { },
|
||||
EngineSpeedRequest => { },
|
||||
EngineTorqureReq => { },
|
||||
FuelCapacity => { },
|
||||
FuelLevel => { },
|
||||
Gear => {
|
||||
Notes => q{
|
||||
in the PrintCSV output, the value for Neutral is set to -1, and Reverse to
|
||||
-100 for compatibility with RaceRender
|
||||
},
|
||||
CSVConv => { 13 => -1, 14 => -100 },
|
||||
PrintConv => { 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 13=>'N', 14=>'R' }
|
||||
},
|
||||
GPSFix => { },
|
||||
InfotainOpMode => { },
|
||||
IntakeAirTemperature => { },
|
||||
IntakeBoostPressure => { },
|
||||
LateralAcceleration => { },
|
||||
LFTyrePressure => { },
|
||||
LFTyreTemp => { },
|
||||
LongitudinalAcceleration => { },
|
||||
LRTyrePressure => { },
|
||||
LRTyreTemp => { },
|
||||
OilPressure => { },
|
||||
OilTemp => { },
|
||||
OutsideAirTemperature => { },
|
||||
RecordingEventOdometer => { },
|
||||
RFTyrePressure => { },
|
||||
RFTyreTemp => { },
|
||||
RPM => { },
|
||||
RRTyrePressure => { },
|
||||
RRTyreTemp => { },
|
||||
Speed => { Groups => { 2 => 'Location' } },
|
||||
SpeedControlResponse => { },
|
||||
SpeedRequestIntervention => { },
|
||||
Steering1Switch => { },
|
||||
Steering2Switch => { },
|
||||
SteeringAngle => { },
|
||||
SuspensionDisplacementLeftFront => { },
|
||||
SuspensionDisplacementLeftRear => { },
|
||||
SuspensionDisplacementRightFront => { },
|
||||
SuspensionDisplacementRightRear => { },
|
||||
SystemBackupPowerEnabled => { },
|
||||
SystemBackupPowerMode => { },
|
||||
SystemPowerMode => { },
|
||||
TractionControlActive => { },
|
||||
TransOilTemp => { },
|
||||
TransportStorageMode => { },
|
||||
ValetMode => { },
|
||||
VehicleStabilityActive => { },
|
||||
VerticalAcceleration => { },
|
||||
WheelspeedLeftDriven => { },
|
||||
'WheelspeedLeftNon-Driven' => { },
|
||||
WheelspeedRightDriven => { },
|
||||
'WheelspeedRightNon-Driven' => { },
|
||||
YawRate => { },
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Print a CSV row
|
||||
# Inputs: 0) ExifTool ref, 1) time stamp
|
||||
sub PrintCSV($;$)
|
||||
{
|
||||
my ($et, $ts) = @_;
|
||||
my $csv = $$et{GMCsv} or return; # get the list of channels with measurements
|
||||
@$csv or return;
|
||||
my $vals = $$et{GMVals};
|
||||
my $gmDict = $$et{GMDictionary};
|
||||
my @items = ('') x scalar(@$gmDict);
|
||||
$items[0] = ($ts || $$et{GMMaxTS}) / 1e7;
|
||||
# fill in scaled measurements for this TimeStamp
|
||||
foreach (@$csv) {
|
||||
my $gmChan = $$gmDict[$_];
|
||||
$items[$_] = $$vals[$_] * $$gmChan{Mult} + $$gmChan{Off};
|
||||
# apply CSV conversion if applicable (ie. Gear)
|
||||
next unless $$gmChan{Conv} and defined $$gmChan{Conv}{$items[$_]};
|
||||
$items[$_] = $$gmChan{Conv}{$items[$_]};
|
||||
}
|
||||
my $out = $$et{OPTIONS}{TextOut};
|
||||
print $out join(',',@items),"\n";
|
||||
@$csv = (); # clear the channel list
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process GM Marlin values ('mrlv' box)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub Process_mrlv($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirLen = length $$dataPt;
|
||||
my $pos = 0;
|
||||
# data lengths for known formats
|
||||
my %fmtLen = (
|
||||
strs => 64, lang => 64, strl => 256, 'time' => 32, date => 32,
|
||||
tmzn => 32, tstm => 8, focc => 4, "kvp\0" => 64+256,
|
||||
);
|
||||
$et->VerboseDir('mrlv', undef, $dirLen);
|
||||
while ($pos + 8 <= $dirLen) {
|
||||
my $tag = substr($$dataPt, $pos, 4);
|
||||
my $fmt = substr($$dataPt, $pos + 4, 4);
|
||||
my $len = $fmtLen{$fmt};
|
||||
unless ($len) {
|
||||
($tag, $fmt) = (PrintableTagID($tag), PrintableTagID($fmt));
|
||||
$et->Warn("Unknown format ($fmt) for tag $tag");
|
||||
last;
|
||||
}
|
||||
$pos + 8 + $len > $dirLen and $et->Warn('Truncated mrlv data'), last;
|
||||
$et->HandleTag($tagTablePtr, $tag, undef,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $dataPos,
|
||||
Start => $pos + 8,
|
||||
Size => $len,
|
||||
);
|
||||
$pos += 8 + $len;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process GM Marlin dictionary ('mrld' box)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub Process_mrld($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = $$dirInfo{DataPos};
|
||||
my $dirLen = length $$dataPt;
|
||||
my $struct = $et->Options('Struct') || 0;
|
||||
my $gmDict = $$et{GMDictionary} = [ ];
|
||||
my $marl = GetTagTable('Image::ExifTool::GM::marl');
|
||||
my ($pos, $item, $csv);
|
||||
|
||||
$et->VerboseDir('mrld', undef, $dirLen);
|
||||
require 'Image/ExifTool/XMPStruct.pl';
|
||||
Image::ExifTool::XMP::AddFlattenedTags($tagTablePtr, undef, undef, 1);
|
||||
$csv = [ ] if $et->Options('PrintCSV');
|
||||
|
||||
for ($pos=0; $pos+448<=$dirLen; $pos+=448) {
|
||||
# unpack 448-byte records:
|
||||
# 0. int32u - channel number
|
||||
# 1. int32u - measurement type
|
||||
# 2. int32u - units number
|
||||
# 3. string[64] - units string
|
||||
# 4. int32u - flags (0.visible, 1.linear conversion, 2.interpolation OK)
|
||||
# 5. int64u - interval
|
||||
# 6. int32s - min reading
|
||||
# 7. int32s - max reading
|
||||
# 8. double - disp min
|
||||
# 9. double - disp max
|
||||
# 10. double - multiplier
|
||||
# 11. double - offset
|
||||
# 12. string[64] - channel name
|
||||
# 13. string[64] - channel description
|
||||
my @a = unpack("x${pos}NNNZ64Na8N2a8a8a8a8Z64Z64", $$dataPt);
|
||||
my $units = $convertUnits{$a[3]} || $a[3];
|
||||
$a[3] = $et->Decode($a[3], 'UTF8'); # convert from UTF8
|
||||
$_ & 0x8000000 and $_ -= 4294967296 foreach @a[6,7]; # convert signed ints
|
||||
map { $_ = GetDouble(\$_,0) } @a[8,9,10,11]; # convert doubles
|
||||
$a[5] = Get64u(\$a[5],0); # convert 64-bit int
|
||||
my $chan = $a[0];
|
||||
my $tag = sprintf('Channel%.2d', $chan);
|
||||
my $tagInfo = $$tagTablePtr{$tag};
|
||||
my $hash = { map { $channel[$_] => $a[$_] } 1..$#a };
|
||||
unless ($tagInfo) {
|
||||
$tagInfo = AddTagToTable($tagTablePtr, $tag, { Name => $tag, Struct => \%channelStruct });
|
||||
Image::ExifTool::XMP::AddFlattenedTags($tagTablePtr, $tag, undef, 1);
|
||||
}
|
||||
# extract channel structure if specified
|
||||
if ($struct) {
|
||||
$$hash{_ordered_keys_} = [ @channel[1..$#channel] ];
|
||||
$et->FoundTag($tagInfo, $hash);
|
||||
}
|
||||
# extract flattened channel elements
|
||||
if ($struct == 0 or $struct == 2) {
|
||||
$et->HandleTag($tagTablePtr, "$tag$channel[$_]", $a[$_]) foreach 1..$#a;
|
||||
}
|
||||
# add corresponding tag to marl table
|
||||
my $name = Image::ExifTool::MakeTagName($a[12]);
|
||||
$tagInfo = $$marl{$name};
|
||||
unless ($tagInfo) {
|
||||
$et->VPrint(0, $$et{INDENT}, "[adding $name]\n");
|
||||
$tagInfo = AddTagToTable($marl, $name, { });
|
||||
}
|
||||
$$tagInfo{Description} = $a[13] unless $$tagInfo{Description};
|
||||
unless ($$tagInfo{PrintConv}) {
|
||||
# add a default print conversion
|
||||
$units =~ tr/"\\//d; # (just to be safe, probably never happen)
|
||||
$$tagInfo{PrintConv} = $printConv{$units} || qq("\$val $units");
|
||||
}
|
||||
# adjust multiplier/offset as necessary to scale to more appropriate units
|
||||
# (ie. to the units actually specified in this dictionary -- d'oh)
|
||||
my $mult = $a[10] * ($changeScale{$units} || 1);
|
||||
my $off = $a[11] * ($changeScale{$units} || 1) + ($changeOffset{$units} || 0);
|
||||
my $init = int(($a[6] + $a[7]) / 2); # initial value for difference readings
|
||||
# save information about this channel necessary for processing the marl data
|
||||
$$gmDict[$chan] = { Name => $name, Mult => $mult, Off => $off, Init => $init };
|
||||
$$gmDict[$chan]{Conv} = $$tagInfo{CSVConv};
|
||||
$csv and $$csv[$chan] = $a[12] . ($a[3] ? " ($a[3])" : '');
|
||||
}
|
||||
# channel 0 must not be defined because we use it for the TimeStamp
|
||||
if (defined $$gmDict[0]) {
|
||||
$et->Warn('Internal error: PDR channel 0 is used');
|
||||
delete $$et{GMDictionary};
|
||||
} elsif ($csv) {
|
||||
$$csv[0] = 'Time (s)';
|
||||
defined $_ or $_ = '' foreach @$csv;
|
||||
my $out = $$et{OPTIONS}{TextOut};
|
||||
print $out join(',',@$csv),"\n";
|
||||
$$et{GMCsv} = [ ];
|
||||
}
|
||||
$et->AddCleanup(\&PrintCSV); # print last CSV line when we are done
|
||||
# initialize variables for processing marl box
|
||||
$$et{GMVals} = [ ];
|
||||
$$et{GMMaxTS} = 0;
|
||||
$$et{GMBadChan} = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process GM 'marl' ctbx data (ref PH)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
# (see https://exiftool.org/forum/index.php?topic=11335.msg61393#msg61393)
|
||||
sub Process_marl($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $dataPos = ($$dirInfo{DataPos} || 0) + ($$dirInfo{Base} || 0);
|
||||
my $dataLen = length $$dataPt;
|
||||
my $vals = $$et{GMVals}; # running values for each channel (0=TimeStamp)
|
||||
my $chan = $$et{GMChan}; # running channel number
|
||||
my $gmDict = $$et{GMDictionary};
|
||||
my $csv = $$et{GMCsv};
|
||||
my $maxTS = $$et{GMMaxTS};
|
||||
my $reqGPSDateTime = $$et{REQ_TAG_LOOKUP}{gpsdatetime};
|
||||
my $reqTimeStamp = $reqGPSDateTime ? $$et{REQ_TAG_LOOKUP}{timestamp} : 1;
|
||||
my ($pos, $verbose2);
|
||||
|
||||
$et->VerboseDir('marl', undef, $dataLen);
|
||||
$gmDict or $et->Warn('Missing marl dictionary'), return 0;
|
||||
my $maxChan = $#$gmDict;
|
||||
$verbose2 = 1 if $et->Options('Verbose') > 1;
|
||||
$$vals[0] = -1 unless defined $$vals[0]; # (we use the 0th channel for the TimeStamp)
|
||||
my $ts = $$vals[0];
|
||||
|
||||
for ($pos=0; $pos + 8 <= $dataLen; $pos += 8) {
|
||||
my @a = unpack("x${pos}NN", $$dataPt);
|
||||
my $ah = $a[0] >> 24;
|
||||
my $a2 = $ah & 0xc0;
|
||||
my ($val, $chanDiff, $valDiff, @ts, $gmChan);
|
||||
if ($a2 == 0xc0) { # 16-byte full record?
|
||||
last if $ah == 0xff; # exit at first empty record
|
||||
$chan = $a[0] & 0x0fffffff;
|
||||
$gmChan = $$gmDict[$chan] or next; # (shouldn't happen)
|
||||
$val = $a[1] - ($a[1] & 0x80000000 ? 4294967296 : 0);
|
||||
$$vals[$chan] = $val;
|
||||
last if $pos + 16 > $dataLen; # (shouldn't happen)
|
||||
$pos += 8; # point at time stamp
|
||||
@ts = unpack("x${pos}NN", $$dataPt);
|
||||
$ts = $ts[0] * 4294967296 + $ts[1];
|
||||
} elsif ($a2 == 0x40) { # 8-byte difference record?
|
||||
next unless defined $chan; # (shouldn't happen)
|
||||
$ts += $a[1]; # increment time stamp
|
||||
$chanDiff = ($ah & 0x3f) - ($ah & 0x20 ? 0x40 : 0);
|
||||
$chan += $chanDiff; # increment the running channel number
|
||||
$gmChan = $$gmDict[$chan] or next; # (shouldn't happen)
|
||||
defined $$vals[$chan] or $$vals[$chan] = $$gmChan{Init}; # init if necessary
|
||||
$valDiff = ($a[0] & 0x00ffffff) - ($a[0] & 0x00800000 ? 0x01000000 : 0);
|
||||
$val = ($$vals[$chan] += $valDiff); # increment the running value for this channel
|
||||
} else {
|
||||
next; # (shouldn't happen)
|
||||
}
|
||||
# ensure that the timestamps are monotonically increasing
|
||||
# (have seen backward steps up to 0.033 sec, so fudge these)
|
||||
if ($ts > $maxTS) {
|
||||
if ($csv) {
|
||||
PrintCSV($et, $maxTS);
|
||||
} else {
|
||||
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
||||
$et->HandleTag($tagTablePtr, TimeStamp => $ts) if $reqTimeStamp;
|
||||
$et->HandleTag($tagTablePtr, GPSDateTime => $ts) if $reqGPSDateTime;
|
||||
}
|
||||
$maxTS = $ts;
|
||||
}
|
||||
$csv and push(@$csv, $chan), next;
|
||||
my $scaled = $val * $$gmChan{Mult} + $$gmChan{Off};
|
||||
$et->HandleTag($tagTablePtr, $$gmChan{Name}, $scaled);
|
||||
if ($verbose2) {
|
||||
my $str = " * $$gmChan{Mult} + $$gmChan{Off} = $scaled";
|
||||
my $p0 = $dataPos + $pos - ($a2 == 0xc0 ? 8 : 0);
|
||||
my ($cd,$vd) = @ts ? ('','') : (sprintf('%+d',$chanDiff),sprintf('%+d',$valDiff));
|
||||
printf "| %8.4x: %.8x %.8x chan$cd=%.2d $$gmChan{Name}$vd = $val$str\n", $p0, @a, $chan;
|
||||
printf("| %8.4x: %.8x %.8x TimeStamp = %.6f sec\n", $dataPos + $pos, @ts, $ts / 1e7) if @ts;
|
||||
}
|
||||
}
|
||||
$$vals[0] = $ts; # save last timestamp
|
||||
$$et{GMChan} = $chan; # save last channel number
|
||||
$$et{GMMaxTS} = $ts;
|
||||
delete $$et{DOC_NUM};
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::GM - Read GM PDR Data from automobile videos
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to read PDR
|
||||
metadata from videos written by some GM models such as Corvette and Camero.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://exiftool.org/forum/index.php?topic=11335>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/GM Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
641
.resources/nix/bin/lib/Image/ExifTool/GPS.pm
Normal file
641
.resources/nix/bin/lib/Image/ExifTool/GPS.pm
Normal file
|
|
@ -0,0 +1,641 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: GPS.pm
|
||||
#
|
||||
# Description: EXIF GPS meta information tags
|
||||
#
|
||||
# Revisions: 12/09/2003 - P. Harvey Created
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::GPS;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool::Exif;
|
||||
|
||||
$VERSION = '1.58';
|
||||
|
||||
my %coordConv = (
|
||||
ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)',
|
||||
ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val)',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)',
|
||||
);
|
||||
|
||||
my %printConvLatRef = (
|
||||
# extract N/S if written from Composite:GPSLatitude
|
||||
# (also allow writing from a signed number)
|
||||
OTHER => sub {
|
||||
my ($val, $inv) = @_;
|
||||
return undef unless $inv;
|
||||
return uc $2 if $val =~ /(^|[^A-Z])([NS])(orth|outh)?\b/i;
|
||||
return $1 eq '-' ? 'S' : 'N' if $val =~ /([-+]?)\d+/;
|
||||
return undef;
|
||||
},
|
||||
N => 'North',
|
||||
S => 'South',
|
||||
);
|
||||
|
||||
my %printConvLonRef = (
|
||||
# extract E/W if written from Composite:GPSLongitude
|
||||
# (also allow writing from a signed number)
|
||||
OTHER => sub {
|
||||
my ($val, $inv) = @_;
|
||||
return undef unless $inv;
|
||||
return uc $2 if $val =~ /(^|[^A-Z])([EW])(ast|est)?\b/i;
|
||||
return $1 eq '-' ? 'W' : 'E' if $val =~ /([-+]?)\d+/;
|
||||
return undef;
|
||||
},
|
||||
E => 'East',
|
||||
W => 'West',
|
||||
);
|
||||
|
||||
%Image::ExifTool::GPS::Main = (
|
||||
GROUPS => { 0 => 'EXIF', 1 => 'GPS', 2 => 'Location' },
|
||||
WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
|
||||
CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
|
||||
WRITABLE => 1,
|
||||
WRITE_GROUP => 'GPS',
|
||||
0x0000 => {
|
||||
Name => 'GPSVersionID',
|
||||
Writable => 'int8u',
|
||||
Mandatory => 1,
|
||||
Count => 4,
|
||||
PrintConv => '$val =~ tr/ /./; $val',
|
||||
PrintConvInv => '$val =~ tr/./ /; $val',
|
||||
},
|
||||
0x0001 => {
|
||||
Name => 'GPSLatitudeRef',
|
||||
Writable => 'string',
|
||||
Notes => q{
|
||||
tags 0x0001-0x0006 used for camera location according to MWG 2.0. ExifTool
|
||||
will also accept a number when writing GPSLatitudeRef, positive for north
|
||||
latitudes or negative for south, or a string containing N, North, S or South
|
||||
},
|
||||
Count => 2,
|
||||
PrintConv => \%printConvLatRef,
|
||||
},
|
||||
0x0002 => {
|
||||
Name => 'GPSLatitude',
|
||||
Writable => 'rational64u',
|
||||
Count => 3,
|
||||
%coordConv,
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lat")',
|
||||
},
|
||||
0x0003 => {
|
||||
Name => 'GPSLongitudeRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
Notes => q{
|
||||
ExifTool will also accept a number when writing this tag, positive for east
|
||||
longitudes or negative for west, or a string containing E, East, W or West
|
||||
},
|
||||
PrintConv => \%printConvLonRef,
|
||||
},
|
||||
0x0004 => {
|
||||
Name => 'GPSLongitude',
|
||||
Writable => 'rational64u',
|
||||
Count => 3,
|
||||
%coordConv,
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lon")',
|
||||
},
|
||||
0x0005 => {
|
||||
Name => 'GPSAltitudeRef',
|
||||
Writable => 'int8u',
|
||||
Notes => q{
|
||||
ExifTool will also accept number when writing this tag, with negative
|
||||
numbers indicating below sea level
|
||||
},
|
||||
PrintConv => {
|
||||
OTHER => sub {
|
||||
my ($val, $inv) = @_;
|
||||
return undef unless $inv and $val =~ /^([-+0-9])/;
|
||||
return($1 eq '-' ? 1 : 0);
|
||||
},
|
||||
0 => 'Above Sea Level', # (ellipsoidal surface, Exif 3.0)
|
||||
1 => 'Below Sea Level', # (ellipsoidal surface, Exif 3.0)
|
||||
2 => 'Positive Sea Level (sea-level ref)', # sea-level reference, Exif 3.0
|
||||
3 => 'Negative Sea Level (sea-level ref)', # sea-level reference, Exif 3.0
|
||||
},
|
||||
},
|
||||
0x0006 => {
|
||||
Name => 'GPSAltitude',
|
||||
Writable => 'rational64u',
|
||||
# extricate unsigned decimal number from string
|
||||
ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
|
||||
PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
|
||||
PrintConvInv => '$val=~s/\s*m$//;$val',
|
||||
},
|
||||
0x0007 => {
|
||||
Name => 'GPSTimeStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Writable => 'rational64u',
|
||||
Count => 3,
|
||||
Shift => 'Time',
|
||||
Notes => q{
|
||||
UTC time of GPS fix. When writing, date is stripped off if present, and
|
||||
time is adjusted to UTC if it includes a timezone
|
||||
},
|
||||
ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)',
|
||||
ValueConvInv => '$val=~tr/:/ /;$val',
|
||||
PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)',
|
||||
# pull time out of any format date/time string
|
||||
# (converting to UTC if a timezone is given)
|
||||
PrintConvInv => sub {
|
||||
my ($v, $et) = @_;
|
||||
$v = $et->TimeNow() if lc($v) eq 'now';
|
||||
my @tz;
|
||||
if ($v =~ s/([-+])(\d{1,2}):?(\d{2})\s*(DST)?$//i) { # remove timezone
|
||||
my $s = $1 eq '-' ? 1 : -1; # opposite sign to convert back to UTC
|
||||
my $t = $2;
|
||||
@tz = ($s*$2, $s*$3);
|
||||
}
|
||||
# (note: we must allow '.' as a time separator, eg. '10.30.00', with is tricky due to decimal seconds)
|
||||
# YYYYmmddHHMMSS[.ss] format
|
||||
my @a = ($v =~ /^[^\d]*\d{4}[^\d]*\d{1,2}[^\d]*\d{1,2}[^\d]*(\d{1,2})[^\d]*(\d{2})[^\d]*(\d{2}(?:\.\d+)?)[^\d]*$/);
|
||||
# HHMMSS[.ss] format
|
||||
@a or @a = ($v =~ /^[^\d]*(\d{1,2})[^\d]*(\d{2})[^\d]*(\d{2}(?:\.\d+)?)[^\d]*$/);
|
||||
@a or warn('Invalid time (use HH:MM:SS[.ss][+/-HH:MM|Z])'), return undef;
|
||||
if (@tz) {
|
||||
# adjust to UTC
|
||||
$a[1] += $tz[1];
|
||||
$a[0] += $tz[0];
|
||||
while ($a[1] >= 60) { $a[1] -= 60; ++$a[0] }
|
||||
while ($a[1] < 0) { $a[1] += 60; --$a[0] }
|
||||
$a[0] = ($a[0] + 24) % 24;
|
||||
}
|
||||
return join(':', @a);
|
||||
},
|
||||
},
|
||||
0x0008 => {
|
||||
Name => 'GPSSatellites',
|
||||
Writable => 'string',
|
||||
},
|
||||
0x0009 => {
|
||||
Name => 'GPSStatus',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
A => 'Measurement Active', # Exif2.2 "Measurement in progress"
|
||||
V => 'Measurement Void', # Exif2.2 "Measurement Interoperability" (WTF?)
|
||||
# (meaning for 'V' taken from status code in NMEA GLL and RMC sentences)
|
||||
},
|
||||
},
|
||||
0x000a => {
|
||||
Name => 'GPSMeasureMode',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
2 => '2-Dimensional Measurement',
|
||||
3 => '3-Dimensional Measurement',
|
||||
},
|
||||
},
|
||||
0x000b => {
|
||||
Name => 'GPSDOP',
|
||||
Description => 'GPS Dilution Of Precision',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
0x000c => {
|
||||
Name => 'GPSSpeedRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
K => 'km/h',
|
||||
M => 'mph',
|
||||
N => 'knots',
|
||||
},
|
||||
},
|
||||
0x000d => {
|
||||
Name => 'GPSSpeed',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
0x000e => {
|
||||
Name => 'GPSTrackRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
M => 'Magnetic North',
|
||||
T => 'True North',
|
||||
},
|
||||
},
|
||||
0x000f => {
|
||||
Name => 'GPSTrack',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
0x0010 => {
|
||||
Name => 'GPSImgDirectionRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
M => 'Magnetic North',
|
||||
T => 'True North',
|
||||
},
|
||||
},
|
||||
0x0011 => {
|
||||
Name => 'GPSImgDirection',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
0x0012 => {
|
||||
Name => 'GPSMapDatum',
|
||||
Writable => 'string',
|
||||
},
|
||||
0x0013 => {
|
||||
Name => 'GPSDestLatitudeRef',
|
||||
Writable => 'string',
|
||||
Notes => 'tags 0x0013-0x001a used for subject location according to MWG 2.0',
|
||||
Count => 2,
|
||||
PrintConv => \%printConvLatRef,
|
||||
},
|
||||
0x0014 => {
|
||||
Name => 'GPSDestLatitude',
|
||||
Writable => 'rational64u',
|
||||
Count => 3,
|
||||
%coordConv,
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lat")',
|
||||
},
|
||||
0x0015 => {
|
||||
Name => 'GPSDestLongitudeRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => \%printConvLonRef,
|
||||
},
|
||||
0x0016 => {
|
||||
Name => 'GPSDestLongitude',
|
||||
Writable => 'rational64u',
|
||||
Count => 3,
|
||||
%coordConv,
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lon")',
|
||||
},
|
||||
0x0017 => {
|
||||
Name => 'GPSDestBearingRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
M => 'Magnetic North',
|
||||
T => 'True North',
|
||||
},
|
||||
},
|
||||
0x0018 => {
|
||||
Name => 'GPSDestBearing',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
0x0019 => {
|
||||
Name => 'GPSDestDistanceRef',
|
||||
Writable => 'string',
|
||||
Count => 2,
|
||||
PrintConv => {
|
||||
K => 'Kilometers',
|
||||
M => 'Miles',
|
||||
N => 'Nautical Miles',
|
||||
},
|
||||
},
|
||||
0x001a => {
|
||||
Name => 'GPSDestDistance',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
0x001b => {
|
||||
Name => 'GPSProcessingMethod',
|
||||
Writable => 'undef',
|
||||
Notes => 'values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec.',
|
||||
# (or QZZSS, GALILEO, GLONASS, BEIDOU or NAVIC in Exif 3.0)
|
||||
RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)',
|
||||
RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)',
|
||||
},
|
||||
0x001c => {
|
||||
Name => 'GPSAreaInformation',
|
||||
Writable => 'undef',
|
||||
RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)',
|
||||
RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)',
|
||||
},
|
||||
0x001d => {
|
||||
Name => 'GPSDateStamp',
|
||||
Groups => { 2 => 'Time' },
|
||||
Writable => 'string',
|
||||
Format => 'undef', # (Casio EX-H20G uses "\0" instead of ":" as a separator)
|
||||
Count => 11,
|
||||
Shift => 'Time',
|
||||
Notes => q{
|
||||
when writing, time is stripped off if present, after adjusting date/time to
|
||||
UTC if time includes a timezone. Format is YYYY:mm:dd
|
||||
},
|
||||
RawConv => '$val =~ s/\0+$//; $val',
|
||||
ValueConv => 'Image::ExifTool::Exif::ExifDate($val)',
|
||||
ValueConvInv => '$val',
|
||||
# pull date out of any format date/time string
|
||||
# (and adjust to UTC if this is a full date/time/timezone value)
|
||||
PrintConvInv => q{
|
||||
my $secs;
|
||||
$val = $self->TimeNow() if lc($val) eq 'now';
|
||||
if ($val =~ /[-+]/ and ($secs = Image::ExifTool::GetUnixTime($val, 1))) {
|
||||
$val = Image::ExifTool::ConvertUnixTime($secs);
|
||||
}
|
||||
return $val =~ /(\d{4}).*?(\d{2}).*?(\d{2})/ ? "$1:$2:$3" : undef;
|
||||
},
|
||||
},
|
||||
0x001e => {
|
||||
Name => 'GPSDifferential',
|
||||
Writable => 'int16u',
|
||||
PrintConv => {
|
||||
0 => 'No Correction',
|
||||
1 => 'Differential Corrected',
|
||||
},
|
||||
},
|
||||
0x001f => {
|
||||
Name => 'GPSHPositioningError',
|
||||
Description => 'GPS Horizontal Positioning Error',
|
||||
PrintConv => '"$val m"',
|
||||
PrintConvInv => '$val=~s/\s*m$//; $val',
|
||||
Writable => 'rational64u',
|
||||
},
|
||||
# 0xea1c - Nokia Lumina 1020, Samsung GT-I8750, and other Windows 8
|
||||
# phones write this (padding) in GPS IFD - PH
|
||||
);
|
||||
|
||||
# Composite GPS tags
|
||||
%Image::ExifTool::GPS::Composite = (
|
||||
GROUPS => { 2 => 'Location' },
|
||||
GPSDateTime => {
|
||||
Description => 'GPS Date/Time',
|
||||
Groups => { 2 => 'Time' },
|
||||
SubDoc => 1, # generate for all sub-documents
|
||||
Require => {
|
||||
0 => 'GPS:GPSDateStamp',
|
||||
1 => 'GPS:GPSTimeStamp',
|
||||
},
|
||||
ValueConv => '"$val[0] $val[1]Z"',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
# Note: The following tags are used by other modules
|
||||
# which must therefore require this module as necessary
|
||||
GPSLatitude => {
|
||||
SubDoc => 1, # generate for all sub-documents
|
||||
Writable => 1,
|
||||
Avoid => 1,
|
||||
Priority => 1, # (necessary because Avoid sets default Priority to 0)
|
||||
Require => {
|
||||
0 => 'GPS:GPSLatitude',
|
||||
1 => 'GPS:GPSLatitudeRef',
|
||||
},
|
||||
WriteAlso => {
|
||||
'GPS:GPSLatitude' => '$val',
|
||||
'GPS:GPSLatitudeRef' => '(defined $val and $val < 0) ? "S" : "N"',
|
||||
},
|
||||
ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
|
||||
},
|
||||
GPSLongitude => {
|
||||
SubDoc => 1, # generate for all sub-documents
|
||||
Writable => 1,
|
||||
Avoid => 1,
|
||||
Priority => 1,
|
||||
Require => {
|
||||
0 => 'GPS:GPSLongitude',
|
||||
1 => 'GPS:GPSLongitudeRef',
|
||||
},
|
||||
WriteAlso => {
|
||||
'GPS:GPSLongitude' => '$val',
|
||||
'GPS:GPSLongitudeRef' => '(defined $val and $val < 0) ? "W" : "E"',
|
||||
},
|
||||
Require => {
|
||||
0 => 'GPS:GPSLongitude',
|
||||
1 => 'GPS:GPSLongitudeRef',
|
||||
},
|
||||
ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
|
||||
},
|
||||
GPSAltitude => {
|
||||
SubDoc => [1,3], # generate for sub-documents if Desire 1 or 3 has a chance to exist
|
||||
Desire => {
|
||||
0 => 'GPS:GPSAltitude',
|
||||
1 => 'GPS:GPSAltitudeRef',
|
||||
2 => 'XMP:GPSAltitude',
|
||||
3 => 'XMP:GPSAltitudeRef',
|
||||
},
|
||||
# Require either GPS:GPSAltitudeRef or XMP:GPSAltitudeRef
|
||||
RawConv => '(defined $val[1] or defined $val[3]) ? $val : undef',
|
||||
ValueConv => q{
|
||||
foreach (0,2) {
|
||||
next unless defined $val[$_] and IsFloat($val[$_]) and defined $val[$_+1];
|
||||
return $val[$_+1] ? -abs($val[$_]) : $val[$_];
|
||||
}
|
||||
return undef;
|
||||
},
|
||||
PrintConv => q{
|
||||
foreach (0,2) {
|
||||
next unless defined $val[$_] and IsFloat($val[$_]);
|
||||
next unless defined $prt[$_+1] and $prt[$_+1] =~ /Sea/;
|
||||
return((int($val[$_]*10)/10) . ' m ' . $prt[$_+1]);
|
||||
}
|
||||
$val = int($val * 10) / 10;
|
||||
return(($val =~ s/^-// ? "$val m Below" : "$val m Above") . " Sea Level");
|
||||
},
|
||||
},
|
||||
GPSDestLatitude => {
|
||||
Require => {
|
||||
0 => 'GPS:GPSDestLatitude',
|
||||
1 => 'GPS:GPSDestLatitudeRef',
|
||||
},
|
||||
ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
GPSDestLongitude => {
|
||||
SubDoc => 1, # generate for all sub-documents
|
||||
Require => {
|
||||
0 => 'GPS:GPSDestLongitude',
|
||||
1 => 'GPS:GPSDestLongitudeRef',
|
||||
},
|
||||
ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
);
|
||||
|
||||
# add our composite tags
|
||||
Image::ExifTool::AddCompositeTags('Image::ExifTool::GPS');
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Convert GPS timestamp value
|
||||
# Inputs: 0) raw timestamp value string
|
||||
# Returns: EXIF-formatted time string
|
||||
sub ConvertTimeStamp($)
|
||||
{
|
||||
my $val = shift;
|
||||
my ($h,$m,$s) = split ' ', $val;
|
||||
my $f = (($h || 0) * 60 + ($m || 0)) * 60 + ($s || 0);
|
||||
$h = int($f / 3600); $f -= $h * 3600;
|
||||
$m = int($f / 60); $f -= $m * 60;
|
||||
my $ss = sprintf('%012.9f', $f);
|
||||
if ($ss >= 60) {
|
||||
$ss = '00';
|
||||
++$m >= 60 and $m -= 60, ++$h;
|
||||
} else {
|
||||
$ss =~ s/\.?0+$//; # trim trailing zeros + decimal
|
||||
}
|
||||
return sprintf("%.2d:%.2d:%s",$h,$m,$ss);
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Print GPS timestamp
|
||||
# Inputs: 0) EXIF-formatted time string
|
||||
# Returns: time rounded to the nearest microsecond
|
||||
sub PrintTimeStamp($)
|
||||
{
|
||||
my $val = shift;
|
||||
return $val unless $val =~ s/:(\d{2}\.\d+)$//;
|
||||
my $s = int($1 * 1000000 + 0.5) / 1000000;
|
||||
$s = "0$s" if $s < 10;
|
||||
return "${val}:$s";
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Convert degrees to DMS, or whatever the current settings are
|
||||
# Inputs: 0) ExifTool reference, 1) Value in degrees,
|
||||
# 2) format code (0=no format, 1=CoordFormat, 2=XMP format, 3=signed unformatted)
|
||||
# 3) 'N' or 'E' if sign is significant and N/S/E/W should be added
|
||||
# Returns: DMS string
|
||||
sub ToDMS($$;$$)
|
||||
{
|
||||
my ($et, $val, $doPrintConv, $ref) = @_;
|
||||
my ($fmt, @fmt, $num, $sign, $minus, $rtnVal, $neg);
|
||||
|
||||
unless (length $val) {
|
||||
# don't convert an empty value
|
||||
return $val if $doPrintConv and $doPrintConv eq '1'; # avoid hiding existing tag when extracting
|
||||
return undef; # avoid writing empty value
|
||||
}
|
||||
if ($ref) {
|
||||
if ($val < 0) {
|
||||
$val = -$val;
|
||||
$ref = {N => 'S', E => 'W'}->{$ref};
|
||||
$sign = '-';
|
||||
$minus = '-';
|
||||
} else {
|
||||
$sign = '+';
|
||||
$minus = '';
|
||||
}
|
||||
$ref = " $ref" unless $doPrintConv and $doPrintConv eq '2';
|
||||
} else {
|
||||
if ($doPrintConv and $doPrintConv eq '3') {
|
||||
$neg = 1 if $val < 0;
|
||||
$doPrintConv = 0;
|
||||
}
|
||||
$val = abs($val);
|
||||
$ref = '';
|
||||
}
|
||||
if ($doPrintConv) {
|
||||
if ($doPrintConv eq '1') {
|
||||
$fmt = $et->Options('CoordFormat');
|
||||
if (not $fmt) {
|
||||
$fmt = q{%d deg %d' %.2f"} . $ref;
|
||||
} elsif ($ref) {
|
||||
# use signed value instead of reference direction if specified
|
||||
$fmt =~ s/%\+/$sign%/g or $fmt =~ s/%-/$minus%/g or $fmt .= $ref;
|
||||
} else {
|
||||
$fmt =~ s/%\+/%/g; # don't know sign, so don't print it
|
||||
}
|
||||
} else {
|
||||
$fmt = "%d,%.8f$ref"; # use XMP format with 8 decimal minutes
|
||||
}
|
||||
# count (and capture) the format specifiers (max 3)
|
||||
while ($fmt =~ /(%(%|[^%]*?[diouxXDOUeEfFgGcs]))/g) {
|
||||
next if $1 eq '%%';
|
||||
push @fmt, $1;
|
||||
last if @fmt >= 3;
|
||||
}
|
||||
$num = scalar @fmt;
|
||||
} else {
|
||||
$num = 3;
|
||||
}
|
||||
my @c; # coordinates (D) or (D,M) or (D,M,S)
|
||||
$c[0] = $val;
|
||||
if ($num > 1) {
|
||||
$c[0] = int($c[0]);
|
||||
$c[1] = ($val - $c[0]) * 60;
|
||||
if ($num > 2) {
|
||||
$c[1] = int($c[1]);
|
||||
$c[2] = ($val - $c[0] - $c[1] / 60) * 3600;
|
||||
}
|
||||
# handle round-off errors to ensure minutes and seconds are
|
||||
# less than 60 (eg. convert "72 59 60.00" to "73 0 0.00")
|
||||
$c[-1] = $doPrintConv ? sprintf($fmt[-1], $c[-1]) : ($c[-1] . '');
|
||||
if ($c[-1] >= 60) {
|
||||
$c[-1] -= 60;
|
||||
($c[-2] += 1) >= 60 and $num > 2 and $c[-2] -= 60, $c[-3] += 1;
|
||||
}
|
||||
}
|
||||
if ($doPrintConv) {
|
||||
$rtnVal = sprintf($fmt, @c);
|
||||
# trim trailing zeros in XMP
|
||||
$rtnVal =~ s/(\d)0+$ref$/$1$ref/ if $doPrintConv eq '2';
|
||||
} else {
|
||||
$neg and map { $_ *= -1 } @c;
|
||||
$rtnVal = "@c$ref";
|
||||
}
|
||||
return $rtnVal;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Convert to decimal degrees
|
||||
# Inputs: 0) a string containing 1-3 decimal numbers and any amount of other garbage
|
||||
# 1) true if value should be negative if coordinate ends in 'S' or 'W',
|
||||
# 2) 'lat' or 'lon' to extract lat or lon from GPSCoordinates string
|
||||
# Returns: Coordinate in degrees, or '' on error
|
||||
sub ToDegrees($;$$)
|
||||
{
|
||||
my ($val, $doSign, $coord) = @_;
|
||||
return '' if $val =~ /\b(inf|undef)\b/; # ignore invalid values
|
||||
# use only lat or lon part of combined GPSCoordinates inputs
|
||||
if ($coord and ($coord eq 'lat' or $coord eq 'lon') and
|
||||
# (two formatted coordinate values with cardinal directions, separated by a comma)
|
||||
$val =~ /^(.*(?:N(?:orth)?|S(?:outh)?)),\s*(.*(?:E(?:ast)?|W(?:est)?))$/i)
|
||||
{
|
||||
$val = $coord eq 'lat' ? $1 : $2;
|
||||
}
|
||||
# extract decimal or floating point values out of any other garbage
|
||||
my ($d, $m, $s) = ($val =~ /((?:[+-]?)(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee][+-]\d+)?)/g);
|
||||
return '' unless defined $d;
|
||||
my $deg = $d + (($m || 0) + ($s || 0)/60) / 60;
|
||||
# make negative if S or W coordinate
|
||||
$deg = -$deg if $doSign ? $val =~ /[^A-Z](S(outh)?|W(est)?)\s*$/i : $deg < 0;
|
||||
return $deg;
|
||||
}
|
||||
|
||||
|
||||
1; #end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::GPS - EXIF GPS meta information tags
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is loaded automatically by Image::ExifTool when required.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to interpret
|
||||
GPS (Global Positioning System) meta information in EXIF data.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<Image::Info|Image::Info>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/GPS Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>,
|
||||
L<Image::Info(3pm)|Image::Info>
|
||||
|
||||
=cut
|
||||
2265
.resources/nix/bin/lib/Image/ExifTool/GeoTiff.pm
Normal file
2265
.resources/nix/bin/lib/Image/ExifTool/GeoTiff.pm
Normal file
File diff suppressed because it is too large
Load diff
BIN
.resources/nix/bin/lib/Image/ExifTool/Geolocation.dat
Normal file
BIN
.resources/nix/bin/lib/Image/ExifTool/Geolocation.dat
Normal file
Binary file not shown.
1045
.resources/nix/bin/lib/Image/ExifTool/Geolocation.pm
Normal file
1045
.resources/nix/bin/lib/Image/ExifTool/Geolocation.pm
Normal file
File diff suppressed because it is too large
Load diff
1692
.resources/nix/bin/lib/Image/ExifTool/Geotag.pm
Normal file
1692
.resources/nix/bin/lib/Image/ExifTool/Geotag.pm
Normal file
File diff suppressed because it is too large
Load diff
944
.resources/nix/bin/lib/Image/ExifTool/GoPro.pm
Normal file
944
.resources/nix/bin/lib/Image/ExifTool/GoPro.pm
Normal file
|
|
@ -0,0 +1,944 @@
|
|||
#------------------------------------------------------------------------------
|
||||
# File: GoPro.pm
|
||||
#
|
||||
# Description: Read information from GoPro videos
|
||||
#
|
||||
# Revisions: 2018/01/12 - P. Harvey Created
|
||||
#
|
||||
# References: 1) https://github.com/gopro/gpmf-parser
|
||||
# 2) https://github.com/stilldavid/gopro-utils
|
||||
# 3) https://github.com/gopro/gpmf-parser
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
package Image::ExifTool::GoPro;
|
||||
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
use Image::ExifTool qw(:DataAccess :Utils);
|
||||
use Image::ExifTool::QuickTime;
|
||||
|
||||
$VERSION = '1.15';
|
||||
|
||||
sub ProcessGoPro($$$);
|
||||
sub ProcessString($$$);
|
||||
sub ScaleValues($$);
|
||||
sub AddUnits($$$);
|
||||
sub ConvertSystemTime($$);
|
||||
|
||||
# GoPro data types that have ExifTool equivalents (ref 1)
|
||||
my %goProFmt = ( # format codes
|
||||
# 0x00 - container (subdirectory)
|
||||
0x62 => 'int8s', # 'b'
|
||||
0x42 => 'int8u', # 'B'
|
||||
0x63 => 'string', # 'c' (possibly null terminated)
|
||||
0x73 => 'int16s', # 's'
|
||||
0x53 => 'int16u', # 'S'
|
||||
0x6c => 'int32s', # 'l'
|
||||
0x4c => 'int32u', # 'L'
|
||||
0x66 => 'float', # 'f'
|
||||
0x64 => 'double', # 'd'
|
||||
0x46 => 'undef', # 'F' (4-char ID)
|
||||
0x47 => 'undef', # 'G' (16-byte uuid)
|
||||
0x6a => 'int64s', # 'j'
|
||||
0x4a => 'int64u', # 'J'
|
||||
0x71 => 'fixed32s', # 'q'
|
||||
0x51 => 'fixed64s', # 'Q'
|
||||
0x55 => 'undef', # 'U' (16-byte date)
|
||||
0x3f => 'undef', # '?' (complex structure)
|
||||
);
|
||||
|
||||
# sizes of format codes if different than what FormatSize() would return
|
||||
my %goProSize = (
|
||||
0x46 => 4,
|
||||
0x47 => 16,
|
||||
0x55 => 16,
|
||||
);
|
||||
|
||||
# tagInfo elements to add units to PrintConv value
|
||||
my %addUnits = (
|
||||
AddUnits => 1,
|
||||
PrintConv => 'Image::ExifTool::GoPro::AddUnits($self, $val, $tag)',
|
||||
);
|
||||
|
||||
my %noYes = ( N => 'No', Y => 'Yes' );
|
||||
|
||||
# Tags found in the GPMF box of Hero6 mp4 videos (ref PH), and
|
||||
# the gpmd-format timed metadata of Hero5 and Hero6 videos (ref 1)
|
||||
%Image::ExifTool::GoPro::GPMF = (
|
||||
PROCESS_PROC => \&ProcessGoPro,
|
||||
GROUPS => { 2 => 'Camera' },
|
||||
NOTES => q{
|
||||
Tags extracted from the GPMF box of GoPro MP4 videos, the APP6 "GoPro"
|
||||
segment of JPEG files, and from the "gpmd" timed metadata if the
|
||||
L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option is enabled. Many more tags exist, but are
|
||||
currently unknown and extracted only with the L<Unknown|../ExifTool.html#Unknown> (-u) option. Please
|
||||
let me know if you discover the meaning of any of these unknown tags. See
|
||||
L<https://github.com/gopro/gpmf-parser> for details about this format.
|
||||
},
|
||||
ABSC => 'AutoBoostScore', #3
|
||||
ACCL => { #2 (gpmd)
|
||||
Name => 'Accelerometer',
|
||||
Notes => 'accelerator readings in m/s2',
|
||||
Binary => 1,
|
||||
},
|
||||
# ANGX (GPMF-GEOC) - seen -0.05 (fmt d, Max)
|
||||
# ANGY (GPMF-GEOC) - seen 179.9 (fmt d, Max)
|
||||
# ANGZ (GPMF-GEOC) - seen 0.152 (fmt d, Max)
|
||||
ALLD => 'AutoLowLightDuration', #1 (gpmd) (untested)
|
||||
APTO => 'AudioProtuneOption', #3
|
||||
ARUW => 'AspectRatioUnwarped', #3
|
||||
ARWA => 'AspectRatioWarped', #3
|
||||
ATTD => { #PH (Karma)
|
||||
Name => 'Attitude',
|
||||
# UNIT=s,rad,rad,rad,rad/s,rad/s,rad/s,
|
||||
# TYPE=LffffffB
|
||||
# SCAL=1000 1 1 1 1 1 1 1
|
||||
Binary => 1,
|
||||
},
|
||||
ATTR => { #PH (Karma)
|
||||
Name => 'AttitudeTarget',
|
||||
# UNIT=s,rad,rad,rad,
|
||||
# TYPE=Jffff
|
||||
# SCAL=1000 1 1 1 1
|
||||
Binary => 1,
|
||||
},
|
||||
AUBT => { Name => 'AudioBlueTooth', PrintConv => \%noYes }, #3
|
||||
AUDO => 'AudioSetting', #PH (GPMF - seen: 'WIND', fmt c)
|
||||
AUPT => { Name => 'AutoProtune', PrintConv => \%noYes },
|
||||
BITR => 'BitrateSetting', #3
|
||||
BPOS => { #PH (Karma)
|
||||
Name => 'Controller',
|
||||
Unknown => 1,
|
||||
# UNIT=deg,deg,m,deg,deg,m,m,m
|
||||
# TYPE=lllfffff
|
||||
# SCAL=10000000 10000000 1000 1 1 1 1 1
|
||||
%addUnits,
|
||||
},
|
||||
# BRID (GPMF) - seen: 0 (fmt B)
|
||||
# BROD (GPMF) - seen: 'ASK','' (fmt c)
|
||||
# CALH (GPMF-GEOC) - seen 3040 (fmt L, Max)
|
||||
# CALW (GPMF-GEOC) - seen 4056 (fmt L, Max)
|
||||
CASN => 'CameraSerialNumber', #PH (GPMF - seen: 'C3221324545448', fmt c)
|
||||
CDAT => { #3
|
||||
Name => 'CreationDate',
|
||||
Groups => { 2 => 'Time' },
|
||||
RawConv => 'ConvertUnixTime($val)',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
CDTM => 'CaptureDelayTimer', #3
|
||||
# CINF (GPMF) - seen: 0x67376be7709bc8876a8baf3940908618, 0xe230988539b30cf5f016627ae8fc5395,
|
||||
# 0x8bcbe424acc5b37d7d77001635198b3b (fmt B) (Camera INFormation?)
|
||||
CLDP => { Name => 'ClassificationDataPresent', PrintConv => \%noYes },
|
||||
# CLKC (GPMF) - seen: 0 (fmt L)
|
||||
# CLKS (GPMF) - seen: 2 (fmt B)
|
||||
# CMOD (GPMF) - seen: 12,13,17 [12 360 video, 13 time-laps video, 17 JPEG] (fmt B) - CameraMode (ref 3)
|
||||
# CPID (GPMF) - seen: '1194885996 3387225026 733916448 2433577768' (fmt L)
|
||||
# CPIN (GPMF) - seen: 1
|
||||
CPIN => 'ChapterNumber',
|
||||
# CRTX (GPMF-BACK/FRNT) - double[1]
|
||||
# CRTY (GPMF-BACK/FRNT) - double[1]
|
||||
CSEN => { #PH (Karma)
|
||||
Name => 'CoyoteSense',
|
||||
# UNIT=s,rad/s,rad/s,rad/s,g,g,g,,,,
|
||||
# TYPE=LffffffLLLL
|
||||
# SCAL=1000 1 1 1 1 1 1 1 1 1 1
|
||||
Binary => 1,
|
||||
},
|
||||
CTRL => 'ControlLevel', #3
|
||||
CYTS => { #PH (Karma)
|
||||
Name => 'CoyoteStatus',
|
||||
# UNIT=s,,,,,rad,rad,rad,,
|
||||
# TYPE=LLLLLfffBB
|
||||
# SCAL=1000 1 1 1 1 1 1 1 1 1
|
||||
Binary => 1,
|
||||
},
|
||||
DEVC => { #PH (gpmd,GPMF, fmt \0)
|
||||
Name => 'DeviceContainer',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
|
||||
# (Max) DVID=1,DVNM='Global Settings',VERS,FMWR,LINF,CINF,CASN,MINF,MUID,CMOD,MTYP,OREN,
|
||||
# DZOM,DZST,SMTR,PRTN,PTWB,PTSH,PTCL,EXPT,PIMX,PIMN,PTEV,RATE,SROT,ZFOV,VLTE,VLTA,
|
||||
# EISE,EISA,AUPT,AUDO,BROD,BRID,PVUL,PRJT,SOFF
|
||||
# (Max) DVID='GEOC',DVNM='Geometry Calibrations',SHFX,SHFY,SHFZ,ANGX,ANGY,ANGZ,CALW,CALH
|
||||
# (Max) DVID='BACK',DVNM='Back Lens',KLNS,CTRX,CTRY,MFOV,SFTR
|
||||
# (Max) DVID='FRNT',DVNM='Front Lens',KLNS,CTRX,CTRY,MFOV,SFTR
|
||||
# (Max) DVID='HLMT',DVNM='Highlights'
|
||||
},
|
||||
# DNSC (GPMF) - seen: 'HIGH' (fmt c)
|
||||
DUST => 'DurationSetting', #3
|
||||
# DVID (GPMF) - DeviceID; seen: 1 (fmt L), HLMT (fmt F), GEOC (fmt F), 'BACK' (fmt F, Max)
|
||||
DVID => { Name => 'DeviceID', Unknown => 1 }, #2 (gpmd)
|
||||
# DVNM (GPMF) seen: 'Video Global Settings' (fmt c), 'Highlights' (fmt c), 'Geometry Calibrations' (Max)
|
||||
# DVNM (gpmd) seen: 'Camera' (Hero5), 'Hero6 Black' (Hero6), 'GoPro Karma v1.0' (Karma)
|
||||
DVNM => 'DeviceName', #PH (n/c)
|
||||
DZOM => { #PH (GPMF - seen: 'Y', fmt c)
|
||||
Name => 'DigitalZoomOn',
|
||||
PrintConv => \%noYes,
|
||||
},
|
||||
DZMX => 'DigitalZoomAmount', #3
|
||||
DZST => 'DigitalZoom', #3
|
||||
EISA => { #PH (GPMF) - seen: 'Y','N','HS EIS','N/A' (fmt c) [N was for a time-lapse video]
|
||||
Name => 'ElectronicImageStabilization',
|
||||
},
|
||||
EISE => { Name => 'ElectronicStabilizationOn', PrintConv => \%noYes }, #3
|
||||
EMPT => { Name => 'Empty', Unknown => 1 }, #2 (gpmd)
|
||||
ESCS => { #PH (Karma)
|
||||
Name => 'EscapeStatus',
|
||||
# UNIT=s,rpm,rpm,rpm,rpm,rpm,rpm,rpm,rpm,degC,degC,degC,degC,V,V,V,V,A,A,A,A,,,,,,,,,
|
||||
# TYPE=JSSSSSSSSssssSSSSSSSSSSSSSSSSB
|
||||
# (no SCAL!)
|
||||
Unknown => 1,
|
||||
%addUnits,
|
||||
},
|
||||
EXPT => 'ExposureType', #3
|
||||
FACE => 'FaceDetected', #PH (gpmd)
|
||||
FCNM => 'FaceNumbers', #PH (gpmd) (faces counted per frame, ref 1)
|
||||
FMWR => 'FirmwareVersion', #PH (GPMF - seen: HD6.01.01.51.00, fmt c)
|
||||
FWVS => 'OtherFirmware', #PH (NC) (gpmd - seen: '1.1.11.0', Karma)
|
||||
GLPI => { #PH (gpmd, Karma)
|
||||
Name => 'GPSPos',
|
||||
# UNIT=s,deg,deg,m,m,m/s,m/s,m/s,deg
|
||||
# TYPE=LllllsssS
|
||||
# SCAL=1000 10000000 10000000 1000 1000 100 100 100 100
|
||||
RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GLPI' },
|
||||
},
|
||||
GPRI => { #PH (gpmd, Karma)
|
||||
Name => 'GPSRaw',
|
||||
# UNIT=s,deg,deg,m,m,m,m/s,deg,,
|
||||
# TYPE=JlllSSSSBB
|
||||
# SCAL=1000000,10000000,10000000,1000,100,100,100,100,1,1
|
||||
Unknown => 1,
|
||||
RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPRI' },
|
||||
},
|
||||
GPS5 => { #2 (gpmd)
|
||||
Name => 'GPSInfo',
|
||||
# UNIT=deg,deg,m,m/s,m/s
|
||||
# TYPE=l
|
||||
# SCAL=10000000,10000000,1000,1000,100
|
||||
RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPS5' },
|
||||
},
|
||||
GPS9 => { #PH (gpmd, Hero 13)
|
||||
Name => 'GPSInfo9',
|
||||
# UNIT=deg,deg,m,m/s,m/s,,s,,
|
||||
# TYPE=lllllllSS
|
||||
# SCAL=10000000 10000000 1000 1000 100 1 1000 100 1
|
||||
RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPS9' },
|
||||
},
|
||||
GPSF => { #2 (gpmd)
|
||||
Name => 'GPSMeasureMode',
|
||||
PrintConv => {
|
||||
2 => '2-Dimensional Measurement',
|
||||
3 => '3-Dimensional Measurement',
|
||||
},
|
||||
},
|
||||
GPSP => { #2 (gpmd)
|
||||
Name => 'GPSHPositioningError',
|
||||
Description => 'GPS Horizontal Positioning Error',
|
||||
ValueConv => '$val / 100', # convert from cm to m
|
||||
},
|
||||
GPSU => { #2 (gpmd)
|
||||
Name => 'GPSDateTime',
|
||||
Groups => { 2 => 'Time' },
|
||||
# (HERO5 writes this in 'c' format, HERO6 writes 'U')
|
||||
ValueConv => '$val =~ s/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/20$1:$2:$3 $4:$5:/; $val',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
GYRO => { #2 (gpmd)
|
||||
Name => 'Gyroscope',
|
||||
Notes => 'gyroscope readings in rad/s',
|
||||
Binary => 1,
|
||||
},
|
||||
LOGS => 'HealthLogs',
|
||||
HCTL => 'HorizonControl', #3
|
||||
HDRV => { Name => 'HDRVideo', PrintConv => \%noYes }, #3/PH (NC)
|
||||
# HFLG (APP6) - seen: 0
|
||||
HSGT => 'HindsightSettings', #3
|
||||
ISOE => 'ISOSpeeds', #PH (gpmd)
|
||||
ISOG => { #2 (gpmd)
|
||||
Name => 'ImageSensorGain',
|
||||
Binary => 1,
|
||||
},
|
||||
KBAT => { #PH (gpmd) (Karma)
|
||||
Name => 'BatteryStatus',
|
||||
# UNIT=A,Ah,J,degC,V,V,V,V,s,%,,,,,%
|
||||
# TYPE=lLlsSSSSSSSBBBb
|
||||
# SCAL=1000,1000,0.00999999977648258,100,1000,1000,1000,1000,0.0166666675359011,1,1,1,1,1,1
|
||||
RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::KBAT' },
|
||||
},
|
||||
# KLNS (GPMF-BACK/FRNT) - double[5] (fmt d, Max)
|
||||
# LINF (GPMF) - seen: LAJ7061916601668,C3341326002180,C33632245450981 (fmt c) (Lens INFormation?)
|
||||
# LMOD (GPMF) - seen: 'NONE' (fmt F)
|
||||
LNED => { #PH (Karma)
|
||||
Name => 'LocalPositionNED',
|
||||
# UNIT=s,m,m,m,m/s,m/s,m/s
|
||||
# TYPE=Lffffff
|
||||
# SCAL=1000 1 1 1 1 1 1
|
||||
Binary => 1,
|
||||
},
|
||||
MAGN => 'Magnetometer', #1 (gpmd) (units of uT)
|
||||
MAPX => 'MappingXCoefficients', #3
|
||||
MAPY => 'MappingYCoefficients', #3
|
||||
# MFOV (GPMF-BACK/FRNT) - seen: 100 (fmt d, Max)
|
||||
MINF => { #PH (GPMF - seen: HERO6 Black, fmt c)
|
||||
Name => 'Model',
|
||||
Groups => { 2 => 'Camera' },
|
||||
Description => 'Camera Model Name',
|
||||
},
|
||||
MMOD => 'MediaMode', #3
|
||||
# MTYP (GPMF) - seen: 0,1,5,11 [1 for time-lapse video, 5 for 360 video, 11 for JPEG] (fmt B) - MediaType (ref 3)
|
||||
MUID => { Name => 'MediaUID', ValueConv => 'join("-", unpack("H8H4H4H4H12", $val))' },
|
||||
MXCF => 'MappingXMode', #3
|
||||
MYCF => 'MappingYMode', #3
|
||||
ORDP => { Name => 'OrientationDataPresent', PrintConv => \%noYes }, #3
|
||||
OREN => { #PH (GPMF - seen: 'U', fmt c)
|
||||
Name => 'AutoRotation',
|
||||
PrintConv => {
|
||||
U => 'Up',
|
||||
D => 'Down', # (NC)
|
||||
A => 'Auto', # (NC)
|
||||
},
|
||||
},
|
||||
# (most of the "P" tags are Protune settings - PH)
|
||||
PHDR => 'HDRSetting', #PH (APP6 - seen: 0)
|
||||
PIMD => 'ProtuneISOMode', #3
|
||||
PIMN => 'AutoISOMin', #PH (GPMF - seen: 100, fmt L)
|
||||
PIMX => 'AutoISOMax', #PH (GPMF - seen: 1600, fmt L)
|
||||
POLY => 'PolynomialCoefficients', #3
|
||||
# PRAW (APP6) - seen: 0, 'N', 'Y' (fmt c)
|
||||
# PRCN (GPMF) - seen: 65 zeros (fmt B)
|
||||
PRES => 'PhotoResolution', #PH (APP6 - seen: '12MP_W')
|
||||
PRJT => 'LensProjection', #3
|
||||
# PRNA (GPMF) - seen 10 (fmt B) - PresetIDs (ref 3)
|
||||
# PRNU (GPMF) - seen 0 (fmt B) - PresetIDs (ref 3)
|
||||
PRTN => { #PH (GPMF - seen: 'N', fmt c)
|
||||
Name => 'Protune',
|
||||
PrintConv => {
|
||||
N => 'Off',
|
||||
Y => 'On', # (NC)
|
||||
},
|
||||
},
|
||||
PTCL => 'ColorMode', #PH (GPMF - seen: 'GOPRO', fmt c' APP6: 'FLAT')
|
||||
PTEV => 'ExposureCompensation', #PH (GPMF - seen: '0.0', fmt c)
|
||||
PTSH => 'Sharpness', #PH (GPMF - seen: 'HIGH', fmt c)
|
||||
PTWB => 'WhiteBalance', #PH (GPMF - seen: 'AUTO', fmt c)
|
||||
# PVUL (APP6) - seen: 'F' (fmt c, Hero8, Max)
|
||||
PWPR => 'PowerProfile', #3
|
||||
PYCF => 'PolynomialPower', #3
|
||||
RAMP => 'SpeedRampSetting', #3
|
||||
RATE => 'Rate', #PH (GPMF - seen: '0_5SEC', fmt c; APP6 - seen: '4_1SEC')
|
||||
RMRK => { #2 (gpmd)
|
||||
Name => 'Comments',
|
||||
ValueConv => '$self->Decode($val, "Latin")',
|
||||
},
|
||||
SCAL => { #2 (gpmd) scale factor for subsequent data
|
||||
Name => 'ScaleFactor',
|
||||
Unknown => 1,
|
||||
},
|
||||
SCAP => { Name => 'ScheduleCapture', PrintConv => \%noYes }, #3
|
||||
SCPR => { #PH (Karma) [stream was empty]
|
||||
Name => 'ScaledPressure',
|
||||
# UNIT=s,Pa,Pa,degC
|
||||
# TYPE=Lffs
|
||||
# SCAL=1000 0.00999999977648258 0.00999999977648258 100
|
||||
%addUnits,
|
||||
},
|
||||
SCTM => 'ScheduleCaptureTime', #3 (seconds since UTC midnight)
|
||||
# SFTR (GPMF-BACK/FRNT) - seen 0.999,1.00004 (fmt d, Max)
|
||||
# SHFX (GPMF-GEOC) - seen 22.92 (fmt d, Max)
|
||||
# SHFY (GPMF-GEOC) - seen 0.123 (fmt d, Max)
|
||||
# SHFZ (GPMF-GEOC) - seen 36.06 (fmt d, Max)
|
||||
SHUT => { #2 (gpmd)
|
||||
Name => 'ExposureTimes',
|
||||
PrintConv => q{
|
||||
my @a = split ' ', $val;
|
||||
$_ = Image::ExifTool::Exif::PrintExposureTime($_) foreach @a;
|
||||
return join ' ', @a;
|
||||
},
|
||||
},
|
||||
SIMU => { #PH (Karma)
|
||||
Name => 'ScaledIMU',
|
||||
# UNIT=s,g,g,g,rad/s,rad/s,rad/s,T,T,T
|
||||
# TYPE=Lsssssssss
|
||||
# SCAL=1000 1000 1000 1000 1000 1000 1000 1000 1000 1000
|
||||
%addUnits,
|
||||
},
|
||||
SIUN => { #2 (gpmd - seen : 'm/s2','rad/s')
|
||||
Name => 'SIUnits',
|
||||
Unknown => 1,
|
||||
ValueConv => '$self->Decode($val, "Latin")',
|
||||
},
|
||||
SMTR => { Name => 'SpotMeter', PrintConv => \%noYes }, #3
|
||||
# SOFF (APP6) - seen: 0 (fmt L, Hero8, Max)
|
||||
SROT => 'SensorReadoutTime', #3
|
||||
STMP => { #1 (gpmd)
|
||||
Name => 'TimeStamp',
|
||||
ValueConv => '$val / 1e6',
|
||||
},
|
||||
STRM => { #2 (gpmd,GPMF, fmt \0)
|
||||
Name => 'NestedSignalStream',
|
||||
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
|
||||
},
|
||||
STNM => { #2 (gpmd)
|
||||
Name => 'StreamName',
|
||||
Unknown => 1,
|
||||
ValueConv => '$self->Decode($val, "Latin")',
|
||||
},
|
||||
SYST => { #PH (Karma)
|
||||
Name => 'SystemTime',
|
||||
# UNIT=s,s
|
||||
# TYPE=JJ
|
||||
# SCAL=1000000 1000
|
||||
# save system time calibrations for later
|
||||
RawConv => q{
|
||||
my @v = split ' ', $val;
|
||||
if (@v == 2) {
|
||||
my $s = $$self{SystemTimeList};
|
||||
$s or $s = $$self{SystemTimeList} = [ ];
|
||||
push @$s, \@v;
|
||||
}
|
||||
return $val;
|
||||
},
|
||||
},
|
||||
# TICK => { Name => 'InTime', Unknown => 1, ValueConv => '$val/1000' }, #1 (gpmd)
|
||||
TMPC => { #2 (gpmd)
|
||||
Name => 'CameraTemperature',
|
||||
PrintConv => '"$val C"',
|
||||
},
|
||||
# TOCK => { Name => 'OutTime', Unknown => 1, ValueConv => '$val/1000' }, #1 (gpmd)
|
||||
TSMP => { Name => 'TotalSamples', Unknown => 1 }, #2 (gpmd)
|
||||
TIMO => 'TimeOffset',
|
||||
TYPE => { Name => 'StructureType', Unknown => 1 }, #2 (gpmd,GPMF - eg 'LLLllfFff', fmt c)
|
||||
TZON => { # (GPMF) - seen: 60 (fmt s)
|
||||
Name => 'TimeZone',
|
||||
PrintConv => 'Image::ExifTool::TimeZoneString($val)',
|
||||
},
|
||||
UNIT => { #2 (gpmd) alternative units
|
||||
Name => 'Units',
|
||||
Unknown => 1,
|
||||
ValueConv => '$self->Decode($val, "Latin")',
|
||||
},
|
||||
VERS => {
|
||||
Name => 'MetadataVersion',
|
||||
PrintConv => '$val =~ tr/ /./; $val',
|
||||
},
|
||||
VFOV => { #PH (GPMF - seen: 'W', fmt c)
|
||||
Name => 'FieldOfView',
|
||||
PrintConv => {
|
||||
W => 'Wide',
|
||||
S => 'Super View', # (NC, not seen)
|
||||
L => 'Linear', # (NC, not seen)
|
||||
},
|
||||
},
|
||||
# VLTA (GPMF) - seen: 78 ('N') (fmt B -- wrong format?)
|
||||
VFPS => { Name => 'VideoFrameRate', PrintConv => '$val=~s( )(/);$val' }, #PH (GPMF, fmt L)
|
||||
VFRH => { #PH (Karma)
|
||||
Name => 'VisualFlightRulesHUD',
|
||||
BinaryData => 1,
|
||||
# UNIT=m/s,m/s,m,m/s,deg,%
|
||||
# TYPE=ffffsS
|
||||
},
|
||||
# VLTE (GPMF) - seen: 'Y','N' (fmt c)
|
||||
VRES => { Name => 'VideoFrameSize', PrintConv => '$val=~s/ /x/;$val' }, #PH (GPMF, fmt L)
|
||||
WBAL => 'ColorTemperatures', #PH (gpmd)
|
||||
WRGB => { #PH (gpmd)
|
||||
Name => 'WhiteBalanceRGB',
|
||||
Binary => 1,
|
||||
},
|
||||
ZFOV => 'DiagonalFieldOfView', #3
|
||||
ZMPL => 'ZoomScaleNormalization', #3
|
||||
#
|
||||
# the following ref forum12825
|
||||
#
|
||||
MUID => {
|
||||
Name => 'MediaUniqueID',
|
||||
PrintConv => q{
|
||||
my @a = split ' ', $val;
|
||||
$_ = sprintf('%.8x',$_) foreach @a;
|
||||
return join('', @a);
|
||||
},
|
||||
},
|
||||
MTRX => 'AccelerometerMatrix',
|
||||
ORIN => 'InputOrientation',
|
||||
ORIO => 'OutputOrientation',
|
||||
UNIF => 'InputUniformity',
|
||||
SROT => 'SensorReadoutTime',
|
||||
# the following are ref https://exiftool.org/forum/index.php?topic=15517.0
|
||||
CORI => { Name => 'CameraOrientation', Binary => 1, Notes => 'quaternions 0-1' },
|
||||
AALP => { Name => 'AudioLevel', Notes => 'dBFS' },
|
||||
GPSA => 'GPSAltitudeSystem', # (eg. 'MSLV')
|
||||
GRAV => { Name => 'GravityVector', Binary => 1 },
|
||||
# DISP - Disparity track
|
||||
HUES => 'PredominantHue',
|
||||
IORI => { Name => 'ImageOrientation', Binary => 1, Notes => 'quaternions 0-1' },
|
||||
# LRVO - ? Part of LRV Frame Skip
|
||||
# LRVS - ? Part of LRV Frame Skip
|
||||
# LSKP - LRV Frame Skip
|
||||
# MSKP - MRV Frame Skip
|
||||
MWET => 'MicrophoneWet',
|
||||
SCEN => 'SceneClassification', # (SNOW,URBA,INDO,WATR,VEGE,BEAC + probability)
|
||||
WNDM => 'WindProcessing',
|
||||
YAVG => 'LumaAverage',
|
||||
);
|
||||
|
||||
# GoPro GPS5 tags (ref 2) (Hero5,Hero6,Hero9)
|
||||
%Image::ExifTool::GoPro::GPS5 = (
|
||||
PROCESS_PROC => \&ProcessString,
|
||||
GROUPS => { 1 => 'GoPro', 2 => 'Location' },
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Index' },
|
||||
0 => { # (unit='deg')
|
||||
Name => 'GPSLatitude',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
1 => { # (unit='deg')
|
||||
Name => 'GPSLongitude',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
2 => { # (unit='m')
|
||||
Name => 'GPSAltitude',
|
||||
PrintConv => '"$val m"',
|
||||
},
|
||||
3 => {
|
||||
Name => 'GPSSpeed',
|
||||
Notes => 'stored as m/s but converted to km/h when extracted',
|
||||
ValueConv => '$val * 3.6',
|
||||
},
|
||||
4 => {
|
||||
Name => 'GPSSpeed3D',
|
||||
Notes => 'stored as m/s but converted to km/h when extracted',
|
||||
ValueConv => '$val * 3.6',
|
||||
},
|
||||
);
|
||||
|
||||
# GoPro GPS9 tags (Hero13)
|
||||
%Image::ExifTool::GoPro::GPS9 = (
|
||||
PROCESS_PROC => \&ProcessString,
|
||||
GROUPS => { 1 => 'GoPro', 2 => 'Location' },
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Index' },
|
||||
0 => { # (unit='deg')
|
||||
Name => 'GPSLatitude',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
1 => { # (unit='deg')
|
||||
Name => 'GPSLongitude',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
2 => { # (unit='m')
|
||||
Name => 'GPSAltitude',
|
||||
PrintConv => '"$val m"',
|
||||
},
|
||||
3 => {
|
||||
Name => 'GPSSpeed',
|
||||
Notes => 'stored as m/s but converted to km/h when extracted',
|
||||
ValueConv => '$val * 3.6',
|
||||
},
|
||||
4 => {
|
||||
Name => 'GPSSpeed3D',
|
||||
Notes => 'stored as m/s but converted to km/h when extracted',
|
||||
ValueConv => '$val * 3.6',
|
||||
},
|
||||
5 => { # days since 2000
|
||||
Name => 'GPSDays',
|
||||
RawConv => '$$self{GPSDays} = $val; undef',
|
||||
Hidden => 1,
|
||||
},
|
||||
6 => { # seconds of date/time
|
||||
Name => 'GPSDateTime',
|
||||
Groups => { 2 => 'Time' },
|
||||
# (10957 days from Jan 1 1970 to Jan 1 2000)
|
||||
RawConv => 'ConvertUnixTime(($$self{GPSDays} + 10957) * 24 * 3600 + $val, undef, 3)',
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
7 => 'GPSDOP',
|
||||
8 => {
|
||||
Name => 'GPSMeasureMode', #PH (NC)
|
||||
PrintConv => {
|
||||
2 => '2-Dimensional Measurement',
|
||||
3 => '3-Dimensional Measurement',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
# GoPro GPRI tags (ref PH) (Karma)
|
||||
%Image::ExifTool::GoPro::GPRI = (
|
||||
PROCESS_PROC => \&ProcessString,
|
||||
GROUPS => { 1 => 'GoPro', 2 => 'Location' },
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Index' },
|
||||
0 => { # (unit='s')
|
||||
Name => 'GPSDateTimeRaw',
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => \&ConvertSystemTime, # convert to date/time based on SystemTime clock
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
1 => { # (unit='deg')
|
||||
Name => 'GPSLatitudeRaw',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
2 => { # (unit='deg')
|
||||
Name => 'GPSLongitudeRaw',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
3 => {
|
||||
Name => 'GPSAltitudeRaw', # (NC)
|
||||
PrintConv => '"$val m"',
|
||||
},
|
||||
# (unknown tags must be defined so that ProcessString() will iterate through all values)
|
||||
4 => { Name => 'GPRI_Unknown4', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' },
|
||||
5 => { Name => 'GPRI_Unknown5', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' },
|
||||
6 => 'GPSSpeedRaw', # (NC) # (unit='m/s' -- should convert to other units?)
|
||||
7 => 'GPSTrackRaw', # (NC) # (unit='deg')
|
||||
8 => { Name => 'GPRI_Unknown8', Unknown => 1, Hidden => 1 }, # (no units)
|
||||
9 => { Name => 'GPRI_Unknown9', Unknown => 1, Hidden => 1 }, # (no units)
|
||||
);
|
||||
|
||||
# GoPro GLPI tags (ref PH) (Karma)
|
||||
%Image::ExifTool::GoPro::GLPI = (
|
||||
PROCESS_PROC => \&ProcessString,
|
||||
GROUPS => { 1 => 'GoPro', 2 => 'Location' },
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Index' },
|
||||
0 => { # (unit='s')
|
||||
Name => 'GPSDateTime',
|
||||
Groups => { 2 => 'Time' },
|
||||
ValueConv => \&ConvertSystemTime, # convert to date/time based on SystemTime clock
|
||||
PrintConv => '$self->ConvertDateTime($val)',
|
||||
},
|
||||
1 => { # (unit='deg')
|
||||
Name => 'GPSLatitude',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
|
||||
},
|
||||
2 => { # (unit='deg')
|
||||
Name => 'GPSLongitude',
|
||||
PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
|
||||
},
|
||||
3 => { # (unit='m')
|
||||
Name => 'GPSAltitude', # (NC)
|
||||
PrintConv => '"$val m"',
|
||||
},
|
||||
# (unknown tags must be defined so that ProcessString() will iterate through all values)
|
||||
4 => { Name => 'GLPI_Unknown4', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' },
|
||||
5 => { Name => 'GPSSpeedX', PrintConv => '"$val m/s"' }, # (NC)
|
||||
6 => { Name => 'GPSSpeedY', PrintConv => '"$val m/s"' }, # (NC)
|
||||
7 => { Name => 'GPSSpeedZ', PrintConv => '"$val m/s"' }, # (NC)
|
||||
8 => { Name => 'GPSTrack' }, # (unit='deg')
|
||||
);
|
||||
|
||||
# GoPro KBAT tags (ref PH)
|
||||
%Image::ExifTool::GoPro::KBAT = (
|
||||
PROCESS_PROC => \&ProcessString,
|
||||
GROUPS => { 1 => 'GoPro', 2 => 'Camera' },
|
||||
VARS => { ID_FMT => 'dec', ID_LABEL => 'Index' },
|
||||
NOTES => 'Battery status information found in GoPro Karma videos.',
|
||||
0 => { Name => 'BatteryCurrent', PrintConv => '"$val A"' },
|
||||
1 => { Name => 'BatteryCapacity', PrintConv => '"$val Ah"' },
|
||||
2 => { Name => 'KBAT_Unknown2', PrintConv => '"$val J"', Unknown => 1, Hidden => 1 },
|
||||
3 => { Name => 'BatteryTemperature', PrintConv => '"$val C"' },
|
||||
4 => { Name => 'BatteryVoltage1', PrintConv => '"$val V"' },
|
||||
5 => { Name => 'BatteryVoltage2', PrintConv => '"$val V"' },
|
||||
6 => { Name => 'BatteryVoltage3', PrintConv => '"$val V"' },
|
||||
7 => { Name => 'BatteryVoltage4', PrintConv => '"$val V"' },
|
||||
8 => { Name => 'BatteryTime', PrintConv => 'ConvertDuration(int($val + 0.5))' }, # (NC)
|
||||
9 => { Name => 'KBAT_Unknown9', PrintConv => '"$val %"', Unknown => 1, Hidden => 1, },
|
||||
10 => { Name => 'KBAT_Unknown10', Unknown => 1, Hidden => 1 }, # (no units)
|
||||
11 => { Name => 'KBAT_Unknown11', Unknown => 1, Hidden => 1 }, # (no units)
|
||||
12 => { Name => 'KBAT_Unknown12', Unknown => 1, Hidden => 1 }, # (no units)
|
||||
13 => { Name => 'KBAT_Unknown13', Unknown => 1, Hidden => 1 }, # (no units)
|
||||
14 => { Name => 'BatteryLevel', PrintConv => '"$val %"' },
|
||||
);
|
||||
|
||||
# GoPro fdsc tags written by the Hero5 and Hero6 (ref PH)
|
||||
%Image::ExifTool::GoPro::fdsc = (
|
||||
GROUPS => { 2 => 'Camera' },
|
||||
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||||
NOTES => q{
|
||||
Tags extracted from the MP4 "fdsc" timed metadata when the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded>
|
||||
(-ee) option is used.
|
||||
},
|
||||
0x08 => { Name => 'FirmwareVersion', Format => 'string[15]' },
|
||||
0x17 => { Name => 'SerialNumber', Format => 'string[16]' },
|
||||
0x57 => { Name => 'OtherSerialNumber', Format => 'string[15]' }, # (NC)
|
||||
0x66 => {
|
||||
Name => 'Model',
|
||||
Description => 'Camera Model Name',
|
||||
Format => 'string[16]',
|
||||
},
|
||||
# ...
|
||||
# after this there are lots of interesting values also found in the GPMF box,
|
||||
# but this block is lacking tag ID's and any directory structure, so the
|
||||
# value offsets are therefore presumably firmware dependent :(
|
||||
);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Convert system time to date/time string
|
||||
# Inputs: 0) system time value, 1) ExifTool ref
|
||||
# Returns: EXIF-format date/time string with milliseconds
|
||||
sub ConvertSystemTime($$)
|
||||
{
|
||||
my ($val, $et) = @_;
|
||||
my $s = $$et{SystemTimeList} or return '<uncalibrated>';
|
||||
unless ($$et{SystemTimeListSorted}) {
|
||||
$s = $$et{SystemTimeList} = [ sort { $$a[0] <=> $$b[0] } @$s ];
|
||||
$$et{SystemTimeListSorted} = 1;
|
||||
}
|
||||
my ($i, $j) = (0, $#$s);
|
||||
# perform binary search to find this system time value
|
||||
while ($j - $i > 1) {
|
||||
my $t = int(($i + $j) / 2);
|
||||
($val < $$s[$t][0] ? $j : $i) = $t;
|
||||
}
|
||||
if ($i == $j or $$s[$j][0] == $$s[$i][0]) {
|
||||
$val = $$s[$i][1];
|
||||
} else {
|
||||
# interpolate between values
|
||||
$val = $$s[$i][1] + ($$s[$j][1] - $$s[$i][1]) * ($val - $$s[$i][0]) / ($$s[$j][0] - $$s[$i][0]);
|
||||
}
|
||||
# (a bit tricky to remove fractional seconds then add them back again after
|
||||
# the date/time conversion while avoiding round-off errors which could
|
||||
# put the seconds out by 1...)
|
||||
my ($t, $f) = ("$val" =~ /^(\d+)(\.\d+)/);
|
||||
return Image::ExifTool::ConvertUnixTime($t, $$et{OPTIONS}{QuickTimeUTC}) . $f;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Scale values by last 'SCAL' constants
|
||||
# Inputs: 0) value or list of values, 1) string of scale factors
|
||||
# Returns: nothing, but updates values
|
||||
sub ScaleValues($$)
|
||||
{
|
||||
my ($val, $scl) = @_;
|
||||
return unless $val and $scl;
|
||||
my @scl = split ' ', $scl or return;
|
||||
my @scaled;
|
||||
my $v = (ref $val eq 'ARRAY') ? $val : [ $val ];
|
||||
foreach $val (@$v) {
|
||||
my @a = split ' ', $val;
|
||||
$a[$_] /= $scl[$_ % @scl] foreach 0..$#a;
|
||||
push @scaled, join(' ', @a);
|
||||
}
|
||||
$_[0] = @scaled > 1 ? \@scaled : $scaled[0];
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Add units to values for human-readable output
|
||||
# Inputs: 0) ExifTool ref, 1) value, 2) tag key
|
||||
# Returns: converted value
|
||||
sub AddUnits($$$)
|
||||
{
|
||||
my ($et, $val, $tag) = @_;
|
||||
if ($et and $$et{TAG_EXTRA}{$tag}{Units}) {
|
||||
my $u = $$et{TAG_EXTRA}{$tag}{Units};
|
||||
$u = [ $u ] unless ref $u eq 'ARRAY';
|
||||
my @a = split ' ', $val;
|
||||
if (@$u == @a) {
|
||||
my $i;
|
||||
for ($i=0; $i<@a; ++$i) {
|
||||
$a[$i] .= ' ' . $$u[$i] if $$u[$i];
|
||||
}
|
||||
$val = join ' ', @a;
|
||||
}
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process string of values (or array of strings) to extract as separate tags
|
||||
# Inputs: 0) ExifTool object ref, 1) directory information ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
sub ProcessString($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my @list = ref $$dataPt eq 'ARRAY' ? @{$$dataPt} : ( $$dataPt );
|
||||
my ($string, $val);
|
||||
$et->VerboseDir('GoPro structure');
|
||||
my $docNum = $$et{DOC_NUM};
|
||||
my $subDoc = 0;
|
||||
foreach $string (@list) {
|
||||
my @val = split ' ', $string;
|
||||
my $i = 0;
|
||||
foreach $val (@val) {
|
||||
$et->HandleTag($tagTablePtr, $i, $val);
|
||||
next if $$tagTablePtr{++$i};
|
||||
# increment subdoc for records stored as string of values (eg. GPS5)
|
||||
$i = 0;
|
||||
++$subDoc;
|
||||
$$et{DOC_NUM} = "$docNum-$subDoc";
|
||||
}
|
||||
if ($i) {
|
||||
# increment subdoc for records stored as array of strings (eg. GPS9)
|
||||
++$subDoc;
|
||||
$$et{DOC_NUM} = "$docNum-$subDoc";
|
||||
}
|
||||
}
|
||||
$$et{DOC_NUM} = $docNum;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process "GP\x06\0" records in MP4 'mdat' atom
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref (RAF and DirLen)
|
||||
# Returns: size of GoPro record, or 0 on error
|
||||
sub ProcessGP6($$)
|
||||
{
|
||||
my ($et, $dirInfo) = @_;
|
||||
my $raf = $$dirInfo{RAF};
|
||||
my $len = $$dirInfo{DirLen};
|
||||
my $buff;
|
||||
while ($len > 16) {
|
||||
$raf->Read($buff, 16) == 16 or last;
|
||||
my ($tag, $size) = unpack('a4N', $buff);
|
||||
last if $size + 16 > $len or $buff !~ /^GP..\0/s;
|
||||
$raf->Read($buff, $size) == $size or last;
|
||||
if ($buff =~ /^DEVC/) {
|
||||
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
||||
my $tagTbl = GetTagTable('Image::ExifTool::GoPro::GPMF');
|
||||
ProcessGoPro($et, { DataPt => \$buff, DataPos => $raf->Tell()-$size }, $tagTbl);
|
||||
}
|
||||
$len -= $size + 16;
|
||||
}
|
||||
delete $$et{DOC_NUM};
|
||||
return $$dirInfo{DirLen} - $len;
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Process GoPro metadata (gpmd samples, GPMF box, or APP6) (ref PH/1/2)
|
||||
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
||||
# Returns: 1 on success
|
||||
# - with hack to check for encrypted text in gpmd data (Rove Stealth 4K)
|
||||
sub ProcessGoPro($$$)
|
||||
{
|
||||
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||||
my $dataPt = $$dirInfo{DataPt};
|
||||
my $base = $$dirInfo{Base};
|
||||
my $pos = $$dirInfo{DirStart} || 0;
|
||||
my $dirEnd = $pos + ($$dirInfo{DirLen} || (length($$dataPt) - $pos));
|
||||
my $verbose = $et->Options('Verbose');
|
||||
my $unknown = $verbose || $et->Options('Unknown');
|
||||
my ($size, $type, $unit, $scal, $setGroup0);
|
||||
|
||||
$et->VerboseDir($$dirInfo{DirName} || 'GPMF', undef, $dirEnd-$pos) if $verbose;
|
||||
$$et{FoundEmbedded} = 1;
|
||||
if ($pos) {
|
||||
my $parent = $$dirInfo{Parent};
|
||||
$setGroup0 = $$et{SET_GROUP0} = 'APP6' if $parent and $parent eq 'APP6';
|
||||
} else {
|
||||
# set group0 to "QuickTime" unless group1 is being changed (to Track#)
|
||||
$setGroup0 = $$et{SET_GROUP0} = 'QuickTime' unless $$et{SET_GROUP1};
|
||||
}
|
||||
|
||||
for (; $pos+8<=$dirEnd; $pos+=($size+3)&0xfffffffc) {
|
||||
my ($tag,$fmt,$len,$count) = unpack("x${pos}a4CCn", $$dataPt);
|
||||
if ($tag =~ /[^-_a-zA-Z0-9 ]/) {
|
||||
$et->Warn('Unrecognized GoPro record') unless $tag eq "\0\0\0\0";
|
||||
last;
|
||||
}
|
||||
$size = $len * $count;
|
||||
$pos += 8;
|
||||
if ($pos + $size > $dirEnd) {
|
||||
$et->Warn('Truncated GoPro record');
|
||||
last;
|
||||
}
|
||||
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||||
last if $tag eq "\0\0\0\0"; # stop at null tag
|
||||
next unless $size or $verbose; # don't save empty values unless verbose
|
||||
my $format = $goProFmt{$fmt} || 'undef';
|
||||
my ($val, $i, $j, $p, @v);
|
||||
if ($fmt == 0x3f and defined $type) {
|
||||
# decode structure with format given by previous 'TYPE'
|
||||
for ($i=0; $i<$count; ++$i) {
|
||||
my (@s, $l);
|
||||
for ($j=0, $p=0; $j<length($type); ++$j, $p+=$l) {
|
||||
my $b = Get8u(\$type, $j);
|
||||
my $f = $goProFmt{$b} or last;
|
||||
$l = $goProSize{$b} || Image::ExifTool::FormatSize($f) or last;
|
||||
last if $p + $l > $len;
|
||||
my $s = ReadValue($dataPt, $pos+$i*$len+$p, $f, undef, $l);
|
||||
last unless defined $s;
|
||||
push @s, $s;
|
||||
}
|
||||
push @v, join ' ', @s if @s;
|
||||
}
|
||||
$val = @v > 1 ? \@v : $v[0];
|
||||
} elsif (($format eq 'undef' or $format eq 'string') and $count > 1 and $len > 1) {
|
||||
# unpack multiple undef/string values as a list
|
||||
my $a = $format eq 'undef' ? 'a' : 'A';
|
||||
$val = [ unpack("x${pos}".("$a$len" x $count), $$dataPt) ];
|
||||
} else {
|
||||
$val = ReadValue($dataPt, $pos, $format, undef, $size);
|
||||
}
|
||||
# save TYPE, UNIT/SIUN and SCAL values for later
|
||||
$type = $val if $tag eq 'TYPE';
|
||||
$unit = $val if $tag eq 'UNIT' or $tag eq 'SIUN';
|
||||
$scal = $val if $tag eq 'SCAL';
|
||||
|
||||
unless ($tagInfo) {
|
||||
next unless $unknown;
|
||||
my $name = Image::ExifTool::QuickTime::PrintableTagID($tag);
|
||||
$tagInfo = { Name => "GoPro_$name", Description => "GoPro $name", Unknown => 1 };
|
||||
$$tagInfo{SubDirectory} = { TagTable => 'Image::ExifTool::GoPro::GPMF' } if not $fmt;
|
||||
AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
||||
}
|
||||
# apply scaling if available to last tag in this container
|
||||
ScaleValues($val, $scal) if $scal and $tag ne 'SCAL' and $pos+$size+3>=$dirEnd;
|
||||
my $key = $et->HandleTag($tagTablePtr, $tag, $val,
|
||||
DataPt => $dataPt,
|
||||
DataPos => $$dirInfo{DataPos},
|
||||
Base => $base,
|
||||
Start => $pos,
|
||||
Size => $size,
|
||||
TagInfo => $tagInfo,
|
||||
Format => $format,
|
||||
Extra => $verbose ? ", type='".($fmt ? chr($fmt) : '\0')."' size=$len count=$count" : undef,
|
||||
);
|
||||
# save units for adding in print conversion if specified
|
||||
$$et{TAG_EXTRA}{$key}{Units} = $unit if $$tagInfo{AddUnits} and $key;
|
||||
}
|
||||
delete $$et{SET_GROUP0} if $setGroup0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
1; # end
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Image::ExifTool::GoPro - Read information from GoPro videos
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module is used by Image::ExifTool
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains definitions required by Image::ExifTool to decode
|
||||
metadata from GoPro MP4 videos.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright 2003-2026, Phil Harvey (philharvey66 at gmail.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl itself.
|
||||
|
||||
=head1 REFERENCES
|
||||
|
||||
=over 4
|
||||
|
||||
=item L<https://github.com/gopro/gpmf-parser>
|
||||
|
||||
=item L<https://github.com/stilldavid/gopro-utils>
|
||||
|
||||
=item L<https://github.com/gopro/gpmf-parser>
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Image::ExifTool::TagNames/GoPro Tags>,
|
||||
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||||
|
||||
=cut
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue