diff --git a/README.md b/README.md index 2c09d01..75bea44 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,39 @@ provided on PyPI. #### Info ``` -$ reolinkfw info file_or_url +usage: reolinkfw info [-h] [--no-cache] [-j [indent]] file_or_url + +positional arguments: + file_or_url URL or on-disk file + +optional arguments: + -h, --help show this help message and exit + --no-cache don't use cache for remote files (URLs) + -j [indent], --json [indent] JSON output with optional indentation level for pretty print ``` Example: ``` -$ reolinkfw info RLC-410-5MP_20_20052300.zip -i 2 +$ reolinkfw info RLC-410-5MP_20_20052300.zip +IPC_51516M5M.20_20052300.RLC-410-5MP.OV05A10.5MP.REOLINK.pak +Model: RLC-410-5MP +Hardware info: IPC_51516M5M +Device type: IPC +Firmware version: v3.0.0.20_20052300 +Build date: 2020-05-23 +Architecture: MIPS +OS: Linux +Kernel image name: Linux-4.1.0 +U-Boot version: U-Boot 2014.07 (Feb 26 2019 - 18:20:07) +File system: squashfs +File system sections: fs +``` + +Or with JSON output: + +``` +$ reolinkfw info RLC-410-5MP_20_20052300.zip -j 2 [ { "firmware_version_prefix": "v3.0.0", diff --git a/reolinkfw/__main__.py b/reolinkfw/__main__.py index 8c86d05..97b6f94 100644 --- a/reolinkfw/__main__.py +++ b/reolinkfw/__main__.py @@ -1,22 +1,50 @@ #!/usr/bin/env python3 -import argparse import asyncio import json import sys +from argparse import ArgumentParser, Namespace +from datetime import datetime from pathlib import Path, PurePath from reolinkfw import __version__, get_info, get_paks from reolinkfw.extract import extract_pak from reolinkfw.util import sha256_pak +HW_FIELDS = ("board_type", "detail_machine_type", "board_name") -async def info(args: argparse.Namespace) -> None: - info = await get_info(args.file_or_url, not args.no_cache) - print(json.dumps(info, indent=args.indent, default=str)) +async def info(args: Namespace) -> None: + pak_infos = await get_info(args.file_or_url, not args.no_cache) + if args.json is None: + width = 21 + for idx, info in enumerate(pak_infos): + info = Namespace(**info) + fs_types = set(fs["type"] for fs in info.filesystems) + fs_names = [fs["name"] for fs in info.filesystems] + version = f"{info.firmware_version_prefix}.{info.version_file}" + hw_names = set(getattr(info, key) for key in HW_FIELDS) + build_date = datetime.strptime(info.build_date, "%y%m%d").date() + print(info.pak) + print(f"{'Model:':{width}}", info.display_type_info) + print(f"{'Hardware info:':{width}}", ', '.join(sorted(hw_names))) + print(f"{'Device type:':{width}}", info.type) + print(f"{'Firmware version:':{width}}", version) + print(f"{'Build date:':{width}}", build_date) + print(f"{'Architecture:':{width}}", info.architecture) + print(f"{'OS:':{width}}", info.os) + print(f"{'Kernel image name:':{width}}", info.kernel_image_name) + print(f"{'U-Boot version:':{width}}", info.uboot_version or "Unknown") + print(f"{'File system:':{width}}", ', '.join(sorted(fs_types))) + print(f"{'File system sections:':{width}}", ', '.join(fs_names)) + if idx != len(pak_infos) - 1: + print() + else: + indent = None if args.json < 0 else args.json + print(json.dumps(pak_infos, indent=indent, default=str)) -async def extract(args: argparse.Namespace) -> None: + +async def extract(args: Namespace) -> None: paks = await get_paks(args.file_or_url, not args.no_cache) if not paks: raise Exception("No PAKs found in ZIP file") @@ -28,16 +56,16 @@ async def extract(args: argparse.Namespace) -> None: def main(): - parser = argparse.ArgumentParser(description="Extract information and files from Reolink firmwares") + parser = ArgumentParser(description="Extract information and files from Reolink firmwares") parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}") subparsers = parser.add_subparsers(required=True) - pcache = argparse.ArgumentParser(add_help=False) + pcache = ArgumentParser(add_help=False) pcache.add_argument("--no-cache", action="store_true", help="don't use cache for remote files (URLs)") parser_i = subparsers.add_parser("info", parents=[pcache]) parser_i.add_argument("file_or_url", help="URL or on-disk file") - parser_i.add_argument("-i", "--indent", type=int, help="indent level for pretty print") + parser_i.add_argument("-j", "--json", nargs='?', type=int, const=-1, metavar="indent", help="JSON output with optional indentation level for pretty print") parser_i.set_defaults(func=info) descex = "Extract the file system from a Reolink firmware"