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
|
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(
|
||||||
|
|||||||
Reference in New Issue
Block a user