Skip to content

Commit 7810001

Browse files
committed
implement basic layer parsing
1 parent 9505ce4 commit 7810001

File tree

7 files changed

+179
-21
lines changed

7 files changed

+179
-21
lines changed

data/Item-01.aep

16.7 KB
Binary file not shown.

data/Layer-01.aep

190 KB
Binary file not shown.

item.go

+29-15
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/rioam2/rifx"
99
)
1010

11-
// ItemTypeName denotes the type of item. See: http://docs.aenhancers.com/items/item/#item-typename
11+
// ItemTypeName denotes the type of item. See: http://docs.aenhancers.com/items/item/#item-ItemType
1212
type ItemTypeName string
1313

1414
const (
@@ -20,38 +20,39 @@ const (
2020
ItemTypeFootage ItemTypeName = "Footage"
2121
)
2222

23-
// FootageType denotes the type of footage of an AVItem (eg: Solid, Placeholder, ...)
24-
type FootageType uint16
23+
// FootageTypeName denotes the type of footage of an AVItem (eg: Solid, Placeholder, ...)
24+
type FootageTypeName uint16
2525

2626
const (
2727
// FootageTypeSolid denotes a Solid source
28-
FootageTypeSolid FootageType = 0x09
28+
FootageTypeSolid FootageTypeName = 0x09
2929
// FootageTypePlaceholder denotes a Placeholder source
30-
FootageTypePlaceholder FootageType = 0x02
30+
FootageTypePlaceholder FootageTypeName = 0x02
3131
)
3232

3333
// Item is a generalized object storing information about folders, compositions, or footage
3434
type Item struct {
3535
Name string
3636
ID uint32
37-
TypeName ItemTypeName
37+
ItemType ItemTypeName
3838
FolderContents []*Item
3939
FootageDimensions [2]uint16
4040
FootageFramerate float64
4141
FootageSeconds float64
42-
FootageType FootageType
42+
FootageType FootageTypeName
4343
BackgroundColor [3]byte
44+
CompositionLayers []*Layer
4445
}
4546

46-
func parseItem(itemHead *rifx.List) (*Item, error) {
47+
func parseItem(itemHead *rifx.List, project *Project) (*Item, error) {
4748
item := &Item{}
4849
isRoot := itemHead.Identifier == "Fold"
4950

5051
// Parse item metadata
5152
if isRoot {
5253
item.ID = 0
5354
item.Name = "root"
54-
item.TypeName = ItemTypeFolder
55+
item.ItemType = ItemTypeFolder
5556
} else {
5657
nameBlock, err := itemHead.FindByType("Utf8")
5758
if err != nil {
@@ -75,20 +76,20 @@ func parseItem(itemHead *rifx.List) (*Item, error) {
7576
item.ID = itemDescriptor.ID
7677
switch itemDescriptor.Type {
7778
case 0x01:
78-
item.TypeName = ItemTypeFolder
79+
item.ItemType = ItemTypeFolder
7980
case 0x04:
80-
item.TypeName = ItemTypeComposition
81+
item.ItemType = ItemTypeComposition
8182
case 0x07:
82-
item.TypeName = ItemTypeFootage
83+
item.ItemType = ItemTypeFootage
8384
}
8485
}
8586

8687
// Parse unique item type information
87-
switch item.TypeName {
88+
switch item.ItemType {
8889
case ItemTypeFolder:
8990
childItemLists := append(itemHead.SublistFilter("Item"), itemHead.SublistMerge("Sfdr").SublistFilter("Item")...)
9091
for _, childItemList := range childItemLists {
91-
childItem, err := parseItem(childItemList)
92+
childItem, err := parseItem(childItemList, project)
9293
if err != nil {
9394
return nil, err
9495
}
@@ -124,7 +125,7 @@ func parseItem(itemHead *rifx.List) (*Item, error) {
124125
return nil, err
125126
}
126127
optiData := optiBlock.Data.([]byte)
127-
item.FootageType = FootageType(binary.BigEndian.Uint16(optiData[4:6]))
128+
item.FootageType = FootageTypeName(binary.BigEndian.Uint16(optiData[4:6]))
128129
switch item.FootageType {
129130
case FootageTypeSolid:
130131
item.Name = fmt.Sprintf("%s", bytes.ReplaceAll(bytes.Trim(optiData[26:255], "\x00"), []byte{0}, []byte{32}))
@@ -156,7 +157,20 @@ func parseItem(itemHead *rifx.List) (*Item, error) {
156157
item.FootageFramerate = float64(compDesc.FramerateDividend) / float64(compDesc.FramerateDivisor)
157158
item.FootageSeconds = float64(compDesc.SecondsDividend) / float64(compDesc.SecondsDivisor)
158159
item.BackgroundColor = compDesc.BackgroundColor
160+
161+
// Parse composition's layers
162+
for index, layerListHead := range itemHead.SublistFilter("Layr") {
163+
layer, err := parseLayer(layerListHead, project)
164+
if err != nil {
165+
return nil, err
166+
}
167+
layer.Index = uint32(index + 1)
168+
item.CompositionLayers = append(item.CompositionLayers, layer)
169+
}
159170
}
160171

172+
// Insert item into project items map
173+
project.Items[item.ID] = item
174+
161175
return item, nil
162176
}

item_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ func TestItemMetadata(t *testing.T) {
1515
folder01 := project.RootFolder.FolderContents[0]
1616
expect(t, folder01.Name, "Folder 01")
1717
expect(t, folder01.ID, uint32(46))
18-
expect(t, folder01.TypeName, ItemTypeFolder)
18+
expect(t, folder01.ItemType, ItemTypeFolder)
1919

2020
folder02 := project.RootFolder.FolderContents[1]
2121
expect(t, folder02.Name, "Folder 02")
2222
expect(t, folder02.ID, uint32(47))
23-
expect(t, folder02.TypeName, ItemTypeFolder)
23+
expect(t, folder02.ItemType, ItemTypeFolder)
2424

2525
comp01 := folder01.FolderContents[0]
2626
expect(t, comp01.Name, "Comp 01")
2727
expect(t, comp01.ID, uint32(48))
28-
expect(t, comp01.TypeName, ItemTypeComposition)
28+
expect(t, comp01.ItemType, ItemTypeComposition)
2929
expect(t, comp01.FootageDimensions, [2]uint16{351, 856})
3030
expect(t, comp01.FootageFramerate, float64(21))
3131
expect(t, comp01.FootageSeconds, float64(31))
@@ -34,7 +34,7 @@ func TestItemMetadata(t *testing.T) {
3434
comp02 := folder02.FolderContents[0]
3535
expect(t, comp02.Name, "Comp 02")
3636
expect(t, comp02.ID, uint32(59))
37-
expect(t, comp02.TypeName, ItemTypeComposition)
37+
expect(t, comp02.ItemType, ItemTypeComposition)
3838
expect(t, comp02.FootageDimensions, [2]uint16{452, 639})
3939
expect(t, comp02.FootageFramerate, float64(29.97))
4040
expect(t, comp02.FootageSeconds, float64(71.338004671338))
@@ -47,7 +47,7 @@ func TestItemMetadata(t *testing.T) {
4747
placeholderFootage := footageFolder.FolderContents[2]
4848
expect(t, placeholderFootage.Name, "Missing Footage")
4949
expect(t, placeholderFootage.ID, uint32(71))
50-
expect(t, placeholderFootage.TypeName, ItemTypeFootage)
50+
expect(t, placeholderFootage.ItemType, ItemTypeFootage)
5151
expect(t, placeholderFootage.FootageSeconds, float64(127))
5252
expect(t, placeholderFootage.FootageFramerate, float64(123.45669555664062))
5353
expect(t, placeholderFootage.FootageDimensions, [2]uint16{1234, 5678})

layer.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package aep
2+
3+
import (
4+
"github.com/rioam2/rifx"
5+
)
6+
7+
// LayerQualityLevel denotes the quality level of a layer (eg: Best, Draft, Wireframe)
8+
type LayerQualityLevel uint16
9+
10+
const (
11+
// LayerQualityBest enumerates the value of a layer with Best Quality
12+
LayerQualityBest LayerQualityLevel = 0x0002
13+
// LayerQualityDraft enumerates the value of a layer with Draft Quality
14+
LayerQualityDraft LayerQualityLevel = 0x0001
15+
// LayerQualityWireframe enumerates the value of a layer with Wireframe Quality
16+
LayerQualityWireframe LayerQualityLevel = 0x0000
17+
)
18+
19+
// LayerSamplingMode denotes the sampling mode of a layer (eg: Bilinear, Bicubic)
20+
type LayerSamplingMode byte
21+
22+
const (
23+
// LayerSamplingModeBilinear enumerates the value of a layer with Bilinear Sampling
24+
LayerSamplingModeBilinear LayerSamplingMode = 0x00
25+
// LayerSamplingModeBicubic enumerates the value of a layer with Bicubic Sampling
26+
LayerSamplingModeBicubic LayerSamplingMode = 0x01
27+
)
28+
29+
// LayerFrameBlendMode denotes the frame blending mode of a layer (eg: Frame mix, Pixel motion)
30+
type LayerFrameBlendMode byte
31+
32+
const (
33+
// LayerFrameBlendModeFrameMix enumerates the value of a layer with Frame Mix Frame Blending
34+
LayerFrameBlendModeFrameMix LayerFrameBlendMode = 0x00
35+
// LayerFrameBlendModePixelMotion enumerates the value of a layer with Pixel Motion Frame Blending
36+
LayerFrameBlendModePixelMotion LayerFrameBlendMode = 0x01
37+
)
38+
39+
// Layer describes a single layer in a composition.
40+
type Layer struct {
41+
Index uint32
42+
Name string
43+
SourceID uint32
44+
Quality LayerQualityLevel
45+
SamplingMode LayerSamplingMode
46+
FrameBlendMode LayerFrameBlendMode
47+
GuideEnabled bool
48+
SoloEnabled bool
49+
ThreeDEnabled bool
50+
AdjustmentLayerEnabled bool
51+
CollapseTransformEnabled bool
52+
ShyEnabled bool
53+
LockEnabled bool
54+
FrameBlendEnabled bool
55+
MotionBlurEnabled bool
56+
EffectsEnabled bool
57+
AudioEnabled bool
58+
VideoEnabled bool
59+
}
60+
61+
func parseLayer(layerHead *rifx.List, project *Project) (*Layer, error) {
62+
layer := &Layer{}
63+
64+
type LDTA struct {
65+
Unknown00 [4]byte // Offset 0B
66+
Quality LayerQualityLevel // Offset 4B
67+
Unknown01 [31]byte // Offset 6B
68+
LayerAttrBits [3]byte // Offset 37B
69+
SourceID uint32 // Offset 40B
70+
}
71+
ldtaBlock, err := layerHead.FindByType("ldta")
72+
if err != nil {
73+
return nil, err
74+
}
75+
ldta := &LDTA{}
76+
ldtaBlock.ToStruct(ldta)
77+
layer.SourceID = ldta.SourceID
78+
layer.Quality = ldta.Quality
79+
layer.SamplingMode = LayerSamplingMode((ldta.LayerAttrBits[0] & (1 << 6)) >> 6)
80+
layer.FrameBlendMode = LayerFrameBlendMode((ldta.LayerAttrBits[0] & (1 << 2)) >> 2)
81+
layer.GuideEnabled = ((ldta.LayerAttrBits[0] & (1 << 1)) >> 1) == 1
82+
layer.SoloEnabled = ((ldta.LayerAttrBits[1] & (1 << 3)) >> 3) == 1
83+
layer.ThreeDEnabled = ((ldta.LayerAttrBits[1] & (1 << 2)) >> 2) == 1
84+
layer.AdjustmentLayerEnabled = ((ldta.LayerAttrBits[1] & (1 << 1)) >> 1) == 1
85+
layer.CollapseTransformEnabled = ((ldta.LayerAttrBits[2] & (1 << 7)) >> 7) == 1
86+
layer.ShyEnabled = ((ldta.LayerAttrBits[2] & (1 << 6)) >> 6) == 1
87+
layer.LockEnabled = ((ldta.LayerAttrBits[2] & (1 << 5)) >> 5) == 1
88+
layer.FrameBlendEnabled = ((ldta.LayerAttrBits[2] & (1 << 4)) >> 4) == 1
89+
layer.MotionBlurEnabled = ((ldta.LayerAttrBits[2] & (1 << 3)) >> 3) == 1
90+
layer.EffectsEnabled = ((ldta.LayerAttrBits[2] & (1 << 2)) >> 2) == 1
91+
layer.AudioEnabled = ((ldta.LayerAttrBits[2] & (1 << 1)) >> 1) == 1
92+
layer.VideoEnabled = ((ldta.LayerAttrBits[2] & (1 << 0)) >> 0) == 1
93+
94+
nameBlock, err := layerHead.FindByType("Utf8")
95+
if err != nil {
96+
return nil, err
97+
}
98+
layer.Name = nameBlock.ToString()
99+
return layer, nil
100+
}

layer_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package aep
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestLayerMetadata(t *testing.T) {
8+
project, err := Open("data/Layer-01.aep")
9+
if err != nil {
10+
t.Fatal(err)
11+
}
12+
13+
comp01 := project.RootFolder.FolderContents[0]
14+
expect(t, comp01.CompositionLayers[0].CollapseTransformEnabled)
15+
expect(t, comp01.CompositionLayers[1].EffectsEnabled)
16+
expect(t, comp01.CompositionLayers[2].MotionBlurEnabled)
17+
expect(t, comp01.CompositionLayers[4].ShyEnabled)
18+
expect(t, comp01.CompositionLayers[5].AdjustmentLayerEnabled)
19+
expect(t, comp01.CompositionLayers[6].ThreeDEnabled)
20+
expect(t, comp01.CompositionLayers[7].SoloEnabled)
21+
expect(t, comp01.CompositionLayers[8].GuideEnabled)
22+
expect(t, comp01.CompositionLayers[9].FrameBlendMode, LayerFrameBlendModePixelMotion)
23+
expect(t, comp01.CompositionLayers[10].FrameBlendMode, LayerFrameBlendModeFrameMix)
24+
expect(t, comp01.CompositionLayers[11].Quality, LayerQualityWireframe)
25+
expect(t, comp01.CompositionLayers[12].Quality, LayerQualityDraft)
26+
expect(t, comp01.CompositionLayers[13].Quality, LayerQualityBest)
27+
expect(t, comp01.CompositionLayers[14].SamplingMode, LayerSamplingModeBilinear)
28+
expect(t, comp01.CompositionLayers[15].SamplingMode, LayerSamplingModeBicubic)
29+
expect(t, comp01.CompositionLayers[16].VideoEnabled)
30+
expect(t, comp01.CompositionLayers[16].AudioEnabled)
31+
}

project.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Project struct {
2424
ExpressionEngine string
2525
Depth BPC
2626
RootFolder *Item
27+
Items map[uint32]*Item
2728
}
2829

2930
// FromReader reads and creates a new project instance from an After Effects project file
@@ -51,6 +52,7 @@ func Open(path string) (*Project, error) {
5152
// parseProject is an internal helper to decode AEP RIFF blocks
5253
func parseProject(root *rifx.List) (*Project, error) {
5354
project := &Project{}
55+
project.Items = make(map[uint32]*Item)
5456

5557
// Parse expression engine
5658
expressionEngineList, err := root.SublistFind("ExEn")
@@ -77,11 +79,22 @@ func parseProject(root *rifx.List) (*Project, error) {
7779
if err != nil {
7880
return nil, err
7981
}
80-
folder, err := parseItem(rootFolderList)
82+
folder, err := parseItem(rootFolderList, project)
8183
if err != nil {
8284
return nil, err
8385
}
8486
project.RootFolder = folder
8587

88+
// Layers that have not been given an explicit name should be named after their source
89+
for _, item := range project.Items {
90+
if item.ItemType == ItemTypeComposition {
91+
for _, layer := range item.CompositionLayers {
92+
if layer.Name == "" {
93+
layer.Name = project.Items[layer.SourceID].Name
94+
}
95+
}
96+
}
97+
}
98+
8699
return project, nil
87100
}

0 commit comments

Comments
 (0)