README
ΒΆ
π¬ Movie CLI
Personal movie & TV show library manager β from the terminal
Scan folders, clean filenames, fetch TMDb metadata, organize files, and track your collection.
β One command, one binary β scan, match against TMDb, and organize your whole library.
π Install in 10 seconds β anyone, any OS:
| πͺ Windows Β· PowerShell |
|
| π§ macOS Β· Linux Β· Bash |
|
Auto-detects your OS & architecture Β· Installs the latest pre-built binary Β· Falls back to a source build if no release is published Β· See Installation for flags, pinned versions, and verification.
π‘ Why this exists
If you've ever ripped years of old DVDs to a hard drive β or just collected
movies the slow way β you know the pain: a folder full of cryptic filenames
like MV_0432.avi or the.matrix.1999.1080p.x264.mkv, no posters, no
ratings, no idea what's actually worth watching tonight.
Movie CLI was built out of that frustration. It scans your existing folders, cleans up the messy release names, matches each file against TMDb, and gives you a real library on your own disk β with titles, posters, ratings, genres, cast, runtime, and watch history β without uploading anything to a cloud service. Your files stay where they are; you just finally know what you have.
β¨ Highlights
- π Smart scan β recursively walks folders, cleans messy release names, and matches them against TMDb
- πΌοΈ Posters & metadata β automatic thumbnail downloads, ratings, genres, cast, runtime
- π¦ Single binary β one statically-linked Go executable, no runtime, no dependencies
- ποΈ SQLite (WAL) β fast, durable, zero-config local database in
./data/movie.db - β©οΈ Undo / redo β every move, rename, scan, and delete is reversible
- π REST API + web UI β
movie rest --openlaunches a local dashboard - π οΈ Self-updating β
movie updatepulls, rebuilds, and hands off in-place - π Cross-platform β Windows, Linux, macOS on
amd64andarm64
π Table of Contents
- Quick Start
- Sample setup used in this README
- Pre-flight checklist
- Jump to a command
- Demo
- Installation
- What It Does
- Command Reference
- Troubleshooting Flowchart
- Command Tree
- Build & Deploy
- Release Workflow
- Project Structure
- Data Storage
- Documentation sync
- Milestones
- Dependencies
- Contributing
- Author
- License
Quick Start
In a hurry? See QUICKSTART.md β minimal copy-paste commands for Linux, macOS, and Windows.
Install latest release
Picks up whatever is currently tagged latest on GitHub β and if no release has been published yet, automatically falls back to a source-build from main so you still end up with a working binary.
Windows (PowerShell)
irm https://raw.githubusercontent.com/alimtvnetwork/movie-cli-v7/main/get.ps1 | iex
Linux / macOS
curl -fsSL https://raw.githubusercontent.com/alimtvnetwork/movie-cli-v7/main/get.sh | bash
The bootstrap probes
releases/latest/download/install.{ps1,sh}first. If a release exists, it installs the pre-built binary. If not, it transparently falls back to cloning and building frommainβ and prints exactly which path it took. See Installation for flags and details.
Install a specific version (pinned)
Installs exactly the version in the URL β never auto-upgrades. Use this for CI pipelines, Dockerfiles, reproducible setups, or when you need to roll back. Replace v2.130.0 with the release tag you want.
Windows (PowerShell)
irm https://github.com/alimtvnetwork/movie-cli-v7/releases/download/v2.130.0/install.ps1 | iex
Linux / macOS
curl -fsSL https://github.com/alimtvnetwork/movie-cli-v7/releases/download/v2.130.0/install.sh | bash
Which one should I use? Use latest for personal machines so you stay current. Use pinned anywhere reproducibility matters β the pinned script is hard-locked to the version in the URL and will install that exact tag forever, even after newer releases ship. (contract spec)
Set up & scan
movie config set tmdb_api_key YOUR_KEY
movie scan ~/Downloads
movie ls
Search & discover
movie search "Inception"
movie suggest 5
Every command supports --help or -h for detailed usage.
Sample setup used in this README
Every "Expected output" snippet below assumes the small reference setup shown here. If your library is larger or your IDs differ, only the numbers will change β the shape of the output stays the same.
Folder layout (source_folder = /mnt/storage/Movies on Linux/macOS, D:\Media\Movies on Windows):
/mnt/storage/Movies/
βββ Inception (2010).mkv
βββ The Matrix (1999).mkv
βββ Arrival (2016).mkv
βββ Interstellar (2014).mkv
βββ The Prestige (2006).mkv
βββ _unsorted/
βββ inception.2010.1080p.mkv β will be cleaned up by `movie rename`
βββ old.movie.1998.mkv β becomes a stale entry after deletion
/mnt/storage/Sorted/ β destination for `movie move`
βββ Action/
Config values (set once with movie config set <key> <value>):
| Key | Value |
|---|---|
source_folder |
/mnt/storage/Movies |
tmdb_api_key |
your TMDb v3 key |
default_player |
mpv |
log_level |
info |
ID β title map (after the first movie scan, your IDs may differ β substitute as needed):
| ID | Title | Year | Used in section |
|---|---|---|---|
1 |
Inception | 2010 | Discovery & Organization (tag add 1 favorite) |
123 |
Inception | 2010 | Scanning & Library, File Management |
124 |
The Matrix | 1999 | Scanning & Library |
125 |
Arrival | 2016 | Scanning & Library |
131 |
The Prestige | 2006 | Discovery & Organization (suggest) |
412 |
Old Movie (1998) | β | Maintenance & Debugging (stale entry) |
418 |
Removed.avi | β | Maintenance & Debugging (stale entry) |
History entry IDs (created by past move / rename / scan ops):
| History ID | Op | Target |
|---|---|---|
87 |
move | Inception (2010).mkv β Sorted/Action |
86 |
rename | The Matrix (1999).mkv |
85 |
scan | /mnt/storage/Movies (12 added) |
42 |
generic placeholder used in --id 42 examples |
β |
Tip: run
movie lsafter your first scan to see your real media IDs, andmovie undo --listto see your real history IDs. Replace the sample numbers above with yours when copying commands.
β Pre-flight checklist
Run these checks before any command in the Jump to a command section. Each row tells you what to verify, the one-liner that confirms it, and the fix if the check fails. Tick boxes as you go.
| β | What to verify | Why it matters |
|---|---|---|
| β | movie binary is on $PATH |
every command starts with movie β¦ |
| β | tmdb_api_key is set in config |
scan / search / suggest fail without it |
| β | source_folder is set in config |
movie scan (no args) needs it |
| β | default_player is set in config |
movie play <id> needs it |
| β | source_folder exists and contains video files |
otherwise scan returns 0 added |
| β | Destination folder for movie move is writable |
otherwise move fails with permission denied |
| β | Port 7777 is free (or pick another with --port) |
needed for movie rest |
| β | Network access to api.themoviedb.org |
needed for TMDb metadata |
| β | Network access to GitHub releases | needed for movie update |
π Quick bootstrap check
After installing, run two commands β the first confirms the binary is on $PATH, the second runs every check above automatically:
command -v movie >/dev/null && echo "β
movie found: $(command -v movie)" || echo "β movie NOT on PATH"
movie doctor
On Windows PowerShell, the equivalent first line is:
if (Get-Command movie -ErrorAction SilentlyContinue) { "β
movie found: $((Get-Command movie).Source)" } else { "β movie NOT on PATH" }
movie doctor
movie doctor runs the config-key, source-folder, port 7777, PATH, deploy and version checks built into the binary, prints the same β
/ β οΈ / β output, and exits non-zero if anything fails β so it's safe to use in CI. Pass --fix to auto-repair fixable findings, or --json for machine-readable output.
All β ? You're ready to run anything in the Jump to a command section. Any β? Fix it first β most failures further down the README trace back to one of these checks.
π§Ύ Diffing
readme.txtagainst the Expected output blocks. All six "β Expected output" snippets in the Jump to a command section are plain```textfences, so you can pull them out with one awk pass and diff them against your generatedreadme.txt:
awk '/^\*\*β Expected output\*\*/{flag=1;next} flag && /^```text/{cap=1;next} flag && cap && /^```/{cap=0;flag=0;print "---"; next} cap' README.md > expected.txt && diff -u expected.txt readme.txt | less
On Windows PowerShell useCompare-Object (Get-Content expected.txt) (Get-Content readme.txt). Lines that differ are usually just your real IDs/sizes vs the README's sample IDs (123,87,412) β see Sample setup for the mapping.
Jump to a command
Skip the demo and jump straight to the command you need. Each link drops you into the matching Command Reference subsection β with the animated walkthrough, copy-paste Bash + PowerShell examples, expected output, and the full subcommand table.
Each row has both a Bash and a PowerShell fenced block β pick the one for your shell, then triple-click any line (or drag-select the whole block) to copy a real, runnable command. The two blocks differ only where shell syntax matters (paths, env vars, quoting).
π‘ Want one-click copy? Run
movie rest --opento launch the dashboard, then press Ctrl/β+K to open the command palette β it fuzzy-searches every command in this section and copies the exactmovie β¦string with one click. Single-letter shortcuts (S, F, H, D, M, C) jump to each subsection.
π Search this README right here. On GitHub, press / to open the file-content search, or use your browser's Ctrl/β+F to find any command, flag, or section name on this page. The flat command index below is built so a single keyword (
scan,undo,tmdb_api_key) lands on the exact row.
π Flat alphabetical command index β press Ctrl/β+F and type any keyword
One line per command. Search lands on the exact row; the section name on the right tells you where to jump.
Click the command to jump straight to its README subsection (with the bash/PowerShell blocks, args, expected output, and "if it differs" notes). Each row also carries a stable anchor (e.g. #movie-scan, #movie-undo-list) shown in the rightmost column β share that fragment and links always land on the exact row. The Example keyword column gives you a minimal command-shaped placeholder (e.g. movie ls --year , movie scan --dry-run , movie config set tmdb_api_key ) β paste it into Ctrl/β+F to find every place that argument pattern appears in this README (index row, usage block, expected output, and tips).
π Auto-generated from a single source.
scripts/gen-command-index.pyowns the canonical command list and rewrites three things in this README on every run: (1) the HTML table + plain-text block in this section, (2) every stale link to one of the six command-section anchors (#scanning--library,#file-management,#history--undo,#discovery--organization,#maintenance--debugging,#configuration--system), and (3) thebash+powershellquick-start pair under each#### π [Section]heading in the Command Reference (between<!-- SECTION-CMDS:<label>:BEGIN -->markers). Edit only the script, then runpython3 scripts/gen-command-index.py. CI runs--checkon every push and fails with afile:lineannotation if anything is stale. Hand-written prose around the markers (Args / Assumptions / Expected output / If it differs) is preserved untouched.
π Plain-text / terminal version β same index, fixed-width so the β arrows line up in monospace
Use this block when reading the README in a terminal (cat README.md, less, bat), in a non-HTML editor, or when piping to grep / fzf. Every row is padded so the β pointer sits in a single column and the Section and Anchor columns start at the same offset on every line. Copy the whole block β it's ASCII-safe (only the β arrow is non-ASCII, U+2192) and renders cleanly in any UTF-8 monospace font.
Command Section Anchor
---------------------------------------- - ------------------------ ------------------------------------
movie cd <id> β File Management #movie-cd-id
movie changelog β Configuration & System #movie-changelog
movie cleanup β Maintenance & Debugging #movie-cleanup
movie config β Configuration & System #movie-config
movie config get <key> β Configuration & System #movie-config-get-key
movie config set <key> <value> β Configuration & System #movie-config-set-key-value
movie config set source_folder <path> β Configuration & System #movie-config-set-source-folder-path
movie config set tmdb_api_key <key> β Configuration & System #movie-config-set-tmdb-api-key-key
movie db β Maintenance & Debugging #movie-db
movie discover β Discovery & Organization #movie-discover
movie duplicates β File Management #movie-duplicates
movie export β Maintenance & Debugging #movie-export
movie export --format csv --out <file> β Maintenance & Debugging #movie-export-format-csv-out-file
movie export --format json --out <file> β Maintenance & Debugging #movie-export-format-json-out-file
movie hello β Configuration & System #movie-hello
movie info <id> β Scanning & Library #movie-info-id
movie info <id> --json β Scanning & Library #movie-info-id-json
movie logs β Maintenance & Debugging #movie-logs
movie ls β Scanning & Library #movie-ls
movie ls --genre <name> β Scanning & Library #movie-ls-genre-name
movie ls --limit <n> β Scanning & Library #movie-ls-limit-n
movie ls --year <yyyy> --sort <field> β Scanning & Library #movie-ls-year-yyyy-sort-field
movie move β File Management #movie-move
movie move --all β File Management #movie-move-all
movie move <id> --to <path> β File Management #movie-move-id-to-path
movie play <id> β File Management #movie-play-id
movie play <id> --player <bin> β File Management #movie-play-id-player-bin
movie popout β File Management #movie-popout
movie redo β History & Undo #movie-redo
movie rename β File Management #movie-rename
movie rename <id> β File Management #movie-rename-id
movie rename --all --pattern <fmt> β File Management #movie-rename-all-pattern-fmt
movie rescan β Scanning & Library #movie-rescan
movie rest β Maintenance & Debugging #movie-rest
movie rest --open β Maintenance & Debugging #movie-rest-open
movie rest --port <n> β Maintenance & Debugging #movie-rest-port-n
movie scan β Scanning & Library #movie-scan
movie scan <path> β Scanning & Library #movie-scan-path
movie scan <path> --dry-run β Scanning & Library #movie-scan-path-dry-run
movie scan <path> --refresh β Scanning & Library #movie-scan-path-refresh
movie search <query> β Scanning & Library #movie-search-query
movie search <query> --year <yyyy> β Scanning & Library #movie-search-query-year-yyyy
movie stats β Discovery & Organization #movie-stats
movie stats --by <dimension> β Discovery & Organization #movie-stats-by-dimension
movie suggest β Discovery & Organization #movie-suggest
movie suggest --genre <name> --limit <n> β Discovery & Organization #movie-suggest-genre-name-limit-n
movie tag add <id> <tag> β Discovery & Organization #movie-tag-add-id-tag
movie tag list <id> β Discovery & Organization #movie-tag-list-id
movie tag list --all β Discovery & Organization #movie-tag-list-all
movie tag remove <id> <tag> β Discovery & Organization #movie-tag-remove-id-tag
movie tag remove <id> --all β Discovery & Organization #movie-tag-remove-id-all
movie undo β History & Undo #movie-undo
movie undo --id <history-id> β History & Undo #movie-undo-id-history-id
movie undo --list β History & Undo #movie-undo-list
movie update β Configuration & System #movie-update
movie version β Configuration & System #movie-version
movie watch add <id> β Discovery & Organization #movie-watch-add-id
movie watch add <id> --priority <level> β Discovery & Organization #movie-watch-add-id-priority-level
movie watch list β Discovery & Organization #movie-watch-list
movie watch list --sort <field> β Discovery & Organization #movie-watch-list-sort-field
π Scanning & Library
Match files against TMDb, browse the library.
movie info <id>
movie info <id> --json
movie ls
movie ls --genre <name>
movie ls --limit <n>
movie ls --year <yyyy> --sort <field>
movie rescan
movie scan
movie scan <path>
movie scan <path> --dry-run
movie scan <path> --refresh
movie search <query>
movie search <query> --year <yyyy>
movie info <id>
movie info <id> --json
movie ls
movie ls --genre <name>
movie ls --limit <n>
movie ls --year <yyyy> --sort <field>
movie rescan
movie scan
movie scan <path>
movie scan <path> --dry-run
movie scan <path> --refresh
movie search <query>
movie search <query> --year <yyyy>
Args:
<path>is the folder to scan (defaults to your configuredsource_folder).123is a media ID β get one frommovie ls."inception"is any free-text query; quote it if it contains spaces.
Assumptions:
source_folderis set (movie config set source_folder /mnt/storage/Movies),tmdb_api_keyis set, and that folder contains video files. The sample IDs123/124/125come from your own library after the firstmovie scan.
β Expected output
$ movie scan
β scanning /mnt/storage/Movies
matched 42
added 12
skipped 3
tmdb hit 41 / miss 1
done in 4.2s
$ movie ls
ID TITLE YEAR GENRE RATING SIZE
123 Inception 2010 Action 8.8 2.1 GB
124 The Matrix 1999 Action 8.7 1.8 GB
125 Arrival 2016 Sci-Fi 7.9 1.4 GB
... (use --limit / --page to paginate)
$ movie info 123
Inception (2010) ID 123 β
8.8 Runtime 148m
Genre: Action, Sci-Fi
Director: Christopher Nolan
File: /mnt/storage/Movies/Inception (2010).mkv
TMDb: https://www.themoviedb.org/movie/27205
If it differs: the most common mismatch is
0 addedortmdb missrates near 100% β that meanstmdb_api_keyis unset or invalid. Fix withmovie config set tmdb_api_key <your key>(get one at https://www.themoviedb.org/settings/api), then re-runmovie scan. Ifmovie lsis empty after a successful scan, yoursource_folderpoints at the wrong directory β verify withmovie config get source_folder.
π¦ File Management
Move, rename, flatten, play files.
movie cd <id>
movie duplicates
movie move
movie move --all
movie move <id> --to <path>
movie play <id>
movie play <id> --player <bin>
movie popout
movie rename
movie rename <id>
movie rename --all --pattern <fmt>
movie cd <id>
movie duplicates
movie move
movie move --all
movie move <id> --to <path>
movie play <id>
movie play <id> --player <bin>
movie popout
movie rename
movie rename <id>
movie rename --all --pattern <fmt>
Args:
123is a media ID (movie lsto find it).--to <path>is the destination folder; quote paths with spaces.move,rename, andpopoutrun interactively when no ID is given.
Assumptions: Media ID
123exists in your DB (runmovie scanfirst), the destination folder (/mnt/storage/Sorted/ActionorD:\Media\Sorted\Action) is writable, anddefault_playeris configured formovie play.
β Expected output
$ movie move 123 --to /mnt/storage/Sorted/Action
β moving "Inception (2010).mkv"
from /mnt/storage/Movies
to /mnt/storage/Sorted/Action
β moved (history id 87)
$ movie rename 123 --dry-run
would rename:
"inception.2010.1080p.mkv" β "Inception (2010).mkv"
(dry run β no files changed)
$ movie play 123
β launching default player for /mnt/storage/Sorted/Action/Inception (2010).mkv
If it differs:
movie moveprintingerror: id 123 not foundmeans your library uses different IDs β runmovie lsand substitute a real one.permission deniedon the destination means the target folder isn't writable:chmod -R u+w /mnt/storage/Sorted(Linux/macOS) or check folder properties on Windows.movie playopening nothing meansdefault_playerisn't set β fix withmovie config set default_player mpv(orvlc,mpv.exe, etc.).
β©οΈ History & Undo
Reverse any move / rename / scan / delete.
movie redo
movie undo
movie undo --id <history-id>
movie undo --list
movie redo
movie undo
movie undo --id <history-id>
movie undo --list
Args:
--id 42is a history entry ID frommovie undo --list. Baremovie undoreverses the most recent operation.movie redore-applies the last undone op.
Assumptions: At least one prior
movie scan,move, orrenamehas recorded an entry in the history table. The sample IDs87/86/85are placeholders β substitute the IDs you see in your ownmovie undo --list.
β Expected output
$ movie undo --list
ID WHEN OP TARGET
87 2025-04-26 14:02:11 move Inception (2010).mkv
86 2025-04-26 13:58:40 rename The Matrix (1999).mkv
85 2025-04-26 13:51:02 scan /mnt/storage/Movies (12 added)
$ movie undo --id 87
? Revert move of "Inception (2010).mkv"? [y/N] y
β reverted (history id 87 β reversed)
$ movie redo
β re-applied move (history id 87)
If it differs: an empty
movie undo --listmeans no reversible operations have been recorded yet β runmovie scan,movie move, ormovie renamefirst.error: history id 87 not foundmeans87is from this README, not your DB; use one from your ownmovie undo --list. Ifmovie redofails withnothing to redo, you haven't undone anything in the current session.
π― Discovery & Organization
Recommendations, genres, tags, watchlist.
movie discover
movie stats
movie stats --by <dimension>
movie suggest
movie suggest --genre <name> --limit <n>
movie tag add <id> <tag>
movie tag list <id>
movie tag list --all
movie tag remove <id> <tag>
movie tag remove <id> --all
movie watch add <id>
movie watch add <id> --priority <level>
movie watch list
movie watch list --sort <field>
movie discover
movie stats
movie stats --by <dimension>
movie suggest
movie suggest --genre <name> --limit <n>
movie tag add <id> <tag>
movie tag list <id>
movie tag list --all
movie tag remove <id> <tag>
movie tag remove <id> --all
movie watch add <id>
movie watch add <id> --priority <level>
movie watch list
movie watch list --sort <field>
Args:
1is a media ID (movie ls).favoriteis any tag name you choose β letters, digits, dashes.movie watch listandmovie statstake no args.
Assumptions: Library is non-empty (
movie scanhas run),tmdb_api_keyis set formovie suggest/movie discover, and media ID1exists. Stats numbers reflect your own library, not the sample.
β Expected output
$ movie suggest
Because you watched Inception (2010):
β’ Interstellar (2014) β
8.6 not in library
β’ Tenet (2020) β
7.4 not in library
β’ The Prestige (2006) β
8.5 in library (id 131)
$ movie tag add 1 favorite
β tagged "Inception (2010)" with: favorite
$ movie stats
Library: 248 titles Β· 612 GB
Top genre: Action (74)
Avg rating: 7.4
Watchlist: 12 pending
If it differs:
movie suggestreturningno recommendationsmeans your library is too small (TMDb needs at least a few scanned titles to pivot from) β scan more first. Wildly different stats numbers are normal; they reflect your library, not the sample.movie tag add 1 favoritefailing withmedia not foundmeans ID1doesn't exist in your DB β pick a real ID frommovie ls.
π Maintenance & Debugging
Stale-entry cleanup, logs, REST server.
movie cleanup
movie db
movie export
movie export --format csv --out <file>
movie export --format json --out <file>
movie logs
movie rest
movie rest --open
movie rest --port <n>
movie cleanup
movie db
movie export
movie export --format csv --out <file>
movie export --format json --out <file>
movie logs
movie rest
movie rest --open
movie rest --port <n>
Args: All of these run with no required args.
movie rest --openopens the dashboard in your browser; add--port 8080to override the default port.movie exportwrites to stdout unless you pass--out <file>.
Assumptions: Default port
7777is free formovie rest, the current working directory is writable formovie export --out, and the DB exists at the configured path. Stale-entry IDs (412/418) only appear if files were removed outside the CLI.
β Expected output
$ movie cleanup --dry-run
stale entries (file no longer exists):
ID 412 "Old Movie (1998).mkv"
ID 418 "Removed.avi"
(dry run β pass --yes to delete)
$ movie rest --open
β REST server listening on http://127.0.0.1:7777
β opened browser at http://127.0.0.1:7777/dashboard
(press Ctrl+C to stop)
$ movie export --format csv --out library.csv
β wrote 248 rows to library.csv
If it differs:
movie restfailing withaddress already in usemeans port7777is taken β pass--port 8080(or any free port).movie cleanup --dry-runprinting nothing is good β it means no stale entries exist.movie exportwriting zero rows means the library is empty; runmovie scanfirst. Ifmovie logsshows nothing, lower the threshold withmovie config set log_level debug.
βοΈ Configuration & System
Settings, TMDb key, version, self-update.
movie changelog
movie config
movie config get <key>
movie config set <key> <value>
movie config set source_folder <path>
movie config set tmdb_api_key <key>
movie hello
movie update
movie version
movie changelog
movie config
movie config get <key>
movie config set <key> <value>
movie config set source_folder <path>
movie config set tmdb_api_key <key>
movie hello
movie update
movie version
Args:
tmdb_api_keyis the config key name (others:source_folder,default_player,log_level).YOUR_KEYis a real TMDb v3 API key β get one at https://www.themoviedb.org/settings/api.movie versionandmovie updatetake no args.
Assumptions: The config file exists at its default OS-specific path (created automatically on first run), the user has write access to it, and
movie updatehas network access to GitHub releases. ReplaceYOUR_KEYwith a real TMDb v3 key.
β Expected output
$ movie config
source_folder = /mnt/storage/Movies
tmdb_api_key = abcdβ¦5678 (set)
default_player = mpv
log_level = info
$ movie config set tmdb_api_key abcd1234efgh5678
β tmdb_api_key updated
$ movie version
movie v2.191.0 (commit a1b2c3d, built 2025-04-26)
$ movie update
β checking github.com/alimtvnetwork/movie-cli-v7 for newer releasesβ¦
β already on the latest version (v2.191.0)
If it differs:
movie configshowingtmdb_api_key = (unset)is the #1 cause of every other failure in this README β set it now.movie updatefailing with a network error usually means GitHub is unreachable from your network or a corporate proxy is blocking it; download the latest binary from the Releases page instead. A version older than whatmovie updatereports means the upgrade succeeded but your shell is still pointing at the old binary β open a fresh terminal.
π Troubleshooting
Common errors and how to fix them β tmdb_api_key not set, 429, database is locked, stale entries.
First time here? Run the env-var check at the top of the Command Reference to confirm
TMDB_KEYis set before you scan.
π₯ Demo
Six short, looping GIFs β one per command group. Each one mirrors a real terminal session, so you can see exactly what to expect before you type a single command.
π Scanning your library
$ movie scan ~/Downloads
π Scanning: /home/user/Downloads
Found 12 video files
[1/12] Scream.2022.1080p.WEBRip.x264-RARBG.mkv
β Title: Scream (2022)
β TMDb: β
6.8 | Horror, Mystery, Thriller
β
Saved to database
...
β
Done β 12 items scanned, 11 new, 1 updated
π― Discovery & suggestions
$ movie suggest 5
π½οΈ Recommendations based on your library:
1 Nope 2022 β
6.8 Horror, Sci-Fi
2 X 2022 β
6.6 Horror, Mystery
3 Pearl 2022 β
7.0 Drama, Horror
...
ποΈ File management β move, rename, organize
β©οΈ History, undo & redo
π οΈ Maintenance & debugging
βοΈ Configuration & system
πΉ Recording your own demos: use VHS (deterministic, scriptable) or asciinema + agg.
vhs assets/screenshots/cmd-scan-library.tape
Installation
One-Liner Install (recommended)
Two flavours β pick based on whether you want auto-updates or a frozen version.
Latest release (auto-tracks newest)
Windows (PowerShell)
irm https://raw.githubusercontent.com/alimtvnetwork/movie-cli-v7/main/get.ps1 | iex
Linux / macOS (Bash)
curl -fsSL https://raw.githubusercontent.com/alimtvnetwork/movie-cli-v7/main/get.sh | bash
get.{ps1,sh} first checks releases/latest/download/install.{ps1,sh}. If a release is published it installs the pre-built binary; otherwise it falls back to a source-build from main, prints exactly which path it took, and tells the maintainer how to publish a release so future installs skip the build step.
Pinned to a specific release
Windows (PowerShell)
irm https://github.com/alimtvnetwork/movie-cli-v7/releases/download/v2.130.0/install.ps1 | iex
Linux / macOS (Bash)
curl -fsSL https://github.com/alimtvnetwork/movie-cli-v7/releases/download/v2.130.0/install.sh | bash
The script attached to each release has the version baked in (PINNED_VERSION="v2.130.0") and will install exactly that tag β it never falls back to "latest" and never delegates to the bootstrap scripts. Replace v2.130.0 with any published release.
When to use which
- Latest β personal machines, demos, "just give me the newest one"
- Pinned β CI pipelines, Dockerfiles, onboarding docs, reproducing a bug on a specific version, controlled rollbacks
Both URLs point at installer assets attached to the GitHub Release. The repo-root
install.ps1andinstall.share unrelated source bootstrap scripts for building locally.
Installer Options
Windows (PowerShell):
| Flag | Description | Example |
|---|---|---|
-InstallDir |
Custom install directory | -InstallDir C:\tools\movie |
-Arch |
Force architecture (amd64, arm64) |
-Arch arm64 |
-NoPath |
Skip adding to user PATH | -NoPath |
Linux / macOS (Bash):
| Flag | Description | Example |
|---|---|---|
--dir |
Custom install directory | --dir ~/bin |
--arch |
Force architecture (amd64, arm64) |
--arch arm64 |
--no-path |
Skip adding to PATH | --no-path |
Clone & Build (Development)
Prerequisites:
| Requirement | Minimum | Check |
|---|---|---|
| Go | 1.22+ | go version |
| Git | 2.x | git --version |
| PowerShell | 5.1+ (Win) / 7+ (Unix) | $PSVersionTable.PSVersion |
git clone https://github.com/alimtvnetwork/movie-cli-v7.git
cd movie-cli-v7
pwsh run.ps1
Using the bootstrap installer:
pwsh install.ps1 # Fresh install (clone + build + deploy)
pwsh install.ps1 -DeployPath ~/bin # Custom deploy path
Verify
movie version
# v1.0.0 (commit: abc1234, built: 2026-04-09)
# Go: go1.22.0
# OS: linux/amd64
Tip: If
movieis not found, add the deploy path to yourPATH. Default:E:\bin-run(Windows) or/usr/local/bin(Unix) for source builds.
What It Does
A portable CLI that manages your personal movie and TV show library entirely from the terminal. Every scan produces:
- Database β structured metadata in SQLite (WAL mode)
- Thumbnails β poster images downloaded from TMDb
- JSON β per-file metadata written to
./data/json/ - Clean filenames β
Scream.2022.1080p.WEBRip.x264.mkvβScream (2022).mkv
All data lives in ./data/ at the project root.
Command Reference
Each section below shows a real-world example of what the command does. Each thumbnail is a short looping walkthrough β hover or click to view the full-size still.
π‘ PowerShell vs Bash quick reference β escaping paths & passing env vars in the examples below
The example commands are written in Bash (macOS / Linux / WSL / Git Bash). On Windows PowerShell a few things differ β use this table to translate any example before running it:
| Concept | Bash (macOS / Linux / WSL) | PowerShell (Windows) |
|---|---|---|
| Home folder | ~/Downloads |
$HOME\Downloads or $env:USERPROFILE\Downloads |
| Path with spaces | "My Movies/Action Films" (double quotes) |
'My Movies\Action Films' (single quotes β no variable expansion) |
| Path separator | / |
\ (PowerShell also accepts /) |
| Escape a literal quote | \" inside "..." |
` " (backtick + quote) or use '...' |
| Read an env var | $TMDB_KEY |
$env:TMDB_KEY |
| Set env var (one command) | TMDB_KEY=abc movie scan ~/Downloads |
$env:TMDB_KEY="abc"; movie scan $HOME\Downloads |
| Set env var (whole session) | export TMDB_KEY=abc |
$env:TMDB_KEY = "abc" |
| Set env var (persistent) | add export ... to ~/.bashrc / ~/.zshrc |
[Environment]::SetEnvironmentVariable("TMDB_KEY","abc","User") |
| Command substitution | cd $(movie cd Movies) |
Set-Location (movie cd Movies) |
| Line continuation | trailing \ |
trailing ` (backtick) |
| Comments | # comment |
# comment (same) |
Rule of thumb: if an example uses ~, $VAR, \", or $(...), swap it for the PowerShell equivalent above. Everything else (flags, subcommands, IDs) is identical across shells.
π Check your env vars β confirm TMDB_KEY is set before running the examples
Run this once at the start of a session. It prints set / MISSING for each variable the CLI looks at, so you catch a missing TMDb token before a movie scan fails halfway through.
Bash (macOS / Linux / WSL / Git Bash)
for v in TMDB_KEY TMDB_API_KEY MOVIE_CONFIG MOVIE_DB_PATH; do
if [ -n "${!v}" ]; then
echo "β $v is set (${#v} chars: ${!v:0:4}β¦)"
else
echo "β $v is MISSING"
fi
done
PowerShell (Windows)
foreach ($v in 'TMDB_KEY','TMDB_API_KEY','MOVIE_CONFIG','MOVIE_DB_PATH') {
$val = [Environment]::GetEnvironmentVariable($v)
if ($val) {
Write-Host "β $v is set ($($val.Length) chars: $($val.Substring(0,[Math]::Min(4,$val.Length)))β¦)"
} else {
Write-Host "β $v is MISSING"
}
}
Expected output when everything is configured:
β TMDB_KEY is set (32 chars: a1b2β¦)
β TMDB_API_KEY is MISSING β optional alias, safe to ignore if TMDB_KEY is set
β MOVIE_CONFIG is set (28 chars: /Useβ¦)
β MOVIE_DB_PATH is MISSING β optional, falls back to the default DB location
Only TMDB_KEY is required for TMDb-backed commands (scan, search, discover, suggest). If it shows MISSING, set it with export TMDB_KEY=... (Bash) or $env:TMDB_KEY = "..." (PowerShell), or persist it via movie config set tmdb_api_key YOUR_KEY.
Scanning & Library
πΈ movie scan walks a folder, cleans messy release names, and matches each file against TMDb.
βΆ Try the example from the screenshot β replace ~/Downloads with any folder containing video files:
# 1. Reproduce the walkthrough above
movie scan ~/Downloads # β swap for your own scan folder
# 2. Re-run for any unmatched titles after the first pass
movie rescan
# 3. Confirm what landed in the library
movie ls
Path placeholders:
~/Downloads= macOS/Linux home folder. On Windows useC:\Users\<you>\Downloadsor$env:USERPROFILE\Downloadsin PowerShell.
πͺ PowerShell version (copy-paste on Windows)
# 1. Reproduce the walkthrough above
movie scan "$env:USERPROFILE\Downloads" # β swap for your own scan folder
# 2. Re-run for any unmatched titles after the first pass
movie rescan
# 3. Confirm what landed in the library
movie ls
β Expected output (sample β yours will list your own files)
Scanning ~/Downloads ... found 12 video files
β Inception.2010.1080p.mkv β Inception (2010) β
8.4
β The.Batman.2022.WEB.mp4 β The Batman (2022) β
7.8
β Dune.Part.Two.2024.2160p.mkv β Dune: Part Two (2024) β
8.3
β random_clip.mp4 β no TMDb match (run `movie rescan` later)
Saved 11 entries to library. Run `movie ls` to browse.
| Command | Description |
|---|---|
movie scan [folder] |
Scan folder β DB + TMDb metadata |
movie rescan |
Re-fetch TMDb metadata for entries with missing data |
movie ls |
Paginated interactive library browser |
movie search <name> |
Live TMDb search β save to DB |
movie info <id|title> |
Detail view (local DB β TMDb fallback) |
movie scan ~/Downloads # scan folder, fetch metadata + posters
movie rescan # re-fetch missing genres/ratings from TMDb
movie ls # browse library with pagination
movie search "Inception" # search TMDb and save result
movie info 1 # show details for media ID 1
movie info "The Batman" # search by title
File Management
πΈ movie move previews the destination for every file before touching the filesystem β fully reversible with movie undo.
βΆ Try the example from the screenshot β preview destinations, accept with a, then undo if needed:
# 1. Interactive preview (the walkthrough's "Select [a]ll, [n]one, or numbers" prompt)
movie move ~/Downloads # β swap for your own source folder
# 2. Or batch-route everything by type (Movies/ vs TV/)
movie move --all ~/Downloads
# 3. Changed your mind? Reverse the entire batch
movie undo
Path placeholders:
~/Downloads= macOS/Linux. Windows:C:\Users\<you>\Downloadsor$env:USERPROFILE\Downloads.
πͺ PowerShell version (copy-paste on Windows)
# 1. Interactive preview (the walkthrough's "Select [a]ll, [n]one, or numbers" prompt)
movie move "$env:USERPROFILE\Downloads" # β swap for your own source folder
# 2. Or batch-route everything by type (Movies\ vs TV\)
movie move --all "$env:USERPROFILE\Downloads"
# 3. Changed your mind? Reverse the entire batch
movie undo
β Expected output (sample preview before confirmation)
Planned moves (3):
[1] Inception.2010.1080p.mkv β Movies/Inception (2010)/Inception.2010.1080p.mkv
[2] The.Batman.2022.WEB.mp4 β Movies/The Batman (2022)/The Batman.2022.mp4
[3] Breaking.Bad.S01E01.mkv β TV/Breaking Bad/Season 01/S01E01.mkv
Select [a]ll, [n]one, or numbers (e.g. 1,3): a
β Moved 3 files. Undo with `movie undo` (batch id 87).
| Command | Description |
|---|---|
movie move [directory] |
Browse, select, move with clean name |
movie move --all |
Batch move all files (auto-route by type) |
movie rename |
Batch rename to clean format |
movie popout [directory] |
Extract video files from subfolders to root |
movie play <id> |
Open with default video player |
movie cd [folder-name] |
Print path of a scanned folder for quick nav |
movie move ~/Downloads # interactive single-file move
movie move --all ~/Downloads # batch move all files
movie rename # clean all filenames
movie popout ~/Downloads # flatten nested subfolders
movie play 1 # play with system player
cd $(movie cd Movies) # navigate to scanned folder
History & Undo
πΈ Every move, rename, scan, and delete is tracked. movie undo --list shows what can be reversed; movie redo re-applies it.
βΆ Try the example from the screenshot β list operations, undo a specific batch by ID, then redo it:
# 1. List recent operations (the walkthrough's "ID When Action Target" table)
movie undo --list
# 2. Revert the batch you saw β replace 42 with the ID from your own list
movie undo --id 42 # β swap 42 for the ID you want to revert
# 3. Re-apply if you undid by mistake
movie redo
ID placeholder:
42is a sample undo ID. Runmovie undo --listto see your own IDs.
πͺ PowerShell version (copy-paste on Windows)
# 1. List recent operations (the walkthrough's "ID When Action Target" table)
movie undo --list
# 2. Revert the batch you saw β replace 42 with the ID from your own list
movie undo --id 42 # β swap 42 for the ID you want to revert
# 3. Re-apply if you undid by mistake
movie redo
β Expected output (sample β IDs and timestamps will differ)
ID When Action Target
ββ ββββββββββββββββ βββββββ βββββββββββββββββββββββββββββββββββββββββββββ
42 2025-04-20 14:02 move 3 files β Movies/
41 2025-04-20 13:55 rename 7 files cleaned
40 2025-04-20 12:10 scan 12 entries added
$ movie undo --id 42
β Reverted batch 42 β 3 files restored to original locations.
| Command | Description |
|---|---|
movie undo |
Revert last move/rename/delete/scan operation |
movie undo --list |
Show recent undoable actions |
movie undo --batch |
Undo the entire last batch (e.g. a full scan) |
movie undo --id <id> |
Undo a specific action by ID |
movie redo |
Re-apply the last undone operation |
movie history |
Show history of all tracked operations |
movie undo # revert most recent operation
movie undo --list # see what can be undone
movie undo --batch # undo entire last scan batch
movie undo --id 42 # undo specific action
movie redo # re-apply last undone operation
movie history # view full operation history
Discovery & Organization
πΈ movie suggest reads your library tastes and surfaces both personalized picks and trending titles from TMDb.
βΆ Try the example from the screenshot β get 5 picks, browse a genre, then add one to your watchlist:
# 1. Reproduce the walkthrough's 5-item recommendation block
movie suggest 5 # β change the number for more/fewer picks
# 2. Drill into a specific genre
movie discover Sci-Fi # β swap for Action, Comedy, Horror, etc.
# 3. Bookmark something to watch later (use any ID from `movie ls`)
movie watch add 3 # β swap 3 for your chosen media ID
Number / genre / ID placeholders:
5= pick count;Sci-Fi= any genre;3= media ID from yourmovie ls.
πͺ PowerShell version (copy-paste on Windows)
# 1. Reproduce the walkthrough's 5-item recommendation block
movie suggest 5 # β change the number for more/fewer picks
# 2. Drill into a specific genre (quote names containing a hyphen to be safe)
movie discover "Sci-Fi" # β swap for Action, Comedy, Horror, etc.
# 3. Bookmark something to watch later (use any ID from `movie ls`)
movie watch add 3 # β swap 3 for your chosen media ID
β Expected output (sample β picks vary based on your library)
Top 5 picks for you (based on your top genres: Sci-Fi, Thriller):
1. Arrival (2016) β
7.9 Sci-Fi Β· Drama
2. Edge of Tomorrow (2014) β
7.9 Sci-Fi Β· Action
3. Ex Machina (2014) β
7.7 Sci-Fi Β· Thriller
4. Annihilation (2018) β
6.8 Sci-Fi Β· Horror
5. Coherence (2013) β
7.2 Sci-Fi Β· Mystery
β Added "Arrival (2016)" to watchlist (id 3).
| Command | Description |
|---|---|
movie suggest [N] |
Genre-based recommendations + trending |
movie discover [genre] |
Browse TMDb by genre (interactive picker or direct) |
movie tag add <id> <tag> |
Add a tag to a media item |
movie tag remove <id> <tag> |
Remove a tag |
movie tag list [id] |
List tags (per item or all) |
movie watch add <id> |
Add a library item to your watchlist |
movie watch done <id> |
Mark a title as watched |
movie watch undo <id> |
Revert a title back to to-watch |
movie watch rm <id> |
Remove a title from your watchlist |
movie watch ls |
List your watchlist |
movie watch export |
Export watchlist as JSON for backup |
movie watch import <file> |
Import watchlist from JSON |
movie stats |
Counts, storage, genre chart, avg ratings |
movie duplicates |
Detect duplicate media entries |
movie suggest 5 # get 5 recommendations
movie discover # interactive genre picker
movie discover Action # discover Action movies from TMDb
movie discover Comedy --type tv # discover Comedy TV shows
movie discover Horror --page 2 # page 2 of Horror movies
movie tag add 1 favorite # tag media #1 as favorite
movie tag list # list all tags
movie watch add 3 # add media #3 to watchlist
movie watch done 3 # mark as watched
movie watch ls # view watchlist
movie stats # library statistics
movie duplicates # find duplicate entries
Maintenance & Debugging
πΈ movie stats renders an instant overview β counts, storage used, top genres, and average rating.
βΆ Try the example from the screenshot β view stats, then prune any stale entries it surfaces:
# 1. Reproduce the walkthrough's library overview + top-genres chart
movie stats
# 2. Dry-run a cleanup to see entries whose files no longer exist
movie cleanup
# 3. Actually remove them once you're happy with the dry-run output
movie cleanup --remove
No placeholders here β
movie statsandmovie cleanuprun as-is.
πͺ PowerShell version (copy-paste on Windows)
# 1. Reproduce the walkthrough's library overview + top-genres chart
movie stats
# 2. Dry-run a cleanup to see entries whose files no longer exist
movie cleanup
# 3. Actually remove them once you're happy with the dry-run output
movie cleanup --remove
β Expected output (sample β numbers reflect your library)
Library: 142 titles Β· 118 movies Β· 24 TV shows Β· 1.7 TB
Top genres: Drama ββββββββββββ 38 Sci-Fi ββββββββ 26 Action ββββββ 19
Average rating: β
7.4
$ movie cleanup
Stale entries (files missing on disk): 4
- Old.Movie.2009.avi (id 17)
- Removed.Show.S02E03.mkv (id 88)
Run `movie cleanup --remove` to delete these from the database.
| Command | Description |
|---|---|
movie cleanup |
Find stale entries where files no longer exist |
movie cleanup --remove |
Delete stale entries (not just preview) |
movie db |
Show resolved database path and status |
movie logs |
Display recent error logs from the database |
movie rest |
Start a local REST API server for the library |
movie rest --open |
Start server and open in browser |
movie export [-o path] |
Dump media table as JSON |
movie cleanup # dry run β show stale entries
movie cleanup --remove # actually remove stale entries
movie db # check database location
movie logs # view recent error/warning logs
movie rest # start REST API on localhost
movie rest --open # start and open browser
movie export -o ~/library.json # export full library as JSON
Configuration & System
πΈ movie config shows every setting; movie version prints the exact build for bug reports.
βΆ Try the example from the screenshot β inspect config, set the TMDb key, then verify the build:
# 1. Reproduce the walkthrough's "Current configuration" block
movie config
# 2. Set your own TMDb API key (replace YOUR_KEY with the real value)
movie config set tmdb_api_key YOUR_KEY # β swap YOUR_KEY for your TMDb token
# 3. Confirm exactly which build is running (use this in bug reports)
movie version
Key placeholder:
YOUR_KEY= your TMDb API token from https://www.themoviedb.org/settings/api.
πͺ PowerShell version (copy-paste on Windows)
# 1. Reproduce the walkthrough's "Current configuration" block
movie config
# 2. Set your own TMDb API key (replace YOUR_KEY with the real value)
# Tip: store it in an env var first so it doesn't end up in shell history:
# $env:TMDB_KEY = "your-real-token"
movie config set tmdb_api_key $env:TMDB_KEY # β or pass the literal token in quotes
# 3. Confirm exactly which build is running (use this in bug reports)
movie version
β Expected output (sample β your build info will differ)
Current configuration:
tmdb_api_key ********************abcd (set)
library_root ~/Media
player vlc
log_level info
$ movie config set tmdb_api_key YOUR_KEY
β Saved tmdb_api_key.
$ movie version
movie v2.178.0 commit a1b2c3d built 2025-04-26 go1.22.2 darwin/arm64
| Command | Description |
|---|---|
movie config |
Show all configuration |
movie config set <key> <val> |
Set a config value |
movie version |
Version, commit, build date, Go, OS/arch |
movie update |
Pull latest, rebuild, and deploy (copy-and-handoff) |
movie update-cleanup |
Remove leftover temp binaries and .bak backups |
movie changelog [--latest] |
Show release notes |
movie config set movies_dir ~/Movies
movie config set tmdb_api_key YOUR_KEY
movie update # full self-update: pull β build β deploy
movie update-cleanup # remove temp update artifacts
movie changelog --latest
Config keys:
| Key | Default | Purpose |
|---|---|---|
movies_dir |
~/Movies |
Movie file destination |
tv_dir |
~/TVShows |
TV show destination |
archive_dir |
~/Archive |
Archive destination |
scan_dir |
~/Downloads |
Default scan source |
tmdb_api_key |
(none) | TMDb API key |
page_size |
20 |
Items per page in ls |
Troubleshooting
Quick Diagnosis Flowchart
Not sure which error you're seeing? Follow this decision tree to find the right fix in seconds.
βββββββββββββββββββββββββββββββββββββββ
β What happened when you ran the β
β command? β
ββββββββββββββββ¬βββββββββββββββββββββββ
β
ββββββββββββ΄βββββββββββ
β β
βββββΌβββββ ββββββΌβββββ
β Every β β Some or β
β file β β all got β
β shows β β skipped β
β "no β β with an β
β TMDb β β error β
β match" β β code β
βββββ¬βββββ ββββββ¬βββββ
β β
β βββββββββββββΌββββββββββββ
β β β β
β ββββββΌβββββ ββββββΌβββββ ββββββΌβββββ
β β 429 / β β 401 / β β timeout β
β β "too β β "unauth-β β / DNS β
β β many β β orized" β β failure β
β β requests"β β β β β
β ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β β
β ββββββΌβββββ ββββββΌβββββ ββββββΌβββββ
β β Wait & β β Check β β Check β
β β re-run β β your β β network β
β β rescan β β API key β β / proxy β
β β β β β β settingsβ
β ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β β
βββββββββββ΄ββββββββββββ΄ββββββββββββ
β
βββββββββββΌββββββββββ
β "database is β
β locked" or β
β SQLITE_BUSY β
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Kill any other β
β movie process, β
β then retry β
ββββββββββββββββββββββ
Map the symptom to the fix:
| Symptom | Likely cause | Jump to fix |
|---|---|---|
Every file shows no TMDb match |
API key missing or wrong | 1. tmdb_api_key not set |
429 too many requests |
Rate limit hit during large scan | 5. TMDb 429 Too Many Requests |
database is locked / SQLITE_BUSY |
Second movie process running |
8. database is locked |
1. tmdb_api_key not set β TMDb requests are skipped
Symptom: movie scan runs but every file is reported as ! no TMDb match β saved as Unknown (see the warning row in the scan walkthrough).
Cause: No TMDb API key configured. The scanner falls back to filename-only parsing.
Fix:
movie config set tmdb_api_key YOUR_KEY # see assets/screenshots/cmd-config-system.gif
movie config # confirm: tmdb_api_key = ******** (set)
movie rescan # backfill metadata for previously-unmatched entries
If the key is set but matches still fail, see error #5 (rate limits).
2. no TMDb match for a known title
Symptom: A file you recognize ends up unmatched in the scan walkthrough, tagged β no TMDb match.
Cause: The release filename is too noisy for the cleaner (extra release-group tags, unusual separators, foreign titles).
Fix: Search and link manually.
movie search "The Matrix" # live TMDb search
movie info "The Matrix" # confirm the right title
movie rescan # re-resolve everything still missing metadata
If the title genuinely isn't in TMDb, the OMDb fallback kicks in automatically when OMDB_API_KEY is set (see error #6).
3. move refuses to run β destination directory missing
Symptom: movie move aborts before showing the planned destinations from the file-management walkthrough, printing movies_dir does not exist or tv_dir does not exist.
Cause: movies_dir / tv_dir point to a folder that hasn't been created yet.
Fix:
movie config # check current paths
mkdir -p ~/Movies ~/TVShows # create the destinations
movie config set movies_dir ~/Movies # or repoint to an existing folder
movie config set tv_dir ~/TVShows
4. Wrong files moved β need to roll back
Symptom: A movie move --all or movie rename batch put files in unexpected places.
Fix: Every operation is tracked. Use the flow shown in the history & undo walkthrough:
movie undo --list # find the batch ID (e.g. 42)
movie undo --id 42 # revert exactly that batch
# changed your mind?
movie redo # re-apply the last undone operation
movie undo always works in reverse chronological order β there is no "permanent" move.
5. TMDb 429 Too Many Requests β rate limited
Symptom: movie scan or movie suggest (see the discovery walkthrough) prints tmdb: 429 too many requests and skips entries.
Cause: TMDb caps free keys at ~50 requests / second. Large scans can briefly exceed it.
Fix: The scanner backs off automatically; just re-run the resolver after a short pause:
sleep 5 && movie rescan # backfill anything skipped
movie logs # inspect any retained warnings
6. OMDB_API_KEY not set β fallback tier silently disabled
Symptom: Some titles still show as Unknown even after movie rescan, and movie logs shows omdb: tier skipped (no key).
Cause: OMDb is the secondary provider used when TMDb has no result. It's opt-in and reads only from the environment β never the config file or repo.
Fix:
export OMDB_API_KEY=your_omdb_key # add to your shell profile to persist
movie rescan
movie logs # confirm the omdb-skip warnings are gone
If you also see omdb: 401 unauthorized, the key is wrong β generate a new one at omdbapi.com.
7. Stale entries β files were moved/deleted outside the CLI
Symptom: movie ls shows entries whose files no longer exist on disk. movie stats (see the maintenance walkthrough) over-reports Total size.
Fix:
movie cleanup # dry-run: list stale entries
movie cleanup --remove # actually delete them from the DB
movie duplicates # also surface accidental dupes after a cleanup
8. database is locked β second movie process running
Symptom: Any command exits with database is locked or SQLITE_BUSY.
Cause: SQLite WAL allows many readers but only one writer at a time. A long-running movie rest server or a hung movie scan can hold the write lock.
Fix:
movie db # confirms the path of the locked DB
# stop any running 'movie rest' / 'movie scan'
ps -ef | grep -i movie # find lingering processes
kill <pid>
If the lock persists after killing all processes, delete data/movie.db-wal and data/movie.db-shm (the live DB file is safe to keep).
9. command not found: movie after movie update
Symptom: Self-update appears to succeed but the next invocation prints command not found.
Cause: The new binary was deployed to a directory not on $PATH, or shell hash cache is stale.
Fix:
movie update-cleanup # remove any half-installed temp binaries
hash -r # bash/zsh: clear the command cache
which movie # verify the resolved path
movie version # confirm the new build (see assets/screenshots/cmd-config-system.gif)
On Windows, restart the terminal so the updated PATH is picked up.
Still stuck?
- Run
movie versionand include the output in any bug report β it pins down the exact commit and build date. - Run
movie logsβ the most recent error rows usually point straight at the failing layer (TMDb / DB / filesystem). - Open an issue with the
versionline, the failing command, and the relevantlogsexcerpt.
Command Tree
movie
βββ hello # Greeting with version
βββ version # Version, commit, build date, Go, OS/arch
βββ changelog [--latest] # Show changelog (full or latest version)
βββ update # Pull β rebuild β deploy (copy-and-handoff)
βββ update-cleanup # Remove temp update artifacts
βββ config [get|set] [key] # View/set configuration
βββ scan [folder] # Scan folder β DB + TMDb metadata
βββ rescan # Re-fetch missing TMDb metadata
βββ ls # Paginated library list (file-backed only)
βββ search <name> # Live TMDb search β save to DB
βββ info <id|title> # Detail view (local DB β TMDb fallback)
βββ suggest [N] # Recommendations + trending
βββ discover [genre] # Browse TMDb by genre
βββ move [directory] # Browse, select, move with clean name
βββ rename # Batch rename to clean format
βββ popout [directory] # Extract videos from subfolders
βββ undo [--list|--batch|--id] # Revert operations (move/delete/scan)
βββ redo # Re-apply last undone operation
βββ history # Show all tracked operations
βββ play <id> # Open with default video player
βββ stats # Counts, storage, genre chart, avg ratings
βββ duplicates # Detect duplicate media entries
βββ cleanup [--remove] # Find/remove stale entries
βββ tag [add|remove|list] # Manage user-defined tags
βββ watch [add|done|undo|rm|ls|export|import] # Manage watchlist + sync
βββ cd [folder-name] # Print scanned folder path
βββ export [-o path] # Dump media table as JSON
βββ db # Show database path and status
βββ logs # View error/warning logs
βββ rest [--open] # Start local REST API server
Build & Deploy
Makefile Targets
| Target | Description |
|---|---|
make build |
Compile for current platform |
make build-all |
Cross-compile all 6 targets into dist/ |
make build-windows |
Windows amd64 (with embedded icon) |
make build-windows-arm |
Windows arm64 (with embedded icon) |
make build-mac-arm |
macOS ARM64 |
make build-mac-intel |
macOS amd64 |
make build-linux |
Linux amd64 |
make build-linux-arm |
Linux arm64 |
make install |
Build + copy to /usr/local/bin |
Build via run.ps1
.\run.ps1 # Full pipeline: pull, build, deploy
.\run.ps1 -NoPull # Skip git pull
.\run.ps1 -NoPull -NoDeploy # Build only
.\run.ps1 -R movie scan D:\movies # Build + run scan
.\run.ps1 -t # Run all unit tests
.\run.ps1 -ForcePull # CI mode: discard changes + pull
| Flag | Description |
|---|---|
-NoPull |
Skip git pull |
-NoDeploy |
Skip deploy step |
-R |
Run movie after build (trailing args forwarded) |
-t |
Run all unit tests |
-ForcePull |
CI mode: discard changes + pull |
See spec/03-general/04-run-guide.md for the full usage guide.
Release Workflow
Releases are fully automated via GitHub Actions. Pushing to a release/** branch or a v* tag triggers:
- Cross-compilation β 6 binaries (Windows/Linux/macOS Γ amd64/arm64)
- Packaging β
.zip(Windows) and.tar.gz(Unix) - SHA256 checksums β
checksums.txtwith all artifact hashes - Install scripts β version-pinned
install.ps1andinstall.sh - GitHub Release β formatted page with changelog, checksums, and install instructions
Creating a Release
# Option A: Push a release branch
git checkout -b release/v1.3.0
git push origin release/v1.3.0
# Option B: Tag directly
git tag v1.3.0
git push origin v1.3.0
Both trigger the same pipeline. Version is resolved from the ref name.
CI Pipeline: Pushing a
release/*branch orv*tag triggers GitHub Actions to cross-compile 6 targets, generate checksums, and create a GitHub release with changelog and install instructions.
See spec/12-ci-cd-pipeline/02-release-pipeline.md for the full pipeline spec.
Project Structure
movie-cli-v7/
βββ main.go # Entry point
βββ cmd/ # Cobra commands (one file per command)
β βββ root.go # Root command, registers subcommands
β βββ movie_config.go # config get/set
β βββ movie_scan.go # scan folder
β βββ movie_rescan.go # re-fetch missing metadata
β βββ movie_ls.go # paginated list
β βββ movie_search.go # TMDb search
β βββ movie_info.go # detail view + shared fetch helpers
β βββ movie_suggest.go # recommendations
β βββ movie_move.go # interactive move
β βββ movie_rename.go # batch rename
β βββ movie_popout.go # extract from subfolders
β βββ movie_undo.go # undo operations
β βββ movie_redo.go # redo undone operations
β βββ movie_history.go # operation history
β βββ movie_play.go # play with system player
β βββ movie_stats.go # library statistics
β βββ movie_duplicates.go # duplicate detection
β βββ movie_cleanup.go # stale entry cleanup
β βββ movie_tag.go # tag management
β βββ movie_watch.go # watchlist management
β βββ movie_cd.go # folder navigation helper
β βββ movie_export.go # JSON export
β βββ movie_db.go # database path/status
β βββ movie_logs.go # error log viewer
β βββ movie_rest.go # REST API server
β βββ movie_resolve.go # shared ID/title resolver
βββ cleaner/cleaner.go # Filename cleaning + slug generation
βββ tmdb/client.go # TMDb API client
βββ db/ # SQLite database layer
β βββ db.go # Connection + migrations
β βββ media.go # Media CRUD operations
β βββ config.go # Config get/set
β βββ history.go # Move + scan history
βββ errlog/ # Centralized error/warning logging
β βββ errlog.go # File + DB logging with stack traces
βββ updater/ # Copy-and-handoff self-update
β βββ run.go # Entry points: Run() + RunWorker()
β βββ repo.go # Repo path resolution
β βββ handoff.go # Binary copy + foreground launch
β βββ script.go # PowerShell script generation
β βββ cleanup.go # Temp artifact removal
βββ version/version.go # Build-time version variables
βββ .github/
β βββ workflows/
β βββ ci.yml # Lint + test + vulncheck + cross-build
β βββ release.yml # Cross-compile + GitHub Release
β βββ vulncheck.yml # Weekly vulnerability scan
βββ run.ps1 # PowerShell build + deploy pipeline
βββ install.ps1 # Bootstrap installer
βββ CHANGELOG.md # Release notes
βββ spec/ # Detailed specifications
Data Storage
All data lives in ./data/:
./data/
βββ movie.db # SQLite database (WAL mode)
βββ thumbnails/ # Downloaded poster images
βββ json/
βββ movie/ # Per-movie JSON metadata
βββ tv/ # Per-show JSON metadata
βββ history/ # Move operation logs (RFC3339)
Documentation sync
The install snippet shown at the top of this README is the canonical source of truth. QUICKSTART.md and spec/03-general/01-install-guide.md are regenerated from it (between <!-- INSTALL:BEGIN --> / <!-- INSTALL:END --> sentinels) by scripts/sync-install-from-readme.sh. CI runs --check on every push.
npm run sync:install # rewrite mirrored docs from README
npm run sync:install:check # exit 1 if any mirror has drifted
npm run sync:install:print # preview the extracted block
npm run sync:install:json # machine-readable output (see schema below)
npm run sync:install:list # show resolved target list
npm run sync:install:discover # auto-find every *.md with the sentinels
--json output schema (stable contract)
bash scripts/sync-install-from-readme.sh --json writes a single JSON object
to stdout (diagnostics go to stderr, so the stream is safe to pipe into
jq, $GITHUB_OUTPUT, or any tool). The schema is additive β new keys
may be added in future versions, but existing keys, types, and meanings will
not change without a major version bump.
| Field | Type | Required | Description |
|---|---|---|---|
source |
string |
yes | Always the literal "README.md" (the canonical source file). |
extracted |
string (enum) |
yes | How the block was located. One of: "sentinels" (preferred β found via <!-- README-INSTALL:BEGIN/END -->) or "heuristic" (fallback β matched the install heading + table). |
lines |
integer |
yes | Number of newline-terminated lines in block. Always β₯ 1. |
bytes |
integer |
yes | UTF-8 byte length of block. Always β₯ 1. |
sha256 |
string (hex) |
yes | Lowercase hex SHA-256 of block. 64 chars. Use this for drift detection in CI without diffing. |
targets |
array<string> |
yes | Resolved sync targets, as repo-root-relative POSIX paths. May be empty if the user passed --targets "". Order is significant (matches resolution order). |
block |
string |
yes | The verbatim install block extracted from the README, with leading/trailing blank lines trimmed. May contain newlines, Markdown, HTML, and emoji. |
Required vs optional fields
Required (always present, never null, never omitted):
sourceβ always the literal string"README.md".extractedβ always one of the enum values below.linesβ integer,>= 1.bytesβ integer,>= 1.sha256β string, exactly 64 lowercase hex chars (^[0-9a-f]{64}$).targetsβ array of strings (possibly empty[], but the key is always present).blockβ non-empty string.
Optional: none. The current schema has no optional fields. Future additive fields (introduced without a major version bump) will be documented here and MUST be treated as optional by consumers β assume the key may be absent on older versions.
Nullability: no field is ever null. Consumers that encounter null for any documented field should treat the output as malformed.
Allowed enum values:
extractedβ exactly one of:"sentinels"β block was located via the canonical<!-- README-INSTALL:BEGIN -->/<!-- README-INSTALL:END -->markers inREADME.md. This is the preferred path."heuristic"β sentinels were not found; the block was located by matching the install heading + first</table>+ trailing<sub>caption. Treat this as a soft warning that the README should be updated to add sentinels.
sourceβ currently always"README.md". Reserved as a string enum so future versions could extend it; consumers should still match exactly"README.md"today.
Unknown fields: consumers MUST ignore any keys not listed in the table above so the schema can grow additively.
Exit codes: 0 on success, 2 if the install block could not be extracted or python3/python is not on PATH (required to safely JSON-encode the block).
Example consumer (jq):
# Fail CI if the README install block has changed since the last release.
EXPECTED=abc123... # sha256 from previous run
ACTUAL=$(bash scripts/sync-install-from-readme.sh --json | jq -r .sha256)
[[ "$EXPECTED" == "$ACTUAL" ]] || { echo "install block drifted"; exit 1; }
Example output (truncated):
{
"source": "README.md",
"extracted": "sentinels",
"lines": 25,
"bytes": 653,
"sha256": "47a8bdd9900aβ¦",
"targets": [
"QUICKSTART.md",
"spec/03-general/01-install-guide.md"
],
"block": "**π Install in 10 seconds β anyone, any OS:**\n\n<table>\nβ¦"
}
Milestones
Project milestones are tracked in MILESTONES.md at the repository root.
-
Location β
MILESTONES.md(repo root, version-controlled) -
Timezone β Malaysia time (UTC+8,
Asia/Kuala_Lumpur) -
Timestamp format β
dd-MMM-YYYY hh:mm AM/PM(e.g.24-Apr-2026 03:33 PM) -
Entry format β one bullet per line under the
## Logheading:- <event> <dd-MMM-YYYY hh:mm AM/PM> β <short note>
Example entries:
- let's start now 24-Apr-2026 03:33 PM β milestone tracker initialized
- run 24-Apr-2026 07:21 PM β app run logged
New entries are appended to the end of the ## Log section. Generate the timestamp with:
TZ='Asia/Kuala_Lumpur' date '+%d-%b-%Y %I:%M %p'
Listing & filtering milestones
The movie milestones command reads MILESTONES.md and prints entries with
optional date / keyword filters:
movie milestones # show all entries
movie milestones --keyword scan # case-insensitive substring
movie milestones --date 2026-04-24 # only entries on this day
movie milestones --since 2026-04-01 # entries on/after this day
movie milestones --since 2026-04-01 -k run -n 20
Flags: --date YYYY-MM-DD, --since YYYY-MM-DD, -k/--keyword TEXT,
-n/--limit N (0 = no cap).
One-shot helper (append + version bump + commit)
The repo ships with a script that appends a new milestone, bumps the patch
version in version/info.go, and creates a single git commit covering both
changes:
Linux / macOS
scripts/log-milestone.sh # default: "- run <ts> β app run logged"
scripts/log-milestone.sh "kickoff complete" # custom note
scripts/log-milestone.sh --event start "kickoff"
Windows (PowerShell)
pwsh scripts/log-milestone.ps1
pwsh scripts/log-milestone.ps1 -Note "kickoff complete"
pwsh scripts/log-milestone.ps1 -Event start -Note "kickoff"
Wire it into the app (e.g. at the end of movie startup, or as a make run
target) to get a milestone + commit on every run. Commit message format:
chore(milestone): <event> <timestamp> β <note> (<new-version>).
Dependencies
| Package | Purpose |
|---|---|
github.com/spf13/cobra |
CLI framework |
modernc.org/sqlite |
Pure-Go SQLite driver (no CGo) |
π€ Contributing
Contributions are welcome! Here's how to get started:
- Fork the repository
- Create a branch for your feature or fix:
git checkout -b feature/my-feature - Follow the coding guidelines in
spec/01-coding-guidelines/ - Keep files small β one file per command, max ~200 lines
- Run tests before submitting:
make tidy go test ./... -v - Open a Pull Request against
mainwith a clear description
Development Setup
git clone https://github.com/alimtvnetwork/movie-cli-v7.git
cd movie-cli-v7
make tidy
make build
See the Install Guide for full setup instructions.
Code Style
- Go standard formatting (
gofmt) - Descriptive variable names, no abbreviations
- Error messages start lowercase, no trailing punctuation
- All new code must pass
go vetandgolangci-lint
π Code of Conduct
We are committed to providing a welcoming and inclusive experience for everyone.
Our Standards:
- Be respectful, constructive, and empathetic
- Welcome newcomers and help them get started
- Accept constructive criticism gracefully
- Focus on what's best for the project and community
Unacceptable Behavior:
- Harassment, trolling, or personal attacks
- Publishing others' private information
- Any conduct that would be considered inappropriate in a professional setting
Enforcement: Project maintainers may remove, edit, or reject contributions that violate this code. Repeated violations may result in a temporary or permanent ban.
This Code of Conduct is adapted from the Contributor Covenant v2.1.
A system architect with 20+ years of professional software engineering experience across enterprise, fintech, and distributed systems. His technology stack spans .NET/C# (18+ years), JavaScript (10+ years), TypeScript (6+ years), and Golang (4+ years).
Recognized as a top 1% talent at Crossover and one of the top software architects globally. He is also the Chief Software Engineer of Riseup Asia LLC and maintains an active presence on Stack Overflow (2,452+ reputation, member since 2010) and LinkedIn (12,500+ followers).
| Website | alimkarim.com Β· my.alimkarim.com |
| linkedin.com/in/alimkarim | |
| Stack Overflow | stackoverflow.com/users/361646/alim-ul-karim |
| Alim Ul Karim | |
| Role | Chief Software Engineer, Riseup Asia LLC |
Riseup Asia LLC
Top Leading Software Company in WY (2026)
| Website | riseup-asia.com |
| riseupasia.talent | |
| Riseup Asia | |
| YouTube | @riseup-asia |
License
Released under the MIT License β free for personal and commercial use, with no warranty.
Built with β€οΈ by Md. Alim Ul Karim Β· Riseup Asia LLC
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
Package apperror provides standardized error wrapping for the CLI.
|
Package apperror provides standardized error wrapping for the CLI. |
|
Package cleaner handles filename cleaning and metadata extraction from file names.
|
Package cleaner handles filename cleaning and metadata extraction from file names. |
|
changelog.go β implements the `movie changelog` command.
|
changelog.go β implements the `movie changelog` command. |
|
action_history.go β ActionHistory table: types and helpers.
|
action_history.go β ActionHistory table: types and helpers. |
|
checks.go β individual diagnostic checks.
|
checks.go β individual diagnostic checks. |
|
Package errlog provides centralized error logging to both file and database.
|
Package errlog provides centralized error logging to both file and database. |
|
Package templates provides embedded HTML templates for the movie CLI.
|
Package templates provides embedded HTML templates for the movie CLI. |
|
Package tmdb provides a client for The Movie Database (TMDb) API.
|
Package tmdb provides a client for The Movie Database (TMDb) API. |
|
Package updater implements the copy-and-handoff self-update mechanism.
|
Package updater implements the copy-and-handoff self-update mechanism. |
|
Package version holds the three build-time variables that describe exactly which binary is running.
|
Package version holds the three build-time variables that describe exactly which binary is running. |