{
  "schemaVersion": 1,
  "slug": "time-lapse-calculator",
  "category": "photography",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "3dfc50be3f3619698c37649f4731ddd27f962ad2ace7b51db7462a167be8e274",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/time-lapse-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "N = ⌊3600t ÷ Δ⌋; L = N ÷ fps; S = N × f ÷ 1000; B = ⌈N ÷ b⌉",
    "vars": [
      [
        "N",
        "Number of frames"
      ],
      [
        "t",
        "Real time (h)"
      ],
      [
        "Δ",
        "Interval (s)"
      ],
      [
        "fps",
        "Playback frame rate"
      ],
      [
        "f",
        "File size per frame (MB)"
      ],
      [
        "b",
        "Shots per battery"
      ]
    ],
    "note": "S uses decimal GB: 1 GB = 1000 MB. Positive inputs are required. Frame count excludes an extra first shot; storage excludes video export and reserve space."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-18",
    "sources": [
      {
        "name": "NIST — Metric (SI) prefixes",
        "url": "https://www.nist.gov/pml/owm/metric-si-prefixes"
      },
      {
        "name": "Nikon — Time-lapse movie interval behavior",
        "url": "https://onlinemanual.nikonimglib.com/z7_z6/en/09_menu_guide_03_31.html"
      }
    ],
    "basis": "The frame budget is the whole-number count obtained by dividing shooting seconds by the interval and rounding down. It does not add an extra first shot. If the interval is longer than the whole shooting duration, the budget is zero frames; clip length is zero and real time per playback second is undefined. Divide frames by playback fps for clip seconds. Storage is frames × average MB per frame / 1000 in decimal GB, before extra reserve space or video export. Full charges to budget are frames divided by shots per charge, rounded up, including the first charge. The calculation does not determine how many spare battery packs you personally need or model continuous camera power use.",
    "limitations": "A time lapse is defined by its interval, because that sets how much real time each frame of playback represents. Dividing the shoot duration by the interval gives the frame count, and the frame count divided by the playback frame rate gives the clip length. For example, 2 hours at a 5-second interval gives 1,440 frames: a 60-second clip at 24 fps. At 25 MB per frame this needs 36 GB before reserve space and video export. Battery count is only an estimate based on your shots-per-charge input.",
    "implementationHash": "3dfc50be3f3619698c37649f4731ddd27f962ad2ace7b51db7462a167be8e274"
  },
  "contract": {
    "compute": "if(![duration,interval,fps,fileSize,batteryShots].every(Number.isFinite)||duration<0.01||duration>1000||interval<0.1||interval>3600||fps<1||fps>120||!Number.isInteger(fps)||fileSize<0.1||fileSize>500||batteryShots<1||batteryShots>100000||!Number.isInteger(batteryShots))return {frames:null,clipLength:null,clipMinutes:null,timePerSecond:null,storageGB:null,batteriesNeeded:null,__error:'inputError'};\nconst seconds = duration*3600; const frames = interval > 0 ? Math.floor(seconds/interval) : 0; const clip = frames/fps; const storage = frames*fileSize/1000; return { frames: frames, clipLength: clip, clipMinutes: clip/60, timePerSecond: clip > 0 ? duration*60/clip : null, storageGB: storage, batteriesNeeded: batteryShots > 0 ? Math.ceil(frames/batteryShots) : 0 };",
    "inputs": [
      {
        "name": "duration",
        "type": "number",
        "default": 2,
        "min": 0.01,
        "max": 1000,
        "step": 0.01,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "interval",
        "type": "number",
        "default": 5,
        "min": 0.1,
        "max": 3600,
        "step": 0.1,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "fps",
        "type": "number",
        "default": 24,
        "min": 1,
        "max": 120,
        "step": 1,
        "slider": true,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "fileSize",
        "type": "number",
        "default": 25,
        "min": 0.1,
        "max": 500,
        "step": 0.1,
        "slider": true,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "batteryShots",
        "type": "number",
        "default": 500,
        "min": 1,
        "max": 100000,
        "step": 10,
        "requiredValue": true,
        "strictRange": true
      }
    ],
    "outputs": [
      {
        "name": "frames",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "clipLength",
        "precision": 1,
        "unit": "seconds"
      },
      {
        "name": "clipMinutes",
        "precision": 2,
        "unit": "minutes"
      },
      {
        "name": "timePerSecond",
        "precision": 1,
        "unit": "minutes",
        "optional": true
      },
      {
        "name": "storageGB",
        "precision": 2,
        "unit": "GB"
      },
      {
        "name": "batteriesNeeded",
        "precision": 0,
        "unit": ""
      }
    ],
    "interpret": null
  }
}
