# How the Python CLI for Google Timeline Visualizer Works

> Discover how the Python CLI for Google Timeline Visualizer transforms your location data into engaging videos. Explore the 14-step conversion pipeline now.

- Repository: [mahlernim/google-timeline-visualizer](https://github.com/mahlernim/google-timeline-visualizer)
- Tags: internals
- Published: 2026-08-23

---

**The Python CLI for Google Timeline Visualizer converts Google Timeline JSON exports into animated travel videos through a 14-step pipeline orchestrated by `main()` in [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py).**

The Google Timeline Visualizer command-line interface transforms raw location history into cinematic travel animations. According to the [mahlernim/google-timeline-visualizer](https://github.com/mahlernim/google-timeline-visualizer) source code, the entire workflow lives in a single entry-point script that handles everything from argument parsing to FFmpeg video encoding.

## CLI Architecture and Entry Point

The CLI is implemented in **[visualizer.py](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py)**. When executed via `python -m visualizer` or directly as [`./visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/./visualizer.py), the `main()` function coordinates all operations.

The design follows a linear pipeline: **arguments → validation → data extraction → camera track → frame generation → animation → video file**. This structure keeps the codebase maintainable while allowing precise control over each rendering stage.

## Step 1: Argument Parsing and Validation

The `build_argument_parser()` function (lines [1003-1034]) constructs an `argparse.ArgumentParser` with all user-facing options:

- Input file path (validated via `existing_file()`)
- Year, start-date, and end-date filtering
- Camera movement presets
- Aspect ratio and resolution
- Output file path

The parser rejects invalid inputs immediately before any expensive processing begins.

## Step 2: Preset Token Handling

Users can supply a Base64-encoded preset token via `--preset`. The `decode_preset()` function (lines [167-207]) decodes this to a `PresetValues` object (lines [139-155]), which overwrites corresponding CLI arguments.

This allows sharing complex configurations as single strings:

```bash

# Decode and apply a preset token

python -m visualizer \
  --input Timeline.json \
  --preset aGV2c2NvcmU= \
  --output mytrip.mp4

```

## Step 3: Date Range Resolution

`parse_date_argument()` (lines [1036-1049]) interprets date strings like `2023-04` and returns `datetime.date` objects. If no dates or year are specified, the CLI defaults to the current year.

## Step 4: FFmpeg Availability Check

Before heavy processing, `ensure_ffmpeg_available()` (lines [996-1002]) verifies that Matplotlib can access the `ffmpeg` writer. If unavailable, the custom `FfmpegUnavailableError` aborts execution with a clear error message.

## Step 5: Timeline JSON Parsing and Cleaning

The `parse_timeline()` function (lines [1051-1088]) orchestrates data loading:

1. Opens [`Timeline.json`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/Timeline.json) and loads via `json.load()`
2. Calls `extract_timeline_points()` (lines [624-660]) to normalize location points across semantic activities, visits, and raw path points
3. Filters GPS spikes using `filter_location_outliers()` (lines [1254-1259])

## Step 6: Geometry Conversion and Distance Calculation

Latitude/longitude pairs are projected to Web-Mercator meters via `latlon_to_meters()` (lines [1212-1245]). Cumulative distances are computed with the Haversine formula (lines [1085-1092]), creating a continuous distance metric for animation timing.

## Step 7: Trip Detection and Journey Pacing

Three functions collaborate on motion dynamics:

- **`transfer_threshold_km()`** (lines [1249-1262]): Determines distance threshold for "trip" legs based on median hop lengths
- **`build_legs()`**: Splits routes into transfer vs. local segments
- **`build_journey_timing()`** (lines [2124-2146]): Creates non-linear distance-vs-progress mapping with configurable compression exponent

## Step 8: Camera Track Generation

`build_camera_track()` (lines [1588-1667]) samples approximately 480 points (`CAMERA_TRACK_SAMPLES`) across the journey. For each sample, `raw_camera_sample()` (lines [1580-1620]) calculates:

- Map center coordinates (`cx`, `cy`)
- Viewport span (`span_x`, `span_y`)
- Padding and zoom based on `CAMERA_MOVEMENTS` preset

The result is a list of viewport tuples defining the camera path.

## Step 9: Overview Viewport Calculation

After the journey completes, `calculate_overview_viewport()` (lines [2589-2599]) computes a final zoom-out view for the video outro.

## Step 10: Frame Data Preparation

A loop (lines [1235-1286]) builds `frame_data` for every video frame:

- Journey frames plus outro frames
- Distance selection per frame
- Viewport selection via `camera_at()` (lines [1444-1455])

## Step 11: Matplotlib Figure and Map Tile Setup

The figure is created with `figsize = width_px/100` inches to achieve target resolution. Map tiles are fetched on-demand through `get_map_image()` (lines [844-865]), which uses `fetch_tile_img()` with a tile cache for performance.

## Step 12: Animation Callback Function

The `update(i)` callback (lines [1312-1365]) handles each frame:

- Updates map extent
- Refreshes map tiles every 4 frames
- Draws travelled trail (historical and recent segments)
- Renders head marker
- Blends to overview during outro
- Updates subtitle with date and cumulative distance

## Step 13: Video Encoding with FFmpeg

The final animation saves via:

```python
ani.save(args.output, writer='ffmpeg', fps=fps, dpi=100)

```

This call at line 1370 completes the rendering pipeline.

## Step 14: Exit Code Handling

Success returns `0`. Any caught `TimelineCliError` (parsing errors, outlier failures, FFmpeg issues) prints to `stderr` and returns `1` (lines [842-847], [864-867]).

## Exception Hierarchy and Error Handling

All custom exceptions reside at the top of [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py):

| Exception | Purpose |
|-----------|---------|
| `TimelineCliError` | Base class for all CLI errors |
| `TimelineParseError` | JSON parsing failures |
| `NoDataFoundError` | Empty or filtered datasets |
| `FfmpegUnavailableError` | Missing encoder dependency |

The test suite **[tests/test_cli_errors.py](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_cli_errors.py)** verifies error handling behavior across these types.

## Practical Usage Examples

Basic year-based rendering:

```bash
python -m visualizer \
  --input path/to/Timeline.json \
  --year 2022 \
  --output travel_2022.mp4

```

Custom date range with portrait orientation:

```bash
python -m visualizer \
  --input Timeline.json \
  --start-date 2023-05 \
  --end-date 2023-07 \
  --aspect-ratio portrait \
  --resolution 1080 \
  --duration 45 \
  --camera-movement dynamic \
  --output summer_2023.mp4

```

Preset-based configuration sharing:

```bash

# Create preset from string

PRESET=$(echo -n "steady square balanced balanced off" | base64 -w0)

python -m visualizer \
  --input Timeline.json \
  --preset $PRESET \
  --output mytrip.mp4

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py) | Core CLI, 2000+ lines containing parsing, camera logic, animation, and export |
| [`tests/test_cli_errors.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_cli_errors.py) | Unit tests for argument validation and error conditions |
| [`tests/test_parser.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/tests/test_parser.py) | Timeline parsing and outlier filtering tests |
| [`requirements.txt`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/requirements.txt) | Dependencies: `dateutil`, `matplotlib`, `numpy`, `pillow` |

## Summary

- The **Python CLI for Google Timeline Visualizer** runs as a single-module application through [`visualizer.py`](https://github.com/mahlernim/google-timeline-visualizer/blob/main/visualizer.py)
- **`main()`** orchestrates 14 sequential steps from argument parsing to video export
- **Preset tokens** enable portable configuration sharing via Base64 encoding
- **Camera track generation** samples ~480 points with configurable movement presets
- **Matplotlib + FFmpeg** handle rendering and encoding with tile-cached map backgrounds
- **Typed exceptions** provide clear error messages with deterministic exit codes

## Frequently Asked Questions

### What input file format does the CLI require?

The CLI requires Google's **Timeline.json** export format. The `parse_timeline()` function loads this with standard `json.load()` and normalizes diverse location record types (semantic activities, visits, raw path points) into uniform point structures.

### How does the CLI handle missing FFmpeg?

`ensure_ffmpeg_available()` (lines [996-1002]) queries Matplotlib for FFmpeg writer availability before any processing. If absent, it raises `FfmpegUnavailableError`, prints to stderr, and exits with code 1. This early validation prevents wasted computation on systems that cannot complete video encoding.

### Can I animate a specific month instead of a full year?

Yes. Use `--start-date` and `--end-date` with `YYYY-MM` format. The `parse_date_argument()` function (lines [1036-1049]) converts these strings to `datetime.date` objects for filtering. Omitting both dates defaults to the current calendar year.

### What camera movement options are available?

The CLI supports multiple `CAMERA_MOVEMENTS` presets including `dynamic`, `steady`, and others. These control how `raw_camera_sample()` (lines [1580-1620]) calculates viewport center, span, padding, and zoom transitions throughout the journey animation.