Skip to content

CLI

Command-line interface for gopro-api. Run gopro-api --help to see all subcommands.

Built with Typer; the Typer application is exposed as gopro_api.cli.app for embedding or testing.

Entry point

gopro_api.cli.main(argv=None)

CLI entrypoint: parse argv and run the selected command.

Parameters:

Name Type Description Default
argv Optional[list[str]]

Argument list (defaults to process arguments when None).

None
Source code in gopro_api/cli/app.py
71
72
73
74
75
76
77
def main(argv: Optional[list[str]] = None) -> None:
    """CLI entrypoint: parse ``argv`` and run the selected command.

    Args:
        argv: Argument list (defaults to process arguments when ``None``).
    """
    app(args=argv)

Application

gopro_api.cli.app

Typer application instance, root callback, and CLI entrypoint.

main(argv=None)

CLI entrypoint: parse argv and run the selected command.

Parameters:

Name Type Description Default
argv Optional[list[str]]

Argument list (defaults to process arguments when None).

None
Source code in gopro_api/cli/app.py
71
72
73
74
75
76
77
def main(argv: Optional[list[str]] = None) -> None:
    """CLI entrypoint: parse ``argv`` and run the selected command.

    Args:
        argv: Argument list (defaults to process arguments when ``None``).
    """
    app(args=argv)

Commands

gopro_api.cli.search_command(ctx, *, start=typer.Option(..., '--start', help='Range start: YYYY-MM-DD or ISO datetime'), end=typer.Option(..., '--end', help='Range end: YYYY-MM-DD or ISO datetime (API treats range as in query string)'), page=typer.Option(1, '--page', help='Page number (default: 1)'), per_page=typer.Option(30, '--per-page', help='Page size (default: 30)'), all_pages=typer.Option(False, '--all-pages', help='Keep requesting pages until a page returns no media'), tsv=typer.Option(False, '--tsv', help='Print tab-separated values (header row + metadata line) for scripting'), json_out=typer.Option(False, '--json', help='Print full API JSON (with --all-pages: list of page payloads)'))

Run search against the cloud API and print results.

Source code in gopro_api/cli/search.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
@app.command(
    "search",
    help=(
        "List media in a capture date range (Rich table by default; "
        "--tsv for tab-separated fields; --json for raw API payloads)"
    ),
)
def search_command(  # pylint: disable=too-many-arguments
    ctx: typer.Context,
    *,
    start: str = typer.Option(
        ...,
        "--start",
        help="Range start: YYYY-MM-DD or ISO datetime",
    ),
    end: str = typer.Option(
        ...,
        "--end",
        help=(
            "Range end: YYYY-MM-DD or ISO datetime "
            "(API treats range as in query string)"
        ),
    ),
    page: int = typer.Option(1, "--page", help="Page number (default: 1)"),
    per_page: int = typer.Option(30, "--per-page", help="Page size (default: 30)"),
    all_pages: bool = typer.Option(
        False,
        "--all-pages",
        help="Keep requesting pages until a page returns no media",
    ),
    tsv: bool = typer.Option(
        False,
        "--tsv",
        help="Print tab-separated values (header row + metadata line) for scripting",
    ),
    json_out: bool = typer.Option(
        False,
        "--json",
        help="Print full API JSON (with --all-pages: list of page payloads)",
    ),
) -> None:
    """Run search against the cloud API and print results."""
    asyncio.run(
        _run_search(
            timeout=ctx.obj["timeout"],
            params=_SearchParams(
                start=start,
                end=end,
                page=page,
                per_page=per_page,
                all_pages=all_pages,
                json_out=json_out,
                tsv=tsv,
            ),
        ),
    )

gopro_api.cli.info_command(ctx, media_id=typer.Argument(..., help='Media id from search'), tsv=typer.Option(False, '--tsv', help='Print tab-separated values for scripting'), json_out=typer.Option(False, '--json', help='Print full API JSON'))

Fetch and display download metadata for media_id.

Source code in gopro_api/cli/info.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
@app.command(
    "info",
    help=(
        "Show download metadata for one media id "
        "(Rich table by default; --tsv for tab-separated; --json for raw API)"
    ),
)
def info_command(
    ctx: typer.Context,
    media_id: str = typer.Argument(..., help="Media id from search"),
    tsv: bool = typer.Option(
        False,
        "--tsv",
        help="Print tab-separated values for scripting",
    ),
    json_out: bool = typer.Option(False, "--json", help="Print full API JSON"),
) -> None:
    """Fetch and display download metadata for ``media_id``."""
    asyncio.run(
        _run_info(
            timeout=ctx.obj["timeout"],
            media_id=media_id,
            json_out=json_out,
            tsv=tsv,
        ),
    )

gopro_api.cli.pull_command(ctx, media_id=typer.Argument(..., help='Media id from search'), destination=typer.Argument(..., help='Path to save the file'), height=typer.Option(None, '--height', metavar='PX', help='For video: pick the variation whose height is closest to PX (default: tallest)'), width=typer.Option(None, '--width', metavar='PX', help='For video: pick the variation whose width is closest to PX (default: tallest)'), tsv=typer.Option(False, '--tsv', help='Print tab-separated summary instead of the Rich table'))

Download all resolved files for media_id into destination.

Source code in gopro_api/cli/pull.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
@app.command(
    "pull",
    help=(
        "Download files from a media id (prints a Rich summary by default; "
        "--tsv for tab-separated)"
    ),
)
def pull_command(  # pylint: disable=too-many-positional-arguments
    ctx: typer.Context,
    media_id: str = typer.Argument(..., help="Media id from search"),
    destination: str = typer.Argument(..., help="Path to save the file"),
    height: Optional[int] = typer.Option(
        None,
        "--height",
        metavar="PX",
        help=(
            "For video: pick the variation whose height is closest to PX "
            "(default: tallest)"
        ),
    ),
    width: Optional[int] = typer.Option(
        None,
        "--width",
        metavar="PX",
        help=(
            "For video: pick the variation whose width is closest to PX "
            "(default: tallest)"
        ),
    ),
    tsv: bool = typer.Option(
        False,
        "--tsv",
        help="Print tab-separated summary instead of the Rich table",
    ),
) -> None:
    """Download all resolved files for ``media_id`` into ``destination``."""
    height = _validate_positive_px(height, "--height")
    width = _validate_positive_px(width, "--width")
    asyncio.run(
        _run_pull(
            timeout=ctx.obj["timeout"],
            media_id=media_id,
            destination=destination,
            height=height,
            width=width,
            tsv=tsv,
        ),
    )

Printers

gopro_api.cli.search.SearchPrinter

Handles all search output formatting: Rich table, TSV, and JSON key renaming.

Source code in gopro_api/cli/search.py
 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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
class SearchPrinter:
    """Handles all search output formatting: Rich table, TSV, and JSON key renaming."""

    def __init__(self, console: Console | None = None) -> None:
        """Initialize with an optional Rich console.

        Args:
            console: Console used for Rich output; a default soft-wrap console is
                created when ``None``.
        """
        self._console = console or Console(soft_wrap=True)

    def page_meta_line(self, page: GoProMediaSearchResponse) -> str:
        """Format the pagination metadata comment line for a search page.

        Args:
            page: Search response containing pagination details.

        Returns:
            A ``# _pages:`` comment string with current page, per-page, total items,
            and total pages.
        """
        pages = page.pages
        return (
            f"# _pages: current_page={pages.current_page} per_page={pages.per_page} "
            f"total_items={pages.total_items} total_pages={pages.total_pages}"
        )

    def emit_embedded_errors(self, page: GoProMediaSearchResponse) -> None:
        """Print any embedded API errors to stderr as yellow comment lines.

        Args:
            page: Search response whose ``_embedded.errors`` list is checked.
        """
        if page.embedded.errors:
            for err in page.embedded.errors:
                typer.secho(
                    f"# _embedded.errors: {json.dumps(err, ensure_ascii=False)}",
                    fg=typer.colors.YELLOW,
                    err=True,
                )

    def cells_plain(self, item: GoProMediaSearchItem) -> list[str]:
        """Build raw string cells for TSV output.

        Args:
            item: A single media item from the search response.

        Returns:
            Ordered list of strings for each field in ``DEFAULT_FIELDS``;
            missing values are represented as empty strings.
        """
        row = item.model_dump(mode="json")
        return ["" if row.get(c) is None else str(row[c]) for c in DEFAULT_FIELDS]

    def cells_rich(self, item: GoProMediaSearchItem) -> list[str]:
        """Build human-formatted cells for Rich table output.

        File sizes are formatted with decimal SI units; all other values are
        stringified as-is.

        Args:
            item: A single media item from the search response.

        Returns:
            Ordered list of display strings for each field in ``DEFAULT_FIELDS``.
        """
        row = item.model_dump(mode="json")
        cells: list[str] = []
        for c in DEFAULT_FIELDS:
            val = row.get(c)
            if val is None:
                cells.append("")
            elif c == "file_size":
                cells.append(format_decimal_size(int(val)))
            else:
                cells.append(str(val))
        return cells

    def make_table(self) -> Table:
        """Build an empty Rich Table with the default search columns.

        Returns:
            A ``rich.table.Table`` configured with per-column overflow settings,
            ready to receive rows via ``add_row``.
        """
        table = Table(show_header=True, header_style="bold")
        for name in DEFAULT_FIELDS:
            col_kw: dict = {}
            if name == "filename":
                col_kw["overflow"] = "ellipsis"
                col_kw["max_width"] = 40
            elif name == "captured_at":
                col_kw["overflow"] = "ellipsis"
                col_kw["max_width"] = 28
            elif name == "id":
                col_kw["overflow"] = "fold"
            elif name == "type":
                col_kw["overflow"] = "ellipsis"
                col_kw["max_width"] = 14
            table.add_column(_renamed_field(name), **col_kw)
        return table

    def print_table(self, table: Table) -> None:
        """Print a Rich table to the console.

        Args:
            table: Fully populated Rich table to render.
        """
        self._console.print(table)

    def print_tsv_page(
        self, page: GoProMediaSearchResponse, *, header: bool = True
    ) -> None:
        """Print a TSV-formatted search page to stdout.

        Args:
            page: Search response page to render.
            header: When ``True`` (default), emit the column-name header row first;
                set to ``False`` for subsequent pages in ``--all-pages`` mode.
        """
        typer.echo(self.page_meta_line(page))
        self.emit_embedded_errors(page)
        if header:
            typer.echo("\t".join(_renamed_field(c) for c in DEFAULT_FIELDS))
        for item in page.embedded.media:
            typer.echo("\t".join(self.cells_plain(item)))

    def print_rich_page(self, page: GoProMediaSearchResponse) -> None:
        """Print a single Rich-formatted search page with metadata and a table.

        Args:
            page: Search response page to render.
        """
        self.emit_embedded_errors(page)
        typer.echo(self.page_meta_line(page))
        table = self.make_table()
        for item in page.embedded.media:
            table.add_row(*self.cells_rich(item))
        self._console.print(table)

    def append_rich_rows(self, table: Table, page: GoProMediaSearchResponse) -> None:
        """Append a page's media rows to an existing Rich table.

        Used in ``--all-pages`` mode to accumulate rows across pages before a
        single final render.

        Args:
            table: Rich table to append rows to.
            page: Search response page whose media items are appended.
        """
        self.emit_embedded_errors(page)
        for item in page.embedded.media:
            table.add_row(*self.cells_rich(item))

    def rename_payload(self, payload: dict) -> dict:
        """Apply display-name aliases to a raw API JSON payload.

        Renames keys inside ``_embedded.media`` items according to
        ``_FIELD_LABELS`` so that JSON output uses the same names as the table
        headers.

        Args:
            payload: Raw API payload dict (typically from ``model_dump``).

        Returns:
            The same ``payload`` dict with ``_embedded.media`` keys renamed in-place.
        """
        embedded = payload.get("_embedded")
        if isinstance(embedded, dict):
            media = embedded.get("media")
            if isinstance(media, list):
                embedded["media"] = [
                    (
                        {_renamed_field(k): v for k, v in it.items()}
                        if isinstance(it, dict)
                        else it
                    )
                    for it in media
                ]
        return payload

__init__(console=None)

Initialize with an optional Rich console.

Parameters:

Name Type Description Default
console Console | None

Console used for Rich output; a default soft-wrap console is created when None.

None
Source code in gopro_api/cli/search.py
45
46
47
48
49
50
51
52
def __init__(self, console: Console | None = None) -> None:
    """Initialize with an optional Rich console.

    Args:
        console: Console used for Rich output; a default soft-wrap console is
            created when ``None``.
    """
    self._console = console or Console(soft_wrap=True)

page_meta_line(page)

Format the pagination metadata comment line for a search page.

Parameters:

Name Type Description Default
page GoProMediaSearchResponse

Search response containing pagination details.

required

Returns:

Type Description
str

A # _pages: comment string with current page, per-page, total items,

str

and total pages.

Source code in gopro_api/cli/search.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def page_meta_line(self, page: GoProMediaSearchResponse) -> str:
    """Format the pagination metadata comment line for a search page.

    Args:
        page: Search response containing pagination details.

    Returns:
        A ``# _pages:`` comment string with current page, per-page, total items,
        and total pages.
    """
    pages = page.pages
    return (
        f"# _pages: current_page={pages.current_page} per_page={pages.per_page} "
        f"total_items={pages.total_items} total_pages={pages.total_pages}"
    )

emit_embedded_errors(page)

Print any embedded API errors to stderr as yellow comment lines.

Parameters:

Name Type Description Default
page GoProMediaSearchResponse

Search response whose _embedded.errors list is checked.

required
Source code in gopro_api/cli/search.py
70
71
72
73
74
75
76
77
78
79
80
81
82
def emit_embedded_errors(self, page: GoProMediaSearchResponse) -> None:
    """Print any embedded API errors to stderr as yellow comment lines.

    Args:
        page: Search response whose ``_embedded.errors`` list is checked.
    """
    if page.embedded.errors:
        for err in page.embedded.errors:
            typer.secho(
                f"# _embedded.errors: {json.dumps(err, ensure_ascii=False)}",
                fg=typer.colors.YELLOW,
                err=True,
            )

cells_plain(item)

Build raw string cells for TSV output.

Parameters:

Name Type Description Default
item GoProMediaSearchItem

A single media item from the search response.

required

Returns:

Type Description
list[str]

Ordered list of strings for each field in DEFAULT_FIELDS;

list[str]

missing values are represented as empty strings.

Source code in gopro_api/cli/search.py
84
85
86
87
88
89
90
91
92
93
94
95
def cells_plain(self, item: GoProMediaSearchItem) -> list[str]:
    """Build raw string cells for TSV output.

    Args:
        item: A single media item from the search response.

    Returns:
        Ordered list of strings for each field in ``DEFAULT_FIELDS``;
        missing values are represented as empty strings.
    """
    row = item.model_dump(mode="json")
    return ["" if row.get(c) is None else str(row[c]) for c in DEFAULT_FIELDS]

cells_rich(item)

Build human-formatted cells for Rich table output.

File sizes are formatted with decimal SI units; all other values are stringified as-is.

Parameters:

Name Type Description Default
item GoProMediaSearchItem

A single media item from the search response.

required

Returns:

Type Description
list[str]

Ordered list of display strings for each field in DEFAULT_FIELDS.

Source code in gopro_api/cli/search.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def cells_rich(self, item: GoProMediaSearchItem) -> list[str]:
    """Build human-formatted cells for Rich table output.

    File sizes are formatted with decimal SI units; all other values are
    stringified as-is.

    Args:
        item: A single media item from the search response.

    Returns:
        Ordered list of display strings for each field in ``DEFAULT_FIELDS``.
    """
    row = item.model_dump(mode="json")
    cells: list[str] = []
    for c in DEFAULT_FIELDS:
        val = row.get(c)
        if val is None:
            cells.append("")
        elif c == "file_size":
            cells.append(format_decimal_size(int(val)))
        else:
            cells.append(str(val))
    return cells

make_table()

Build an empty Rich Table with the default search columns.

Returns:

Type Description
Table

A rich.table.Table configured with per-column overflow settings,

Table

ready to receive rows via add_row.

Source code in gopro_api/cli/search.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def make_table(self) -> Table:
    """Build an empty Rich Table with the default search columns.

    Returns:
        A ``rich.table.Table`` configured with per-column overflow settings,
        ready to receive rows via ``add_row``.
    """
    table = Table(show_header=True, header_style="bold")
    for name in DEFAULT_FIELDS:
        col_kw: dict = {}
        if name == "filename":
            col_kw["overflow"] = "ellipsis"
            col_kw["max_width"] = 40
        elif name == "captured_at":
            col_kw["overflow"] = "ellipsis"
            col_kw["max_width"] = 28
        elif name == "id":
            col_kw["overflow"] = "fold"
        elif name == "type":
            col_kw["overflow"] = "ellipsis"
            col_kw["max_width"] = 14
        table.add_column(_renamed_field(name), **col_kw)
    return table

print_table(table)

Print a Rich table to the console.

Parameters:

Name Type Description Default
table Table

Fully populated Rich table to render.

required
Source code in gopro_api/cli/search.py
145
146
147
148
149
150
151
def print_table(self, table: Table) -> None:
    """Print a Rich table to the console.

    Args:
        table: Fully populated Rich table to render.
    """
    self._console.print(table)

print_tsv_page(page, *, header=True)

Print a TSV-formatted search page to stdout.

Parameters:

Name Type Description Default
page GoProMediaSearchResponse

Search response page to render.

required
header bool

When True (default), emit the column-name header row first; set to False for subsequent pages in --all-pages mode.

True
Source code in gopro_api/cli/search.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def print_tsv_page(
    self, page: GoProMediaSearchResponse, *, header: bool = True
) -> None:
    """Print a TSV-formatted search page to stdout.

    Args:
        page: Search response page to render.
        header: When ``True`` (default), emit the column-name header row first;
            set to ``False`` for subsequent pages in ``--all-pages`` mode.
    """
    typer.echo(self.page_meta_line(page))
    self.emit_embedded_errors(page)
    if header:
        typer.echo("\t".join(_renamed_field(c) for c in DEFAULT_FIELDS))
    for item in page.embedded.media:
        typer.echo("\t".join(self.cells_plain(item)))

print_rich_page(page)

Print a single Rich-formatted search page with metadata and a table.

Parameters:

Name Type Description Default
page GoProMediaSearchResponse

Search response page to render.

required
Source code in gopro_api/cli/search.py
170
171
172
173
174
175
176
177
178
179
180
181
def print_rich_page(self, page: GoProMediaSearchResponse) -> None:
    """Print a single Rich-formatted search page with metadata and a table.

    Args:
        page: Search response page to render.
    """
    self.emit_embedded_errors(page)
    typer.echo(self.page_meta_line(page))
    table = self.make_table()
    for item in page.embedded.media:
        table.add_row(*self.cells_rich(item))
    self._console.print(table)

append_rich_rows(table, page)

Append a page's media rows to an existing Rich table.

Used in --all-pages mode to accumulate rows across pages before a single final render.

Parameters:

Name Type Description Default
table Table

Rich table to append rows to.

required
page GoProMediaSearchResponse

Search response page whose media items are appended.

required
Source code in gopro_api/cli/search.py
183
184
185
186
187
188
189
190
191
192
193
194
195
def append_rich_rows(self, table: Table, page: GoProMediaSearchResponse) -> None:
    """Append a page's media rows to an existing Rich table.

    Used in ``--all-pages`` mode to accumulate rows across pages before a
    single final render.

    Args:
        table: Rich table to append rows to.
        page: Search response page whose media items are appended.
    """
    self.emit_embedded_errors(page)
    for item in page.embedded.media:
        table.add_row(*self.cells_rich(item))

rename_payload(payload)

Apply display-name aliases to a raw API JSON payload.

Renames keys inside _embedded.media items according to _FIELD_LABELS so that JSON output uses the same names as the table headers.

Parameters:

Name Type Description Default
payload dict

Raw API payload dict (typically from model_dump).

required

Returns:

Type Description
dict

The same payload dict with _embedded.media keys renamed in-place.

Source code in gopro_api/cli/search.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def rename_payload(self, payload: dict) -> dict:
    """Apply display-name aliases to a raw API JSON payload.

    Renames keys inside ``_embedded.media`` items according to
    ``_FIELD_LABELS`` so that JSON output uses the same names as the table
    headers.

    Args:
        payload: Raw API payload dict (typically from ``model_dump``).

    Returns:
        The same ``payload`` dict with ``_embedded.media`` keys renamed in-place.
    """
    embedded = payload.get("_embedded")
    if isinstance(embedded, dict):
        media = embedded.get("media")
        if isinstance(media, list):
            embedded["media"] = [
                (
                    {_renamed_field(k): v for k, v in it.items()}
                    if isinstance(it, dict)
                    else it
                )
                for it in media
            ]
    return payload

gopro_api.cli.info.InfoPrinter

Handles Rich table and TSV rendering for the info command.

Source code in gopro_api/cli/info.py
 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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class InfoPrinter:
    """Handles Rich table and TSV rendering for the info command."""

    VARIATION_HEADERS = ["idx", "label", "quality", "type", "dim", "available", "url"]
    FILE_HEADERS = ["idx", "item", "camera", "dim", "available", "url"]
    SIDECAR_HEADERS = ["idx", "label", "type", "fps", "available", "url"]

    def __init__(self, console: Console | None = None) -> None:
        """Initialize with an optional Rich console.

        Args:
            console: Console used for Rich output; a default soft-wrap console is
                created when ``None``.
        """
        self._console = console or Console(soft_wrap=True)

    def variation_cells(self, idx: int, v: GoProMediaDownloadVariation) -> list[str]:
        """Build row cells for a video variation entry.

        Args:
            idx: Zero-based row index shown in the ``idx`` column.
            v: Variation object from the download metadata.

        Returns:
            Ordered list of strings matching ``VARIATION_HEADERS``.
        """
        return [
            str(idx),
            v.label,
            v.quality,
            v.type,
            f"{v.width}x{v.height}",
            _yes_no(v.available),
            v.url,
        ]

    def file_cells(self, idx: int, f: GoProMediaDownloadFile) -> list[str]:
        """Build row cells for a multi-lens file entry.

        Args:
            idx: Zero-based row index shown in the ``idx`` column.
            f: File object from the download metadata.

        Returns:
            Ordered list of strings matching ``FILE_HEADERS``.
        """
        return [
            str(idx),
            str(f.item_number),
            f.camera_position,
            f"{f.width}x{f.height}",
            _yes_no(f.available),
            f.url,
        ]

    def sidecar_cells(self, idx: int, s: GoProMediaDownloadSidecarFile) -> list[str]:
        """Build row cells for a sidecar file entry.

        Args:
            idx: Zero-based row index shown in the ``idx`` column.
            s: Sidecar file object from the download metadata.

        Returns:
            Ordered list of strings matching ``SIDECAR_HEADERS``.
        """
        return [
            str(idx),
            s.label,
            s.type,
            str(s.fps),
            _yes_no(s.available),
            s.url,
        ]

    def print_rich(self, meta: GoProMediaDownloadResponse) -> None:
        """Print a Rich table of variations or files, plus sidecars when present.

        Args:
            meta: Download metadata response from the GoPro API.
        """
        typer.secho(meta.filename, bold=True)
        if is_video_filename(meta.filename):
            table = _build_basic_table(self.VARIATION_HEADERS)
            for idx, v in enumerate(meta.embedded.variations):
                table.add_row(*self.variation_cells(idx, v))
        else:
            table = _build_basic_table(self.FILE_HEADERS)
            for idx, f in enumerate(meta.embedded.files):
                table.add_row(*self.file_cells(idx, f))
        self._console.print(table)
        if meta.embedded.sidecar_files:
            typer.secho("sidecars", bold=True)
            sidecars = _build_basic_table(self.SIDECAR_HEADERS)
            for idx, s in enumerate(meta.embedded.sidecar_files):
                sidecars.add_row(*self.sidecar_cells(idx, s))
            self._console.print(sidecars)

    def print_tsv(self, meta: GoProMediaDownloadResponse) -> None:
        """Print tab-separated rows of variations or files, plus sidecars when present.

        Args:
            meta: Download metadata response from the GoPro API.
        """
        typer.echo(f"# filename: {meta.filename}")
        if is_video_filename(meta.filename):
            typer.echo("\t".join(self.VARIATION_HEADERS))
            for idx, v in enumerate(meta.embedded.variations):
                typer.echo("\t".join(self.variation_cells(idx, v)))
        else:
            typer.echo("\t".join(self.FILE_HEADERS))
            for idx, f in enumerate(meta.embedded.files):
                typer.echo("\t".join(self.file_cells(idx, f)))
        if meta.embedded.sidecar_files:
            typer.echo("# sidecars")
            typer.echo("\t".join(self.SIDECAR_HEADERS))
            for idx, s in enumerate(meta.embedded.sidecar_files):
                typer.echo("\t".join(self.sidecar_cells(idx, s)))

__init__(console=None)

Initialize with an optional Rich console.

Parameters:

Name Type Description Default
console Console | None

Console used for Rich output; a default soft-wrap console is created when None.

None
Source code in gopro_api/cli/info.py
31
32
33
34
35
36
37
38
def __init__(self, console: Console | None = None) -> None:
    """Initialize with an optional Rich console.

    Args:
        console: Console used for Rich output; a default soft-wrap console is
            created when ``None``.
    """
    self._console = console or Console(soft_wrap=True)

variation_cells(idx, v)

Build row cells for a video variation entry.

Parameters:

Name Type Description Default
idx int

Zero-based row index shown in the idx column.

required
v GoProMediaDownloadVariation

Variation object from the download metadata.

required

Returns:

Type Description
list[str]

Ordered list of strings matching VARIATION_HEADERS.

Source code in gopro_api/cli/info.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def variation_cells(self, idx: int, v: GoProMediaDownloadVariation) -> list[str]:
    """Build row cells for a video variation entry.

    Args:
        idx: Zero-based row index shown in the ``idx`` column.
        v: Variation object from the download metadata.

    Returns:
        Ordered list of strings matching ``VARIATION_HEADERS``.
    """
    return [
        str(idx),
        v.label,
        v.quality,
        v.type,
        f"{v.width}x{v.height}",
        _yes_no(v.available),
        v.url,
    ]

file_cells(idx, f)

Build row cells for a multi-lens file entry.

Parameters:

Name Type Description Default
idx int

