File size: 5,190 Bytes
aeae670
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""
cli.py
โ”€โ”€โ”€โ”€โ”€โ”€
ๅ‘ฝไปค่กŒ้€‚้…ๅฑ‚๏ผˆๅฏ้€‰๏ผ‰ใ€‚

ๅญๅ‘ฝไปค๏ผš
    search   ่ฏญไน‰ๆœ็ดขๆ ‡็ญพ
    related  ๅŸบไบŽๅ…ฑ็Žฐ่กจๆŸฅๅ…ณ่”ๆŽจ่

็”จๆณ•๏ผš
    python cli.py search "็™ฝ่‰ฒๆฐดๆ‰‹ๆœ็š„ๅฅณๅญฉ" --limit 10 --no-nsfw
    python cli.py related "white_serafuku,sailor_collar" --limit 20
    python cli.py related "white_serafuku,sailor_collar" --no-nsfw --show-sources
"""

import argparse
import asyncio

from core.engine import DanbooruTagger
from core.models import SearchRequest


# โ”€โ”€ search โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

async def cmd_search(args):
    tagger = await DanbooruTagger.get_instance()
    request = SearchRequest(
        query=args.query,
        top_k=args.top_k,
        limit=args.limit,
        popularity_weight=args.weight,
        show_nsfw=not args.no_nsfw,
        use_segmentation=not args.no_seg,
    )
    resp = await asyncio.to_thread(tagger.search, request)

    print(f"\n{'='*60}")
    print(f"ๆŸฅ่ฏข๏ผš{args.query}  |  ๅ…ฑ {len(resp.results)} ๆก็ป“ๆžœ")
    print(f"{'='*60}")
    print(f"ๆŽจ่ Prompt๏ผš\n  {resp.tags_sfw if args.no_nsfw else resp.tags_all}\n")

    for r in resp.results:
        nsfw_mark = "๐Ÿ”ด" if r.nsfw == '1' else "๐ŸŸข"
        print(f"  {nsfw_mark} [{r.final_score:.3f}] {r.tag:<30}  {r.cn_name[:20]:<20}  {r.category}")


# โ”€โ”€ related โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

async def cmd_related(args):
    seed_tags = [t.strip() for t in args.tags.split(',') if t.strip()]
    if not seed_tags:
        print("้”™่ฏฏ๏ผš่ฏทๆไพ›่‡ณๅฐ‘ไธ€ไธช็งๅญๆ ‡็ญพ๏ผŒๅคšไธชๆ ‡็ญพไปฅ้€—ๅทๅˆ†้š”ใ€‚")
        return

    tagger = await DanbooruTagger.get_instance()
    results = await asyncio.to_thread(
        tagger.get_related,
        seed_tags,
        set(seed_tags),   # exclude ็งๅญๆ ‡็ญพ่‡ช่บซ
        args.limit,
        not args.no_nsfw,
    )

    if not results:
        print("ๆœชๆ‰พๅˆฐๅ…ณ่”ๆŽจ่๏ผˆๅ…ฑ็Žฐ่กจๅฏ่ƒฝๆœชๅŠ ่ฝฝ๏ผŒๆˆ–็งๅญๆ ‡็ญพไธๅœจๅบ“ไธญ๏ผ‰ใ€‚")
        return

    print(f"\n{'='*60}")
    print(f"็งๅญๆ ‡็ญพ๏ผš{', '.join(seed_tags)}  |  ๅ…ฑ {len(results)} ๆกๆŽจ่")
    print(f"{'='*60}")

    # ่พ“ๅ‡บ้€—ๅทๅˆ†้š”็š„ๆ ‡็ญพไธฒ๏ผˆๆ–นไพฟ็›ดๆŽฅๅคๅˆถไฝฟ็”จ๏ผ‰
    tag_list = [r.tag for r in results if not (r.nsfw == '1' and args.no_nsfw)]
    print(f"ๆŽจ่ๆ ‡็ญพ๏ผš\n  {', '.join(tag_list)}\n")

    # ๆ˜Ž็ป†่กจ
    for r in results:
        if r.nsfw == '1' and args.no_nsfw:
            continue
        nsfw_mark   = "๐Ÿ”ด" if r.nsfw == '1' else "๐ŸŸข"
        sources_str = f"  โ† {', '.join(r.sources)}" if args.show_sources else ""
        print(
            f"  {nsfw_mark} [{r.cooc_score:.3f}] {r.tag:<30}"
            f"  {r.cn_name[:20]:<20}  {r.category}"
            f"  (ๅ…ฑ็Žฐ:{r.cooc_count:,}){sources_str}"
        )


# โ”€โ”€ ๅ…ฅๅฃ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

async def main():
    parser = argparse.ArgumentParser(
        description="Danbooru Tag CLI Searcher",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
็คบไพ‹๏ผš
  python cli.py search "็™ฝ่‰ฒๆฐดๆ‰‹ๆœ็š„ๅฅณๅญฉ" --limit 10
  python cli.py related "white_serafuku,sailor_collar"
  python cli.py related "white_serafuku" --limit 30 --no-nsfw --show-sources
        """,
    )
    sub = parser.add_subparsers(dest='cmd', required=True)

    # โ”€โ”€ search ๅญๅ‘ฝไปค โ”€โ”€
    p_search = sub.add_parser('search', help='่ฏญไน‰ๆœ็ดขๆ ‡็ญพ')
    p_search.add_argument('query',       help='ๆœ็ดข่ฏ๏ผˆๆ”ฏๆŒไธญ่‹ฑๆ–‡่‡ช็„ถ่ฏญ่จ€๏ผ‰')
    p_search.add_argument('--top-k',   type=int,   default=5,    help='ๆฏๅฑ‚่ฟ”ๅ›žๆ•ฐ้‡๏ผˆ้ป˜่ฎค 5๏ผ‰')
    p_search.add_argument('--limit',   type=int,   default=20,   help='็ป“ๆžœไธŠ้™๏ผˆ้ป˜่ฎค 20๏ผ‰')
    p_search.add_argument('--weight',  type=float, default=0.15, help='็ƒญๅบฆๆƒ้‡๏ผˆ้ป˜่ฎค 0.15๏ผ‰')
    p_search.add_argument('--no-nsfw', action='store_true',      help='่ฟ‡ๆปค NSFW ๅ†…ๅฎน')
    p_search.add_argument('--no-seg',  action='store_true',      help='็ฆ็”จๆ™บ่ƒฝๅˆ†่ฏ')

    # โ”€โ”€ related ๅญๅ‘ฝไปค โ”€โ”€
    p_related = sub.add_parser('related', help='ๅŸบไบŽๅ…ฑ็Žฐ่กจๆŸฅๅ…ณ่”ๆŽจ่')
    p_related.add_argument('tags',           help='็งๅญๆ ‡็ญพ๏ผŒไปฅ่‹ฑๆ–‡้€—ๅทๅˆ†้š”๏ผˆๅฆ‚ white_serafuku,sailor_collar๏ผ‰')
    p_related.add_argument('--limit',        type=int, default=50, help='ๆŽจ่็ป“ๆžœไธŠ้™๏ผˆ้ป˜่ฎค 50๏ผ‰')
    p_related.add_argument('--no-nsfw',      action='store_true',  help='่ฟ‡ๆปค NSFW ๅ†…ๅฎน')
    p_related.add_argument('--show-sources', action='store_true',  help='ๆ˜พ็คบๆฏๆกๆŽจ่็”ฑๅ“ชไธช็งๅญ่งฆๅ‘')

    args = parser.parse_args()
    if args.cmd == 'search':
        await cmd_search(args)
    elif args.cmd == 'related':
        await cmd_related(args)


if __name__ == "__main__":
    asyncio.run(main())