Skip to content

Commit

Permalink
Add/Port granular video code processing
Browse files Browse the repository at this point in the history
  • Loading branch information
titusz committed Jun 26, 2024
1 parent 380e300 commit 1e98b54
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 1 deletion.
19 changes: 18 additions & 1 deletion iscc_sdk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,27 @@ def code_video(fp, extract_meta=None, create_thumb=None):
thumbnail_durl = idk.image_to_data_url(thumbnail_image)
meta["thumbnail"] = thumbnail_durl

features = idk.video_features_extract(fp)
sig, scenes = None, []
if idk.sdk_opts.granular:
sig, scenes = idk.video_mp7sig_extract_scenes(fp)
else:
sig = idk.video_mp7sig_extract(fp)

if idk.sdk_opts.video_store_mp7sig:
outp = fp + ".iscc.mp7sig"
with open(outp, "wb") as outf:
outf.write(sig)

frames = idk.read_mp7_signature(sig)
features = [tuple(frame.vector.tolist()) for frame in frames]

code_obj = ic.gen_video_code_v0(features, bits=idk.core_opts.video_bits)
meta.update(code_obj)

if idk.sdk_opts.granular:
granular = idk.video_compute_granular(frames, scenes)
meta["features"] = granular

return idk.IsccMeta.construct(**meta)


Expand Down
1 change: 1 addition & 0 deletions iscc_sdk/mp7.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

__all__ = [
"read_mp7_signature",
"Frame",
]


Expand Down
34 changes: 34 additions & 0 deletions iscc_sdk/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from secrets import token_hex
from PIL import Image, ImageEnhance
import iscc_sdk as idk
import iscc_core as ic


__all__ = [
Expand All @@ -21,6 +22,7 @@
"video_mp7sig_extract_scenes",
"video_features_extract",
"video_parse_scenes",
"video_compute_granular",
]

VIDEO_META_MAP = {
Expand Down Expand Up @@ -325,3 +327,35 @@ def video_parse_scenes(scene_text, scene_limit=None):
cutpoints.append(times[-1])

return cutpoints[1:]


def video_compute_granular(frames, scenes):
# type: (List[idk.Frame], List[float]) -> dict
"""Compute video signatures for individual scenes in video.
Returns a dictionary conforming to `shema.Feature`- objects.
"""
features, sizes, segment = [], [], []
start_frame = 0
for cidx, cutpoint in enumerate(scenes):
try:
frames = frames[start_frame:]
except IndexError: # pragma: no cover
break
for fidx, frame in enumerate(frames):
frame_t = tuple(frame.vector.tolist())
segment.append(frame_t)
if frame.elapsed >= cutpoint:
features.append(ic.encode_base64(ic.soft_hash_video_v0(segment, 64)))
segment = []
prev_cutpoint = 0 if cidx == 0 else scenes[cidx - 1]
duration = round(cutpoint - prev_cutpoint, 3)
sizes.append(duration)
start_frame = fidx + 1
break
if not features:
log.info("No scenes detected. Use all frames")
segment = [tuple(frame.vector.tolist()) for frame in frames]
features = [ic.encode_base64(ic.soft_hash_video_v0(segment, 64))]
sizes = [round(float(frames[-1].elapsed), 3)]

return dict(kind="video", version=0, features=features, sizes=sizes)
86 changes: 86 additions & 0 deletions tests/test_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,89 @@ def test_code_video_nometa_nothumb(mp4_file):
assert meta.dict() == {"iscc": "ISCC:EMAV4DUD6QORW4X4"}
idk.sdk_opts.extract_metadata = True
idk.sdk_opts.create_thumbnail = True


def test_code_video_granular_scenes(mp4_file):
idk.sdk_opts.granular = True
idk.sdk_opts.video_scene_limit = 0.2
idk.sdk_opts.create_thumbnail = False
idk.sdk_opts.extract_metadata = True
result = idk.code_video(mp4_file).dict()
assert result == {
"iscc": "ISCC:EMAV4DUD6QORW4X4",
"name": "Kali by Anokato - Spiral Sessions 2019",
"features": {
"kind": "video",
"version": 0,
"sizes": [7.625, 2.5, 5.083, 20.792, 2.458, 1.5, 6.667],
"features": [
"XxqT9x1acvw",
"HEqSawAW8oQ",
"VA7Q9A0esuw",
"Xg4H9H1S8vU",
"l06Qp9wbcow",
"UWAQkpxZMqg",
"HloAnYYVUqU",
],
},
}


def test_code_iscc_video_granular(mp4_file):
idk.sdk_opts.granular = True
idk.sdk_opts.video_scene_limit = 0.2
idk.sdk_opts.create_thumbnail = False
idk.sdk_opts.extract_metadata = True
result = idk.code_iscc(mp4_file).dict()
assert result == {
"@type": "VideoObject",
"iscc": "ISCC:KMCV6UK6BSXJ3I4GLYHIH5A5DNZPYBWQO33FNHPQFOOUCLLW3HKRNUA",
"name": "Kali by Anokato - Spiral Sessions 2019",
"mode": "video",
"mediatype": "video/mp4",
"filename": "video.mp4",
"filesize": 2161914,
"metahash": "1e2096c0a53475a186ce37622aba7ba70651fc62cc8150f59eee6d17dc16d9bfbf25",
"datahash": "1e209d412d76d9d516d07bb60f1ab3c1a5c1b176ed4f1cec94c96222a5d013ec3e38",
"features": {
"kind": "video",
"version": 0,
"features": [
"XxqT9x1acvw",
"HEqSawAW8oQ",
"VA7Q9A0esuw",
"Xg4H9H1S8vU",
"l06Qp9wbcow",
"UWAQkpxZMqg",
"HloAnYYVUqU",
],
"sizes": [7.625, 2.5, 5.083, 20.792, 2.458, 1.5, 6.667],
},
}


def test_code_iscc_video_granular_no_scenes(mp4_file):
idk.sdk_opts.granular = True
idk.sdk_opts.video_scene_limit = 0.8
idk.sdk_opts.create_thumbnail = False
idk.sdk_opts.extract_metadata = True
result = idk.code_iscc(mp4_file).dict()
assert result == {
"@type": "VideoObject",
"iscc": "ISCC:KMCV6UK6BSXJ3I4GLYHIH5A5DNZPYBWQO33FNHPQFOOUCLLW3HKRNUA",
"name": "Kali by Anokato - Spiral Sessions 2019",
"mode": "video",
"mediatype": "video/mp4",
"filename": "video.mp4",
"filesize": 2161914,
"metahash": "1e2096c0a53475a186ce37622aba7ba70651fc62cc8150f59eee6d17dc16d9bfbf25",
"datahash": "1e209d412d76d9d516d07bb60f1ab3c1a5c1b176ed4f1cec94c96222a5d013ec3e38",
"features": {
"kind": "video",
"version": 0,
"features": [
"Xg6D9B0bcvw",
],
"sizes": [59.8],
},
}

0 comments on commit 1e98b54

Please sign in to comment.