1use crate::q_hash;
10use crate::sparql_ast::*;
11use crate::NQuin;
12
13pub mod ma_ont {
15 use crate::q_hash;
16
17 pub const HAS_FRAGMENT: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasFragment");
18 pub const HAS_TEMPORAL_FRAGMENT: u64 =
19 q_hash("http://www.w3.org/ns/ma-ont#hasTemporalFragment");
20 pub const HAS_SPATIAL_FRAGMENT: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasSpatialFragment");
21 pub const HAS_TRACK_FRAGMENT: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasTrackFragment");
22
23 pub const HAS_START_TIME: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasStartTime");
24 pub const HAS_END_TIME: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasEndTime");
25 pub const DURATION: u64 = q_hash("http://www.w3.org/ns/ma-ont#duration");
26
27 pub const HAS_X: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasX");
28 pub const HAS_Y: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasY");
29 pub const HAS_WIDTH: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasWidth");
30 pub const HAS_HEIGHT: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasHeight");
31
32 pub const HAS_TRACK: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasTrack");
33 pub const HAS_TRACK_NAME: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasTrackName");
34 pub const HAS_TRACK_NUMBER: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasTrackNumber");
35
36 pub const HAS_FORMAT: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasFormat");
37 pub const HAS_MIME_TYPE: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasMimeType");
38 pub const HAS_CODEC: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasCodec");
39
40 pub const HAS_BITRATE: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasBitrate");
41 pub const HAS_FRAMERATE: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasFramerate");
42 pub const HAS_SAMPLERATE: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasSamplerate");
43 pub const HAS_CHANNELS: u64 = q_hash("http://www.w3.org/ns/ma-ont#hasChannels");
44
45 pub const MEDIA_TIME_MS: u64 = q_hash("https://ns.webizen.org/q42/mediaTimeMs");
47}
48
49pub mod c2pa {
51 use crate::q_hash;
52
53 pub const HAS_CREDENTIAL: u64 = q_hash("http://ns.c2pa.org/credentials/hasCredential");
54 pub const HAS_MANIFEST: u64 = q_hash("http://ns.c2pa.org/manifest/hasManifest");
55 pub const HAS_SIGNATURE: u64 = q_hash("http://ns.c2pa.org/signature/hasSignature");
56 pub const HAS_PROVENANCE: u64 = q_hash("http://ns.c2pa.org/provenance/hasProvenance");
57 pub const HAS_ASSERTION: u64 = q_hash("http://ns.c2pa.org/assertion/hasAssertion");
58
59 pub const CREATED_AT: u64 = q_hash("http://ns.c2pa.org/provenance/createdAt");
60 pub const CREATED_BY: u64 = q_hash("http://ns.c2pa.org/provenance/createdBy");
61 pub const MODIFIED_AT: u64 = q_hash("http://ns.c2pa.org/provenance/modifiedAt");
62 pub const MODIFIED_BY: u64 = q_hash("http://ns.c2pa.org/provenance/modifiedBy");
63 pub const HAS_TOOL: u64 = q_hash("http://ns.c2pa.org/provenance/hasTool");
64
65 pub const DERIVED_FROM: u64 = q_hash("http://ns.c2pa.org/asset/derivedFrom");
66 pub const COMPONENT_OF: u64 = q_hash("http://ns.c2pa.org/asset/componentOf");
67 pub const HAS_COMPONENT: u64 = q_hash("http://ns.c2pa.org/asset/hasComponent");
68
69 pub const IS_VERIFIED: u64 = q_hash("http://ns.c2pa.org/validation/isVerified");
70 pub const VERIFICATION_STATUS: u64 = q_hash("http://ns.c2pa.org/validation/verificationStatus");
71 pub const HAS_CERTIFICATE: u64 = q_hash("http://ns.c2pa.org/validation/hasCertificate");
72}
73
74#[repr(u8)]
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum C2paVerificationStatus {
79 Unsupported = 0,
81 ParsedOnly = 1,
83 IntegrityChecked = 2,
85 SignatureVerified = 3,
87 TrustChainEvaluated = 4,
89}
90
91#[repr(C)]
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum MediaFragmentDimension {
95 Temporal {
96 start: u64,
97 end: u64,
98 },
99 Spatial {
100 x: u32,
101 y: u32,
102 width: u32,
103 height: u32,
104 },
105 Track {
106 track_id: u64,
107 track_number: u32,
108 },
109}
110
111#[repr(C)]
113#[derive(Debug, Clone, Copy)]
114pub struct MediaFragment {
115 pub media_uri: u64,
116 pub dimensions: [Option<MediaFragmentDimension>; 4],
117 pub dimension_count: u8,
118}
119
120#[repr(C)]
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum WindowType {
124 Tumbling { size_ms: u64 },
125 Sliding { size_ms: u64, slide_ms: u64 },
130 Session { gap_ms: u64 },
131}
132
133#[repr(C)]
135#[derive(Debug, Clone, Copy)]
136pub struct TimeWindow {
137 pub window_type: WindowType,
138 pub start_ms: u64,
139 pub end_ms: u64,
140}
141
142pub struct SparqlMmHandler<'a> {
144 pub quins: &'a [NQuin],
145 pub windows: [TimeWindow; 64],
146 pub window_count: u8,
147 pub media_fragments: [MediaFragment; 128],
148 pub fragment_count: u8,
149}
150
151impl<'a> SparqlMmHandler<'a> {
152 pub fn new(quins: &'a [NQuin]) -> Self {
153 Self {
154 quins,
155 windows: [TimeWindow {
156 window_type: WindowType::Tumbling { size_ms: 1000 },
157 start_ms: 0,
158 end_ms: 0,
159 }; 64],
160 window_count: 0,
161 media_fragments: [MediaFragment {
162 media_uri: 0,
163 dimensions: [None; 4],
164 dimension_count: 0,
165 }; 128],
166 fragment_count: 0,
167 }
168 }
169
170 pub fn make_media_fragment(
175 media_uri: u64,
176 dimensions: &[MediaFragmentDimension],
177 ) -> Result<MediaFragment, String> {
178 if dimensions.len() > 4 {
179 return Err("At most 4 fragment dimensions".to_string());
180 }
181 let mut fragment = MediaFragment {
182 media_uri,
183 dimensions: [None; 4],
184 dimension_count: 0,
185 };
186 for (i, d) in dimensions.iter().enumerate() {
187 fragment.dimensions[i] = Some(*d);
188 fragment.dimension_count += 1;
189 }
190 Ok(fragment)
191 }
192
193 pub fn parse_media_fragment(&mut self, fragment_uri: u64) -> Result<MediaFragment, String> {
196 Self::make_media_fragment(fragment_uri, &[])
197 }
198
199 #[inline]
201 pub fn pack_spatial_u64(x: u32, y: u32, width: u32, height: u32) -> u64 {
202 let x = (x.min(0xFFFF)) as u64;
204 let y = (y.min(0xFFFF)) as u64;
205 let w = (width.min(0xFFFF)) as u64;
206 let h = (height.min(0xFFFF)) as u64;
207 x | (y << 16) | (w << 32) | (h << 48)
208 }
209
210 #[inline]
211 pub fn unpack_spatial_u64(v: u64) -> (u32, u32, u32, u32) {
212 (
213 (v & 0xFFFF) as u32,
214 ((v >> 16) & 0xFFFF) as u32,
215 ((v >> 32) & 0xFFFF) as u32,
216 ((v >> 48) & 0xFFFF) as u32,
217 )
218 }
219
220 #[inline]
222 pub fn spatial_intersects(
223 ax: u32,
224 ay: u32,
225 aw: u32,
226 ah: u32,
227 bx: u32,
228 by: u32,
229 bw: u32,
230 bh: u32,
231 ) -> bool {
232 let ax1 = ax.saturating_add(aw);
233 let ay1 = ay.saturating_add(ah);
234 let bx1 = bx.saturating_add(bw);
235 let by1 = by.saturating_add(bh);
236 ax < bx1 && ax1 > bx && ay < by1 && ay1 > by
237 }
238
239 pub fn media_time_ms(quin: &NQuin) -> Option<u64> {
242 if quin.predicate == ma_ont::MEDIA_TIME_MS {
243 Some(quin.object)
244 } else {
245 None
246 }
247 }
248
249 pub fn get_ma_property(&self, media_uri: u64, predicate: u64) -> Result<u64, String> {
251 for quin in self.quins {
252 if quin.subject == media_uri && quin.predicate == predicate {
253 return Ok(quin.object);
254 }
255 }
256 Err("Property not found".to_string())
257 }
258
259 pub fn get_temporal_fragment(&self, media_uri: u64) -> Result<(u64, u64), String> {
261 let start = self.get_ma_property(media_uri, ma_ont::HAS_START_TIME)?;
262 let end = self.get_ma_property(media_uri, ma_ont::HAS_END_TIME)?;
263 Ok((start, end))
264 }
265
266 pub fn get_spatial_fragment(&self, media_uri: u64) -> Result<(u32, u32, u32, u32), String> {
268 let x = self.get_ma_property(media_uri, ma_ont::HAS_X)? as u32;
269 let y = self.get_ma_property(media_uri, ma_ont::HAS_Y)? as u32;
270 let width = self.get_ma_property(media_uri, ma_ont::HAS_WIDTH)? as u32;
271 let height = self.get_ma_property(media_uri, ma_ont::HAS_HEIGHT)? as u32;
272 Ok((x, y, width, height))
273 }
274
275 pub fn get_track_fragment(&self, media_uri: u64) -> Result<(u64, u32), String> {
277 let track_id = self.get_ma_property(media_uri, ma_ont::HAS_TRACK)?;
278 let track_number = self.get_ma_property(media_uri, ma_ont::HAS_TRACK_NUMBER)? as u32;
279 Ok((track_id, track_number))
280 }
281
282 pub fn add_media_fragment(&mut self, fragment: MediaFragment) -> Result<u8, String> {
284 if self.fragment_count >= 128 {
285 return Err("Fragment overflow".to_string());
286 }
287 let idx = self.fragment_count;
288 self.media_fragments[idx as usize] = fragment;
289 self.fragment_count += 1;
290 Ok(idx)
291 }
292
293 pub fn create_tumbling_window(&mut self, size_ms: u64, start_ms: u64) -> Result<u8, String> {
295 if self.window_count >= 64 {
296 return Err("Window overflow".to_string());
297 }
298 let idx = self.window_count;
299 self.windows[idx as usize] = TimeWindow {
300 window_type: WindowType::Tumbling { size_ms },
301 start_ms,
302 end_ms: start_ms + size_ms,
303 };
304 self.window_count += 1;
305 Ok(idx)
306 }
307
308 pub fn create_sliding_window(
310 &mut self,
311 size_ms: u64,
312 slide_ms: u64,
313 start_ms: u64,
314 ) -> Result<u8, String> {
315 if self.window_count >= 64 {
316 return Err("Window overflow".to_string());
317 }
318 let idx = self.window_count;
319 self.windows[idx as usize] = TimeWindow {
320 window_type: WindowType::Sliding { size_ms, slide_ms },
321 start_ms,
322 end_ms: start_ms + size_ms,
323 };
324 self.window_count += 1;
325 Ok(idx)
326 }
327
328 pub fn create_session_window(&mut self, gap_ms: u64, start_ms: u64) -> Result<u8, String> {
330 if self.window_count >= 64 {
331 return Err("Window overflow".to_string());
332 }
333 let idx = self.window_count;
334 self.windows[idx as usize] = TimeWindow {
335 window_type: WindowType::Session { gap_ms },
336 start_ms,
337 end_ms: 0, };
339 self.window_count += 1;
340 Ok(idx)
341 }
342
343 pub fn query_window(
346 &self,
347 window_id: u8,
348 _timestamp_field: u64,
349 ) -> Result<Vec<&NQuin>, String> {
350 let mut buf = [None; 256];
351 let n = self.query_window_into(window_id, &mut buf)?;
352 Ok(buf[..n].iter().filter_map(|x| *x).collect())
353 }
354
355 pub fn query_window_into(
359 &'a self,
360 window_id: u8,
361 out: &mut [Option<&'a NQuin>],
362 ) -> Result<usize, String> {
363 let window = self
364 .windows
365 .get(window_id as usize)
366 .filter(|_| (window_id as usize) < self.window_count as usize)
367 .ok_or("Window ID out of bounds")?;
368
369 let mut w = 0usize;
370 for quin in self.quins {
371 let t = if let Some(ms) = Self::media_time_ms(quin) {
372 ms
373 } else {
374 quin.metadata & 0x1FFF_FFFF
375 };
376 if t >= window.start_ms && t <= window.end_ms {
377 if w >= out.len() {
378 break;
379 }
380 out[w] = Some(quin);
381 w += 1;
382 }
383 }
384 Ok(w)
385 }
386
387 pub fn query_media_fragment(&self, fragment_id: u8) -> Result<Vec<&NQuin>, String> {
389 let mut buf = [None; 256];
390 let n = self.query_media_fragment_into(fragment_id, &mut buf)?;
391 Ok(buf[..n].iter().filter_map(|x| *x).collect())
392 }
393
394 pub fn query_media_fragment_into(
396 &'a self,
397 fragment_id: u8,
398 out: &mut [Option<&'a NQuin>],
399 ) -> Result<usize, String> {
400 let fragment = self
401 .media_fragments
402 .get(fragment_id as usize)
403 .filter(|_| (fragment_id as usize) < self.fragment_count as usize)
404 .ok_or("Fragment ID out of bounds")?;
405
406 let mut w = 0usize;
407 for quin in self.quins {
408 if !self.check_fragment_match(quin, fragment) {
409 continue;
410 }
411 if w >= out.len() {
412 break;
413 }
414 out[w] = Some(quin);
415 w += 1;
416 }
417 Ok(w)
418 }
419
420 fn check_fragment_match(&self, quin: &NQuin, fragment: &MediaFragment) -> bool {
421 if fragment.dimension_count == 0 {
422 return quin.subject == fragment.media_uri
423 || self.quin_linked_to_media(quin, fragment.media_uri);
424 }
425
426 if !(quin.subject == fragment.media_uri
427 || self.quin_linked_to_media(quin, fragment.media_uri))
428 {
429 return false;
430 }
431
432 for i in 0..fragment.dimension_count as usize {
433 let Some(dim) = fragment.dimensions[i] else {
434 continue;
435 };
436 match dim {
437 MediaFragmentDimension::Temporal { start, end } => {
438 let Some(quin_time) = Self::media_time_ms(quin).or_else(|| {
439 if quin.predicate == ma_ont::HAS_START_TIME
440 || quin.predicate == ma_ont::HAS_END_TIME
441 {
442 Some(quin.object)
443 } else {
444 None
445 }
446 }) else {
447 continue;
449 };
450 if quin_time < start || quin_time > end {
451 return false;
452 }
453 }
454 MediaFragmentDimension::Spatial {
455 x,
456 y,
457 width,
458 height,
459 } => {
460 if let Some((qx, qy, qw, qh)) = self.quin_spatial_box(quin) {
462 if !Self::spatial_intersects(qx, qy, qw, qh, x, y, width, height) {
463 return false;
464 }
465 }
466 }
467 MediaFragmentDimension::Track { track_id, .. } => {
468 let is_track_pred = quin.predicate == ma_ont::HAS_TRACK
469 || quin.predicate == ma_ont::HAS_TRACK_NUMBER
470 || quin.predicate == q_hash("https://ns.webizen.org/q42/hasTrackId");
471 if is_track_pred && quin.object != track_id {
472 return false;
473 }
474 }
475 }
476 }
477 true
478 }
479
480 fn quin_linked_to_media(&self, quin: &NQuin, media_uri: u64) -> bool {
481 if quin.subject == media_uri {
482 return true;
483 }
484 for q in self.quins {
486 if q.subject == media_uri && q.object == quin.subject {
487 return true;
488 }
489 }
490 false
491 }
492
493 fn quin_spatial_box(&self, quin: &NQuin) -> Option<(u32, u32, u32, u32)> {
495 if quin.predicate == ma_ont::HAS_SPATIAL_FRAGMENT
496 || quin.predicate == q_hash("https://ns.webizen.org/q42/hasBoundingBox")
497 {
498 let (x, y, w, h) = Self::unpack_spatial_u64(quin.object);
499 if quin.predicate == q_hash("https://ns.webizen.org/q42/hasBoundingBox") {
501 let x1 = w;
502 let y1 = h;
503 let width = x1.saturating_sub(x);
504 let height = y1.saturating_sub(y);
505 return Some((x, y, width, height));
506 }
507 return Some((x, y, w, h));
508 }
509 let mut x = None;
511 let mut y = None;
512 let mut width = None;
513 let mut height = None;
514 for q in self.quins {
515 if q.subject != quin.subject {
516 continue;
517 }
518 match q.predicate {
519 p if p == ma_ont::HAS_X => x = Some(q.object as u32),
520 p if p == ma_ont::HAS_Y => y = Some(q.object as u32),
521 p if p == ma_ont::HAS_WIDTH => width = Some(q.object as u32),
522 p if p == ma_ont::HAS_HEIGHT => height = Some(q.object as u32),
523 _ => {}
524 }
525 }
526 match (x, y, width, height) {
527 (Some(x), Some(y), Some(w), Some(h)) => Some((x, y, w, h)),
528 _ => None,
529 }
530 }
531
532 pub fn get_media_duration(&self, media_uri: u64) -> Result<u64, String> {
534 self.get_ma_property(media_uri, ma_ont::DURATION)
535 }
536
537 pub fn get_media_dimensions(&self, media_uri: u64) -> Result<(u32, u32), String> {
539 let width = self.get_ma_property(media_uri, ma_ont::HAS_WIDTH)? as u32;
540 let height = self.get_ma_property(media_uri, ma_ont::HAS_HEIGHT)? as u32;
541 Ok((width, height))
542 }
543
544 pub fn get_media_format(&self, media_uri: u64) -> Result<u64, String> {
546 self.get_ma_property(media_uri, ma_ont::HAS_FORMAT)
547 }
548
549 pub fn get_media_mime_type(&self, media_uri: u64) -> Result<u64, String> {
551 self.get_ma_property(media_uri, ma_ont::HAS_MIME_TYPE)
552 }
553
554 pub fn get_media_codec(&self, media_uri: u64) -> Result<u64, String> {
556 self.get_ma_property(media_uri, ma_ont::HAS_CODEC)
557 }
558
559 pub fn get_media_bitrate(&self, media_uri: u64) -> Result<u64, String> {
561 self.get_ma_property(media_uri, ma_ont::HAS_BITRATE)
562 }
563
564 pub fn get_media_framerate(&self, media_uri: u64) -> Result<u64, String> {
566 self.get_ma_property(media_uri, ma_ont::HAS_FRAMERATE)
567 }
568
569 pub fn get_credential(&self, media_uri: u64) -> Result<u64, String> {
571 self.get_ma_property(media_uri, c2pa::HAS_CREDENTIAL)
572 }
573
574 pub fn get_manifest(&self, media_uri: u64) -> Result<u64, String> {
576 self.get_ma_property(media_uri, c2pa::HAS_MANIFEST)
577 }
578
579 pub fn get_signature(&self, media_uri: u64) -> Result<u64, String> {
581 self.get_ma_property(media_uri, c2pa::HAS_SIGNATURE)
582 }
583
584 pub fn get_provenance(&self, media_uri: u64) -> Result<u64, String> {
586 self.get_ma_property(media_uri, c2pa::HAS_PROVENANCE)
587 }
588
589 pub fn is_verified(&self, media_uri: u64) -> Result<bool, String> {
595 let status = self.c2pa_status(media_uri)?;
596 Ok(matches!(
597 status,
598 C2paVerificationStatus::SignatureVerified | C2paVerificationStatus::TrustChainEvaluated
599 ))
600 }
601
602 pub fn c2pa_status(&self, media_uri: u64) -> Result<C2paVerificationStatus, String> {
604 let has_manifest = self.get_ma_property(media_uri, c2pa::HAS_MANIFEST).is_ok();
606 let has_sig = self.get_ma_property(media_uri, c2pa::HAS_SIGNATURE).is_ok();
607 if !has_manifest && !has_sig {
608 return Ok(C2paVerificationStatus::Unsupported);
609 }
610 let _claimed = self.get_ma_property(media_uri, c2pa::IS_VERIFIED);
612 Ok(C2paVerificationStatus::ParsedOnly)
613 }
614
615 pub fn get_verification_status(&self, media_uri: u64) -> Result<u64, String> {
617 Ok(self.c2pa_status(media_uri)? as u64)
618 }
619
620 pub fn get_created_at(&self, media_uri: u64) -> Result<u64, String> {
622 self.get_ma_property(media_uri, c2pa::CREATED_AT)
623 }
624
625 pub fn get_created_by(&self, media_uri: u64) -> Result<u64, String> {
627 self.get_ma_property(media_uri, c2pa::CREATED_BY)
628 }
629
630 pub fn get_modified_at(&self, media_uri: u64) -> Result<u64, String> {
632 self.get_ma_property(media_uri, c2pa::MODIFIED_AT)
633 }
634
635 pub fn get_modified_by(&self, media_uri: u64) -> Result<u64, String> {
637 self.get_ma_property(media_uri, c2pa::MODIFIED_BY)
638 }
639
640 pub fn get_tool(&self, media_uri: u64) -> Result<u64, String> {
642 self.get_ma_property(media_uri, c2pa::HAS_TOOL)
643 }
644
645 pub fn get_derived_from(&self, media_uri: u64) -> Result<u64, String> {
647 self.get_ma_property(media_uri, c2pa::DERIVED_FROM)
648 }
649
650 pub fn get_component_of(&self, media_uri: u64) -> Result<u64, String> {
652 self.get_ma_property(media_uri, c2pa::COMPONENT_OF)
653 }
654
655 pub fn get_components(&self, media_uri: u64) -> Result<Vec<u64>, String> {
657 let mut components = Vec::new();
658 for quin in self.quins {
659 if quin.subject == media_uri && quin.predicate == c2pa::HAS_COMPONENT {
660 components.push(quin.object);
661 }
662 }
663 Ok(components)
664 }
665
666 pub fn verify_signature(&self, media_uri: u64) -> Result<bool, String> {
670 let _signature = self.get_signature(media_uri)?;
671 Ok(false)
673 }
674
675 pub fn window_aggregate(
677 &self,
678 window_id: u8,
679 aggregate_fn: fn(&[&NQuin]) -> u64,
680 ) -> Result<u64, String> {
681 let quins = self.query_window(window_id, 0)?;
682 Ok(aggregate_fn(&quins))
683 }
684}
685
686impl<'a> Default for SparqlMmHandler<'a> {
687 fn default() -> Self {
688 Self::new(&[])
689 }
690}
691
692pub fn mm_duration(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
694 if args.is_empty() {
695 return false;
696 }
697 let media_uri = args[0];
698
699 let handler = SparqlMmHandler::new(quins);
700 match handler.get_media_duration(media_uri) {
701 Ok(duration) => {
702 result.slots[0] = Some(duration);
703 true
704 }
705 Err(_) => false,
706 }
707}
708
709pub fn mm_dimensions(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
710 if args.is_empty() {
711 return false;
712 }
713 let media_uri = args[0];
714
715 let handler = SparqlMmHandler::new(quins);
716 match handler.get_media_dimensions(media_uri) {
717 Ok((width, height)) => {
718 result.slots[0] = Some(width as u64);
719 result.slots[1] = Some(height as u64);
720 true
721 }
722 Err(_) => false,
723 }
724}
725
726pub fn mm_temporal_fragment(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
727 if args.len() < 2 {
728 return false;
729 }
730 let media_uri = args[0];
731 let start = args[1];
732 let end = args.get(2).copied().unwrap_or(start);
733
734 let mut handler = SparqlMmHandler::new(quins);
735 let fragment = MediaFragment {
736 media_uri,
737 dimensions: [
738 Some(MediaFragmentDimension::Temporal { start, end }),
739 None,
740 None,
741 None,
742 ],
743 dimension_count: 1,
744 };
745
746 match handler.add_media_fragment(fragment) {
747 Ok(_) => {
748 result.slots[0] = Some(1); true
750 }
751 Err(_) => false,
752 }
753}
754
755pub fn ma_format(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
757 if args.is_empty() {
758 return false;
759 }
760 let media_uri = args[0];
761
762 let handler = SparqlMmHandler::new(quins);
763 match handler.get_media_format(media_uri) {
764 Ok(format) => {
765 result.slots[0] = Some(format);
766 true
767 }
768 Err(_) => false,
769 }
770}
771
772pub fn ma_mime_type(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
773 if args.is_empty() {
774 return false;
775 }
776 let media_uri = args[0];
777
778 let handler = SparqlMmHandler::new(quins);
779 match handler.get_media_mime_type(media_uri) {
780 Ok(mime_type) => {
781 result.slots[0] = Some(mime_type);
782 true
783 }
784 Err(_) => false,
785 }
786}
787
788pub fn ma_codec(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
789 if args.is_empty() {
790 return false;
791 }
792 let media_uri = args[0];
793
794 let handler = SparqlMmHandler::new(quins);
795 match handler.get_media_codec(media_uri) {
796 Ok(codec) => {
797 result.slots[0] = Some(codec);
798 true
799 }
800 Err(_) => false,
801 }
802}
803
804pub fn ma_bitrate(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
805 if args.is_empty() {
806 return false;
807 }
808 let media_uri = args[0];
809
810 let handler = SparqlMmHandler::new(quins);
811 match handler.get_media_bitrate(media_uri) {
812 Ok(bitrate) => {
813 result.slots[0] = Some(bitrate);
814 true
815 }
816 Err(_) => false,
817 }
818}
819pub fn ma_framerate(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
820 if args.is_empty() {
821 return false;
822 }
823 let media_uri = args[0];
824
825 let handler = SparqlMmHandler::new(quins);
826 match handler.get_media_framerate(media_uri) {
827 Ok(framerate) => {
828 result.slots[0] = Some(framerate);
829 true
830 }
831 Err(_) => false,
832 }
833}
834
835pub fn c2pa_credential(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
839 if args.is_empty() {
840 return false;
841 }
842 let media_uri = args[0];
843
844 let handler = SparqlMmHandler::new(quins);
845 match handler.get_credential(media_uri) {
846 Ok(credential) => {
847 result.slots[0] = Some(credential);
848 true
849 }
850 Err(_) => false,
851 }
852}
853
854pub fn c2pa_is_verified(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
856 if args.is_empty() {
857 return false;
858 }
859 let media_uri = args[0];
860
861 let handler = SparqlMmHandler::new(quins);
862 match handler.is_verified(media_uri) {
863 Ok(verified) => {
864 result.slots[0] = Some(if verified { 1 } else { 0 });
865 true
866 }
867 Err(_) => false,
868 }
869}
870
871pub fn c2pa_verification_status(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
873 if args.is_empty() {
874 return false;
875 }
876 let media_uri = args[0];
877
878 let handler = SparqlMmHandler::new(quins);
879 match handler.get_verification_status(media_uri) {
880 Ok(status) => {
881 result.slots[0] = Some(status);
882 true
883 }
884 Err(_) => false,
885 }
886}
887
888pub fn c2pa_created_at(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
890 if args.is_empty() {
891 return false;
892 }
893 let media_uri = args[0];
894
895 let handler = SparqlMmHandler::new(quins);
896 match handler.get_created_at(media_uri) {
897 Ok(timestamp) => {
898 result.slots[0] = Some(timestamp);
899 true
900 }
901 Err(_) => false,
902 }
903}
904
905pub fn c2pa_created_by(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
907 if args.is_empty() {
908 return false;
909 }
910 let media_uri = args[0];
911
912 let handler = SparqlMmHandler::new(quins);
913 match handler.get_created_by(media_uri) {
914 Ok(creator) => {
915 result.slots[0] = Some(creator);
916 true
917 }
918 Err(_) => false,
919 }
920}
921
922pub fn c2pa_verify_signature(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
924 if args.is_empty() {
925 return false;
926 }
927 let media_uri = args[0];
928
929 let handler = SparqlMmHandler::new(quins);
930 match handler.verify_signature(media_uri) {
931 Ok(verified) => {
932 result.slots[0] = Some(if verified { 1 } else { 0 });
933 true
934 }
935 Err(_) => false,
936 }
937}
938
939pub fn c2pa_derived_from(args: &[u64], quins: &[NQuin], result: &mut BindingRow) -> bool {
941 if args.is_empty() {
942 return false;
943 }
944 let media_uri = args[0];
945
946 let handler = SparqlMmHandler::new(quins);
947 match handler.get_derived_from(media_uri) {
948 Ok(source) => {
949 result.slots[0] = Some(source);
950 true
951 }
952 Err(_) => false,
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 use super::*;
959
960 #[test]
961 fn test_mm_handler_creation() {
962 let quins = vec![];
963 let handler = SparqlMmHandler::new(&quins);
964 assert_eq!(handler.window_count, 0);
965 }
966
967 #[test]
968 fn test_create_tumbling_window() {
969 let quins = vec![];
970 let mut handler = SparqlMmHandler::new(&quins);
971
972 let result = handler.create_tumbling_window(1000, 0);
973 assert!(result.is_ok());
974 assert_eq!(handler.window_count, 1);
975 }
976
977 #[test]
978 fn test_create_sliding_window() {
979 let quins = vec![];
980 let mut handler = SparqlMmHandler::new(&quins);
981
982 let result = handler.create_sliding_window(1000, 500, 0);
983 assert!(result.is_ok());
984 assert_eq!(handler.window_count, 1);
985 }
986
987 #[test]
988 fn test_parse_media_fragment_no_invented_dims() {
989 let quins = vec![];
990 let mut handler = SparqlMmHandler::new(&quins);
991
992 let fragment = handler.parse_media_fragment(12345).unwrap();
993 assert_eq!(fragment.media_uri, 12345);
994 assert_eq!(fragment.dimension_count, 0);
995 }
996
997 #[test]
998 fn ma_ont_constants_are_distinct() {
999 assert_ne!(ma_ont::HAS_FRAGMENT, ma_ont::HAS_CODEC);
1001 assert_ne!(ma_ont::HAS_BITRATE, ma_ont::HAS_TEMPORAL_FRAGMENT);
1002 assert_ne!(ma_ont::HAS_X, ma_ont::HAS_Y);
1003 assert_ne!(c2pa::HAS_CREDENTIAL, c2pa::HAS_MANIFEST);
1004 assert_ne!(c2pa::DERIVED_FROM, ma_ont::HAS_MIME_TYPE);
1005 }
1006
1007 #[test]
1008 fn spatial_intersection_real() {
1009 assert!(SparqlMmHandler::spatial_intersects(
1010 0, 0, 10, 10, 5, 5, 10, 10
1011 ));
1012 assert!(!SparqlMmHandler::spatial_intersects(
1013 0, 0, 10, 10, 20, 20, 5, 5
1014 ));
1015 }
1016
1017 #[test]
1018 fn query_window_into_media_time() {
1019 let media = q_hash("media:clip");
1020 let quins = [
1021 NQuin {
1022 subject: media,
1023 predicate: ma_ont::MEDIA_TIME_MS,
1024 object: 500,
1025 context: 0,
1026 metadata: 0,
1027 parity: 0,
1028 },
1029 NQuin {
1030 subject: media,
1031 predicate: ma_ont::MEDIA_TIME_MS,
1032 object: 2500,
1033 context: 0,
1034 metadata: 0,
1035 parity: 0,
1036 },
1037 ];
1038 let mut handler = SparqlMmHandler::new(&quins);
1040 handler.create_tumbling_window(1000, 0).unwrap(); let mut out = [None; 8];
1042 let n = handler.query_window_into(0, &mut out).unwrap();
1043 assert_eq!(n, 1);
1044 assert_eq!(out[0].unwrap().object, 500);
1045 }
1046
1047 #[test]
1048 fn query_media_fragment_into_spatial() {
1049 let media = q_hash("media:img");
1050 let packed = SparqlMmHandler::pack_spatial_u64(10, 10, 50, 50);
1051 let quins = [NQuin {
1052 subject: media,
1053 predicate: ma_ont::HAS_SPATIAL_FRAGMENT,
1054 object: packed,
1055 context: 0,
1056 metadata: 0,
1057 parity: 0,
1058 }];
1059 let mut handler = SparqlMmHandler::new(&quins);
1060 let frag = SparqlMmHandler::make_media_fragment(
1061 media,
1062 &[MediaFragmentDimension::Spatial {
1063 x: 20,
1064 y: 20,
1065 width: 20,
1066 height: 20,
1067 }],
1068 )
1069 .unwrap();
1070 handler.add_media_fragment(frag).unwrap();
1071 let mut out = [None; 4];
1072 let n = handler.query_media_fragment_into(0, &mut out).unwrap();
1073 assert_eq!(n, 1);
1074 }
1075
1076 #[test]
1077 fn c2pa_never_claims_verified_from_field_presence() {
1078 let media = q_hash("media:photo");
1079 let quins = [
1080 NQuin {
1081 subject: media,
1082 predicate: c2pa::HAS_SIGNATURE,
1083 object: 0xABC,
1084 context: 0,
1085 metadata: 0,
1086 parity: 0,
1087 },
1088 NQuin {
1089 subject: media,
1090 predicate: c2pa::IS_VERIFIED,
1091 object: 1,
1092 context: 0,
1093 metadata: 0,
1094 parity: 0,
1095 },
1096 ];
1097 let handler = SparqlMmHandler::new(&quins);
1098 assert_eq!(
1099 handler.c2pa_status(media).unwrap(),
1100 C2paVerificationStatus::ParsedOnly
1101 );
1102 assert!(!handler.is_verified(media).unwrap());
1103 assert!(!handler.verify_signature(media).unwrap());
1104 }
1105}