Zero-based row index shown in the idx column.

required
f GoProMediaDownloadFile

File object from the download metadata.

required

Returns:

Type Description
list[str]

Ordered list of strings matching FILE_HEADERS.

Source code in gopro_api/cli/info.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def file_cells(self, idx: int, f: GoProMediaDownloadFile) -> list[str]:
    """Build row cells for a multi-lens file entry.

    Args:
        idx: Zero-based row index shown in the ``idx`` column.
        f: File object from the download metadata.

    Returns:
        Ordered list of strings matching ``FILE_HEADERS``.
    """
    return [
        str(idx),
        str(f.item_number),
        f.camera_position,
        f"{f.width}x{f.height}",
        _yes_no(f.available),
        f.url,
    ]

sidecar_cells(idx, s)

Build row cells for a sidecar file entry.

Parameters:

Name Type Description Default
idx int

Zero-based row index shown in the idx column.

required
s GoProMediaDownloadSidecarFile

Sidecar file object from the download metadata.

required

Returns:

Type Description
list[str]

Ordered list of strings matching SIDECAR_HEADERS.

Source code in gopro_api/cli/info.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def sidecar_cells(self, idx: int, s: GoProMediaDownloadSidecarFile) -> list[str]:
    """Build row cells for a sidecar file entry.

    Args:
        idx: Zero-based row index shown in the ``idx`` column.
        s: Sidecar file object from the download metadata.

    Returns:
        Ordered list of strings matching ``SIDECAR_HEADERS``.
    """
    return [
        str(idx),
        s.label,
        s.type,
        str(s.fps),
        _yes_no(s.available),
        s.url,
    ]

print_rich(meta)

Print a Rich table of variations or files, plus sidecars when present.

Parameters:

Name Type Description Default
meta GoProMediaDownloadResponse

Download metadata response from the GoPro API.

required
Source code in gopro_api/cli/info.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def print_rich(self, meta: GoProMediaDownloadResponse) -> None:
    """Print a Rich table of variations or files, plus sidecars when present.

    Args:
        meta: Download metadata response from the GoPro API.
    """
    typer.secho(meta.filename, bold=True)
    if is_video_filename(meta.filename):
        table = _build_basic_table(self.VARIATION_HEADERS)
        for idx, v in enumerate(meta.embedded.variations):
            table.add_row(*self.variation_cells(idx, v))
    else:
        table = _build_basic_table(self.FILE_HEADERS)
        for idx, f in enumerate(meta.embedded.files):
            table.add_row(*self.file_cells(idx, f))
    self._console.print(table)
    if meta.embedded.sidecar_files:
        typer.secho("sidecars", bold=True)
        sidecars = _build_basic_table(self.SIDECAR_HEADERS)
        for idx, s in enumerate(meta.embedded.sidecar_files):
            sidecars.add_row(*self.sidecar_cells(idx, s))
        self._console.print(sidecars)

print_tsv(meta)

Print tab-separated rows of variations or files, plus sidecars when present.

Parameters:

Name Type Description Default
meta GoProMediaDownloadResponse

Download metadata response from the GoPro API.

required
Source code in gopro_api/cli/info.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def print_tsv(self, meta: GoProMediaDownloadResponse) -> None:
    """Print tab-separated rows of variations or files, plus sidecars when present.

    Args:
        meta: Download metadata response from the GoPro API.
    """
    typer.echo(f"# filename: {meta.filename}")
    if is_video_filename(meta.filename):
        typer.echo("\t".join(self.VARIATION_HEADERS))
        for idx, v in enumerate(meta.embedded.variations):
            typer.echo("\t".join(self.variation_cells(idx, v)))
    else:
        typer.echo("\t".join(self.FILE_HEADERS))
        for idx, f in enumerate(meta.embedded.files):
            typer.echo("\t".join(self.file_cells(idx, f)))
    if meta.embedded.sidecar_files:
        typer.echo("# sidecars")
        typer.echo("\t".join(self.SIDECAR_HEADERS))
        for idx, s in enumerate(meta.embedded.sidecar_files):
            typer.echo("\t".join(self.sidecar_cells(idx, s)))

gopro_api.cli.pull.PullPrinter

Handles Rich table and TSV rendering for the pull command.

