feat(cli): add list command
This commit is contained in:
152
ripper.py
Normal file → Executable file
152
ripper.py
Normal file → Executable file
@@ -672,6 +672,147 @@ def generate_nfo(mkv_path: str) -> str:
|
||||
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 ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
@@ -683,6 +824,7 @@ def main():
|
||||
" %(prog)s --imdb tt6977338\n"
|
||||
" %(prog)s --name 'Game Night' --year 2018\n"
|
||||
" %(prog)s --scan-only\n"
|
||||
" %(prog)s --list\n"
|
||||
" %(prog)s --name Inception --year 2010 --input /dev/sr0\n"
|
||||
),
|
||||
)
|
||||
@@ -712,8 +854,18 @@ def main():
|
||||
action="store_true",
|
||||
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()
|
||||
|
||||
# ── Library listing (no disc needed) ─────────────────────────────────
|
||||
if args.list:
|
||||
list_library(args.output_base)
|
||||
return
|
||||
|
||||
# ── Banner ───────────────────────────────────────────────────────────
|
||||
console.print()
|
||||
console.print(
|
||||
|
||||
Reference in New Issue
Block a user