Skip to content

Fix unmarshalling of sgpd RollDistancesL - #188

Merged
Kubo, Ryosuke (sunfish-shogi) merged 1 commit into
abema:masterfrom
MaxFreedomPollard:fix/sgpd-roll-distances-l
Oct 23, 2026
Merged

Kubo, Ryosuke (sunfish-shogi) merged 1 commit into
abema:masterfrom
MaxFreedomPollard:fix/sgpd-roll-distances-l

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

Kubo, Ryosuke (@sunfish-shogi)

A version 1 sgpd box with grouping type roll or prol and default_length 0 cannot be unmarshalled. ISO/IEC 14496-12 §8.9.3.2 starts each entry of such a box with if (default_length==0) { unsigned int(32) description_length; }, and a roll/prol entry is a single signed int(16) roll_distance, so each element is 48 bits. go-mp4 models this as RollDistancesL []RollDistanceWithLength (DescriptionLength 32 bits, RollDistance 16 bits), but box_types_iso14496_12.go:1557 tags the field size=16, the size of a bare roll_distance in RollDistances on the line above. unmarshalStructInternal (marshaller.go:366-378) requires a struct element to consume exactly size bits, so the first 48-bit element makes Unmarshal return invalid alignment. Marshal does not use the size for struct elements, so such a box can be written but not read back, and mp4tool dump stops with the same error because it parses every sgpd. The tag has been like this since 68a2dc0 added the field, and no test covered it.

The fix tags the field len=dynamic,opt=dynamic, like AlternativeStartupEntriesL, VisualRandomAccessEntriesL and TemporalLevelEntriesL. Sgpd.GetFieldLength already returns EntryCount for RollDistancesL, so nothing else changes. size=48 would also parse, but len=dynamic matches the sibling fields and uses that existing case.

The new case sgpd: version 1 roll no-default-length in TestBoxTypesISO14496_12 has two entries with description_length 2. I wrote its bytes and String() output by hand following the neighbouring sgpd cases and checked the bytes with a small Python encoder written from the §8.9.3.2 syntax, which also reproduces the existing roll, prol and version 2 vectors.

I had no Go toolchain available, so this change has not been compiled or run with go test; CI will be its first real run. As a stand-in I ported the parts of field.go, marshaller.go, string.go and internal/bitio that Sgpd reaches to Python and ran every sgpd case of TestBoxTypesISO14496_12 through Marshal, Unmarshal and Stringify. The eight existing cases pass with either tag; the new case fails at Unmarshal with invalid alignment under size=16 and passes under len=dynamic. The runtime behaviour described above comes from reading the code and from that port, not from a Go run.

🤖 Generated with Claude Code

A version 1 sgpd box with grouping_type 'roll' or 'prol' and
default_length 0 has a 32-bit description_length before each 16-bit
roll_distance (ISO/IEC 14496-12, 8.9.3.2), which go-mp4 models as
RollDistancesL []RollDistanceWithLength. The field was tagged size=16,
the size of a bare roll_distance. unmarshalStructInternal requires a
struct element to consume exactly size bits, so each 48-bit element
failed with "invalid alignment". Marshal ignores the size for struct
elements, so such a box could be written but not read back.

Tag the field len=dynamic,opt=dynamic like AlternativeStartupEntriesL,
VisualRandomAccessEntriesL and TemporalLevelEntriesL. Sgpd's
GetFieldLength already returns EntryCount for RollDistancesL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@sunfish-shogi

Copy link
Copy Markdown
Contributor

Thanks!
LGTM.

@sunfish-shogi
Kubo, Ryosuke (sunfish-shogi) merged commit 0f35e18 into abema:master Oct 23, 2026
19 of 21 checks passed
@abema ABEMA (abema) deleted a comment from greptile-apps Bot Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants