Skip to content

Extract capture date from QuickTime video attachments for saleStart#406

Merged
techeng322 merged 1 commit into
mainfrom
techengme/myc-5016-telegram-video-uploads-didnt-set-salestart-from-capture-time
Jul 12, 2026
Merged

Extract capture date from QuickTime video attachments for saleStart#406
techeng322 merged 1 commit into
mainfrom
techengme/myc-5016-telegram-video-uploads-didnt-set-salestart-from-capture-time

Conversation

@techeng322

@techeng322 techeng322 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Telegram bot only set saleStart from EXIF capture time on image attachments; video (MOV/MP4) always fell back to upload time because exifr doesn't support QuickTime metadata at all (Unknown file format)
  • iPhone-recorded MOV embeds com.apple.quicktime.creationdate (ISO 8601 with UTC offset already included) in moov/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 existing readFtypBrands.ts pattern
  • extractQuickTimeCaptureDate.ts regex-validates the offset-bearing value and computes UTC epoch seconds, wired into createMomentsFromGroup.ts alongside the existing extractExifCaptureDate path for images
  • Same fail-closed policy as the image EXIF path: missing/malformed atom → fall back to upload time, no timezone guessing. Scope limited to capture date only (no GPS). Only covers Apple's com.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 --noEmit clean on touched files
  • Manual: send an iPhone-recorded MOV to the Telegram bot and confirm saleStart matches the video's capture time instead of upload time

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for extracting capture dates from QuickTime/MP4 media metadata.
    • Non-image attachments can now use embedded capture dates when available, improving moment timestamps.
  • Bug Fixes

    • Added validation and safe handling for missing, malformed, or unsupported media metadata.
  • Tests

    • Added comprehensive coverage for MP4 metadata parsing, date extraction, malformed data, and timezone formats.
  • Documentation

    • Clarified how capture dates are selected when media metadata is unavailable.

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>
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
in-process-api Ready Ready Preview Jul 12, 2026 2:50am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

QuickTime capture date extraction

Layer / File(s) Summary
MP4 box parsing
src/lib/media/readMp4BoxHeader.ts, src/lib/media/listMp4ChildBoxes.ts, src/lib/media/__tests__/*
Adds bounded MP4 box-header parsing and child-box traversal, including extended sizes, end-of-buffer boxes, and malformed-header handling with tests.
QuickTime metadata lookup
src/lib/media/findQuickTimeKeyIndex.ts, src/lib/media/findQuickTimeIlstStringValue.ts, src/lib/media/readQuickTimeCreationDate.ts, src/lib/media/__tests__/*
Locates the QuickTime creation-date key and matching ILST value through the moovmetakeys/ilst structure.
Telegram capture-date integration
src/lib/telegram/chat/attachment/*, src/lib/telegram/chat/moment/*
Validates QuickTime timestamps, converts them to Unix seconds, and uses them for non-image attachment capture dates while updating related documentation.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: extracting QuickTime capture dates for saleStart from video attachments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch techengme/myc-5016-telegram-video-uploads-didnt-set-salestart-from-capture-time

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/lib/media/__tests__/findQuickTimeIlstStringValue.test.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /.eslintrc.json
at JSON.stringify ()
at /node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/

... [truncated 446 characters] ...

c/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

src/lib/media/__tests__/findQuickTimeKeyIndex.test.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /.eslintrc.json
at JSON.stringify ()
at /node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/

... [truncated 446 characters] ...

c/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

src/lib/media/__tests__/listMp4ChildBoxes.test.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'configs' -> object with constructor 'Object'
| property 'flat' -> object with constructor 'Object'
| ...
| property 'plugins' -> object with constructor 'Object'
--- property 'react' closes the circle
Referenced from: /.eslintrc.json
at JSON.stringify ()
at /node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2255:45
at Array.map ()
at ConfigValidator.formatErrors (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2246:23)
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:84)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/

... [truncated 446 characters] ...

c/dist/eslintrc.cjs:3261:25)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)

  • 11 others

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@techeng322 techeng322 merged commit dd03bbd into main Jul 12, 2026
2 of 3 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/lib/media/findQuickTimeKeyIndex.ts (1)

13-18: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider adding a bounds check before reading the entry count.

If the keys box content is smaller than 8 bytes (version/flags + count), buffer.readUInt32BE(offset) at line 14 will throw a RangeError. Similarly, if count exceeds the actual number of entries, line 18 can throw when offset reaches the buffer end. The top-level catch in extractQuickTimeCaptureDate handles 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

📥 Commits

Reviewing files that changed from the base of the PR and between bb65b96 and ebfda9a.

📒 Files selected for processing (14)
  • src/lib/media/__tests__/findQuickTimeIlstStringValue.test.ts
  • src/lib/media/__tests__/findQuickTimeKeyIndex.test.ts
  • src/lib/media/__tests__/listMp4ChildBoxes.test.ts
  • src/lib/media/__tests__/readMp4BoxHeader.test.ts
  • src/lib/media/__tests__/readQuickTimeCreationDate.test.ts
  • src/lib/media/findQuickTimeIlstStringValue.ts
  • src/lib/media/findQuickTimeKeyIndex.ts
  • src/lib/media/listMp4ChildBoxes.ts
  • src/lib/media/readMp4BoxHeader.ts
  • src/lib/media/readQuickTimeCreationDate.ts
  • src/lib/telegram/chat/attachment/__tests__/extractQuickTimeCaptureDate.test.ts
  • src/lib/telegram/chat/attachment/extractQuickTimeCaptureDate.ts
  • src/lib/telegram/chat/moment/buildCreateBatchInput.ts
  • src/lib/telegram/chat/moment/createMomentsFromGroup.ts

@@ -0,0 +1,61 @@
import { describe, it, expect } from 'vitest';
import readMp4BoxHeader from '../readMp4BoxHeader';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant