diff --git a/Makefile b/Makefile
index fdfdae6f..a4fce7fe 100644
--- a/Makefile
+++ b/Makefile
@@ -398,6 +398,8 @@ go-lexicons:
&& echo 'func (t *MediaDefs_SourceClip) UnmarshalCBOR(r io.Reader) error { return nil }' >> pkg/streamplace/cbor_gen.go \
&& echo 'func (t *Video_Connection) MarshalCBOR(w io.Writer) error { return nil }' >> pkg/streamplace/cbor_gen.go \
&& echo 'func (t *Video_Connection) UnmarshalCBOR(r io.Reader) error { return nil }' >> pkg/streamplace/cbor_gen.go \
+ && echo 'func (t *MediaTrack_CommonMetadata) MarshalCBOR(w io.Writer) error { return nil }' >> pkg/streamplace/cbor_gen.go \
+ && echo 'func (t *MediaTrack_CommonMetadata) UnmarshalCBOR(r io.Reader) error { return nil }' >> pkg/streamplace/cbor_gen.go \
&& sed -i.bak 's/\tlexutil\.RegisterType/\/\/\tlexutil.RegisterType/' $$(find ./pkg/streamplace -type f) \
&& go run golang.org/x/tools/cmd/goimports@latest -w $$(find ./pkg/streamplace ./pkg/gamesgamesgamesgames -type f) \
&& go run ./pkg/gen/gen_stubs.go \
diff --git a/js/docs/src/content/docs/lex-reference/media/place-stream-media-defs.md b/js/docs/src/content/docs/lex-reference/media/place-stream-media-defs.md
index 212c9014..0ccfecb7 100644
--- a/js/docs/src/content/docs/lex-reference/media/place-stream-media-defs.md
+++ b/js/docs/src/content/docs/lex-reference/media/place-stream-media-defs.md
@@ -51,13 +51,14 @@ A track backed by a MUXL container
**Properties:**
-| Name | Type | Req'd | Description | Constraints |
-| ------------ | -------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
-| `blob` | `string` | ✅ | BLAKE-3 content hash (BDASL CID) of the source video segment. | |
-| `trackId` | `string` | ✅ | ID of the track within the MUXL container. 1-indexed for MP4 reasons. | |
-| `mediaType` | `string` | ✅ | Type of the track: video, audio, or text. | |
-| `language` | `string` | ❌ | Language of the track, if applicable. | |
-| `signingKey` | `string` | ❌ | did:key of the ephemeral key that C2PA-signed this track's segments. The private key is discarded once the track is produced, so it can only ever have signed this content. | Format: `did` |
+| Name | Type | Req'd | Description | Constraints |
+| ------------ | --------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
+| `blob` | `string` | ✅ | BLAKE-3 content hash (BDASL CID) of the source video segment. | |
+| `size` | `integer` | ❌ | Size in bytes of the source video segment. | |
+| `trackId` | `string` | ✅ | ID of the track within the MUXL container. 1-indexed for MP4 reasons. | |
+| `mediaType` | `string` | ✅ | Type of the track: video, audio, or text. | |
+| `language` | `string` | ❌ | Language of the track, if applicable. | |
+| `signingKey` | `string` | ❌ | did:key of the ephemeral key that C2PA-signed this track's segments. The private key is discarded once the track is produced, so it can only ever have signed this content. | Format: `did` |
---
@@ -112,6 +113,10 @@ A track backed by a MUXL container
"type": "string",
"description": "BLAKE-3 content hash (BDASL CID) of the source video segment."
},
+ "size": {
+ "type": "integer",
+ "description": "Size in bytes of the source video segment."
+ },
"trackId": {
"type": "string",
"description": "ID of the track within the MUXL container. 1-indexed for MP4 reasons."
diff --git a/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md b/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md
index 9923de06..db48e267 100644
--- a/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md
+++ b/js/docs/src/content/docs/lex-reference/media/place-stream-media-track.md
@@ -13,7 +13,7 @@ description: Reference for the place.stream.media.track lexicon
**Type:** `record`
-A track for a video stream, either part of the source or a custom additional track.
+A track for a video stream, either part of the source or a custom additional track. One of: video, audio, subtitles.
**Record Key:** `tid`
@@ -24,7 +24,7 @@ A track for a video stream, either part of the source or a custom additional tra
| `track` | Union of:
[`place.stream.media.defs#muxlTrack`](/lex-reference/place-stream-media-defs#muxltrack) | ✅ | | |
| `video` | `string` | ❌ | If this is a derived track like a transcode or a transcript, what was the source place.stream.video? | Format: `at-uri` |
| `parentTrack` | [`com.atproto.repo.strongRef`](https://github.com/bluesky-social/atproto/tree/main/lexicons/com/atproto/repo/strongref.json#undefined) | ❌ | If this is a derived track like a transcode or a transcript, what was the parent track? | |
-| `metadata` | [`place.stream.media.track#commonMetadata`](/lex-reference/place-stream-media-track#commonmetadata) | ❌ | | |
+| `metadata` | Union of:
[`place.stream.media.track#commonMetadata`](/lex-reference/place-stream-media-track#commonmetadata) | ❌ | | |
---
@@ -41,6 +41,7 @@ Metadata common to all media types. Contains subobjects for other media types.
| Name | Type | Req'd | Description | Constraints |
| ---------- | ------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------- | ----------- |
| `language` | `string` | ❌ | IETF BCP 47 language tag corresponding to the content of this track | |
+| `duration` | `integer` | ❌ | duration of this track in milliseconds | |
| `video` | [`place.stream.segment#video`](/lex-reference/place-stream-segment#video) | ❌ | | |
| `audio` | [`place.stream.segment#audio`](/lex-reference/place-stream-segment#audio) | ❌ | | |
@@ -55,7 +56,7 @@ Metadata common to all media types. Contains subobjects for other media types.
"defs": {
"main": {
"type": "record",
- "description": "A track for a video stream, either part of the source or a custom additional track.",
+ "description": "A track for a video stream, either part of the source or a custom additional track. One of: video, audio, subtitles.",
"key": "tid",
"record": {
"required": ["track"],
@@ -76,8 +77,8 @@ Metadata common to all media types. Contains subobjects for other media types.
"description": "If this is a derived track like a transcode or a transcript, what was the parent track?"
},
"metadata": {
- "type": "ref",
- "ref": "place.stream.media.track#commonMetadata"
+ "type": "union",
+ "refs": ["place.stream.media.track#commonMetadata"]
}
}
}
@@ -91,6 +92,10 @@ Metadata common to all media types. Contains subobjects for other media types.
"type": "string",
"description": "IETF BCP 47 language tag corresponding to the content of this track"
},
+ "duration": {
+ "type": "integer",
+ "description": "duration of this track in milliseconds"
+ },
"video": {
"type": "ref",
"ref": "place.stream.segment#video"
diff --git a/js/docs/src/content/docs/lex-reference/place-stream-video.md b/js/docs/src/content/docs/lex-reference/place-stream-video.md
index daff58c9..03683afb 100644
--- a/js/docs/src/content/docs/lex-reference/place-stream-video.md
+++ b/js/docs/src/content/docs/lex-reference/place-stream-video.md
@@ -22,13 +22,14 @@ Some audiovisual content.
| Name | Type | Req'd | Description | Constraints |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `title` | `string` | ✅ | Title of the video referenced by this record | Max Length: 1400
Max Graphemes: 140 |
-| `source` | Union of:
[`place.stream.media.defs#sourceTracks`](/lex-reference/place-stream-media-defs#sourcetracks)
[`place.stream.media.defs#sourceClip`](/lex-reference/place-stream-media-defs#sourceclip) | ❌ | What is the source of this video? | |
+| `source` | Union of:
[`place.stream.media.defs#sourceTracks`](/lex-reference/place-stream-media-defs#sourcetracks)
[`place.stream.media.defs#sourceClip`](/lex-reference/place-stream-media-defs#sourceclip) | ✅ | What is the source of this video? | |
| `description` | `string` | ❌ | Description of this video | Max Length: 50000
Max Graphemes: 5000 |
-| `duration` | `integer` | ❌ | Duration of the video in milliseconds | |
-| `descriptionFacets` | Array of [`place.stream.richtext.facet`](/lex-reference/place-stream-richtext-facet) | ❌ | Annotations of text (mentions, URLs, etc) | |
+| `duration` | `integer` | ✅ | Duration of the video in milliseconds | |
+| `descriptionFacets` | Array of [`place.stream.richtext.videoFacet`](/lex-reference/place-stream-richtext-videofacet) | ❌ | Annotations of text (mentions, URLs, etc) | |
| `thumb` | `blob` | ❌ | Thumbnail image for the video. | Accept: `image/*`
Max Size: 1000000 bytes |
| `connections` | Array of Union of:
[`#connection`](#connection) | ❌ | Free-form list of atproto records related in some way to this video | |
-| `contentPolicy` | [`place.stream.metadata.configuration`](/lex-reference/place-stream-metadata-configuration) | ❌ | copyright, distribution, and content warning data | |
+| `contentWarnings` | [`place.stream.metadata.contentWarnings`](/lex-reference/place-stream-metadata-contentwarnings) | ❌ | content warning data for this VOD | |
+| `contentRights` | [`place.stream.metadata.contentRights`](/lex-reference/place-stream-metadata-contentrights) | ❌ | copyright and licensing information for this VOD | |
| `activity` | Union of:
[`place.stream.defs#activityGame`](/lex-reference/place-stream-defs#activitygame)
[`place.stream.defs#activityLabel`](/lex-reference/place-stream-defs#activitylabel) | ❌ | The game or activity in the video. | |
| `tags` | Array of `string` | ❌ | Freeform tags for this stream. Each tag must be alphanumeric (a-z, A-Z, 0-9) plus colon. Tags with colons indicate a specific tag group (e.g. 'lang:en' indicates the stream's primary language). | Max Items: 10 |
@@ -48,20 +49,6 @@ Some audiovisual content.
---
-
-
-### `videoSourceContent`
-
-**Type:** `object`
-
-**Properties:**
-
-| Name | Type | Req'd | Description | Constraints |
-| ----- | -------------------------------------------------------------------------------------------------------------------------------------- | ----- | ---------------------------------------------------------------------------- | ----------- |
-| `ref` | [`com.atproto.repo.strongRef`](https://github.com/bluesky-social/atproto/tree/main/lexicons/com/atproto/repo/strongref.json#undefined) | ❌ | place.stream.media.source record providing the content for this video record | |
-
----
-
## Lexicon Source
```json
@@ -74,7 +61,7 @@ Some audiovisual content.
"description": "Some audiovisual content.",
"key": "tid",
"record": {
- "required": ["title"],
+ "required": ["title", "source", "duration"],
"type": "object",
"properties": {
"title": {
@@ -106,7 +93,7 @@ Some audiovisual content.
"description": "Annotations of text (mentions, URLs, etc)",
"items": {
"type": "ref",
- "ref": "place.stream.richtext.facet"
+ "ref": "place.stream.richtext.videoFacet"
}
},
"thumb": {
@@ -123,10 +110,15 @@ Some audiovisual content.
"refs": ["#connection"]
}
},
- "contentPolicy": {
- "description": "copyright, distribution, and content warning data",
+ "contentWarnings": {
+ "description": "content warning data for this VOD",
"type": "ref",
- "ref": "place.stream.metadata.configuration"
+ "ref": "place.stream.metadata.contentWarnings"
+ },
+ "contentRights": {
+ "description": "copyright and licensing information for this VOD",
+ "type": "ref",
+ "ref": "place.stream.metadata.contentRights"
},
"activity": {
"type": "union",
@@ -157,16 +149,6 @@ Some audiovisual content.
"ref": "com.atproto.repo.strongRef"
}
}
- },
- "videoSourceContent": {
- "type": "object",
- "properties": {
- "ref": {
- "description": "place.stream.media.source record providing the content for this video record",
- "type": "ref",
- "ref": "com.atproto.repo.strongRef"
- }
- }
}
}
}
diff --git a/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-facet.md b/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-facet.md
index cebfc538..c8e8f3e6 100644
--- a/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-facet.md
+++ b/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-facet.md
@@ -13,7 +13,7 @@ description: Reference for the place.stream.richtext.facet lexicon
**Type:** `object`
-Annotation of a sub-string within rich text.
+Annotation of a sub-string within rich text in a livestream chat message.
**Properties:**
@@ -33,7 +33,7 @@ Annotation of a sub-string within rich text.
"defs": {
"main": {
"type": "object",
- "description": "Annotation of a sub-string within rich text.",
+ "description": "Annotation of a sub-string within rich text in a livestream chat message.",
"required": ["index", "features"],
"properties": {
"index": {
diff --git a/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-videofacet.md b/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-videofacet.md
new file mode 100644
index 00000000..ef687c91
--- /dev/null
+++ b/js/docs/src/content/docs/lex-reference/richtext/place-stream-richtext-videofacet.md
@@ -0,0 +1,57 @@
+---
+title: place.stream.richtext.videoFacet
+description: Reference for the place.stream.richtext.videoFacet lexicon
+---
+
+**Lexicon Version:** 1
+
+## Definitions
+
+
+
+### `main`
+
+**Type:** `object`
+
+Annotation of a sub-string within rich text in a VOD description or comment.
+
+**Properties:**
+
+| Name | Type | Req'd | Description | Constraints |
+| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ----------- | ----------- |
+| `index` | [`app.bsky.richtext.facet#byteSlice`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/richtext/facet.json#byteSlice) | ✅ | | |
+| `features` | Array of Union of:
[`app.bsky.richtext.facet#mention`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/richtext/facet.json#mention)
[`app.bsky.richtext.facet#link`](https://github.com/bluesky-social/atproto/tree/main/lexicons/app/bsky/richtext/facet.json#link) | ✅ | | |
+
+---
+
+## Lexicon Source
+
+```json
+{
+ "lexicon": 1,
+ "id": "place.stream.richtext.videoFacet",
+ "defs": {
+ "main": {
+ "type": "object",
+ "description": "Annotation of a sub-string within rich text in a VOD description or comment.",
+ "required": ["index", "features"],
+ "properties": {
+ "index": {
+ "type": "ref",
+ "ref": "app.bsky.richtext.facet#byteSlice"
+ },
+ "features": {
+ "type": "array",
+ "items": {
+ "type": "union",
+ "refs": [
+ "app.bsky.richtext.facet#mention",
+ "app.bsky.richtext.facet#link"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
+```
diff --git a/lexicons/place/stream/media/defs.json b/lexicons/place/stream/media/defs.json
index 9ac68466..f7b4a974 100644
--- a/lexicons/place/stream/media/defs.json
+++ b/lexicons/place/stream/media/defs.json
@@ -46,6 +46,10 @@
"type": "string",
"description": "BLAKE-3 content hash (BDASL CID) of the source video segment."
},
+ "size": {
+ "type": "integer",
+ "description": "Size in bytes of the source video segment."
+ },
"trackId": {
"type": "string",
"description": "ID of the track within the MUXL container. 1-indexed for MP4 reasons."
diff --git a/lexicons/place/stream/media/track.json b/lexicons/place/stream/media/track.json
index 0e6d2d18..e40f459d 100644
--- a/lexicons/place/stream/media/track.json
+++ b/lexicons/place/stream/media/track.json
@@ -4,7 +4,7 @@
"defs": {
"main": {
"type": "record",
- "description": "A track for a video stream, either part of the source or a custom additional track.",
+ "description": "A track for a video stream, either part of the source or a custom additional track. One of: video, audio, subtitles.",
"key": "tid",
"record": {
"required": ["track"],
@@ -25,8 +25,8 @@
"description": "If this is a derived track like a transcode or a transcript, what was the parent track?"
},
"metadata": {
- "type": "ref",
- "ref": "place.stream.media.track#commonMetadata"
+ "type": "union",
+ "refs": ["place.stream.media.track#commonMetadata"]
}
}
}
@@ -40,6 +40,10 @@
"type": "string",
"description": "IETF BCP 47 language tag corresponding to the content of this track"
},
+ "duration": {
+ "type": "integer",
+ "description": "duration of this track in milliseconds"
+ },
"video": {
"type": "ref",
"ref": "place.stream.segment#video"
diff --git a/lexicons/place/stream/richtext/facet.json b/lexicons/place/stream/richtext/facet.json
index 9a461e55..09c0af82 100644
--- a/lexicons/place/stream/richtext/facet.json
+++ b/lexicons/place/stream/richtext/facet.json
@@ -4,7 +4,7 @@
"defs": {
"main": {
"type": "object",
- "description": "Annotation of a sub-string within rich text.",
+ "description": "Annotation of a sub-string within rich text in a livestream chat message.",
"required": ["index", "features"],
"properties": {
"index": { "type": "ref", "ref": "app.bsky.richtext.facet#byteSlice" },
diff --git a/lexicons/place/stream/richtext/videoFacet.json b/lexicons/place/stream/richtext/videoFacet.json
new file mode 100644
index 00000000..a0e0ef13
--- /dev/null
+++ b/lexicons/place/stream/richtext/videoFacet.json
@@ -0,0 +1,24 @@
+{
+ "lexicon": 1,
+ "id": "place.stream.richtext.videoFacet",
+ "defs": {
+ "main": {
+ "type": "object",
+ "description": "Annotation of a sub-string within rich text in a VOD description or comment.",
+ "required": ["index", "features"],
+ "properties": {
+ "index": { "type": "ref", "ref": "app.bsky.richtext.facet#byteSlice" },
+ "features": {
+ "type": "array",
+ "items": {
+ "type": "union",
+ "refs": [
+ "app.bsky.richtext.facet#mention",
+ "app.bsky.richtext.facet#link"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lexicons/place/stream/video.json b/lexicons/place/stream/video.json
index 82083500..9d72c809 100644
--- a/lexicons/place/stream/video.json
+++ b/lexicons/place/stream/video.json
@@ -7,7 +7,7 @@
"description": "Some audiovisual content.",
"key": "tid",
"record": {
- "required": ["title"],
+ "required": ["title", "source", "duration"],
"type": "object",
"properties": {
"title": {
@@ -37,7 +37,10 @@
"descriptionFacets": {
"type": "array",
"description": "Annotations of text (mentions, URLs, etc)",
- "items": { "type": "ref", "ref": "place.stream.richtext.facet" }
+ "items": {
+ "type": "ref",
+ "ref": "place.stream.richtext.videoFacet"
+ }
},
"thumb": {
"description": "Thumbnail image for the video.",
@@ -53,10 +56,15 @@
"refs": ["#connection"]
}
},
- "contentPolicy": {
- "description": "copyright, distribution, and content warning data",
+ "contentWarnings": {
+ "description": "content warning data for this VOD",
+ "type": "ref",
+ "ref": "place.stream.metadata.contentWarnings"
+ },
+ "contentRights": {
+ "description": "copyright and licensing information for this VOD",
"type": "ref",
- "ref": "place.stream.metadata.configuration"
+ "ref": "place.stream.metadata.contentRights"
},
"activity": {
"type": "union",
@@ -87,16 +95,6 @@
"ref": "com.atproto.repo.strongRef"
}
}
- },
- "videoSourceContent": {
- "type": "object",
- "properties": {
- "ref": {
- "description": "place.stream.media.source record providing the content for this video record",
- "type": "ref",
- "ref": "com.atproto.repo.strongRef"
- }
- }
}
}
}
diff --git a/pkg/atproto/sync.go b/pkg/atproto/sync.go
index 7b9965d2..7ba19bf5 100644
--- a/pkg/atproto/sync.go
+++ b/pkg/atproto/sync.go
@@ -740,9 +740,7 @@ func (atsync *ATProtoSynchronizer) handleCreateUpdate(ctx context.Context, userD
Record: *recCBOR,
IndexedAt: now,
}
- if rec.Duration != nil {
- v.DurationMS = rec.Duration
- }
+ v.DurationMS = &rec.Duration
if err := atsync.Model.UpsertVideo(ctx, v); err != nil {
return fmt.Errorf("failed to upsert video: %w", err)
}
diff --git a/pkg/gen/gen.go b/pkg/gen/gen.go
index 7fe38235..5b9299f0 100644
--- a/pkg/gen/gen.go
+++ b/pkg/gen/gen.go
@@ -21,6 +21,7 @@ func main() {
streamplace.Segment_Framerate{},
streamplace.ChatMessage{},
streamplace.RichtextFacet{},
+ streamplace.RichtextVideoFacet{},
streamplace.ChatProfile{},
streamplace.ChatProfile_BadgeSelections{},
streamplace.ChatProfile_StreamerBadgeSelection{},
diff --git a/pkg/streamplace/cbor_gen.go b/pkg/streamplace/cbor_gen.go
index 89866aac..89b85e3b 100644
--- a/pkg/streamplace/cbor_gen.go
+++ b/pkg/streamplace/cbor_gen.go
@@ -2979,6 +2979,183 @@ func (t *RichtextFacet) UnmarshalCBOR(r io.Reader) (err error) {
return nil
}
+func (t *RichtextVideoFacet) MarshalCBOR(w io.Writer) error {
+ if t == nil {
+ _, err := w.Write(cbg.CborNull)
+ return err
+ }
+
+ cw := cbg.NewCborWriter(w)
+
+ if _, err := cw.Write([]byte{162}); err != nil {
+ return err
+ }
+
+ // t.Index (bsky.RichtextFacet_ByteSlice) (struct)
+ if len("index") > 1000000 {
+ return xerrors.Errorf("Value in field \"index\" was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("index"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("index")); err != nil {
+ return err
+ }
+
+ if err := t.Index.MarshalCBOR(cw); err != nil {
+ return err
+ }
+
+ // t.Features ([]*streamplace.RichtextVideoFacet_Features_Elem) (slice)
+ if len("features") > 1000000 {
+ return xerrors.Errorf("Value in field \"features\" was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("features"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("features")); err != nil {
+ return err
+ }
+
+ if len(t.Features) > 8192 {
+ return xerrors.Errorf("Slice value in field t.Features was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Features))); err != nil {
+ return err
+ }
+ for _, v := range t.Features {
+ if err := v.MarshalCBOR(cw); err != nil {
+ return err
+ }
+
+ }
+ return nil
+}
+
+func (t *RichtextVideoFacet) UnmarshalCBOR(r io.Reader) (err error) {
+ *t = RichtextVideoFacet{}
+
+ cr := cbg.NewCborReader(r)
+
+ maj, extra, err := cr.ReadHeader()
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ }()
+
+ if maj != cbg.MajMap {
+ return fmt.Errorf("cbor input should be of type map")
+ }
+
+ if extra > cbg.MaxLength {
+ return fmt.Errorf("RichtextVideoFacet: map struct too large (%d)", extra)
+ }
+
+ n := extra
+
+ nameBuf := make([]byte, 8)
+ for i := uint64(0); i < n; i++ {
+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
+ if err != nil {
+ return err
+ }
+
+ if !ok {
+ // Field doesn't exist on this type, so ignore it
+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
+ return err
+ }
+ continue
+ }
+
+ switch string(nameBuf[:nameLen]) {
+ // t.Index (bsky.RichtextFacet_ByteSlice) (struct)
+ case "index":
+
+ {
+
+ b, err := cr.ReadByte()
+ if err != nil {
+ return err
+ }
+ if b != cbg.CborNull[0] {
+ if err := cr.UnreadByte(); err != nil {
+ return err
+ }
+ t.Index = new(bsky.RichtextFacet_ByteSlice)
+ if err := t.Index.UnmarshalCBOR(cr); err != nil {
+ return xerrors.Errorf("unmarshaling t.Index pointer: %w", err)
+ }
+ }
+
+ }
+ // t.Features ([]*streamplace.RichtextVideoFacet_Features_Elem) (slice)
+ case "features":
+
+ maj, extra, err = cr.ReadHeader()
+ if err != nil {
+ return err
+ }
+
+ if extra > 8192 {
+ return fmt.Errorf("t.Features: array too large (%d)", extra)
+ }
+
+ if maj != cbg.MajArray {
+ return fmt.Errorf("expected cbor array")
+ }
+
+ if extra > 0 {
+ t.Features = make([]*RichtextVideoFacet_Features_Elem, extra)
+ }
+
+ for i := 0; i < int(extra); i++ {
+ {
+ var maj byte
+ var extra uint64
+ var err error
+ _ = maj
+ _ = extra
+ _ = err
+
+ {
+
+ b, err := cr.ReadByte()
+ if err != nil {
+ return err
+ }
+ if b != cbg.CborNull[0] {
+ if err := cr.UnreadByte(); err != nil {
+ return err
+ }
+ t.Features[i] = new(RichtextVideoFacet_Features_Elem)
+ if err := t.Features[i].UnmarshalCBOR(cr); err != nil {
+ return xerrors.Errorf("unmarshaling t.Features[i] pointer: %w", err)
+ }
+ }
+
+ }
+
+ }
+ }
+
+ default:
+ // Field doesn't exist on this type, so ignore it
+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
func (t *ChatProfile) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
@@ -8115,7 +8292,7 @@ func (t *Video) MarshalCBOR(w io.Writer) error {
}
cw := cbg.NewCborWriter(w)
- fieldCount := 11
+ fieldCount := 12
if t.Activity == nil {
fieldCount--
@@ -8125,23 +8302,19 @@ func (t *Video) MarshalCBOR(w io.Writer) error {
fieldCount--
}
- if t.ContentPolicy == nil {
- fieldCount--
- }
-
- if t.Description == nil {
+ if t.ContentRights == nil {
fieldCount--
}
- if t.DescriptionFacets == nil {
+ if t.ContentWarnings == nil {
fieldCount--
}
- if t.Duration == nil {
+ if t.Description == nil {
fieldCount--
}
- if t.Source == nil {
+ if t.DescriptionFacets == nil {
fieldCount--
}
@@ -8255,22 +8428,19 @@ func (t *Video) MarshalCBOR(w io.Writer) error {
}
// t.Source (streamplace.Video_Source) (struct)
- if t.Source != nil {
-
- if len("source") > 1000000 {
- return xerrors.Errorf("Value in field \"source\" was too long")
- }
+ if len("source") > 1000000 {
+ return xerrors.Errorf("Value in field \"source\" was too long")
+ }
- if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("source"))); err != nil {
- return err
- }
- if _, err := cw.WriteString(string("source")); err != nil {
- return err
- }
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("source"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("source")); err != nil {
+ return err
+ }
- if err := t.Source.MarshalCBOR(cw); err != nil {
- return err
- }
+ if err := t.Source.MarshalCBOR(cw); err != nil {
+ return err
}
// t.Activity (streamplace.Video_Activity) (struct)
@@ -8293,35 +8463,25 @@ func (t *Video) MarshalCBOR(w io.Writer) error {
}
// t.Duration (int64) (int64)
- if t.Duration != nil {
+ if len("duration") > 1000000 {
+ return xerrors.Errorf("Value in field \"duration\" was too long")
+ }
- if len("duration") > 1000000 {
- return xerrors.Errorf("Value in field \"duration\" was too long")
- }
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("duration"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("duration")); err != nil {
+ return err
+ }
- if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("duration"))); err != nil {
+ if t.Duration >= 0 {
+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Duration)); err != nil {
return err
}
- if _, err := cw.WriteString(string("duration")); err != nil {
+ } else {
+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Duration-1)); err != nil {
return err
}
-
- if t.Duration == nil {
- if _, err := cw.Write(cbg.CborNull); err != nil {
- return err
- }
- } else {
- if *t.Duration >= 0 {
- if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.Duration)); err != nil {
- return err
- }
- } else {
- if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.Duration-1)); err != nil {
- return err
- }
- }
- }
-
}
// t.Connections ([]*streamplace.Video_Connections_Elem) (slice)
@@ -8385,26 +8545,45 @@ func (t *Video) MarshalCBOR(w io.Writer) error {
}
}
- // t.ContentPolicy (streamplace.MetadataConfiguration) (struct)
- if t.ContentPolicy != nil {
+ // t.ContentRights (streamplace.MetadataContentRights) (struct)
+ if t.ContentRights != nil {
- if len("contentPolicy") > 1000000 {
- return xerrors.Errorf("Value in field \"contentPolicy\" was too long")
+ if len("contentRights") > 1000000 {
+ return xerrors.Errorf("Value in field \"contentRights\" was too long")
}
- if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("contentPolicy"))); err != nil {
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("contentRights"))); err != nil {
return err
}
- if _, err := cw.WriteString(string("contentPolicy")); err != nil {
+ if _, err := cw.WriteString(string("contentRights")); err != nil {
return err
}
- if err := t.ContentPolicy.MarshalCBOR(cw); err != nil {
+ if err := t.ContentRights.MarshalCBOR(cw); err != nil {
return err
}
}
- // t.DescriptionFacets ([]*streamplace.RichtextFacet) (slice)
+ // t.ContentWarnings (streamplace.MetadataContentWarnings) (struct)
+ if t.ContentWarnings != nil {
+
+ if len("contentWarnings") > 1000000 {
+ return xerrors.Errorf("Value in field \"contentWarnings\" was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("contentWarnings"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("contentWarnings")); err != nil {
+ return err
+ }
+
+ if err := t.ContentWarnings.MarshalCBOR(cw); err != nil {
+ return err
+ }
+ }
+
+ // t.DescriptionFacets ([]*streamplace.RichtextVideoFacet) (slice)
if t.DescriptionFacets != nil {
if len("descriptionFacets") > 1000000 {
@@ -8601,38 +8780,28 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) {
// t.Duration (int64) (int64)
case "duration":
{
-
- b, err := cr.ReadByte()
+ maj, extra, err := cr.ReadHeader()
if err != nil {
return err
}
- if b != cbg.CborNull[0] {
- if err := cr.UnreadByte(); err != nil {
- return err
- }
- maj, extra, err := cr.ReadHeader()
- if err != nil {
- return err
+ var extraI int64
+ switch maj {
+ case cbg.MajUnsignedInt:
+ extraI = int64(extra)
+ if extraI < 0 {
+ return fmt.Errorf("int64 positive overflow")
}
- var extraI int64
- switch maj {
- case cbg.MajUnsignedInt:
- extraI = int64(extra)
- if extraI < 0 {
- return fmt.Errorf("int64 positive overflow")
- }
- case cbg.MajNegativeInt:
- extraI = int64(extra)
- if extraI < 0 {
- return fmt.Errorf("int64 negative overflow")
- }
- extraI = -1 - extraI
- default:
- return fmt.Errorf("wrong type for int64 field: %d", maj)
+ case cbg.MajNegativeInt:
+ extraI = int64(extra)
+ if extraI < 0 {
+ return fmt.Errorf("int64 negative overflow")
}
-
- t.Duration = (*int64)(&extraI)
+ extraI = -1 - extraI
+ default:
+ return fmt.Errorf("wrong type for int64 field: %d", maj)
}
+
+ t.Duration = int64(extraI)
}
// t.Connections ([]*streamplace.Video_Connections_Elem) (slice)
case "connections":
@@ -8704,8 +8873,8 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) {
t.Description = (*string)(&sval)
}
}
- // t.ContentPolicy (streamplace.MetadataConfiguration) (struct)
- case "contentPolicy":
+ // t.ContentRights (streamplace.MetadataContentRights) (struct)
+ case "contentRights":
{
@@ -8717,14 +8886,34 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) {
if err := cr.UnreadByte(); err != nil {
return err
}
- t.ContentPolicy = new(MetadataConfiguration)
- if err := t.ContentPolicy.UnmarshalCBOR(cr); err != nil {
- return xerrors.Errorf("unmarshaling t.ContentPolicy pointer: %w", err)
+ t.ContentRights = new(MetadataContentRights)
+ if err := t.ContentRights.UnmarshalCBOR(cr); err != nil {
+ return xerrors.Errorf("unmarshaling t.ContentRights pointer: %w", err)
}
}
}
- // t.DescriptionFacets ([]*streamplace.RichtextFacet) (slice)
+ // t.ContentWarnings (streamplace.MetadataContentWarnings) (struct)
+ case "contentWarnings":
+
+ {
+
+ b, err := cr.ReadByte()
+ if err != nil {
+ return err
+ }
+ if b != cbg.CborNull[0] {
+ if err := cr.UnreadByte(); err != nil {
+ return err
+ }
+ t.ContentWarnings = new(MetadataContentWarnings)
+ if err := t.ContentWarnings.UnmarshalCBOR(cr); err != nil {
+ return xerrors.Errorf("unmarshaling t.ContentWarnings pointer: %w", err)
+ }
+ }
+
+ }
+ // t.DescriptionFacets ([]*streamplace.RichtextVideoFacet) (slice)
case "descriptionFacets":
maj, extra, err = cr.ReadHeader()
@@ -8741,7 +8930,7 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) {
}
if extra > 0 {
- t.DescriptionFacets = make([]*RichtextFacet, extra)
+ t.DescriptionFacets = make([]*RichtextVideoFacet, extra)
}
for i := 0; i < int(extra); i++ {
@@ -8763,7 +8952,7 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) {
if err := cr.UnreadByte(); err != nil {
return err
}
- t.DescriptionFacets[i] = new(RichtextFacet)
+ t.DescriptionFacets[i] = new(RichtextVideoFacet)
if err := t.DescriptionFacets[i].UnmarshalCBOR(cr); err != nil {
return xerrors.Errorf("unmarshaling t.DescriptionFacets[i] pointer: %w", err)
}
@@ -8876,7 +9065,7 @@ func (t *MediaTrack) MarshalCBOR(w io.Writer) error {
}
}
- // t.Metadata (streamplace.MediaTrack_CommonMetadata) (struct)
+ // t.Metadata (streamplace.MediaTrack_Metadata) (struct)
if t.Metadata != nil {
if len("metadata") > 1000000 {
@@ -9009,7 +9198,7 @@ func (t *MediaTrack) UnmarshalCBOR(r io.Reader) (err error) {
t.Video = (*string)(&sval)
}
}
- // t.Metadata (streamplace.MediaTrack_CommonMetadata) (struct)
+ // t.Metadata (streamplace.MediaTrack_Metadata) (struct)
case "metadata":
{
@@ -9022,7 +9211,7 @@ func (t *MediaTrack) UnmarshalCBOR(r io.Reader) (err error) {
if err := cr.UnreadByte(); err != nil {
return err
}
- t.Metadata = new(MediaTrack_CommonMetadata)
+ t.Metadata = new(MediaTrack_Metadata)
if err := t.Metadata.UnmarshalCBOR(cr); err != nil {
return xerrors.Errorf("unmarshaling t.Metadata pointer: %w", err)
}
@@ -9279,7 +9468,7 @@ func (t *MediaDefs_MuxlTrack) MarshalCBOR(w io.Writer) error {
}
cw := cbg.NewCborWriter(w)
- fieldCount := 6
+ fieldCount := 7
if t.Language == nil {
fieldCount--
@@ -9289,6 +9478,10 @@ func (t *MediaDefs_MuxlTrack) MarshalCBOR(w io.Writer) error {
fieldCount--
}
+ if t.Size == nil {
+ fieldCount--
+ }
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
return err
}
@@ -9316,6 +9509,38 @@ func (t *MediaDefs_MuxlTrack) MarshalCBOR(w io.Writer) error {
return err
}
+ // t.Size (int64) (int64)
+ if t.Size != nil {
+
+ if len("size") > 1000000 {
+ return xerrors.Errorf("Value in field \"size\" was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("size"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("size")); err != nil {
+ return err
+ }
+
+ if t.Size == nil {
+ if _, err := cw.Write(cbg.CborNull); err != nil {
+ return err
+ }
+ } else {
+ if *t.Size >= 0 {
+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.Size)); err != nil {
+ return err
+ }
+ } else {
+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.Size-1)); err != nil {
+ return err
+ }
+ }
+ }
+
+ }
+
// t.LexiconTypeID (string) (string)
if len("$type") > 1000000 {
return xerrors.Errorf("Value in field \"$type\" was too long")
@@ -9499,6 +9724,42 @@ func (t *MediaDefs_MuxlTrack) UnmarshalCBOR(r io.Reader) (err error) {
t.Blob = string(sval)
}
+ // t.Size (int64) (int64)
+ case "size":
+ {
+
+ b, err := cr.ReadByte()
+ if err != nil {
+ return err
+ }
+ if b != cbg.CborNull[0] {
+ if err := cr.UnreadByte(); err != nil {
+ return err
+ }
+ maj, extra, err := cr.ReadHeader()
+ if err != nil {
+ return err
+ }
+ var extraI int64
+ switch maj {
+ case cbg.MajUnsignedInt:
+ extraI = int64(extra)
+ if extraI < 0 {
+ return fmt.Errorf("int64 positive overflow")
+ }
+ case cbg.MajNegativeInt:
+ extraI = int64(extra)
+ if extraI < 0 {
+ return fmt.Errorf("int64 negative overflow")
+ }
+ extraI = -1 - extraI
+ default:
+ return fmt.Errorf("wrong type for int64 field: %d", maj)
+ }
+
+ t.Size = (*int64)(&extraI)
+ }
+ }
// t.LexiconTypeID (string) (string)
case "$type":
@@ -9989,12 +10250,16 @@ func (t *MediaTrack_CommonMetadata) MarshalCBOR(w io.Writer) error {
}
cw := cbg.NewCborWriter(w)
- fieldCount := 3
+ fieldCount := 5
if t.Audio == nil {
fieldCount--
}
+ if t.Duration == nil {
+ fieldCount--
+ }
+
if t.Language == nil {
fieldCount--
}
@@ -10007,6 +10272,25 @@ func (t *MediaTrack_CommonMetadata) MarshalCBOR(w io.Writer) error {
return err
}
+ // t.LexiconTypeID (string) (string)
+ if len("$type") > 1000000 {
+ return xerrors.Errorf("Value in field \"$type\" was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("$type")); err != nil {
+ return err
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("place.stream.media.track#commonMetadata"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("place.stream.media.track#commonMetadata")); err != nil {
+ return err
+ }
+
// t.Audio (streamplace.Segment_Audio) (struct)
if t.Audio != nil {
@@ -10045,6 +10329,38 @@ func (t *MediaTrack_CommonMetadata) MarshalCBOR(w io.Writer) error {
}
}
+ // t.Duration (int64) (int64)
+ if t.Duration != nil {
+
+ if len("duration") > 1000000 {
+ return xerrors.Errorf("Value in field \"duration\" was too long")
+ }
+
+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("duration"))); err != nil {
+ return err
+ }
+ if _, err := cw.WriteString(string("duration")); err != nil {
+ return err
+ }
+
+ if t.Duration == nil {
+ if _, err := cw.Write(cbg.CborNull); err != nil {
+ return err
+ }
+ } else {
+ if *t.Duration >= 0 {
+ if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.Duration)); err != nil {
+ return err
+ }
+ } else {
+ if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.Duration-1)); err != nil {
+ return err
+ }
+ }
+ }
+
+ }
+
// t.Language (string) (string)
if t.Language != nil {
@@ -10120,7 +10436,18 @@ func (t *MediaTrack_CommonMetadata) UnmarshalCBOR(r io.Reader) (err error) {
}
switch string(nameBuf[:nameLen]) {
- // t.Audio (streamplace.Segment_Audio) (struct)
+ // t.LexiconTypeID (string) (string)
+ case "$type":
+
+ {
+ sval, err := cbg.ReadStringWithMax(cr, 1000000)
+ if err != nil {
+ return err
+ }
+
+ t.LexiconTypeID = string(sval)
+ }
+ // t.Audio (streamplace.Segment_Audio) (struct)
case "audio":
{
@@ -10160,6 +10487,42 @@ func (t *MediaTrack_CommonMetadata) UnmarshalCBOR(r io.Reader) (err error) {
}
}
+ // t.Duration (int64) (int64)
+ case "duration":
+ {
+
+ b, err := cr.ReadByte()
+ if err != nil {
+ return err
+ }
+ if b != cbg.CborNull[0] {
+ if err := cr.UnreadByte(); err != nil {
+ return err
+ }
+ maj, extra, err := cr.ReadHeader()
+ if err != nil {
+ return err
+ }
+ var extraI int64
+ switch maj {
+ case cbg.MajUnsignedInt:
+ extraI = int64(extra)
+ if extraI < 0 {
+ return fmt.Errorf("int64 positive overflow")
+ }
+ case cbg.MajNegativeInt:
+ extraI = int64(extra)
+ if extraI < 0 {
+ return fmt.Errorf("int64 negative overflow")
+ }
+ extraI = -1 - extraI
+ default:
+ return fmt.Errorf("wrong type for int64 field: %d", maj)
+ }
+
+ t.Duration = (*int64)(&extraI)
+ }
+ }
// t.Language (string) (string)
case "language":
diff --git a/pkg/streamplace/mediadefs.go b/pkg/streamplace/mediadefs.go
index 18688416..7461866f 100644
--- a/pkg/streamplace/mediadefs.go
+++ b/pkg/streamplace/mediadefs.go
@@ -21,6 +21,8 @@ type MediaDefs_MuxlTrack struct {
MediaType string `json:"mediaType" cborgen:"mediaType"`
// signingKey: did:key of the ephemeral key that C2PA-signed this track's segments. The private key is discarded once the track is produced, so it can only ever have signed this content.
SigningKey *string `json:"signingKey,omitempty" cborgen:"signingKey,omitempty"`
+ // size: Size in bytes of the source video segment.
+ Size *int64 `json:"size,omitempty" cborgen:"size,omitempty"`
// trackId: ID of the track within the MUXL container. 1-indexed for MP4 reasons.
TrackId string `json:"trackId" cborgen:"trackId"`
}
diff --git a/pkg/streamplace/mediatrack.go b/pkg/streamplace/mediatrack.go
index 8198653c..e98d9553 100644
--- a/pkg/streamplace/mediatrack.go
+++ b/pkg/streamplace/mediatrack.go
@@ -20,8 +20,8 @@ func init() {
}
type MediaTrack struct {
- LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.media.track"`
- Metadata *MediaTrack_CommonMetadata `json:"metadata,omitempty" cborgen:"metadata,omitempty"`
+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.media.track"`
+ Metadata *MediaTrack_Metadata `json:"metadata,omitempty" cborgen:"metadata,omitempty"`
// parentTrack: If this is a derived track like a transcode or a transcript, what was the parent track?
ParentTrack *comatproto.RepoStrongRef `json:"parentTrack,omitempty" cborgen:"parentTrack,omitempty"`
Track *MediaTrack_Track `json:"track" cborgen:"track"`
@@ -33,12 +33,69 @@ type MediaTrack struct {
//
// Metadata common to all media types. Contains subobjects for other media types.
type MediaTrack_CommonMetadata struct {
- Audio *Segment_Audio `json:"audio,omitempty" cborgen:"audio,omitempty"`
+ LexiconTypeID string `json:"$type" cborgen:"$type,const=place.stream.media.track#commonMetadata"`
+ Audio *Segment_Audio `json:"audio,omitempty" cborgen:"audio,omitempty"`
+ // duration: duration of this track in milliseconds
+ Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
// language: IETF BCP 47 language tag corresponding to the content of this track
Language *string `json:"language,omitempty" cborgen:"language,omitempty"`
Video *Segment_Video `json:"video,omitempty" cborgen:"video,omitempty"`
}
+type MediaTrack_Metadata struct {
+ MediaTrack_CommonMetadata *MediaTrack_CommonMetadata
+}
+
+func (t *MediaTrack_Metadata) MarshalJSON() ([]byte, error) {
+ if t.MediaTrack_CommonMetadata != nil {
+ t.MediaTrack_CommonMetadata.LexiconTypeID = "place.stream.media.track#commonMetadata"
+ return json.Marshal(t.MediaTrack_CommonMetadata)
+ }
+ return nil, fmt.Errorf("can not marshal empty union as JSON")
+}
+
+func (t *MediaTrack_Metadata) UnmarshalJSON(b []byte) error {
+ typ, err := lexutil.TypeExtract(b)
+ if err != nil {
+ return err
+ }
+
+ switch typ {
+ case "place.stream.media.track#commonMetadata":
+ t.MediaTrack_CommonMetadata = new(MediaTrack_CommonMetadata)
+ return json.Unmarshal(b, t.MediaTrack_CommonMetadata)
+ default:
+ return nil
+ }
+}
+
+func (t *MediaTrack_Metadata) MarshalCBOR(w io.Writer) error {
+
+ if t == nil {
+ _, err := w.Write(cbg.CborNull)
+ return err
+ }
+ if t.MediaTrack_CommonMetadata != nil {
+ return t.MediaTrack_CommonMetadata.MarshalCBOR(w)
+ }
+ return fmt.Errorf("can not marshal empty union as CBOR")
+}
+
+func (t *MediaTrack_Metadata) UnmarshalCBOR(r io.Reader) error {
+ typ, b, err := lexutil.CborTypeExtractReader(r)
+ if err != nil {
+ return err
+ }
+
+ switch typ {
+ case "place.stream.media.track#commonMetadata":
+ t.MediaTrack_CommonMetadata = new(MediaTrack_CommonMetadata)
+ return t.MediaTrack_CommonMetadata.UnmarshalCBOR(bytes.NewReader(b))
+ default:
+ return nil
+ }
+}
+
type MediaTrack_Track struct {
MediaDefs_MuxlTrack *MediaDefs_MuxlTrack
}
diff --git a/pkg/streamplace/richtextfacet.go b/pkg/streamplace/richtextfacet.go
index 6061bea4..7953e87a 100644
--- a/pkg/streamplace/richtextfacet.go
+++ b/pkg/streamplace/richtextfacet.go
@@ -17,7 +17,7 @@ import (
// RichtextFacet is a "main" in the place.stream.richtext.facet schema.
//
-// Annotation of a sub-string within rich text.
+// Annotation of a sub-string within rich text in a livestream chat message.
type RichtextFacet struct {
Features []*RichtextFacet_Features_Elem `json:"features" cborgen:"features"`
Index *appbsky.RichtextFacet_ByteSlice `json:"index" cborgen:"index"`
diff --git a/pkg/streamplace/richtextvideoFacet.go b/pkg/streamplace/richtextvideoFacet.go
new file mode 100644
index 00000000..5bd89ee3
--- /dev/null
+++ b/pkg/streamplace/richtextvideoFacet.go
@@ -0,0 +1,92 @@
+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
+
+// Lexicon schema: place.stream.richtext.videoFacet
+
+package streamplace
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+
+ appbsky "github.com/bluesky-social/indigo/api/bsky"
+ lexutil "github.com/bluesky-social/indigo/lex/util"
+ cbg "github.com/whyrusleeping/cbor-gen"
+)
+
+// RichtextVideoFacet is a "main" in the place.stream.richtext.videoFacet schema.
+//
+// Annotation of a sub-string within rich text in a VOD description or comment.
+type RichtextVideoFacet struct {
+ Features []*RichtextVideoFacet_Features_Elem `json:"features" cborgen:"features"`
+ Index *appbsky.RichtextFacet_ByteSlice `json:"index" cborgen:"index"`
+}
+
+type RichtextVideoFacet_Features_Elem struct {
+ RichtextFacet_Mention *appbsky.RichtextFacet_Mention
+ RichtextFacet_Link *appbsky.RichtextFacet_Link
+}
+
+func (t *RichtextVideoFacet_Features_Elem) MarshalJSON() ([]byte, error) {
+ if t.RichtextFacet_Mention != nil {
+ t.RichtextFacet_Mention.LexiconTypeID = "app.bsky.richtext.facet#mention"
+ return json.Marshal(t.RichtextFacet_Mention)
+ }
+ if t.RichtextFacet_Link != nil {
+ t.RichtextFacet_Link.LexiconTypeID = "app.bsky.richtext.facet#link"
+ return json.Marshal(t.RichtextFacet_Link)
+ }
+ return nil, fmt.Errorf("can not marshal empty union as JSON")
+}
+
+func (t *RichtextVideoFacet_Features_Elem) UnmarshalJSON(b []byte) error {
+ typ, err := lexutil.TypeExtract(b)
+ if err != nil {
+ return err
+ }
+
+ switch typ {
+ case "app.bsky.richtext.facet#mention":
+ t.RichtextFacet_Mention = new(appbsky.RichtextFacet_Mention)
+ return json.Unmarshal(b, t.RichtextFacet_Mention)
+ case "app.bsky.richtext.facet#link":
+ t.RichtextFacet_Link = new(appbsky.RichtextFacet_Link)
+ return json.Unmarshal(b, t.RichtextFacet_Link)
+ default:
+ return nil
+ }
+}
+
+func (t *RichtextVideoFacet_Features_Elem) MarshalCBOR(w io.Writer) error {
+
+ if t == nil {
+ _, err := w.Write(cbg.CborNull)
+ return err
+ }
+ if t.RichtextFacet_Mention != nil {
+ return t.RichtextFacet_Mention.MarshalCBOR(w)
+ }
+ if t.RichtextFacet_Link != nil {
+ return t.RichtextFacet_Link.MarshalCBOR(w)
+ }
+ return fmt.Errorf("can not marshal empty union as CBOR")
+}
+
+func (t *RichtextVideoFacet_Features_Elem) UnmarshalCBOR(r io.Reader) error {
+ typ, b, err := lexutil.CborTypeExtractReader(r)
+ if err != nil {
+ return err
+ }
+
+ switch typ {
+ case "app.bsky.richtext.facet#mention":
+ t.RichtextFacet_Mention = new(appbsky.RichtextFacet_Mention)
+ return t.RichtextFacet_Mention.UnmarshalCBOR(bytes.NewReader(b))
+ case "app.bsky.richtext.facet#link":
+ t.RichtextFacet_Link = new(appbsky.RichtextFacet_Link)
+ return t.RichtextFacet_Link.UnmarshalCBOR(bytes.NewReader(b))
+ default:
+ return nil
+ }
+}
diff --git a/pkg/streamplace/streamvideo.go b/pkg/streamplace/streamvideo.go
index 778828e6..28d0e28e 100644
--- a/pkg/streamplace/streamvideo.go
+++ b/pkg/streamplace/streamvideo.go
@@ -25,16 +25,18 @@ type Video struct {
Activity *Video_Activity `json:"activity,omitempty" cborgen:"activity,omitempty"`
// connections: Free-form list of atproto records related in some way to this video
Connections []*Video_Connections_Elem `json:"connections,omitempty" cborgen:"connections,omitempty"`
- // contentPolicy: copyright, distribution, and content warning data
- ContentPolicy *MetadataConfiguration `json:"contentPolicy,omitempty" cborgen:"contentPolicy,omitempty"`
+ // contentRights: copyright and licensing information for this VOD
+ ContentRights *MetadataContentRights `json:"contentRights,omitempty" cborgen:"contentRights,omitempty"`
+ // contentWarnings: content warning data for this VOD
+ ContentWarnings *MetadataContentWarnings `json:"contentWarnings,omitempty" cborgen:"contentWarnings,omitempty"`
// description: Description of this video
Description *string `json:"description,omitempty" cborgen:"description,omitempty"`
// descriptionFacets: Annotations of text (mentions, URLs, etc)
- DescriptionFacets []*RichtextFacet `json:"descriptionFacets,omitempty" cborgen:"descriptionFacets,omitempty"`
+ DescriptionFacets []*RichtextVideoFacet `json:"descriptionFacets,omitempty" cborgen:"descriptionFacets,omitempty"`
// duration: Duration of the video in milliseconds
- Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
+ Duration int64 `json:"duration" cborgen:"duration"`
// source: What is the source of this video?
- Source *Video_Source `json:"source,omitempty" cborgen:"source,omitempty"`
+ Source *Video_Source `json:"source" cborgen:"source"`
// tags: Freeform tags for this stream. Each tag must be alphanumeric (a-z, A-Z, 0-9) plus colon. Tags with colons indicate a specific tag group (e.g. 'lang:en' indicates the stream's primary language).
Tags []string `json:"tags,omitempty" cborgen:"tags,omitempty"`
// thumb: Thumbnail image for the video.
@@ -240,9 +242,3 @@ func (t *Video_Source) UnmarshalCBOR(r io.Reader) error {
return nil
}
}
-
-// Video_VideoSourceContent is a "videoSourceContent" in the place.stream.video schema.
-type Video_VideoSourceContent struct {
- // ref: place.stream.media.source record providing the content for this video record
- Ref *comatproto.RepoStrongRef `json:"ref,omitempty" cborgen:"ref,omitempty"`
-}
diff --git a/pkg/vod/publish.go b/pkg/vod/publish.go
index d9ecd258..1373a826 100644
--- a/pkg/vod/publish.go
+++ b/pkg/vod/publish.go
@@ -84,7 +84,7 @@ func publishRecords(ctx context.Context, p publishParams) error {
var sourceTracks []*comatproto.RepoStrongRef
if p.probe.Video != nil {
- ref, err := publishTrack(ctx, client, p.in.RepoDID, p.cid, "1", "video", p.signingKey, p.probe.Video, nil)
+ ref, err := publishTrack(ctx, client, p.in.RepoDID, p.cid, p.size, p.probe.DurationMS, "1", "video", p.signingKey, p.probe.Video, nil)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "video_track")
@@ -93,7 +93,7 @@ func publishRecords(ctx context.Context, p publishParams) error {
sourceTracks = append(sourceTracks, ref)
}
if p.probe.Audio != nil {
- ref, err := publishTrack(ctx, client, p.in.RepoDID, p.cid, "2", "audio", p.signingKey, nil, p.probe.Audio)
+ ref, err := publishTrack(ctx, client, p.in.RepoDID, p.cid, p.size, p.probe.DurationMS, "2", "audio", p.signingKey, nil, p.probe.Audio)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "audio_track")
@@ -149,11 +149,14 @@ func publishOrigin(ctx context.Context, cli *config.CLI, cid string, size int64,
// publishTrack creates one place.stream.media.track record in the
// user's repo. trackID is the 1-indexed track within the MUXL
// container ("1" for video, "2" for audio in our standard layout).
+// blobSize is the byte size of the shared MUXL container blob;
+// durationMS is the source duration in milliseconds (same for every
+// track of one upload since they all read from the same container).
// signingKey is the did:key whose ephemeral private half C2PA-signed
// this upload's segments — the same key signs every track of an upload.
// Exactly one of videoMeta / audioMeta should be non-nil; the other
// is ignored.
-func publishTrack(ctx context.Context, client XRPCClient, did, cid, trackID, mediaType, signingKey string, videoMeta *media.VODVideoTrack, audioMeta *media.VODAudioTrack) (*comatproto.RepoStrongRef, error) {
+func publishTrack(ctx context.Context, client XRPCClient, did, cid string, blobSize, durationMS int64, trackID, mediaType, signingKey string, videoMeta *media.VODVideoTrack, audioMeta *media.VODAudioTrack) (*comatproto.RepoStrongRef, error) {
ctx, span := vodTracer.Start(ctx, "vod.publishTrack", trace.WithAttributes(
attribute.String("cid", cid),
attribute.String("track_id", trackID),
@@ -161,7 +164,10 @@ func publishTrack(ctx context.Context, client XRPCClient, did, cid, trackID, med
))
defer span.End()
- meta := &streamplace.MediaTrack_CommonMetadata{}
+ meta := &streamplace.MediaTrack_CommonMetadata{
+ LexiconTypeID: "place.stream.media.track#commonMetadata",
+ Duration: &durationMS,
+ }
if videoMeta != nil {
meta.Video = &streamplace.Segment_Video{
Codec: "h264",
@@ -189,12 +195,15 @@ func publishTrack(ctx context.Context, client XRPCClient, did, cid, trackID, med
MediaDefs_MuxlTrack: &streamplace.MediaDefs_MuxlTrack{
LexiconTypeID: "place.stream.media.defs#muxlTrack",
Blob: cid,
+ Size: &blobSize,
TrackId: trackID,
MediaType: mediaType,
SigningKey: &signingKey,
},
},
- Metadata: meta,
+ Metadata: &streamplace.MediaTrack_Metadata{
+ MediaTrack_CommonMetadata: meta,
+ },
}
rkey := spid.TIDClock.Next().String()
@@ -247,6 +256,7 @@ func publishVideo(ctx context.Context, client XRPCClient, in Input, probe media.
rec := &streamplace.Video{
LexiconTypeID: constants.PLACE_STREAM_VIDEO,
Title: title,
+ Duration: duration,
Source: &streamplace.Video_Source{
MediaDefs_SourceTracks: &streamplace.MediaDefs_SourceTracks{
LexiconTypeID: "place.stream.media.defs#sourceTracks",
@@ -254,9 +264,6 @@ func publishVideo(ctx context.Context, client XRPCClient, in Input, probe media.
},
},
}
- if duration > 0 {
- rec.Duration = &duration
- }
if len(thumbnail) > 0 {
var uploadOut comatproto.RepoUploadBlob_Output