From 40e4cf12364147e8f81a5a796e7eebe3e37da915 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 17 May 2026 19:13:29 -0700 Subject: [PATCH 1/2] lexicons: rename duration to durationMs on video + track metadata Spell the unit in the field name now, ahead of beta. place.stream.segment keeps `duration` (nanoseconds) since renaming it would be churn on the live-stream path that's not in scope here. Co-Authored-By: Claude Opus 4.7 --- .../media/place-stream-media-track.md | 14 +- .../docs/lex-reference/place-stream-video.md | 6 +- lexicons/place/stream/media/track.json | 2 +- lexicons/place/stream/video.json | 4 +- pkg/streamplace/cbor_gen.go | 138 +++++++++--------- pkg/streamplace/mediatrack.go | 4 +- pkg/streamplace/streamvideo.go | 4 +- pkg/vod/publish.go | 4 +- 8 files changed, 88 insertions(+), 88 deletions(-) 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 db48e2672..74f8bafee 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 @@ -38,12 +38,12 @@ Metadata common to all media types. Contains subobjects for other media types. **Properties:** -| 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) | ❌ | | | +| Name | Type | Req'd | Description | Constraints | +| ------------ | ------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------- | ----------- | +| `language` | `string` | ❌ | IETF BCP 47 language tag corresponding to the content of this track | | +| `durationMs` | `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) | ❌ | | | --- @@ -92,7 +92,7 @@ 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": { + "durationMs": { "type": "integer", "description": "duration of this track in milliseconds" }, 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 03683afb4..6604c169e 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 @@ -24,7 +24,7 @@ Some audiovisual content. | `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? | | | `description` | `string` | ❌ | Description of this video | Max Length: 50000
Max Graphemes: 5000 | -| `duration` | `integer` | ✅ | Duration of the video in milliseconds | | +| `durationMs` | `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 | | @@ -61,7 +61,7 @@ Some audiovisual content. "description": "Some audiovisual content.", "key": "tid", "record": { - "required": ["title", "source", "duration"], + "required": ["title", "source", "durationMs"], "type": "object", "properties": { "title": { @@ -84,7 +84,7 @@ Some audiovisual content. "maxGraphemes": 5000, "description": "Description of this video" }, - "duration": { + "durationMs": { "type": "integer", "description": "Duration of the video in milliseconds" }, diff --git a/lexicons/place/stream/media/track.json b/lexicons/place/stream/media/track.json index e40f459d9..e2ea1fe3a 100644 --- a/lexicons/place/stream/media/track.json +++ b/lexicons/place/stream/media/track.json @@ -40,7 +40,7 @@ "type": "string", "description": "IETF BCP 47 language tag corresponding to the content of this track" }, - "duration": { + "durationMs": { "type": "integer", "description": "duration of this track in milliseconds" }, diff --git a/lexicons/place/stream/video.json b/lexicons/place/stream/video.json index 9d72c8099..d72d66652 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", "source", "duration"], + "required": ["title", "source", "durationMs"], "type": "object", "properties": { "title": { @@ -30,7 +30,7 @@ "maxGraphemes": 5000, "description": "Description of this video" }, - "duration": { + "durationMs": { "type": "integer", "description": "Duration of the video in milliseconds" }, diff --git a/pkg/streamplace/cbor_gen.go b/pkg/streamplace/cbor_gen.go index f7208a209..2d4310d6c 100644 --- a/pkg/streamplace/cbor_gen.go +++ b/pkg/streamplace/cbor_gen.go @@ -8462,24 +8462,24 @@ func (t *Video) MarshalCBOR(w io.Writer) error { } } - // t.Duration (int64) (int64) - if len("duration") > 1000000 { - return xerrors.Errorf("Value in field \"duration\" was too long") + // t.DurationMs (int64) (int64) + if len("durationMs") > 1000000 { + return xerrors.Errorf("Value in field \"durationMs\" was too long") } - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("duration"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("durationMs"))); err != nil { return err } - if _, err := cw.WriteString(string("duration")); err != nil { + if _, err := cw.WriteString(string("durationMs")); err != nil { return err } - if t.Duration >= 0 { - if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Duration)); err != nil { + if t.DurationMs >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.DurationMs)); err != nil { return err } } else { - if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Duration-1)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.DurationMs-1)); err != nil { return err } } @@ -8777,8 +8777,8 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) { } } - // t.Duration (int64) (int64) - case "duration": + // t.DurationMs (int64) (int64) + case "durationMs": { maj, extra, err := cr.ReadHeader() if err != nil { @@ -8801,7 +8801,7 @@ func (t *Video) UnmarshalCBOR(r io.Reader) (err error) { return fmt.Errorf("wrong type for int64 field: %d", maj) } - t.Duration = int64(extraI) + t.DurationMs = int64(extraI) } // t.Connections ([]*streamplace.Video_Connections_Elem) (slice) case "connections": @@ -10256,7 +10256,7 @@ func (t *MediaTrack_CommonMetadata) MarshalCBOR(w io.Writer) error { fieldCount-- } - if t.Duration == nil { + if t.DurationMs == nil { fieldCount-- } @@ -10329,38 +10329,6 @@ 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 { @@ -10392,6 +10360,38 @@ func (t *MediaTrack_CommonMetadata) MarshalCBOR(w io.Writer) error { } } } + + // t.DurationMs (int64) (int64) + if t.DurationMs != nil { + + if len("durationMs") > 1000000 { + return xerrors.Errorf("Value in field \"durationMs\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("durationMs"))); err != nil { + return err + } + if _, err := cw.WriteString(string("durationMs")); err != nil { + return err + } + + if t.DurationMs == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if *t.DurationMs >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.DurationMs)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.DurationMs-1)); err != nil { + return err + } + } + } + + } return nil } @@ -10420,7 +10420,7 @@ func (t *MediaTrack_CommonMetadata) UnmarshalCBOR(r io.Reader) (err error) { n := extra - nameBuf := make([]byte, 8) + nameBuf := make([]byte, 10) for i := uint64(0); i < n; i++ { nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) if err != nil { @@ -10487,8 +10487,29 @@ func (t *MediaTrack_CommonMetadata) UnmarshalCBOR(r io.Reader) (err error) { } } - // t.Duration (int64) (int64) - case "duration": + // t.Language (string) (string) + case "language": + + { + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + sval, err := cbg.ReadStringWithMax(cr, 1000000) + if err != nil { + return err + } + + t.Language = (*string)(&sval) + } + } + // t.DurationMs (int64) (int64) + case "durationMs": { b, err := cr.ReadByte() @@ -10520,28 +10541,7 @@ func (t *MediaTrack_CommonMetadata) UnmarshalCBOR(r io.Reader) (err error) { return fmt.Errorf("wrong type for int64 field: %d", maj) } - t.Duration = (*int64)(&extraI) - } - } - // t.Language (string) (string) - case "language": - - { - b, err := cr.ReadByte() - if err != nil { - return err - } - if b != cbg.CborNull[0] { - if err := cr.UnreadByte(); err != nil { - return err - } - - sval, err := cbg.ReadStringWithMax(cr, 1000000) - if err != nil { - return err - } - - t.Language = (*string)(&sval) + t.DurationMs = (*int64)(&extraI) } } diff --git a/pkg/streamplace/mediatrack.go b/pkg/streamplace/mediatrack.go index e98d9553a..35d00403c 100644 --- a/pkg/streamplace/mediatrack.go +++ b/pkg/streamplace/mediatrack.go @@ -35,8 +35,8 @@ type MediaTrack struct { type MediaTrack_CommonMetadata struct { 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"` + // durationMs: duration of this track in milliseconds + DurationMs *int64 `json:"durationMs,omitempty" cborgen:"durationMs,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"` diff --git a/pkg/streamplace/streamvideo.go b/pkg/streamplace/streamvideo.go index 28d0e28eb..0833d9a08 100644 --- a/pkg/streamplace/streamvideo.go +++ b/pkg/streamplace/streamvideo.go @@ -33,8 +33,8 @@ type Video struct { Description *string `json:"description,omitempty" cborgen:"description,omitempty"` // descriptionFacets: Annotations of text (mentions, URLs, etc) DescriptionFacets []*RichtextVideoFacet `json:"descriptionFacets,omitempty" cborgen:"descriptionFacets,omitempty"` - // duration: Duration of the video in milliseconds - Duration int64 `json:"duration" cborgen:"duration"` + // durationMs: Duration of the video in milliseconds + DurationMs int64 `json:"durationMs" cborgen:"durationMs"` // source: What is the source of this video? 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). diff --git a/pkg/vod/publish.go b/pkg/vod/publish.go index 1373a826c..4b82e93c5 100644 --- a/pkg/vod/publish.go +++ b/pkg/vod/publish.go @@ -166,7 +166,7 @@ func publishTrack(ctx context.Context, client XRPCClient, did, cid string, blobS meta := &streamplace.MediaTrack_CommonMetadata{ LexiconTypeID: "place.stream.media.track#commonMetadata", - Duration: &durationMS, + DurationMs: &durationMS, } if videoMeta != nil { meta.Video = &streamplace.Segment_Video{ @@ -256,7 +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, + DurationMs: duration, Source: &streamplace.Video_Source{ MediaDefs_SourceTracks: &streamplace.MediaDefs_SourceTracks{ LexiconTypeID: "place.stream.media.defs#sourceTracks", -- knotmirror 0.0.1 From 4f7d9a94eb866b5564352d46c08db523df267e31 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Sun, 17 May 2026 21:12:57 -0700 Subject: [PATCH 2/2] muxl: bump to sequence number --- Cargo.lock | 4 ++-- pkg/vod/process.go | 7 +++++++ rust/muxl-wasm/Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb2039962..537a39d0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3303,7 +3303,7 @@ dependencies = [ [[package]] name = "muxl" version = "0.1.0" -source = "git+https://github.com/streamplace/muxl.git?rev=53c29504edcf6025f14850e1117ca2ad22e47881#53c29504edcf6025f14850e1117ca2ad22e47881" +source = "git+https://github.com/streamplace/muxl.git?rev=87d9fc2f5093589a1c018b01669d5cf31644dffc#87d9fc2f5093589a1c018b01669d5cf31644dffc" dependencies = [ "blake3", "clap", @@ -3319,7 +3319,7 @@ dependencies = [ [[package]] name = "muxl-sign" version = "0.1.0" -source = "git+https://github.com/streamplace/muxl.git?rev=53c29504edcf6025f14850e1117ca2ad22e47881#53c29504edcf6025f14850e1117ca2ad22e47881" +source = "git+https://github.com/streamplace/muxl.git?rev=87d9fc2f5093589a1c018b01669d5cf31644dffc#87d9fc2f5093589a1c018b01669d5cf31644dffc" dependencies = [ "c2pa 0.58.0 (git+https://github.com/streamplace/c2pa-rs.git?rev=81d737bd)", "clap", diff --git a/pkg/vod/process.go b/pkg/vod/process.go index 048eef7d5..81b028845 100644 --- a/pkg/vod/process.go +++ b/pkg/vod/process.go @@ -44,6 +44,7 @@ const ( stageStaging = "start_staging" stageSigner = "create_signer" stagePipeline = "gstreamer_pipeline" + stageEmptyOutput = "empty_muxl_output" stageStagingComplete = "store_complete" stageContentAddressCopy = "content_address_move" stageMetafile = "write_metafile" @@ -176,6 +177,12 @@ func ProcessVOD(ctx context.Context, cli *config.CLI, state *statedb.StatefulDB, } spmetrics.VODOutputBytes.Observe(float64(counter.n)) + if counter.n == 0 { + err := errors.New("muxl produced zero bytes") + recordErr(span, stageEmptyOutput, err) + return "", err + } + if err := completeStaging(ctx, staging, stagingKey); err != nil { recordErr(span, stageStagingComplete, err) return "", fmt.Errorf("complete staging upload: %w", err) diff --git a/rust/muxl-wasm/Cargo.toml b/rust/muxl-wasm/Cargo.toml index bf7cc9edc..ea937c2ae 100644 --- a/rust/muxl-wasm/Cargo.toml +++ b/rust/muxl-wasm/Cargo.toml @@ -10,6 +10,6 @@ path = "src/main.rs" [dependencies] -muxl-sign = { git = "https://github.com/streamplace/muxl.git", rev = "53c29504edcf6025f14850e1117ca2ad22e47881", package = "muxl-sign" } +muxl-sign = { git = "https://github.com/streamplace/muxl.git", rev = "87d9fc2f5093589a1c018b01669d5cf31644dffc", package = "muxl-sign" } # temporary local version to test # muxl-sign = { path = "../../../muxl/crates/muxl-sign" } -- knotmirror 0.0.1