Extract capture date from QuickTime video attachments for saleStart#406
Conversation
Telegram bot only computed saleStart from EXIF capture time on image attachments; video (MOV/MP4) always fell back to upload time since exifr doesn't support QuickTime metadata. iPhone-recorded MOV embeds com.apple.quicktime.creationdate (ISO 8601 with UTC offset) in moov/meta/keys+ilst, so add a small MP4 box parser to read it and feed it through the same saleStart path as image EXIF, falling back to upload time when the atom is missing or malformed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds MP4/QuickTime atom parsing helpers, extracts and validates QuickTime creation dates, and uses them as capture-date fallbacks for non-image Telegram attachments. New Vitest suites cover parsing, metadata lookup, date conversion, and integration behavior. ChangesQuickTime capture date extraction
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant createMomentsFromGroup
participant extractQuickTimeCaptureDate
participant readQuickTimeCreationDate
participant MP4Buffer
createMomentsFromGroup->>extractQuickTimeCaptureDate: pass non-image attachment buffer
extractQuickTimeCaptureDate->>readQuickTimeCreationDate: request creation date
readQuickTimeCreationDate->>MP4Buffer: parse moov/meta/keys/ilst atoms
MP4Buffer-->>readQuickTimeCreationDate: return creation-date string or undefined
readQuickTimeCreationDate-->>extractQuickTimeCaptureDate: return raw metadata
extractQuickTimeCaptureDate-->>createMomentsFromGroup: return Unix timestamp or undefined
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/lib/media/__tests__/findQuickTimeIlstStringValue.test.tsOops! Something went wrong! :( ESLint: 9.39.4 TypeError: Converting circular structure to JSON ... [truncated 446 characters] ... c/dist/eslintrc.cjs:3261:25) src/lib/media/__tests__/findQuickTimeKeyIndex.test.tsOops! Something went wrong! :( ESLint: 9.39.4 TypeError: Converting circular structure to JSON ... [truncated 446 characters] ... c/dist/eslintrc.cjs:3261:25) src/lib/media/__tests__/listMp4ChildBoxes.test.tsOops! Something went wrong! :( ESLint: 9.39.4 TypeError: Converting circular structure to JSON ... [truncated 446 characters] ... c/dist/eslintrc.cjs:3261:25)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/lib/media/findQuickTimeKeyIndex.ts (1)
13-18: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider adding a bounds check before reading the entry count.
If the
keysbox content is smaller than 8 bytes (version/flags + count),buffer.readUInt32BE(offset)at line 14 will throw aRangeError. Similarly, ifcountexceeds the actual number of entries, line 18 can throw whenoffsetreaches the buffer end. The top-level catch inextractQuickTimeCaptureDatehandles this, but an explicit guard would make the "return undefined on malformed" contract clearer and avoid relying on exceptions for expected conditions.🛡️ Optional bounds guard
const findQuickTimeKeyIndex = ( buffer: Buffer, keysBox: Mp4Box, targetKeyName: string ): number | undefined => { + if (keysBox.contentStart + 8 > keysBox.contentEnd) return undefined; let offset = keysBox.contentStart + 4; const count = buffer.readUInt32BE(offset); offset += 4; for (let index = 1; index <= count; index += 1) { + if (offset + 4 > keysBox.contentEnd) return undefined; const entrySize = buffer.readUInt32BE(offset);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/media/findQuickTimeKeyIndex.ts` around lines 13 - 18, In findQuickTimeKeyIndex, add explicit bounds checks before reading the entry count and each entry header, returning undefined when the keys box lacks the required bytes or count exceeds the available data. Preserve normal parsing for valid boxes and the existing malformed-input contract without relying on RangeError handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/media/__tests__/readMp4BoxHeader.test.ts`:
- Line 2: Update the import in readMp4BoxHeader tests to use the configured `@/`*
alias for the src-resolved readMp4BoxHeader module instead of a relative path.
In `@src/lib/media/listMp4ChildBoxes.ts`:
- Line 1: Update the import of readMp4BoxHeader and Mp4Box in listMp4ChildBoxes
to use the configured `@/`* alias for the corresponding src module instead of a
relative path.
---
Nitpick comments:
In `@src/lib/media/findQuickTimeKeyIndex.ts`:
- Around line 13-18: In findQuickTimeKeyIndex, add explicit bounds checks before
reading the entry count and each entry header, returning undefined when the keys
box lacks the required bytes or count exceeds the available data. Preserve
normal parsing for valid boxes and the existing malformed-input contract without
relying on RangeError handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f249bc70-eda5-4d86-83fb-2ae38f233ca3
📒 Files selected for processing (14)
src/lib/media/__tests__/findQuickTimeIlstStringValue.test.tssrc/lib/media/__tests__/findQuickTimeKeyIndex.test.tssrc/lib/media/__tests__/listMp4ChildBoxes.test.tssrc/lib/media/__tests__/readMp4BoxHeader.test.tssrc/lib/media/__tests__/readQuickTimeCreationDate.test.tssrc/lib/media/findQuickTimeIlstStringValue.tssrc/lib/media/findQuickTimeKeyIndex.tssrc/lib/media/listMp4ChildBoxes.tssrc/lib/media/readMp4BoxHeader.tssrc/lib/media/readQuickTimeCreationDate.tssrc/lib/telegram/chat/attachment/__tests__/extractQuickTimeCaptureDate.test.tssrc/lib/telegram/chat/attachment/extractQuickTimeCaptureDate.tssrc/lib/telegram/chat/moment/buildCreateBatchInput.tssrc/lib/telegram/chat/moment/createMomentsFromGroup.ts
| @@ -0,0 +1,61 @@ | |||
| import { describe, it, expect } from 'vitest'; | |||
| import readMp4BoxHeader from '../readMp4BoxHeader'; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use @/* path alias for imports pointing to src/.
As per coding guidelines, imports resolving under src/ should use the @/* alias instead of relative paths.
♻️ Proposed fix
-import readMp4BoxHeader from '../readMp4BoxHeader';
+import readMp4BoxHeader from '`@/lib/media/readMp4BoxHeader`';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import readMp4BoxHeader from '../readMp4BoxHeader'; | |
| import readMp4BoxHeader from '`@/lib/media/readMp4BoxHeader`'; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/media/__tests__/readMp4BoxHeader.test.ts` at line 2, Update the
import in readMp4BoxHeader tests to use the configured `@/`* alias for the
src-resolved readMp4BoxHeader module instead of a relative path.
Source: Coding guidelines
| @@ -0,0 +1,26 @@ | |||
| import readMp4BoxHeader, { type Mp4Box } from './readMp4BoxHeader'; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use @/* path alias for imports pointing to src/.
As per coding guidelines, imports resolving under src/ should use the @/* alias instead of relative paths.
♻️ Proposed fix
-import readMp4BoxHeader, { type Mp4Box } from './readMp4BoxHeader';
+import readMp4BoxHeader, { type Mp4Box } from '`@/lib/media/readMp4BoxHeader`';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import readMp4BoxHeader, { type Mp4Box } from './readMp4BoxHeader'; | |
| import readMp4BoxHeader, { type Mp4Box } from '`@/lib/media/readMp4BoxHeader`'; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/media/listMp4ChildBoxes.ts` at line 1, Update the import of
readMp4BoxHeader and Mp4Box in listMp4ChildBoxes to use the configured `@/`* alias
for the corresponding src module instead of a relative path.
Source: Coding guidelines
Summary
saleStartfrom EXIF capture time on image attachments; video (MOV/MP4) always fell back to upload time becauseexifrdoesn't support QuickTime metadata at all (Unknown file format)com.apple.quicktime.creationdate(ISO 8601 with UTC offset already included) inmoov/meta/keys+ilst, so add a small dependency-free MP4 box parser (src/lib/media/readMp4BoxHeader.ts,listMp4ChildBoxes.ts,findQuickTimeKeyIndex.ts,findQuickTimeIlstStringValue.ts,readQuickTimeCreationDate.ts) to read it directly, mirroring the existingreadFtypBrands.tspatternextractQuickTimeCaptureDate.tsregex-validates the offset-bearing value and computes UTC epoch seconds, wired intocreateMomentsFromGroup.tsalongside the existingextractExifCaptureDatepath for imagescom.apple.quicktime.*keyed-metadata convention — non-iPhone/re-encoded MOV/MP4 without this atom safely fall back to upload time.Test plan
pnpm vitest run src/lib/media src/lib/telegram/chat— 309 tests passing, including new synthetic-buffer unit tests for the box parser (no real video fixture committed, to avoid checking in personal GPS/device metadata)tsc --noEmitclean on touched filessaleStartmatches the video's capture time instead of upload time🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation