Skip to content

Commit a017663

Browse files
dylanuyskmiyachikenobijonSmartDever02
authored
Release 4.3.0 (#300)
* generative flow improvements * update readme with contributiong section * update prompt-gen pipeline (#296) * update prompt-gen pipeline * flash attention, TypeError when wrong cache dir is read * a bit less opportunistic verification --------- Co-authored-by: kenobijon <kjmiyachi@gmail.com> Co-authored-by: dylan <dylan@bitmind.ai> * Preserve c2pa in gen miner services (#299) * presrve c2pa in openrouter and openai services * separate configurable servies for image/video --------- Co-authored-by: dylan <dylan@bitmind.ai> * only check for duplicates within same prompt id to avoid overhead of entire db * fix: burn rate by excluding special UIDs from normalization (#297) * library version updates * fix c2pareader call * option to save gen media locally * conditional loading of local models depending on IMAGE_SERVICE and VIDEO_SERVICE * preserve openrouter c2pa * option to specify none for a service * nanobanana pro default * ttl on miner types * move deepwiki to end of readme * clean up fix comments * bump version * updating c2pa services * temp cache clearing for this release * get cache dir from .env.validator --------- Co-authored-by: kenobijon <kjmiyachi@gmail.com> Co-authored-by: dylan <dylan@bitmind.ai> Co-authored-by: Kenobi <108417131+kenobijon@users.noreply.github.com> Co-authored-by: aka James4u <smart.jamesjin@gmail.com>
1 parent a73639a commit a017663

25 files changed

+3104
-234
lines changed

.env.gen_miner.template

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
# Copy this file to .env.miner and configure your settings
55
# cp .env.gen_miner.template .env.gen_miner
66

7+
# =============================================================================
8+
# GENERATION SERVICE SELECTION
9+
# =============================================================================
10+
# Choose which service to use for each modality:
11+
# openai - DALL-E 3 (requires OPENAI_API_KEY)
12+
# openrouter - Google Gemini via OpenRouter (requires OPEN_ROUTER_API_KEY)
13+
# local - Local Stable Diffusion models (requires GPU)
14+
#
15+
# If not set, loads ALL available services (wastes memory if using API services)
16+
IMAGE_SERVICE=
17+
VIDEO_SERVICE=
18+
19+
720
# =============================================================================
821
# API KEYS (configure the services you want to use)
922
# =============================================================================
@@ -49,9 +62,12 @@ MINER_MAX_CONCURRENT_TASKS=5
4962
MINER_TASK_TIMEOUT=300
5063

5164
# Storage
52-
MINER_OUTPUT_DIR=/workspace/miner_generated_content
5365
HF_HOME=/workspace/.cache/huggingface
5466

67+
# Set to true to save generated images/videos to MINER_OUTPUT_DIR
68+
MINER_SAVE_LOCALLY=true
69+
MINER_OUTPUT_DIR=./miner_generated_content
70+
5571

5672
# =============================================================================
5773
# LOGGING

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ Validators are responsible for challenging and scoring both miner types. Generat
109109
</a>
110110
</p>
111111

112-
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/BitMind-AI/bitmind-subnet)
113-
114112
## Contributing
115113

116114
Contributions are welcome and can be made via a pull request to the `testnet` branch.
115+
116+
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/BitMind-AI/bitmind-subnet)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.2.2
1+
4.3.0

gas/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "4.2.2"
1+
__version__ = "4.3.0"
22

33
version_split = __version__.split(".")
44
__spec_version__ = (

gas/cache/content_db.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,55 @@ def _init_database(self):
270270
except Exception as e:
271271
bt.logging.error(f"Error updating NULL uploaded values: {e}")
272272

273+
# Add perceptual_hash column for duplicate detection (migration)
274+
try:
275+
conn.execute("ALTER TABLE media ADD COLUMN perceptual_hash TEXT")
276+
bt.logging.info("Added 'perceptual_hash' column to media table")
277+
conn.execute(
278+
"CREATE INDEX IF NOT EXISTS idx_media_perceptual_hash ON media (perceptual_hash)"
279+
)
280+
conn.commit()
281+
except Exception as e:
282+
if "duplicate column name" not in str(e).lower() and "already exists" not in str(e).lower():
283+
bt.logging.error(f"Unexpected error adding 'perceptual_hash' column: {e}")
284+
else:
285+
try:
286+
conn.execute(
287+
"CREATE INDEX IF NOT EXISTS idx_media_perceptual_hash ON media (perceptual_hash)"
288+
)
289+
conn.commit()
290+
except Exception:
291+
pass
292+
293+
# Add c2pa_verified column for C2PA content credentials verification (migration)
294+
try:
295+
conn.execute("ALTER TABLE media ADD COLUMN c2pa_verified BOOLEAN DEFAULT 0")
296+
bt.logging.info("Added 'c2pa_verified' column to media table")
297+
conn.execute(
298+
"CREATE INDEX IF NOT EXISTS idx_media_c2pa_verified ON media (c2pa_verified)"
299+
)
300+
conn.commit()
301+
except Exception as e:
302+
if "duplicate column name" not in str(e).lower() and "already exists" not in str(e).lower():
303+
bt.logging.error(f"Unexpected error adding 'c2pa_verified' column: {e}")
304+
else:
305+
try:
306+
conn.execute(
307+
"CREATE INDEX IF NOT EXISTS idx_media_c2pa_verified ON media (c2pa_verified)"
308+
)
309+
conn.commit()
310+
except Exception:
311+
pass
312+
313+
# Add c2pa_issuer column for C2PA issuer tracking (migration)
314+
try:
315+
conn.execute("ALTER TABLE media ADD COLUMN c2pa_issuer TEXT")
316+
bt.logging.info("Added 'c2pa_issuer' column to media table")
317+
conn.commit()
318+
except Exception as e:
319+
if "duplicate column name" not in str(e).lower() and "already exists" not in str(e).lower():
320+
bt.logging.error(f"Unexpected error adding 'c2pa_issuer' column: {e}")
321+
273322
# Temporary data hygiene: prune prompts whose source_media_id no longer exists in media table
274323
# Note: This only checks DB-level association, not filesystem existence.
275324
try:
@@ -405,6 +454,21 @@ def _row_to_media_entry(self, row) -> MediaEntry:
405454
if "rewarded" in row.keys() and row["rewarded"] is not None
406455
else False
407456
),
457+
perceptual_hash=(
458+
row["perceptual_hash"]
459+
if "perceptual_hash" in row.keys() and row["perceptual_hash"] is not None
460+
else None
461+
),
462+
c2pa_verified=(
463+
bool(row["c2pa_verified"])
464+
if "c2pa_verified" in row.keys() and row["c2pa_verified"] is not None
465+
else False
466+
),
467+
c2pa_issuer=(
468+
row["c2pa_issuer"]
469+
if "c2pa_issuer" in row.keys() and row["c2pa_issuer"] is not None
470+
else None
471+
),
408472
created_at=row["created_at"],
409473
generation_args=(
410474
json.loads(row["generation_args"]) if row["generation_args"] else None
@@ -436,6 +500,9 @@ def add_media_entry(
436500
resolution: Optional[tuple[int, int]] = None,
437501
file_size: Optional[int] = None,
438502
format: Optional[str] = None,
503+
perceptual_hash: Optional[str] = None,
504+
c2pa_verified: Optional[bool] = None,
505+
c2pa_issuer: Optional[str] = None,
439506
) -> str:
440507
media_id = str(uuid.uuid4())
441508
created_at = time.time()
@@ -449,8 +516,9 @@ def add_media_entry(
449516
INSERT INTO media (id, prompt_id, file_path, modality, media_type, source_type,
450517
model_name, download_url, scraper_name, dataset_name,
451518
dataset_source_file, dataset_index, uid, hotkey, verified,
452-
created_at, generation_args, mask_path, timestamp, resolution, file_size, format)
453-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
519+
created_at, generation_args, mask_path, timestamp, resolution,
520+
file_size, format, perceptual_hash, c2pa_verified, c2pa_issuer)
521+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
454522
""",
455523
(
456524
media_id,
@@ -475,6 +543,9 @@ def add_media_entry(
475543
json.dumps(list(resolution)) if resolution else None,
476544
file_size,
477545
format,
546+
perceptual_hash,
547+
c2pa_verified,
548+
c2pa_issuer,
478549
),
479550
)
480551
conn.commit()

gas/cache/content_manager.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ def write_miner_media(
147147
content_type: str,
148148
task_id: str,
149149
model_name: Optional[str] = None,
150+
perceptual_hash: Optional[str] = None,
151+
c2pa_verified: Optional[bool] = None,
152+
c2pa_issuer: Optional[str] = None,
150153
) -> Optional[str]:
151154
"""
152155
Write miner-generated binary media to storage.
@@ -159,6 +162,9 @@ def write_miner_media(
159162
content_type: MIME content type (e.g., "image/png", "video/mp4")
160163
task_id: Unique task identifier for filename
161164
model_name: Optional model name
165+
perceptual_hash: Pre-computed perceptual hash for duplicate detection
166+
c2pa_verified: Whether C2PA verification passed
167+
c2pa_issuer: C2PA issuer name if verified
162168
163169
Returns:
164170
Path to saved file if successful, None if failed
@@ -194,6 +200,9 @@ def write_miner_media(
194200
resolution=resolution,
195201
file_size=file_size,
196202
format=media_data.format,
203+
perceptual_hash=perceptual_hash,
204+
c2pa_verified=c2pa_verified,
205+
c2pa_issuer=c2pa_issuer,
197206
)
198207

199208
bt.logging.info(f"Saved miner media to {save_path} with database ID: {media_id}")
@@ -870,3 +879,54 @@ def needs_more_data(self, source_type: SourceType, source_name: str) -> bool:
870879
True if the source needs more data
871880
"""
872881
return self.get_source_count(source_type, source_name) < self.min_source_threshold
882+
883+
def check_duplicate(
884+
self,
885+
perceptual_hash: str,
886+
threshold: int = 8,
887+
limit: int = 1000,
888+
prompt_id: Optional[str] = None,
889+
) -> Optional[tuple]:
890+
"""
891+
Check if a perceptual hash has duplicates in the database.
892+
893+
Args:
894+
perceptual_hash: Hash to check for duplicates
895+
threshold: Maximum Hamming distance to consider as duplicate
896+
limit: Maximum number of hashes to check
897+
prompt_id: If provided, only check duplicates within this prompt
898+
899+
Returns:
900+
Tuple of (media_id, hamming_distance) for closest match, or None if no duplicate
901+
"""
902+
from gas.verification.duplicate_detection import check_duplicate_in_db
903+
return check_duplicate_in_db(self.content_db, perceptual_hash, threshold, limit, prompt_id=prompt_id)
904+
905+
def compute_and_check_duplicate(
906+
self,
907+
media_content: bytes,
908+
modality: str,
909+
threshold: int = 8,
910+
) -> tuple:
911+
"""
912+
Compute perceptual hash for media and check for duplicates.
913+
914+
Args:
915+
media_content: Raw media bytes
916+
modality: "image" or "video"
917+
threshold: Hamming distance threshold for duplicates
918+
919+
Returns:
920+
Tuple of (perceptual_hash, is_duplicate, duplicate_info)
921+
where duplicate_info is (media_id, distance) if duplicate found, else None
922+
"""
923+
from gas.verification.duplicate_detection import compute_media_hash
924+
925+
perceptual_hash = compute_media_hash(media_content, modality=modality)
926+
if not perceptual_hash:
927+
return None, False, None
928+
929+
duplicate_info = self.check_duplicate(perceptual_hash, threshold)
930+
is_duplicate = duplicate_info is not None
931+
932+
return perceptual_hash, is_duplicate, duplicate_info

gas/cache/types.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ class MediaEntry:
7979

8080
prompt_content: Optional[str] = None # for hf uploads
8181

82+
# Duplicate detection
83+
perceptual_hash: Optional[str] = None # pHash for duplicate detection
84+
85+
# C2PA content credentials
86+
c2pa_verified: Optional[bool] = False # C2PA validation passed
87+
c2pa_issuer: Optional[str] = None # Issuer name if C2PA verified
88+
8289
# Common fields
8390
created_at: float = None
8491
resolution: Optional[tuple[int, int]] = None # (width, height)

0 commit comments

Comments
 (0)