Live video on the AT Protocol
79
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove redundant distribution policy expiresAt

Assisted-by: Claude Code

authored by

makeworld and committed by
Eli Mallon
9694da88 e0a7371f

+35 -81
+15 -19
js/docs/src/content/docs/video-metadata/c2pa-integration.md
··· 17 17 18 18 ```json 19 19 { 20 - "active_manifest": "urn:c2pa:bb3d7649-9cab-49b9-aaa5-635193c10015", 20 + "active_manifest": "urn:c2pa:66130743-a4cd-4f73-a4c7-201079ef127a", 21 21 "manifests": { 22 - "urn:c2pa:bb3d7649-9cab-49b9-aaa5-635193c10015": { 22 + "urn:c2pa:66130743-a4cd-4f73-a4c7-201079ef127a": { 23 23 "claim_generator_info": [ 24 24 { 25 25 "name": "c2pa-rs", ··· 27 27 "org.contentauth.c2pa_rs": "0.58.0" 28 28 } 29 29 ], 30 - "title": "Livestream Segment at 2025-11-05T16:08:56.428Z", 31 - "instance_id": "xmp:iid:013f5406-2e1e-4459-be38-ee9bb13d97b7", 30 + "title": "Livestream Segment at 2025-11-13T20:41:30.499Z", 31 + "instance_id": "xmp:iid:a031a30d-e1eb-4548-a20f-6453e15c2ace", 32 32 "ingredients": [], 33 33 "assertions": [ 34 34 { ··· 37 37 "actions": [ 38 38 { 39 39 "action": "c2pa.created", 40 - "when": "2025-11-05T16:08:56.428Z" 40 + "when": "2025-11-13T20:41:30.499Z" 41 41 }, 42 42 { 43 43 "action": "c2pa.published", 44 - "when": "2025-11-05T16:08:56.428Z" 44 + "when": "2025-11-13T20:41:30.499Z" 45 45 } 46 46 ], 47 47 "allActionsIncluded": false ··· 85 85 } 86 86 ], 87 87 "alg": "sha256", 88 - "hash": "Uj/4bjqYZpu9ks+tSJ5F0inOE/E9gnnflyrT2m3a7n8=", 88 + "hash": "ZHobn5CtL1NsTLAMMPT8D7koSrMr2Ijm5eJt73ED4HA=", 89 89 "name": "jumbf manifest" 90 90 } 91 91 }, ··· 93 93 "label": "cawg.metadata", 94 94 "data": { 95 95 "@context": { 96 - "Iptc4xmpExt": "http://iptc.org/std/Iptc4xmpExt/2008-02-29/", 97 96 "photoshop": "http://ns.adobe.com/photoshop/1.0/", 98 - "dc": "http://purl.org/dc/elements/1.1/", 99 97 "xmpRights": "http://ns.adobe.com/xap/1.0/rights/", 100 - "streamplace": "https://ns.stream.place/metadata/0.1" 98 + "Iptc4xmpExt": "http://iptc.org/std/Iptc4xmpExt/2008-02-29/", 99 + "dc": "http://purl.org/dc/elements/1.1/" 101 100 }, 102 101 "xmpRights:UsageTerms": "All rights reserved", 103 - "dc:title": "Test", 104 - "streamplace:distributionPolicy": { 105 - "deleteAfter": "2025-11-05T16:13:56.000Z" 106 - }, 107 - "dc:date": "2025-11-05T16:08:56.428Z", 108 102 "dc:creator": "did:plc:2j2ounbiyi3ftihronlw5qhj", 103 + "dc:date": "2025-11-13T20:41:30.499Z", 109 104 "Iptc4xmpExt:ContentWarning": [ 110 105 "cwarn:flashingLights" 111 - ] 106 + ], 107 + "dc:title": "Test" 112 108 }, 113 109 "kind": "Json" 114 110 }, ··· 156 152 "signature_info": { 157 153 "issuer": "Streamplace", 158 154 "common_name": "did:key:zQ3shiYS17LRhT7x6mfd6HfsHHzz1aD9DpGJUP3aT5f2ghdAy", 159 - "cert_serial_number": "154067456662585157774248475905339097818" 155 + "cert_serial_number": "34409281865771434870888029768053492928" 160 156 }, 161 - "label": "urn:c2pa:bb3d7649-9cab-49b9-aaa5-635193c10015" 157 + "label": "urn:c2pa:66130743-a4cd-4f73-a4c7-201079ef127a" 162 158 } 163 - }, 159 + } 164 160 } 165 161 ``` 166 162
-19
pkg/media/manifest_builder.go
··· 77 77 "Iptc4xmpExt": "http://iptc.org/std/Iptc4xmpExt/2008-02-29/", 78 78 "photoshop": "http://ns.adobe.com/photoshop/1.0/", 79 79 "xmpRights": "http://ns.adobe.com/xap/1.0/rights/", 80 - "streamplace": "https://ns.stream.place/metadata/0.1", 81 80 }, 82 81 "dc:creator": streamerName, 83 82 "dc:title": "livestream", ··· 237 236 // Unknown warnings remain unchanged 238 237 } 239 238 mani["assertions"].([]obj)[1]["data"].(obj)["Iptc4xmpExt:ContentWarning"] = metadata.ContentWarnings.Warnings 240 - } 241 - 242 - if metadata.DistributionPolicy != nil { 243 - // Convert the distribution policy duration to an absolute expiry timestamp 244 - // deleteAfter is in seconds, startTimeMillis is in milliseconds 245 - if metadata.DistributionPolicy.DeleteAfter != nil { 246 - // Calculate expiry: start time (seconds) + duration (seconds) = expiry timestamp (seconds) 247 - startTimeSeconds := startTimeMillis / 1000 248 - expiresAtSeconds := startTimeSeconds + *metadata.DistributionPolicy.DeleteAfter 249 - 250 - // Convert to ISO 8601 datetime string for C2PA manifest 251 - // Note: In the manifest, we store this in "deleteAfter" field but with timestamp value instead of duration 252 - deleteAfterTimestamp := aqtime.FromMillis(expiresAtSeconds * 1000).String() 253 - 254 - mani["assertions"].([]obj)[1]["data"].(obj)["streamplace:distributionPolicy"] = obj{ 255 - "deleteAfter": deleteAfterTimestamp, 256 - } 257 - } 258 239 } 259 240 260 241 return mani
+9 -30
pkg/media/media.go
··· 9 9 "fmt" 10 10 "io" 11 11 "sync" 12 - "time" 13 12 14 13 "github.com/google/uuid" 15 14 "github.com/pion/interceptor" ··· 37 36 const CertFile = "cert.pem" 38 37 const SegmentsDir = "segments" 39 38 40 - var StreamplaceMetadata = "cawg.metadata" 39 + const StreamplaceMetadata = "cawg.metadata" 41 40 42 41 type MediaManager struct { 43 42 cli *config.CLI ··· 372 371 373 372 // extractDistributionPolicy extracts distribution policy from the C2PA manifest 374 373 func extractDistributionPolicy(mani *c2patypes.Manifest, segmentStart aqtime.AQTime) *model.DistributionPolicy { 375 - ass := findAssertion(mani, StreamplaceMetadata) 376 - if ass == nil { 377 - return nil 378 - } 379 - 380 - data, ok := ass.Data.(map[string]interface{}) 381 - if !ok { 382 - return nil 383 - } 384 - 385 - policy, ok := data["streamplace:distributionPolicy"] 386 - if !ok { 387 - return nil 388 - } 389 - 390 - policyMap, ok := policy.(map[string]interface{}) 391 - if !ok { 374 + metadataConfig := extractMetadataConfiguration(mani) 375 + if metadataConfig == nil { 392 376 return nil 393 377 } 394 378 395 - expiry, ok := policyMap["deleteAfter"] 396 - if !ok { 379 + if metadataConfig.DistributionPolicy == nil { 397 380 return nil 398 381 } 399 382 400 - // deleteAfter now contains a timestamp string (RFC3339/ISO 8601 format) 401 - expiryStr, ok := expiry.(string) 402 - if !ok { 383 + if metadataConfig.DistributionPolicy.DeleteAfter == nil { 403 384 return nil 404 385 } 405 386 406 - expiryTime, err := time.Parse(time.RFC3339, expiryStr) 407 - if err != nil { 408 - return nil 409 - } 387 + // deleteAfter contains an offset in seconds from creation time 388 + deleteAfterSeconds := *metadataConfig.DistributionPolicy.DeleteAfter 410 389 411 390 return &model.DistributionPolicy{ 412 - ExpiresAt: &expiryTime, 391 + DeleteAfterSeconds: &deleteAfterSeconds, 413 392 } 414 393 } 415 394 416 - // extractDistributionPolicy extracts the place.stream.metadata.configuration from the C2PA manifest 395 + // extractMetadataConfiguration extracts the place.stream.metadata.configuration from the C2PA manifest 417 396 func extractMetadataConfiguration(mani *c2patypes.Manifest) *streamplace.MetadataConfiguration { 418 397 ass := findAssertion(mani, "place.stream.metadata.configuration") 419 398 if ass == nil {
+8 -6
pkg/media/validate.go
··· 113 113 return err 114 114 } 115 115 var deleteAfter *time.Time 116 - if meta.DistributionPolicy != nil && meta.DistributionPolicy.ExpiresAt != nil { 117 - deleteAfter = meta.DistributionPolicy.ExpiresAt 116 + if meta.DistributionPolicy != nil && meta.DistributionPolicy.DeleteAfterSeconds != nil { 117 + expiryTime := meta.StartTime.Time().Add(time.Duration(*meta.DistributionPolicy.DeleteAfterSeconds) * time.Second) 118 + deleteAfter = &expiryTime 118 119 } 119 120 seg := &model.Segment{ 120 121 ID: *label, ··· 173 174 174 175 // Check distribution policy (if enabled) 175 176 if mm.cli.ContentFilters.DistributionPolicy.Enabled && meta.DistributionPolicy != nil { 176 - if meta.DistributionPolicy.ExpiresAt != nil { 177 - if time.Now().After(*meta.DistributionPolicy.ExpiresAt) { 178 - reason := fmt.Sprintf("distribution policy expired: segment expires at %s", meta.DistributionPolicy.ExpiresAt) 177 + if meta.DistributionPolicy.DeleteAfterSeconds != nil { 178 + expiresAt := meta.StartTime.Time().Add(time.Duration(*meta.DistributionPolicy.DeleteAfterSeconds) * time.Second) 179 + if time.Now().After(expiresAt) { 180 + reason := fmt.Sprintf("distribution policy expired: segment expires at %s", expiresAt) 179 181 log.Log(ctx, "content filtered", 180 182 "reason", reason, 181 183 "filter_type", "distribution_policy", 182 184 "creator", meta.Creator, 183 185 "start_time", meta.StartTime, 184 - "expires_at", *meta.DistributionPolicy.ExpiresAt) 186 + "expires_at", expiresAt) 185 187 return fmt.Errorf("content filtered: %s", reason) 186 188 } 187 189 }
+3 -7
pkg/model/segment.go
··· 85 85 86 86 // DistributionPolicy represents distribution policy information 87 87 type DistributionPolicy struct { 88 - ExpiresAt *time.Time `json:"expiresAt,omitempty"` 88 + DeleteAfterSeconds *int64 `json:"deleteAfterSeconds,omitempty"` 89 89 } 90 90 91 91 // Scan scan value into DistributionPolicy, implements sql.Scanner interface ··· 185 185 } 186 186 187 187 var distributionPolicy *streamplace.MetadataDistributionPolicy 188 - if s.DistributionPolicy != nil && s.DistributionPolicy.ExpiresAt != nil { 189 - // Convert the absolute timestamp back to a duration (in seconds) from segment start 190 - startTimeUnix := s.StartTime.Unix() 191 - expiresAtUnix := s.DistributionPolicy.ExpiresAt.Unix() 192 - deleteAfterSecs := expiresAtUnix - startTimeUnix 188 + if s.DistributionPolicy != nil && s.DistributionPolicy.DeleteAfterSeconds != nil { 193 189 distributionPolicy = &streamplace.MetadataDistributionPolicy{ 194 - DeleteAfter: &deleteAfterSecs, 190 + DeleteAfter: s.DistributionPolicy.DeleteAfterSeconds, 195 191 } 196 192 } 197 193