feat(cli): add list command

This commit is contained in:
Jan Meyer
2026-02-10 13:10:53 +01:00
parent 9b81c94bcb
commit 2314ea9588

152
ripper.py Normal file → Executable file
View File

@@ -672,6 +672,147 @@ def generate_nfo(mkv_path: str) -> str:
return nfo_path return nfo_path
def parse_scene_name(scene: str) -> dict:
"""
Parse a scene-style directory/file name into components.
Example: Game.Night.2018.1080p.BluRay.10bit.x265.DTS-HD.MA.5.1.MULTI-6.Audio
"""
info: dict = {"raw": scene}
# Extract known tokens by pattern
res_match = re.search(r"\b(2160p|1080p|720p|480p)\b", scene, re.I)
info["resolution"] = res_match.group(1) if res_match else "?"
source_match = re.search(r"\b(BluRay|DVD|BDRip|WEB-DL|WEBRip|HDTV)\b", scene, re.I)
info["source"] = source_match.group(1) if source_match else "?"
codec_match = re.search(r"\b(x265|x264|HEVC|AVC|h\.?265|h\.?264)\b", scene, re.I)
info["codec"] = codec_match.group(1) if codec_match else "?"
# Audio: find codec tags like DTS-HD.MA.5.1, DD.5.1, TrueHD.7.1, etc.
audio_match = re.search(
r"\b(DTS-HD\.MA|DTS-HD\.HR|DTS|TrueHD|DDP|DD|AAC|FLAC|LPCM|MP3|OPUS)"
r"(?:\.(\d\.\d))?",
scene, re.I,
)
if audio_match:
info["audio"] = audio_match.group(0)
else:
info["audio"] = "?"
multi_match = re.search(r"MULTI-(\d+)", scene, re.I)
info["languages"] = int(multi_match.group(1)) if multi_match else 1
# Year: 4-digit number that looks like a year (1900-2099)
year_match = re.search(r"\.((?:19|20)\d{2})\.", scene)
info["year"] = year_match.group(1) if year_match else "?"
# Title: everything before the year (or before the resolution if no year)
if year_match:
info["title"] = scene[:year_match.start()].replace(".", " ")
elif res_match:
info["title"] = scene[:res_match.start()].rstrip(".").replace(".", " ")
else:
info["title"] = scene.replace(".", " ")
return info
def format_size(size_bytes: int) -> str:
"""Format bytes into human-readable size."""
for unit in ("B", "KB", "MB", "GB", "TB"):
if size_bytes < 1024:
return f"{size_bytes:.1f} {unit}"
size_bytes /= 1024
return f"{size_bytes:.1f} PB"
def list_library(output_base: str) -> None:
"""
Scan the output directory for ripped movies and display them in a table.
"""
base = Path(output_base)
if not base.is_dir():
console.print(f"[bold red]ERROR:[/] Output directory not found: [dim]{output_base}[/]")
sys.exit(1)
movies: list[dict] = []
for entry in sorted(base.iterdir()):
if not entry.is_dir():
continue
# Look for MKV files in the directory
mkv_files = list(entry.glob("*.mkv"))
if not mkv_files:
continue
mkv = mkv_files[0] # primary MKV
nfo = entry / (mkv.stem + ".nfo")
info = parse_scene_name(entry.name)
try:
info["mkv_size"] = mkv.stat().st_size
except OSError:
info["mkv_size"] = 0
info["has_nfo"] = nfo.exists()
info["path"] = str(entry)
movies.append(info)
if not movies:
console.print()
console.print(
Panel(
"[dim]No ripped movies found.[/]\n"
f"Output directory: [dim]{output_base}[/]",
title="[bold cyan]Library[/]",
border_style="dim",
padding=(0, 2),
)
)
console.print()
return
# Build table
table = Table(
title=f"Library · {len(movies)} movies",
box=box.ROUNDED,
title_style="bold cyan",
header_style="bold",
show_lines=True,
padding=(0, 1),
)
table.add_column("#", style="dim", width=3, justify="right")
table.add_column("Title", style="bold white", min_width=20)
table.add_column("Year", style="cyan", justify="center", width=6)
table.add_column("Res", style="green", justify="center", width=6)
table.add_column("Source", style="yellow", width=7)
table.add_column("Audio", style="magenta")
table.add_column("Lang", justify="center", width=5)
table.add_column("Size", style="dim", justify="right", width=9)
table.add_column("NFO", justify="center", width=3)
total_size = 0
for i, m in enumerate(movies, 1):
total_size += m["mkv_size"]
table.add_row(
str(i),
m["title"],
m["year"],
m["resolution"],
m["source"],
m["audio"],
str(m["languages"]),
format_size(m["mkv_size"]),
"[green]✓[/]" if m["has_nfo"] else "[dim]✗[/]",
)
console.print()
console.print(table)
console.print(f" [dim]Total: {format_size(total_size)} across {len(movies)} movies · {output_base}[/]")
console.print()
# ── Main ───────────────────────────────────────────────────────────────────── # ── Main ─────────────────────────────────────────────────────────────────────
def main(): def main():
@@ -683,6 +824,7 @@ def main():
" %(prog)s --imdb tt6977338\n" " %(prog)s --imdb tt6977338\n"
" %(prog)s --name 'Game Night' --year 2018\n" " %(prog)s --name 'Game Night' --year 2018\n"
" %(prog)s --scan-only\n" " %(prog)s --scan-only\n"
" %(prog)s --list\n"
" %(prog)s --name Inception --year 2010 --input /dev/sr0\n" " %(prog)s --name Inception --year 2010 --input /dev/sr0\n"
), ),
) )
@@ -712,8 +854,18 @@ def main():
action="store_true", action="store_true",
help="Only scan the disc and print track info, don't encode.", help="Only scan the disc and print track info, don't encode.",
) )
parser.add_argument(
"--list", "-l",
action="store_true",
help="List all ripped movies in the output directory.",
)
args = parser.parse_args() args = parser.parse_args()
# ── Library listing (no disc needed) ─────────────────────────────────
if args.list:
list_library(args.output_base)
return
# ── Banner ─────────────────────────────────────────────────────────── # ── Banner ───────────────────────────────────────────────────────────
console.print() console.print()
console.print( console.print(