bonanza-ugc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bonanza_ugc-0.1.0/PKG-INFO +73 -0
- bonanza_ugc-0.1.0/README.md +56 -0
- bonanza_ugc-0.1.0/bonanza_ugc/__init__.py +9 -0
- bonanza_ugc-0.1.0/bonanza_ugc/cli.py +227 -0
- bonanza_ugc-0.1.0/bonanza_ugc/config.py +75 -0
- bonanza_ugc-0.1.0/bonanza_ugc/generator.py +387 -0
- bonanza_ugc-0.1.0/bonanza_ugc.egg-info/PKG-INFO +73 -0
- bonanza_ugc-0.1.0/bonanza_ugc.egg-info/SOURCES.txt +11 -0
- bonanza_ugc-0.1.0/bonanza_ugc.egg-info/dependency_links.txt +1 -0
- bonanza_ugc-0.1.0/bonanza_ugc.egg-info/entry_points.txt +2 -0
- bonanza_ugc-0.1.0/bonanza_ugc.egg-info/top_level.txt +1 -0
- bonanza_ugc-0.1.0/pyproject.toml +31 -0
- bonanza_ugc-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bonanza-ugc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-generated UGC videos via Higgsfield Marketing Studio
|
|
5
|
+
Author: Bonanza Labs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: ugc,ai,video,higgsfield,marketing
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Multimedia :: Video
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Bonanza UGC
|
|
19
|
+
|
|
20
|
+
AI-generated UGC videos via Higgsfield Marketing Studio.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install bonanza-ugc
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from bonanza_ugc import UGCGenerator, UGCConfig
|
|
32
|
+
|
|
33
|
+
gen = UGCGenerator()
|
|
34
|
+
video = gen.generate(
|
|
35
|
+
prompt="A stylish young woman shows off her silver chain necklace...",
|
|
36
|
+
avatar="mei",
|
|
37
|
+
mode="ugc",
|
|
38
|
+
duration=15,
|
|
39
|
+
)
|
|
40
|
+
print(f"Video URL: {video.result_url}")
|
|
41
|
+
print(f"Virality: {video.virality_score}/100")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CLI
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Generate a video
|
|
48
|
+
ugc generate --prompt "..." --avatar mei --mode ugc
|
|
49
|
+
|
|
50
|
+
# Use a template
|
|
51
|
+
ugc generate --template silverjstore --product "Venetian Chain" --avatar mei
|
|
52
|
+
|
|
53
|
+
# Score virality
|
|
54
|
+
ugc score <video_path_or_url>
|
|
55
|
+
|
|
56
|
+
# List avatars
|
|
57
|
+
ugc avatars
|
|
58
|
+
|
|
59
|
+
# Batch generate
|
|
60
|
+
ugc batch --leads leads.json --mode ugc
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- 🎬 Generate UGC videos with AI avatars
|
|
66
|
+
- 📊 Score video virality with Brain Activity analysis
|
|
67
|
+
- 🎯 Prompt templates for product categories
|
|
68
|
+
- 📦 Batch generation support
|
|
69
|
+
- 🔄 Product URL fetching and integration
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Bonanza UGC
|
|
2
|
+
|
|
3
|
+
AI-generated UGC videos via Higgsfield Marketing Studio.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install bonanza-ugc
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from bonanza_ugc import UGCGenerator, UGCConfig
|
|
15
|
+
|
|
16
|
+
gen = UGCGenerator()
|
|
17
|
+
video = gen.generate(
|
|
18
|
+
prompt="A stylish young woman shows off her silver chain necklace...",
|
|
19
|
+
avatar="mei",
|
|
20
|
+
mode="ugc",
|
|
21
|
+
duration=15,
|
|
22
|
+
)
|
|
23
|
+
print(f"Video URL: {video.result_url}")
|
|
24
|
+
print(f"Virality: {video.virality_score}/100")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## CLI
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Generate a video
|
|
31
|
+
ugc generate --prompt "..." --avatar mei --mode ugc
|
|
32
|
+
|
|
33
|
+
# Use a template
|
|
34
|
+
ugc generate --template silverjstore --product "Venetian Chain" --avatar mei
|
|
35
|
+
|
|
36
|
+
# Score virality
|
|
37
|
+
ugc score <video_path_or_url>
|
|
38
|
+
|
|
39
|
+
# List avatars
|
|
40
|
+
ugc avatars
|
|
41
|
+
|
|
42
|
+
# Batch generate
|
|
43
|
+
ugc batch --leads leads.json --mode ugc
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- 🎬 Generate UGC videos with AI avatars
|
|
49
|
+
- 📊 Score video virality with Brain Activity analysis
|
|
50
|
+
- 🎯 Prompt templates for product categories
|
|
51
|
+
- 📦 Batch generation support
|
|
52
|
+
- 🔄 Product URL fetching and integration
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from bonanza_ugc.generator import UGCGenerator, UGCVideo
|
|
9
|
+
from bonanza_ugc.config import UGCConfig
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cmd_generate(args):
|
|
14
|
+
"""Generate a UGC video."""
|
|
15
|
+
gen = UGCGenerator(UGCConfig.from_env())
|
|
16
|
+
|
|
17
|
+
if args.template:
|
|
18
|
+
video = gen.generate_from_template(
|
|
19
|
+
template=args.template,
|
|
20
|
+
product_name=args.product or "silver jewelry",
|
|
21
|
+
avatar=args.avatar,
|
|
22
|
+
mode=args.mode,
|
|
23
|
+
gender=args.gender or "woman",
|
|
24
|
+
duration=args.duration,
|
|
25
|
+
resolution=args.resolution,
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
if not args.prompt:
|
|
29
|
+
print("❌ --prompt is required when not using --template")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
video = gen.generate(
|
|
33
|
+
prompt=args.prompt,
|
|
34
|
+
avatar=args.avatar,
|
|
35
|
+
mode=args.mode,
|
|
36
|
+
duration=args.duration,
|
|
37
|
+
resolution=args.resolution,
|
|
38
|
+
aspect_ratio=args.aspect_ratio,
|
|
39
|
+
product_url=args.product_url or "",
|
|
40
|
+
wait=not args.no_wait,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
print(f"\n🎬 UGC Video Generated!")
|
|
44
|
+
print(f" Job ID: {video.job_id}")
|
|
45
|
+
print(f" Status: {video.status}")
|
|
46
|
+
print(f" URL: {video.result_url}")
|
|
47
|
+
if video.local_path:
|
|
48
|
+
print(f" Local: {video.local_path}")
|
|
49
|
+
if video.virality_score:
|
|
50
|
+
print(f" Virality: {video.virality_score}/100")
|
|
51
|
+
print()
|
|
52
|
+
|
|
53
|
+
# Save result
|
|
54
|
+
result_file = Path(gen.output_dir) / f"result_{video.job_id[:8]}.json"
|
|
55
|
+
with open(result_file, "w") as f:
|
|
56
|
+
json.dump(video.to_dict(), f, indent=2)
|
|
57
|
+
print(f" Result saved: {result_file}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cmd_score(args):
|
|
61
|
+
"""Score a video's virality."""
|
|
62
|
+
gen = UGCGenerator(UGCConfig.from_env())
|
|
63
|
+
|
|
64
|
+
video = args.video
|
|
65
|
+
# If it's a job ID, get the result URL first
|
|
66
|
+
if len(video) < 40 and not video.startswith("http") and not Path(video).exists():
|
|
67
|
+
# It's likely a local file in output dir
|
|
68
|
+
local = Path(gen.output_dir) / video
|
|
69
|
+
if local.exists():
|
|
70
|
+
video = str(local)
|
|
71
|
+
|
|
72
|
+
score = gen.score(video)
|
|
73
|
+
|
|
74
|
+
print(f"\n📊 Virality Score")
|
|
75
|
+
print(f" Overall: {score.overall_score}/100")
|
|
76
|
+
print(f" Hook: {score.hook_score}/100")
|
|
77
|
+
print(f" Viral: {score.viral_potential}/100")
|
|
78
|
+
print(f" Engagement: {score.brain_engagement}/100")
|
|
79
|
+
print(f" Sustain: {score.sustain_score}/100")
|
|
80
|
+
print(f" Peak at: {score.peak_second}s")
|
|
81
|
+
if score.recommendations:
|
|
82
|
+
print(f"\n 💡 Recommendations:")
|
|
83
|
+
for rec in score.recommendations:
|
|
84
|
+
print(f" • {rec}")
|
|
85
|
+
print()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_avatars(args):
|
|
89
|
+
"""List available avatars."""
|
|
90
|
+
gen = UGCGenerator(UGCConfig.from_env())
|
|
91
|
+
avatars = gen.list_avatars()
|
|
92
|
+
|
|
93
|
+
if not avatars:
|
|
94
|
+
# Fallback to config
|
|
95
|
+
print("Available avatars (from config):")
|
|
96
|
+
for name, id in gen.config.avatars.items():
|
|
97
|
+
print(f" {name:12s} {id}")
|
|
98
|
+
else:
|
|
99
|
+
print("Available avatars:")
|
|
100
|
+
for av in avatars:
|
|
101
|
+
name = av.get("name", "?")
|
|
102
|
+
gender = av.get("gender", "?")
|
|
103
|
+
av_id = av.get("id", "?")
|
|
104
|
+
print(f" {name:12s} {gender:6s} {av_id}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cmd_products(args):
|
|
108
|
+
"""List imported products."""
|
|
109
|
+
gen = UGCGenerator(UGCConfig.from_env())
|
|
110
|
+
products = gen.list_products()
|
|
111
|
+
|
|
112
|
+
if not products:
|
|
113
|
+
print("No products imported yet.")
|
|
114
|
+
else:
|
|
115
|
+
print("Imported products:")
|
|
116
|
+
for p in products:
|
|
117
|
+
title = p.get("title", "?")
|
|
118
|
+
status = p.get("status", "?")
|
|
119
|
+
p_id = p.get("id", "?")[:8]
|
|
120
|
+
print(f" {p_id} {status:12s} {title}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def cmd_batch(args):
|
|
124
|
+
"""Generate videos for multiple products/scenarios."""
|
|
125
|
+
gen = UGCGenerator(UGCConfig.from_env())
|
|
126
|
+
|
|
127
|
+
leads_path = Path(args.leads)
|
|
128
|
+
if not leads_path.exists():
|
|
129
|
+
print(f"❌ File not found: {args.leads}")
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
|
|
132
|
+
with open(leads_path) as f:
|
|
133
|
+
items = json.load(f)
|
|
134
|
+
|
|
135
|
+
print(f"🎬 Batch generation: {len(items)} videos")
|
|
136
|
+
print()
|
|
137
|
+
|
|
138
|
+
results = []
|
|
139
|
+
for i, item in enumerate(items, 1):
|
|
140
|
+
prompt = item.get("prompt", "")
|
|
141
|
+
avatar = item.get("avatar", args.avatar)
|
|
142
|
+
mode = item.get("mode", args.mode)
|
|
143
|
+
product_url = item.get("product_url", "")
|
|
144
|
+
|
|
145
|
+
print(f" [{i}/{len(items)}] Generating: {prompt[:60]}...")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
video = gen.generate(
|
|
149
|
+
prompt=prompt,
|
|
150
|
+
avatar=avatar,
|
|
151
|
+
mode=mode,
|
|
152
|
+
duration=args.duration,
|
|
153
|
+
product_url=product_url,
|
|
154
|
+
wait=True,
|
|
155
|
+
)
|
|
156
|
+
results.append(video.to_dict())
|
|
157
|
+
print(f" ✅ Score: {video.virality_score}/100 | {video.result_url[:60]}...")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print(f" ❌ Failed: {e}")
|
|
160
|
+
results.append({"error": str(e), "prompt": prompt})
|
|
161
|
+
|
|
162
|
+
# Save batch results
|
|
163
|
+
batch_file = Path(gen.output_dir) / f"batch_{int(__import__('time').time())}.json"
|
|
164
|
+
with open(batch_file, "w") as f:
|
|
165
|
+
json.dump(results, f, indent=2)
|
|
166
|
+
|
|
167
|
+
print(f"\n✅ Batch complete! Results: {batch_file}")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main():
|
|
171
|
+
parser = argparse.ArgumentParser(
|
|
172
|
+
description="Bonanza UGC — AI-generated UGC videos",
|
|
173
|
+
prog="ugc.py",
|
|
174
|
+
)
|
|
175
|
+
sub = parser.add_subparsers(dest="command", help="Commands")
|
|
176
|
+
|
|
177
|
+
# Generate
|
|
178
|
+
p_gen = sub.add_parser("generate", help="Generate a UGC video")
|
|
179
|
+
p_gen.add_argument("--prompt", help="Video prompt")
|
|
180
|
+
p_gen.add_argument("--avatar", default="mei", help="Avatar name or ID (default: mei)")
|
|
181
|
+
p_gen.add_argument("--mode", default="ugc", choices=UGCGenerator.MODES, help="Marketing Studio mode")
|
|
182
|
+
p_gen.add_argument("--duration", type=int, default=15, help="Duration in seconds (10, 15)")
|
|
183
|
+
p_gen.add_argument("--resolution", default="720p", help="Resolution (720p, 1080p)")
|
|
184
|
+
p_gen.add_argument("--aspect-ratio", default="9:16", help="Aspect ratio (9:16, 16:9)")
|
|
185
|
+
p_gen.add_argument("--product-url", help="Product URL to fetch")
|
|
186
|
+
p_gen.add_argument("--template", help="Prompt template (silverjstore)")
|
|
187
|
+
p_gen.add_argument("--product", help="Product name for template")
|
|
188
|
+
p_gen.add_argument("--gender", help="Gender for template")
|
|
189
|
+
p_gen.add_argument("--no-wait", action="store_true", help="Don't wait for completion")
|
|
190
|
+
|
|
191
|
+
# Score
|
|
192
|
+
p_score = sub.add_parser("score", help="Score video virality")
|
|
193
|
+
p_score.add_argument("video", help="Video file path, URL, or job ID")
|
|
194
|
+
|
|
195
|
+
# Avatars
|
|
196
|
+
sub.add_parser("avatars", help="List available avatars")
|
|
197
|
+
|
|
198
|
+
# Products
|
|
199
|
+
sub.add_parser("products", help="List imported products")
|
|
200
|
+
|
|
201
|
+
# Batch
|
|
202
|
+
p_batch = sub.add_parser("batch", help="Batch generate videos")
|
|
203
|
+
p_batch.add_argument("--leads", required=True, help="JSON file with prompts")
|
|
204
|
+
p_batch.add_argument("--avatar", default="mei", help="Default avatar")
|
|
205
|
+
p_batch.add_argument("--mode", default="ugc", help="Default mode")
|
|
206
|
+
p_batch.add_argument("--duration", type=int, default=15, help="Duration")
|
|
207
|
+
|
|
208
|
+
args = parser.parse_args()
|
|
209
|
+
|
|
210
|
+
commands = {
|
|
211
|
+
"generate": cmd_generate,
|
|
212
|
+
"score": cmd_score,
|
|
213
|
+
"avatars": cmd_avatars,
|
|
214
|
+
"products": cmd_products,
|
|
215
|
+
"batch": cmd_batch,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if args.command in commands:
|
|
219
|
+
commands[args.command](args)
|
|
220
|
+
else:
|
|
221
|
+
parser.print_help()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if __name__ == "__main__":
|
|
225
|
+
import logging
|
|
226
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
227
|
+
main()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Configuration for Bonanza UGC."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class UGCConfig:
|
|
12
|
+
"""UGC generation configuration."""
|
|
13
|
+
|
|
14
|
+
# Higgsfield defaults
|
|
15
|
+
default_avatar: str = "44ee57aa" # Mei (female, diverse)
|
|
16
|
+
default_mode: str = "ugc"
|
|
17
|
+
default_duration: int = 15
|
|
18
|
+
default_resolution: str = "720p"
|
|
19
|
+
default_aspect_ratio: str = "9:16"
|
|
20
|
+
default_generate_audio: bool = True
|
|
21
|
+
|
|
22
|
+
# Output
|
|
23
|
+
output_dir: str = str(Path.home() / ".openclaw" / "workspace" / "ugc-output")
|
|
24
|
+
|
|
25
|
+
# Virality scoring
|
|
26
|
+
score_virality: bool = True
|
|
27
|
+
min_virality_score: int = 40 # Warn if below this
|
|
28
|
+
|
|
29
|
+
# Product config (SilverJStore defaults)
|
|
30
|
+
product_base_url: str = "https://silverjstore.com"
|
|
31
|
+
|
|
32
|
+
# Avatar mapping (name → id)
|
|
33
|
+
avatars: dict = None
|
|
34
|
+
|
|
35
|
+
def __post_init__(self):
|
|
36
|
+
if self.avatars is None:
|
|
37
|
+
self.avatars = {
|
|
38
|
+
"mei": "44ee57aa-d1f4-4a0a-a55f-cdf9dacee265",
|
|
39
|
+
"yuna": "24d6d9cc-42df-43c7-bdfc-be06f317b924",
|
|
40
|
+
"adriana": "aa9260cc",
|
|
41
|
+
"clara": "daf4bf2e",
|
|
42
|
+
"sofia": "bba3087a",
|
|
43
|
+
"valentina": "cd6fb78c",
|
|
44
|
+
"jia": "ffc8862b",
|
|
45
|
+
"lily": "cec35719",
|
|
46
|
+
"maria": "bbf8e803",
|
|
47
|
+
"nia": "e6231767",
|
|
48
|
+
"hana": "ed87431a",
|
|
49
|
+
"jayden": "672be390",
|
|
50
|
+
"stefan": "35cd52c0",
|
|
51
|
+
"tae": "6c21ac3e",
|
|
52
|
+
"felix": "83711427",
|
|
53
|
+
"malik": "94950cff",
|
|
54
|
+
"liam": "734451fd",
|
|
55
|
+
"joon": "48b5553f",
|
|
56
|
+
"erik": "e572fd1d",
|
|
57
|
+
"ryu": "e1c17f9c",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_env(cls) -> "UGCConfig":
|
|
62
|
+
return cls(
|
|
63
|
+
output_dir=os.getenv("UGC_OUTPUT_DIR", str(Path.home() / ".openclaw" / "workspace" / "ugc-output")),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def get_avatar_id(self, name_or_id: str) -> str:
|
|
67
|
+
"""Get full avatar ID from name or partial ID."""
|
|
68
|
+
# Already a full UUID
|
|
69
|
+
if len(name_or_id) == 36 and "-" in name_or_id:
|
|
70
|
+
return name_or_id
|
|
71
|
+
# Short ID (8 chars)
|
|
72
|
+
if len(name_or_id) == 8 and name_or_id.isalnum():
|
|
73
|
+
return name_or_id
|
|
74
|
+
# Name lookup
|
|
75
|
+
return self.avatars.get(name_or_id.lower(), name_or_id)
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""UGC Generator — Generate AI UGC videos via Higgsfield Marketing Studio."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from .config import UGCConfig
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class UGCVideo:
|
|
21
|
+
"""A generated UGC video."""
|
|
22
|
+
job_id: str = ""
|
|
23
|
+
status: str = "pending"
|
|
24
|
+
result_url: str = ""
|
|
25
|
+
local_path: str = ""
|
|
26
|
+
prompt: str = ""
|
|
27
|
+
mode: str = ""
|
|
28
|
+
avatar: str = ""
|
|
29
|
+
duration: int = 0
|
|
30
|
+
virality_score: int = 0
|
|
31
|
+
brain_activity: dict = field(default_factory=dict)
|
|
32
|
+
product_url: str = ""
|
|
33
|
+
product_id: str = ""
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> dict:
|
|
36
|
+
return {
|
|
37
|
+
"job_id": self.job_id,
|
|
38
|
+
"status": self.status,
|
|
39
|
+
"result_url": self.result_url,
|
|
40
|
+
"local_path": self.local_path,
|
|
41
|
+
"prompt": self.prompt,
|
|
42
|
+
"mode": self.mode,
|
|
43
|
+
"avatar": self.avatar,
|
|
44
|
+
"duration": self.duration,
|
|
45
|
+
"virality_score": self.virality_score,
|
|
46
|
+
"product_url": self.product_url,
|
|
47
|
+
"product_id": self.product_id,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class UGCScore:
|
|
53
|
+
"""Virality score from Brain Activity."""
|
|
54
|
+
job_id: str = ""
|
|
55
|
+
overall_score: int = 0
|
|
56
|
+
hook_score: int = 0
|
|
57
|
+
viral_potential: int = 0
|
|
58
|
+
brain_engagement: int = 0
|
|
59
|
+
sustain_score: int = 0
|
|
60
|
+
peak_second: int = 0
|
|
61
|
+
regions: dict = field(default_factory=dict)
|
|
62
|
+
recommendations: list = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
return {
|
|
66
|
+
"overall_score": self.overall_score,
|
|
67
|
+
"hook_score": self.hook_score,
|
|
68
|
+
"viral_potential": self.viral_potential,
|
|
69
|
+
"brain_engagement": self.brain_engagement,
|
|
70
|
+
"sustain_score": self.sustain_score,
|
|
71
|
+
"peak_second": self.peak_second,
|
|
72
|
+
"regions": self.regions,
|
|
73
|
+
"recommendations": self.recommendations,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class UGCGenerator:
|
|
78
|
+
"""Generate UGC videos using Higgsfield Marketing Studio.
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
from bonanza_ugc import UGCGenerator, UGCConfig
|
|
82
|
+
|
|
83
|
+
gen = UGCGenerator()
|
|
84
|
+
video = gen.generate(
|
|
85
|
+
prompt="Dutch woman shows off her silver chain necklace...",
|
|
86
|
+
avatar="mei",
|
|
87
|
+
mode="ugc",
|
|
88
|
+
duration=15,
|
|
89
|
+
)
|
|
90
|
+
print(f"Video URL: {video.result_url}")
|
|
91
|
+
print(f"Virality: {video.virality_score}/100")
|
|
92
|
+
|
|
93
|
+
# Score a video
|
|
94
|
+
score = gen.score(video)
|
|
95
|
+
print(f"Hook: {score.hook_score}/100")
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# Marketing Studio modes
|
|
99
|
+
MODES = [
|
|
100
|
+
"ugc", "ugc_how_to", "ugc_unboxing", "product_showcase",
|
|
101
|
+
"product_review", "tv_spot", "wild_card",
|
|
102
|
+
"ugc_virtual_try_on", "virtual_try_on",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Product-specific prompt templates
|
|
106
|
+
PROMPT_TEMPLATES = {
|
|
107
|
+
"silverjstore": {
|
|
108
|
+
"ugc": "A stylish young {gender} enthusiastically shows off their beautiful 925 sterling silver {product_name}. They hold it up to the light, talking about how it sparkles and makes them feel elegant and confident. They mention it's affordable luxury from SilverJStore with free shipping across the Netherlands. Natural lighting, warm tone, authentic UGC style.",
|
|
109
|
+
"ugc_unboxing": "Excited {gender} unboxes a package from SilverJStore, revealing a stunning 925 sterling silver {product_name}. They react to the luxury packaging, show the hallmark, and try it on. Authentic unboxing experience, warm lighting.",
|
|
110
|
+
"product_showcase": "Close-up product showcase of a 925 sterling silver {product_name} from SilverJStore. The piece catches the light beautifully, showing the craftsmanship and hallmark. Professional but approachable, 9:16 vertical format.",
|
|
111
|
+
"product_review": "A {gender} gives an honest review of their 925 sterling silver {product_name} from SilverJStore. They show the quality, the hallmark, mention the free shipping and 14-day return policy. Authentic, trustworthy review style.",
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def __init__(self, config: Optional[UGCConfig] = None):
|
|
116
|
+
self.config = config or UGCConfig()
|
|
117
|
+
self.output_dir = Path(self.config.output_dir)
|
|
118
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
|
|
120
|
+
def _higgsfield(self, *args: str, json_output: bool = True) -> dict | list:
|
|
121
|
+
"""Run a higgsfield CLI command and return parsed JSON output."""
|
|
122
|
+
cmd = ["higgsfield"] + list(args)
|
|
123
|
+
if json_output:
|
|
124
|
+
cmd.append("--json")
|
|
125
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
126
|
+
if result.returncode != 0:
|
|
127
|
+
logger.error(f"Higgsfield error: {result.stderr}")
|
|
128
|
+
raise RuntimeError(f"Higgsfield command failed: {result.stderr}")
|
|
129
|
+
if json_output and result.stdout.strip():
|
|
130
|
+
return json.loads(result.stdout)
|
|
131
|
+
return {"raw": result.stdout}
|
|
132
|
+
|
|
133
|
+
def _higgsfield_wait(self, job_id: str, timeout: int = 600, interval: int = 15) -> dict:
|
|
134
|
+
"""Wait for a Higgsfield job to complete."""
|
|
135
|
+
start = time.time()
|
|
136
|
+
while time.time() - start < timeout:
|
|
137
|
+
result = self._higgsfield("generate", "get", job_id)
|
|
138
|
+
status = result.get("status", "unknown")
|
|
139
|
+
if status == "completed":
|
|
140
|
+
return result
|
|
141
|
+
elif status == "failed":
|
|
142
|
+
raise RuntimeError(f"Job {job_id} failed: {result.get('fail_reason', 'unknown')}")
|
|
143
|
+
logger.info(f"Job {job_id}: {status}...")
|
|
144
|
+
time.sleep(interval)
|
|
145
|
+
raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
|
|
146
|
+
|
|
147
|
+
def generate(
|
|
148
|
+
self,
|
|
149
|
+
prompt: str,
|
|
150
|
+
avatar: str = "mei",
|
|
151
|
+
mode: str = "ugc",
|
|
152
|
+
duration: int = 15,
|
|
153
|
+
resolution: str = "720p",
|
|
154
|
+
aspect_ratio: str = "9:16",
|
|
155
|
+
generate_audio: bool = True,
|
|
156
|
+
product_url: str = "",
|
|
157
|
+
wait: bool = True,
|
|
158
|
+
timeout: int = 600,
|
|
159
|
+
) -> UGCVideo:
|
|
160
|
+
"""Generate a UGC video.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
prompt: Video prompt describing the scene
|
|
164
|
+
avatar: Avatar name (e.g., "mei", "jayden") or ID
|
|
165
|
+
mode: Marketing Studio mode (ugc, ugc_how_to, etc.)
|
|
166
|
+
duration: Video duration in seconds (10, 15)
|
|
167
|
+
resolution: Video resolution (720p, 1080p)
|
|
168
|
+
aspect_ratio: Video aspect ratio (9:16, 16:9, 1:1)
|
|
169
|
+
generate_audio: Whether to generate speech audio
|
|
170
|
+
product_url: Product URL to fetch and include
|
|
171
|
+
wait: Whether to wait for completion
|
|
172
|
+
timeout: Max wait time in seconds
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
UGCVideo with result_url if wait=True
|
|
176
|
+
"""
|
|
177
|
+
avatar_id = self.config.get_avatar_id(avatar)
|
|
178
|
+
|
|
179
|
+
# Build command
|
|
180
|
+
cmd_args = [
|
|
181
|
+
"generate", "create", "marketing_studio_video",
|
|
182
|
+
"--prompt", prompt,
|
|
183
|
+
"--mode", mode,
|
|
184
|
+
"--duration", str(duration),
|
|
185
|
+
"--resolution", resolution,
|
|
186
|
+
"--aspect-ratio", aspect_ratio,
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
if generate_audio:
|
|
190
|
+
cmd_args.extend(["--generate-audio", "true"])
|
|
191
|
+
|
|
192
|
+
# Avatar
|
|
193
|
+
avatar_data = [{"id": avatar_id, "type": "preset"}]
|
|
194
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
195
|
+
json.dump(avatar_data, f)
|
|
196
|
+
avatar_file = f.name
|
|
197
|
+
cmd_args.extend(["--avatars", f"@{avatar_file}"])
|
|
198
|
+
|
|
199
|
+
# Product
|
|
200
|
+
product_id = ""
|
|
201
|
+
if product_url:
|
|
202
|
+
try:
|
|
203
|
+
result = self._higgsfield(
|
|
204
|
+
"marketing-studio", "products", "fetch",
|
|
205
|
+
"--url", product_url, "--json"
|
|
206
|
+
)
|
|
207
|
+
product_id = result.get("id", "")
|
|
208
|
+
if product_id:
|
|
209
|
+
product_data = [product_id]
|
|
210
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
211
|
+
json.dump(product_data, f)
|
|
212
|
+
cmd_args.extend(["--product_ids", f"@{f.name}"])
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.warning(f"Failed to fetch product: {e}")
|
|
215
|
+
|
|
216
|
+
# Create job
|
|
217
|
+
if not wait:
|
|
218
|
+
cmd_args.append("--json")
|
|
219
|
+
result = self._higgsfield(*cmd_args[2:]) # skip "higgsfield" prefix
|
|
220
|
+
job_ids = result if isinstance(result, list) else [result]
|
|
221
|
+
video = UGCVideo(
|
|
222
|
+
job_id=job_ids[0] if job_ids else "",
|
|
223
|
+
status="submitted",
|
|
224
|
+
prompt=prompt,
|
|
225
|
+
mode=mode,
|
|
226
|
+
avatar=avatar,
|
|
227
|
+
duration=duration,
|
|
228
|
+
product_url=product_url,
|
|
229
|
+
product_id=product_id,
|
|
230
|
+
)
|
|
231
|
+
return video
|
|
232
|
+
|
|
233
|
+
# Wait for completion
|
|
234
|
+
cmd_args.extend(["--wait", "--wait-timeout", f"{timeout}s"])
|
|
235
|
+
try:
|
|
236
|
+
result = self._higgsfield(*cmd_args[2:])
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"Video generation failed: {e}")
|
|
239
|
+
# Try to get job ID from error
|
|
240
|
+
return UGCVideo(status="failed", prompt=prompt)
|
|
241
|
+
|
|
242
|
+
video = UGCVideo(
|
|
243
|
+
job_id=result.get("id", ""),
|
|
244
|
+
status=result.get("status", "unknown"),
|
|
245
|
+
result_url=result.get("result_url", ""),
|
|
246
|
+
prompt=prompt,
|
|
247
|
+
mode=mode,
|
|
248
|
+
avatar=avatar,
|
|
249
|
+
duration=duration,
|
|
250
|
+
product_url=product_url,
|
|
251
|
+
product_id=product_id,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Download if we have a URL
|
|
255
|
+
if video.result_url:
|
|
256
|
+
local_path = self.download(video.result_url, f"{mode}_{avatar}_{int(time.time())}.mp4")
|
|
257
|
+
video.local_path = str(local_path)
|
|
258
|
+
|
|
259
|
+
# Auto-score virality
|
|
260
|
+
if self.config.score_virality and video.result_url:
|
|
261
|
+
try:
|
|
262
|
+
score = self.score(video)
|
|
263
|
+
video.virality_score = score.overall_score
|
|
264
|
+
video.brain_activity = score.to_dict()
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.warning(f"Virality scoring failed: {e}")
|
|
267
|
+
|
|
268
|
+
return video
|
|
269
|
+
|
|
270
|
+
def score(self, video: UGCVideo | str, wait: bool = True) -> UGCScore:
|
|
271
|
+
"""Score a video's virality using Brain Activity.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
video: UGCVideo object, video URL, or local file path
|
|
275
|
+
wait: Whether to wait for completion
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
UGCScore with virality metrics
|
|
279
|
+
"""
|
|
280
|
+
video_path = ""
|
|
281
|
+
if isinstance(video, UGCVideo):
|
|
282
|
+
video_path = video.local_path or video.result_url
|
|
283
|
+
else:
|
|
284
|
+
video_path = str(video)
|
|
285
|
+
|
|
286
|
+
if not video_path:
|
|
287
|
+
raise ValueError("No video path available")
|
|
288
|
+
|
|
289
|
+
cmd_args = ["generate", "create", "brain_activity", "--video", video_path, "--json"]
|
|
290
|
+
|
|
291
|
+
if wait:
|
|
292
|
+
cmd_args.extend(["--wait", "--wait-timeout", "300s"])
|
|
293
|
+
|
|
294
|
+
result = self._higgsfield(*cmd_args[2:])
|
|
295
|
+
|
|
296
|
+
if wait:
|
|
297
|
+
analysis = result.get("params", {}).get("analysis", {})
|
|
298
|
+
scores = analysis.get("scores", {})
|
|
299
|
+
summary = analysis.get("summary", {})
|
|
300
|
+
regions = analysis.get("regions", [])
|
|
301
|
+
|
|
302
|
+
# Build recommendations
|
|
303
|
+
recommendations = []
|
|
304
|
+
hook = scores.get("hook_score", 0)
|
|
305
|
+
if hook < 50:
|
|
306
|
+
recommendations.append(f"Hook score ({hook}/100) is weak. Start with the product visible in the first second.")
|
|
307
|
+
overall = scores.get("overall_score", 0)
|
|
308
|
+
if overall < 40:
|
|
309
|
+
recommendations.append("Overall score below 40. Try different hooks, angles, or avatar expressions.")
|
|
310
|
+
sustain = scores.get("sustain", 0)
|
|
311
|
+
if sustain < 70:
|
|
312
|
+
recommendations.append("Sustain is low. Add variety — close-ups, different angles, text overlays.")
|
|
313
|
+
|
|
314
|
+
return UGCScore(
|
|
315
|
+
job_id=result.get("id", ""),
|
|
316
|
+
overall_score=scores.get("overall_score", 0),
|
|
317
|
+
hook_score=scores.get("hook_score", 0),
|
|
318
|
+
viral_potential=scores.get("viral_potential", 0),
|
|
319
|
+
brain_engagement=scores.get("brain_engagement", 0),
|
|
320
|
+
sustain_score=scores.get("sustain", 0),
|
|
321
|
+
peak_second=scores.get("peak_second", 0),
|
|
322
|
+
regions={r.get("id", ""): r for r in regions},
|
|
323
|
+
recommendations=recommendations,
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
job_ids = result if isinstance(result, list) else [result]
|
|
327
|
+
return UGCScore(job_id=job_ids[0] if job_ids else "", status="submitted")
|
|
328
|
+
|
|
329
|
+
def download(self, url: str, filename: str = "") -> Path:
|
|
330
|
+
"""Download a video from URL to local storage."""
|
|
331
|
+
if not filename:
|
|
332
|
+
filename = f"ugc_{int(time.time())}.mp4"
|
|
333
|
+
local_path = self.output_dir / filename
|
|
334
|
+
|
|
335
|
+
import urllib.request
|
|
336
|
+
urllib.request.urlretrieve(url, str(local_path))
|
|
337
|
+
logger.info(f"Downloaded: {local_path}")
|
|
338
|
+
return local_path
|
|
339
|
+
|
|
340
|
+
def list_avatars(self) -> list[dict]:
|
|
341
|
+
"""List all available preset avatars."""
|
|
342
|
+
try:
|
|
343
|
+
return self._higgsfield("marketing-studio", "avatars", "list")
|
|
344
|
+
except Exception:
|
|
345
|
+
return []
|
|
346
|
+
|
|
347
|
+
def list_products(self) -> list[dict]:
|
|
348
|
+
"""List all imported products."""
|
|
349
|
+
try:
|
|
350
|
+
return self._higgsfield("marketing-studio", "products", "list")
|
|
351
|
+
except Exception:
|
|
352
|
+
return []
|
|
353
|
+
|
|
354
|
+
def generate_from_template(
|
|
355
|
+
self,
|
|
356
|
+
template: str = "silverjstore",
|
|
357
|
+
product_name: str = "",
|
|
358
|
+
avatar: str = "mei",
|
|
359
|
+
mode: str = "ugc",
|
|
360
|
+
gender: str = "woman",
|
|
361
|
+
**kwargs,
|
|
362
|
+
) -> UGCVideo:
|
|
363
|
+
"""Generate a UGC video from a prompt template.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
template: Template name (currently: "silverjstore")
|
|
367
|
+
product_name: Product name to include in prompt
|
|
368
|
+
avatar: Avatar name or ID
|
|
369
|
+
mode: Marketing Studio mode
|
|
370
|
+
gender: Gender for prompt template
|
|
371
|
+
**kwargs: Additional args passed to generate()
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
UGCVideo
|
|
375
|
+
"""
|
|
376
|
+
templates = self.PROMPT_TEMPLATES.get(template, {})
|
|
377
|
+
prompt_template = templates.get(mode, templates.get("ugc", ""))
|
|
378
|
+
|
|
379
|
+
if not prompt_template:
|
|
380
|
+
raise ValueError(f"No template found for '{template}' mode '{mode}'")
|
|
381
|
+
|
|
382
|
+
prompt = prompt_template.format(
|
|
383
|
+
product_name=product_name or "silver jewelry",
|
|
384
|
+
gender=gender,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return self.generate(prompt=prompt, avatar=avatar, mode=mode, **kwargs)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bonanza-ugc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-generated UGC videos via Higgsfield Marketing Studio
|
|
5
|
+
Author: Bonanza Labs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: ugc,ai,video,higgsfield,marketing
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Multimedia :: Video
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Bonanza UGC
|
|
19
|
+
|
|
20
|
+
AI-generated UGC videos via Higgsfield Marketing Studio.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install bonanza-ugc
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from bonanza_ugc import UGCGenerator, UGCConfig
|
|
32
|
+
|
|
33
|
+
gen = UGCGenerator()
|
|
34
|
+
video = gen.generate(
|
|
35
|
+
prompt="A stylish young woman shows off her silver chain necklace...",
|
|
36
|
+
avatar="mei",
|
|
37
|
+
mode="ugc",
|
|
38
|
+
duration=15,
|
|
39
|
+
)
|
|
40
|
+
print(f"Video URL: {video.result_url}")
|
|
41
|
+
print(f"Virality: {video.virality_score}/100")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CLI
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Generate a video
|
|
48
|
+
ugc generate --prompt "..." --avatar mei --mode ugc
|
|
49
|
+
|
|
50
|
+
# Use a template
|
|
51
|
+
ugc generate --template silverjstore --product "Venetian Chain" --avatar mei
|
|
52
|
+
|
|
53
|
+
# Score virality
|
|
54
|
+
ugc score <video_path_or_url>
|
|
55
|
+
|
|
56
|
+
# List avatars
|
|
57
|
+
ugc avatars
|
|
58
|
+
|
|
59
|
+
# Batch generate
|
|
60
|
+
ugc batch --leads leads.json --mode ugc
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- 🎬 Generate UGC videos with AI avatars
|
|
66
|
+
- 📊 Score video virality with Brain Activity analysis
|
|
67
|
+
- 🎯 Prompt templates for product categories
|
|
68
|
+
- 📦 Batch generation support
|
|
69
|
+
- 🔄 Product URL fetching and integration
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
bonanza_ugc/__init__.py
|
|
4
|
+
bonanza_ugc/cli.py
|
|
5
|
+
bonanza_ugc/config.py
|
|
6
|
+
bonanza_ugc/generator.py
|
|
7
|
+
bonanza_ugc.egg-info/PKG-INFO
|
|
8
|
+
bonanza_ugc.egg-info/SOURCES.txt
|
|
9
|
+
bonanza_ugc.egg-info/dependency_links.txt
|
|
10
|
+
bonanza_ugc.egg-info/entry_points.txt
|
|
11
|
+
bonanza_ugc.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bonanza_ugc
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bonanza-ugc"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI-generated UGC videos via Higgsfield Marketing Studio"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Bonanza Labs" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ugc", "ai", "video", "higgsfield", "marketing"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Multimedia :: Video",
|
|
24
|
+
]
|
|
25
|
+
dependencies = []
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
ugc = "bonanza_ugc.cli:main"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
include = ["bonanza_ugc*"]
|