Source code in gopro_api/cli/pull.py
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
class PullPrinter:
    """Handles Rich table and TSV rendering for the pull command."""

    HEADERS = ["filename", "dim", "available", "url"]

    def __init__(self, console: Console | None = None) -> None:
        """Initialize with an optional Rich console.

        Args:
            console: Console used for Rich output; a default soft-wrap console is
                created when ``None``.
        """
        self._console = console or Console(soft_wrap=True)

    def summary_cells(self, filename: str, asset: DownloadAsset) -> list[str]:
        """Build row cells for a single download asset.

        Args:
            filename: Local filename that the asset will be saved as.
            asset: Resolved download asset containing URL and dimensions.

        Returns:
            Ordered list of strings matching ``HEADERS``.
        """
        return [
            filename,
            f"{asset.width}x{asset.height}",
            _yes_no(asset.available),
            asset.url,
        ]

    def print_rich(self, assets: dict[str, DownloadAsset], destination: str) -> None:
        """Print a Rich summary table of all assets to be downloaded.

        Args:
            assets: Mapping of local filename to resolved download asset.
            destination: Target directory path shown in the header line.
        """
        typer.secho(
            f"Pulling {len(assets)} file(s) to {destination}",
            bold=True,
        )
        table = _build_basic_table(self.HEADERS)
        for filename, asset in assets.items():
            table.add_row(*self.summary_cells(filename, asset))
        self._console.print(table)

    def print_tsv(self, assets: dict[str, DownloadAsset], destination: str) -> None:
        """Print a tab-separated summary of all assets for scripting.

        Args:
            assets: Mapping of local filename to resolved download asset.
            destination: Target directory path shown in the comment header.
        """
        typer.echo(f"# destination: {destination}")
        typer.echo("\t".join(self.HEADERS))
        for filename, asset in assets.items():
            typer.echo("\t".join(self.summary_cells(filename, asset)))

__init__(console=None)

Initialize with an optional Rich console.

Parameters:

Name Type Description Default
console Console | None

Console used for Rich output; a default soft-wrap console is created when None.

None
Source code in gopro_api/cli/pull.py
25
26
27
28
29
30
31
32
def __init__(self, console: Console | None = None) -> None:
    """Initialize with an optional Rich console.

    Args:
        console: Console used for Rich output; a default soft-wrap console is
            created when ``None``.
    """
    self._console = console or Console(soft_wrap=True)

summary_cells(filename, asset)

Build row cells for a single download asset.

Parameters:

Name Type Description Default
filename str

Local filename that the asset will be saved as.

required
asset DownloadAsset

Resolved download asset containing URL and dimensions.

required

Returns:

Type Description
list[str]

Ordered list of strings matching HEADERS.

Source code in gopro_api/cli/pull.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def summary_cells(self, filename: str, asset: DownloadAsset) -> list[str]:
    """Build row cells for a single download asset.

    Args:
        filename: Local filename that the asset will be saved as.
        asset: Resolved download asset containing URL and dimensions.

    Returns:
        Ordered list of strings matching ``HEADERS``.
    """
    return [
        filename,
        f"{asset.width}x{asset.height}",
        _yes_no(asset.available),
        asset.url,
    ]

print_rich(assets, destination)

Print a Rich summary table of all assets to be downloaded.

Parameters:

Name Type Description Default
assets dict[str, DownloadAsset]

Mapping of local filename to resolved download asset.

required
destination str

Target directory path shown in the header line.

required
Source code in gopro_api/cli/pull.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def print_rich(self, assets: dict[str, DownloadAsset], destination: str) -> None:
    """Print a Rich summary table of all assets to be downloaded.

    Args:
        assets: Mapping of local filename to resolved download asset.
        destination: Target directory path shown in the header line.
    """
    typer.secho(
        f"Pulling {len(assets)} file(s) to {destination}",
        bold=True,
    )
    table = _build_basic_table(self.HEADERS)
    for filename, asset in assets.items():
        table.add_row(*self.summary_cells(filename, asset))
    self._console.print(table)

print_tsv(assets, destination)

Print a tab-separated summary of all assets for scripting.

Parameters:

Name Type Description Default
assets dict[str, DownloadAsset]

Mapping of local filename to resolved download asset.

required
destination str

Target directory path shown in the comment header.

required
Source code in gopro_api/cli/pull.py
67
68
69
70
71
72
73
74
75
76
77
def print_tsv(self, assets: dict[str, DownloadAsset], destination: str) -> None:
    """Print a tab-separated summary of all assets for scripting.

    Args:
        assets: Mapping of local filename to resolved download asset.
        destination: Target directory path shown in the comment header.
    """
    typer.echo(f"# destination: {destination}")
    typer.echo("\t".join(self.HEADERS))
    for filename, asset in assets.items():
        typer.echo("\t".join(self.summary_cells(filename, asset)))