{
  "name": "LumiBear tools",
  "version": 1,
  "site": "https://lumibear.dev",
  "about": "Free file tools (PDF, images, video, audio, GIF). The runner opens the tool page in a hidden local Chrome or Edge and processes your files there — they are never uploaded.",
  "runner": {
    "guide": "https://lumibear.dev/ai/",
    "download": "https://lumibear.dev/ai/lumibear-tools.zip",
    "usage": "node scripts/lumibear.mjs <tool> <file...> [--option value ...] [--out <dir>]",
    "flags": "An option without a value means true; --no-<option> means false. Outputs are saved next to the first input (never overwritten).",
    "result": "{ ok, tool, outputs: [{ path, size }], info, note } or { ok: false, error }"
  },
  "tools": [
    {
      "slug": "gif-maker",
      "name": "Video to GIF",
      "url": "https://lumibear.dev/tools/gif-maker/",
      "does": "Turn a video clip or a GIF into a GIF that fits a size limit.",
      "input": "One video (MP4, MOV, WebM) or GIF",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,image/gif,.gif",
      "multiple": false,
      "output": "<name>.gif",
      "tip": "Keep clips short (--start/--end in seconds). The size limit (MB) is met by lowering size, fps and colours.",
      "options": [
        {
          "name": "size",
          "type": "choice",
          "values": [
            "320",
            "480",
            "640",
            "0"
          ],
          "default": "480",
          "desc": "Width in px (0 = original)"
        },
        {
          "name": "fps",
          "type": "choice",
          "values": [
            "0",
            "10",
            "15",
            "20"
          ],
          "default": "15",
          "desc": "Frames per second (0 = keep the GIF’s own timing; GIF input only)"
        },
        {
          "name": "colors",
          "type": "choice",
          "values": [
            "255",
            "127",
            "63",
            "31"
          ],
          "default": "255",
          "desc": "Most colours per frame (255 = 256, 127 = 128, 63 = 64, 31 = 32)"
        },
        {
          "name": "speed",
          "type": "choice",
          "values": [
            "1",
            "1.5",
            "2",
            "3",
            "4",
            "8"
          ],
          "default": "1",
          "desc": "Playback speed"
        },
        {
          "name": "limit",
          "type": "choice",
          "values": [
            "1",
            "2",
            "5",
            "10",
            "20",
            "0"
          ],
          "default": "10",
          "desc": "Size limit in MB (0 = none); the GIF is made smaller until it fits"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "loop",
          "type": "flag",
          "default": true,
          "desc": "Loop forever"
        }
      ]
    },
    {
      "slug": "gif-compress",
      "name": "GIF compressor",
      "url": "https://lumibear.dev/tools/gif-compress/",
      "does": "Shrink a GIF to fit a size limit (a video works too).",
      "input": "One GIF or video",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,image/gif,.gif",
      "multiple": false,
      "output": "<name>.gif",
      "options": [
        {
          "name": "size",
          "type": "choice",
          "values": [
            "320",
            "480",
            "640",
            "0"
          ],
          "default": "480",
          "desc": "Width in px (0 = original)"
        },
        {
          "name": "fps",
          "type": "choice",
          "values": [
            "0",
            "10",
            "15",
            "20"
          ],
          "default": "15",
          "desc": "Frames per second (0 = keep the GIF’s own timing; GIF input only)"
        },
        {
          "name": "colors",
          "type": "choice",
          "values": [
            "255",
            "127",
            "63",
            "31"
          ],
          "default": "255",
          "desc": "Most colours per frame (255 = 256, 127 = 128, 63 = 64, 31 = 32)"
        },
        {
          "name": "speed",
          "type": "choice",
          "values": [
            "1",
            "1.5",
            "2",
            "3",
            "4",
            "8"
          ],
          "default": "1",
          "desc": "Playback speed"
        },
        {
          "name": "limit",
          "type": "choice",
          "values": [
            "1",
            "2",
            "5",
            "10",
            "20",
            "0"
          ],
          "default": "10",
          "desc": "Size limit in MB (0 = none); the GIF is made smaller until it fits"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "loop",
          "type": "flag",
          "default": true,
          "desc": "Loop forever"
        }
      ]
    },
    {
      "slug": "discord-emoji-maker",
      "name": "Discord emoji maker",
      "url": "https://lumibear.dev/tools/discord-emoji-maker/",
      "does": "Square animated emoji GIF for Discord (under 256 KB).",
      "input": "One video or GIF",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,image/gif,.gif",
      "multiple": false,
      "output": "<name>.gif",
      "tip": "Videos use their first 3 seconds unless --start/--end are given. limit is in KB.",
      "options": [
        {
          "name": "shape",
          "type": "choice",
          "values": [
            "crop",
            "fit"
          ],
          "default": "crop",
          "desc": "crop = fill the square (centre crop), fit = whole picture with transparent edges"
        },
        {
          "name": "size",
          "type": "choice",
          "values": [
            "64",
            "96",
            "128"
          ],
          "default": "128",
          "desc": "Square side in px"
        },
        {
          "name": "fps",
          "type": "choice",
          "values": [
            "0",
            "10",
            "15",
            "20"
          ],
          "default": "15",
          "desc": "Frames per second (0 = keep the GIF’s own timing; GIF input only)"
        },
        {
          "name": "colors",
          "type": "choice",
          "values": [
            "255",
            "127",
            "63",
            "31"
          ],
          "default": "255",
          "desc": "Most colours per frame (255 = 256, 127 = 128, 63 = 64, 31 = 32)"
        },
        {
          "name": "speed",
          "type": "choice",
          "values": [
            "1",
            "1.5",
            "2",
            "3",
            "4",
            "8"
          ],
          "default": "1",
          "desc": "Playback speed"
        },
        {
          "name": "limit",
          "type": "choice",
          "values": [
            "128",
            "256"
          ],
          "default": "256",
          "desc": "Size limit in KB"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "loop",
          "type": "flag",
          "default": true,
          "desc": "Loop forever"
        }
      ]
    },
    {
      "slug": "slack-emoji-maker",
      "name": "Slack emoji maker",
      "url": "https://lumibear.dev/tools/slack-emoji-maker/",
      "does": "Square animated emoji GIF for Slack (under 128 KB, at most 50 frames).",
      "input": "One video or GIF",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,image/gif,.gif",
      "multiple": false,
      "output": "<name>.gif",
      "tip": "limit is in KB.",
      "options": [
        {
          "name": "shape",
          "type": "choice",
          "values": [
            "crop",
            "fit"
          ],
          "default": "crop",
          "desc": "crop = fill the square (centre crop), fit = whole picture with transparent edges"
        },
        {
          "name": "size",
          "type": "choice",
          "values": [
            "64",
            "96",
            "128"
          ],
          "default": "128",
          "desc": "Square side in px"
        },
        {
          "name": "fps",
          "type": "choice",
          "values": [
            "0",
            "10",
            "15",
            "20"
          ],
          "default": "15",
          "desc": "Frames per second (0 = keep the GIF’s own timing; GIF input only)"
        },
        {
          "name": "colors",
          "type": "choice",
          "values": [
            "255",
            "127",
            "63",
            "31"
          ],
          "default": "255",
          "desc": "Most colours per frame (255 = 256, 127 = 128, 63 = 64, 31 = 32)"
        },
        {
          "name": "speed",
          "type": "choice",
          "values": [
            "1",
            "1.5",
            "2",
            "3",
            "4",
            "8"
          ],
          "default": "1",
          "desc": "Playback speed"
        },
        {
          "name": "limit",
          "type": "choice",
          "values": [
            "64",
            "128"
          ],
          "default": "128",
          "desc": "Size limit in KB"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "loop",
          "type": "flag",
          "default": true,
          "desc": "Loop forever"
        }
      ]
    },
    {
      "slug": "twitch-emote-maker",
      "name": "Twitch emote maker",
      "url": "https://lumibear.dev/tools/twitch-emote-maker/",
      "does": "Square animated emote GIF for Twitch (112 or 224 px, up to 1 MB, at most 60 frames).",
      "input": "One video or GIF",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,image/gif,.gif",
      "multiple": false,
      "output": "<name>.gif",
      "tip": "limit is in KB.",
      "options": [
        {
          "name": "shape",
          "type": "choice",
          "values": [
            "crop",
            "fit"
          ],
          "default": "crop",
          "desc": "crop = fill the square (centre crop), fit = whole picture with transparent edges"
        },
        {
          "name": "size",
          "type": "choice",
          "values": [
            "112",
            "224"
          ],
          "default": "112",
          "desc": "Square side in px"
        },
        {
          "name": "fps",
          "type": "choice",
          "values": [
            "0",
            "10",
            "15",
            "20"
          ],
          "default": "15",
          "desc": "Frames per second (0 = keep the GIF’s own timing; GIF input only)"
        },
        {
          "name": "colors",
          "type": "choice",
          "values": [
            "255",
            "127",
            "63",
            "31"
          ],
          "default": "255",
          "desc": "Most colours per frame (255 = 256, 127 = 128, 63 = 64, 31 = 32)"
        },
        {
          "name": "speed",
          "type": "choice",
          "values": [
            "1",
            "1.5",
            "2",
            "3",
            "4",
            "8"
          ],
          "default": "1",
          "desc": "Playback speed"
        },
        {
          "name": "limit",
          "type": "choice",
          "values": [
            "512",
            "1024"
          ],
          "default": "1024",
          "desc": "Size limit in KB"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "loop",
          "type": "flag",
          "default": true,
          "desc": "Loop forever"
        }
      ]
    },
    {
      "slug": "image-compress",
      "name": "Image compressor",
      "url": "https://lumibear.dev/tools/image-compress/",
      "does": "Make photos smaller (JPG, PNG, WebP, HEIC), many at once.",
      "input": "One or more images",
      "accept": "image/*,.heic,.heif,.avif",
      "multiple": true,
      "output": "One file per image",
      "options": [
        {
          "name": "format",
          "type": "choice",
          "values": [
            "keep",
            "jpeg",
            "webp",
            "png"
          ],
          "default": "keep",
          "desc": "keep = same as the input, or jpeg, webp, png"
        },
        {
          "name": "size",
          "type": "choice",
          "values": [
            "0",
            "2560",
            "1920",
            "1280",
            "800"
          ],
          "default": "0",
          "desc": "Longest side in px (0 = original)"
        },
        {
          "name": "target",
          "type": "choice",
          "values": [
            "0",
            "200",
            "500",
            "1024",
            "2048"
          ],
          "default": "0",
          "desc": "Size limit per image in KB (0 = none)"
        },
        {
          "name": "quality",
          "type": "number",
          "default": "80",
          "desc": "JPEG/WebP quality 30–100 (used when target is 0) (30–100)"
        }
      ]
    },
    {
      "slug": "heic-to-jpg",
      "name": "HEIC to JPG",
      "url": "https://lumibear.dev/tools/heic-to-jpg/",
      "does": "Convert iPhone HEIC photos to JPG, many at once.",
      "input": "One or more HEIC photos",
      "accept": "image/*,.heic,.heif,.avif",
      "multiple": true,
      "output": "One JPG per photo",
      "options": [
        {
          "name": "format",
          "type": "choice",
          "values": [
            "keep",
            "jpeg",
            "webp",
            "png"
          ],
          "default": "jpeg",
          "desc": "keep = same as the input, or jpeg, webp, png"
        },
        {
          "name": "size",
          "type": "choice",
          "values": [
            "0",
            "2560",
            "1920",
            "1280",
            "800"
          ],
          "default": "0",
          "desc": "Longest side in px (0 = original)"
        },
        {
          "name": "target",
          "type": "choice",
          "values": [
            "0",
            "200",
            "500",
            "1024",
            "2048"
          ],
          "default": "0",
          "desc": "Size limit per image in KB (0 = none)"
        },
        {
          "name": "quality",
          "type": "number",
          "default": "92",
          "desc": "JPEG/WebP quality 30–100 (used when target is 0) (30–100)"
        }
      ]
    },
    {
      "slug": "video-compress",
      "name": "Video compressor",
      "url": "https://lumibear.dev/tools/video-compress/",
      "does": "Make a video smaller to fit an upload limit.",
      "input": "One video (MP4, MOV, WebM, MKV)",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,.mkv",
      "multiple": false,
      "output": "<name>-small.mp4",
      "tip": "target is picked from the file size when not given. --target 0 uses --quality instead of a limit.",
      "options": [
        {
          "name": "target",
          "type": "choice",
          "values": [
            "10",
            "25",
            "50",
            "100",
            "0"
          ],
          "default": "0",
          "desc": "Size limit in MB (0 = none, then quality is used)"
        },
        {
          "name": "quality",
          "type": "choice",
          "values": [
            "high",
            "medium",
            "low"
          ],
          "default": "medium",
          "desc": "high, medium or low — only when target is 0"
        },
        {
          "name": "res",
          "type": "choice",
          "values": [
            "auto",
            "0",
            "1080",
            "720",
            "480"
          ],
          "default": "auto",
          "desc": "Resolution: auto (picked from the size limit), 0 = original, or the shorter side in px"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "mute",
          "type": "flag",
          "default": false,
          "desc": "Remove the sound"
        }
      ]
    },
    {
      "slug": "video-trim",
      "name": "Video trimmer",
      "url": "https://lumibear.dev/tools/video-trim/",
      "does": "Cut a video down to the part you want.",
      "input": "One video",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,.mkv",
      "multiple": false,
      "output": "<name>-cut.mp4",
      "tip": "Give --start and --end in seconds. --cut exact re-encodes to start on the exact frame.",
      "options": [
        {
          "name": "cut",
          "type": "choice",
          "values": [
            "fast",
            "exact"
          ],
          "default": "fast",
          "desc": "fast = copy without re-encoding (may start at the key frame just before), exact = re-encode for an exact start"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "mute",
          "type": "flag",
          "default": false,
          "desc": "Remove the sound"
        }
      ]
    },
    {
      "slug": "mov-to-mp4",
      "name": "MOV to MP4",
      "url": "https://lumibear.dev/tools/mov-to-mp4/",
      "does": "Convert MOV, MKV or WebM to MP4. H.264 is copied as it is; iPhone HEVC is re-encoded to H.264 so it plays everywhere.",
      "input": "One video",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,.mkv",
      "multiple": false,
      "output": "<name>.mp4",
      "tip": "conv is picked from the video when not given (copy for H.264, h264 for the rest).",
      "options": [
        {
          "name": "conv",
          "type": "choice",
          "values": [
            "copy",
            "h264"
          ],
          "default": "copy",
          "desc": "copy = move the video into MP4 as it is, h264 = re-encode to H.264 that plays everywhere"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "mute",
          "type": "flag",
          "default": false,
          "desc": "Remove the sound"
        }
      ]
    },
    {
      "slug": "mp4-to-mp3",
      "name": "MP4 to MP3",
      "url": "https://lumibear.dev/tools/mp4-to-mp3/",
      "does": "Save the sound of a video (or an audio file) as MP3, M4A or WAV.",
      "input": "One video or audio file",
      "accept": "video/*,audio/*,.mp4,.mov,.m4v,.webm,.mkv,.m4a,.mp3,.wav,.ogg,.oga,.flac,.aac",
      "multiple": false,
      "output": "<name>.mp3 (.m4a, .wav)",
      "options": [
        {
          "name": "afmt",
          "type": "choice",
          "values": [
            "mp3",
            "m4a",
            "wav"
          ],
          "default": "mp3",
          "desc": "mp3, m4a (copies AAC sound when it can, no loss) or wav (uncompressed)"
        },
        {
          "name": "abr",
          "type": "choice",
          "values": [
            "128",
            "192",
            "320"
          ],
          "default": "192",
          "desc": "MP3 bitrate in kbps (only for mp3)"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        }
      ]
    },
    {
      "slug": "discord-video-compressor",
      "name": "Discord video compressor",
      "url": "https://lumibear.dev/tools/discord-video-compressor/",
      "does": "Compress a video to fit Discord's upload limit (20 MB without Nitro).",
      "input": "One video",
      "accept": "video/*,.mp4,.mov,.m4v,.webm,.mkv",
      "multiple": false,
      "output": "<name>-small.mp4",
      "options": [
        {
          "name": "target",
          "type": "choice",
          "values": [
            "10",
            "20",
            "50"
          ],
          "default": "20",
          "desc": "Size limit in MB"
        },
        {
          "name": "quality",
          "type": "choice",
          "values": [
            "high",
            "medium",
            "low"
          ],
          "default": "medium",
          "desc": "high, medium or low — only when target is 0"
        },
        {
          "name": "res",
          "type": "choice",
          "values": [
            "auto",
            "0",
            "1080",
            "720",
            "480"
          ],
          "default": "auto",
          "desc": "Resolution: auto (picked from the size limit), 0 = original, or the shorter side in px"
        },
        {
          "name": "start",
          "type": "number",
          "desc": "Start, in seconds from the beginning"
        },
        {
          "name": "end",
          "type": "number",
          "desc": "End, in seconds from the beginning"
        },
        {
          "name": "mute",
          "type": "flag",
          "default": false,
          "desc": "Remove the sound"
        }
      ]
    },
    {
      "slug": "pdf-compress",
      "name": "Compress PDF",
      "url": "https://lumibear.dev/tools/pdf-compress/",
      "does": "Make a PDF smaller.",
      "input": "One PDF",
      "accept": "application/pdf,.pdf",
      "multiple": false,
      "output": "<name>-small.pdf — or no file when it could not get smaller (the note says why)",
      "tip": "Start with the defaults. For scans that must be smallest: --method flatten. Still too big? --level strong.",
      "options": [
        {
          "name": "level",
          "type": "choice",
          "values": [
            "light",
            "medium",
            "strong"
          ],
          "default": "medium",
          "desc": "light, medium or strong — stronger means a smaller file and softer pictures"
        },
        {
          "name": "method",
          "type": "choice",
          "values": [
            "keep",
            "flatten"
          ],
          "default": "keep",
          "desc": "keep = text stays text and only pictures shrink; flatten = every page becomes a picture (smallest, text no longer selectable)"
        }
      ]
    },
    {
      "slug": "pdf-to-jpg",
      "name": "PDF to JPG",
      "url": "https://lumibear.dev/tools/pdf-to-jpg/",
      "does": "Save PDF pages as JPG or PNG images.",
      "input": "One PDF",
      "accept": "application/pdf,.pdf",
      "multiple": false,
      "output": "One image per page: <name>-<page>.jpg",
      "options": [
        {
          "name": "format",
          "type": "choice",
          "values": [
            "jpg",
            "png"
          ],
          "default": "jpg",
          "desc": "jpg or png"
        },
        {
          "name": "dpi",
          "type": "choice",
          "values": [
            "72",
            "150",
            "300"
          ],
          "default": "150",
          "desc": "Resolution of the images"
        },
        {
          "name": "pages",
          "type": "text",
          "desc": "Pages like \"1-3, 5, 8-\" (empty = all pages)"
        }
      ]
    },
    {
      "slug": "pdf-merge",
      "name": "Merge PDF",
      "url": "https://lumibear.dev/tools/pdf-merge/",
      "does": "Merge PDFs into one, in the order the files are given.",
      "input": "Two or more PDFs",
      "accept": "application/pdf,.pdf",
      "multiple": true,
      "output": "merged.pdf",
      "options": []
    },
    {
      "slug": "pdf-split",
      "name": "Split PDF",
      "url": "https://lumibear.dev/tools/pdf-split/",
      "does": "Keep or delete chosen pages of a PDF, or split it into parts.",
      "input": "One PDF",
      "accept": "application/pdf,.pdf",
      "multiple": false,
      "output": "One PDF, or one PDF per part",
      "tip": "Keep pages 1-3: --mode extract --pages \"1-3\". Delete page 2: --mode remove --pages 2. One file per page: --mode each.",
      "options": [
        {
          "name": "mode",
          "type": "choice",
          "values": [
            "extract",
            "remove",
            "each",
            "every"
          ],
          "default": "extract",
          "desc": "extract = keep the pages, remove = delete the pages, each = one PDF per page, every = one PDF per N pages"
        },
        {
          "name": "every",
          "type": "choice",
          "values": [
            "2",
            "3",
            "5",
            "10"
          ],
          "default": "2",
          "desc": "Pages per file when mode is every"
        },
        {
          "name": "pages",
          "type": "text",
          "desc": "Pages like \"1-3, 5, 8-\" (empty = all pages)"
        }
      ]
    },
    {
      "slug": "jpg-to-pdf",
      "name": "Images to PDF",
      "url": "https://lumibear.dev/tools/jpg-to-pdf/",
      "does": "Put photos into one PDF, one photo per page, in the order the files are given.",
      "input": "JPG, PNG, WebP or HEIC photos",
      "accept": "image/*,.heic,.heif",
      "multiple": true,
      "output": "<name>.pdf (photos.pdf for several)",
      "options": [
        {
          "name": "paper",
          "type": "choice",
          "values": [
            "a4",
            "letter",
            "fit"
          ],
          "default": "letter",
          "desc": "Page size: a4, letter, or fit (each page the size of its photo)"
        },
        {
          "name": "orient",
          "type": "choice",
          "values": [
            "auto",
            "portrait",
            "landscape"
          ],
          "default": "auto",
          "desc": "Page orientation"
        },
        {
          "name": "margin",
          "type": "choice",
          "values": [
            "0",
            "18",
            "36"
          ],
          "default": "18",
          "desc": "Margin in points (0, 18 = small, 36 = normal)"
        },
        {
          "name": "shrink",
          "type": "flag",
          "default": false,
          "desc": "Make large photos smaller for a smaller PDF"
        }
      ]
    }
  ]
}