Files
go-gst/generator.go
2025-09-16 22:36:48 +02:00

393 lines
13 KiB
Go

package main
//go:generate go run . -o ./pkg/
import (
"encoding/xml"
"log"
"slices"
"strings"
"github.com/go-gst/go-glib/gir"
"github.com/go-gst/go-glib/gir/cmd/gir-generate/gendata"
"github.com/go-gst/go-glib/gir/cmd/gir-generate/genmain"
"github.com/go-gst/go-glib/gir/girgen/strcases"
"github.com/go-gst/go-glib/gir/girgen/typesystem"
girfiles_gst "github.com/go-gst/go-gst/girs"
)
const Module = "github.com/go-gst/go-gst/pkg"
var Data = genmain.Data{
Module: Module,
GirFiles: girfiles_gst.GirFiles,
Preprocessors: []gir.Preprocessor{
gir.MustIntrospect("Gst-1.Message.copy"),
// Bitfield has a member of same name:
gir.PreprocessorFunc(func(r gir.Repositories) {
bitfield := r.FindFullType("Gst-1.BufferCopyFlags").(*gir.Bitfield)
for _, m := range bitfield.Members {
if m.CIdentifier == "GST_BUFFER_COPY_FLAGS" {
m.CIdentifier = "GST_BUFFER_COPY_BUFFER_FLAGS"
m.Doc.String += " (go-gst: renamed from BufferCopyFlags)"
}
}
}),
// a member of the enum is generated twice:
DedupBitfieldMembers("GstVideo-1.VideoBufferFlags"),
DedupBitfieldMembers("GstVideo-1.VideoFrameFlags"),
DedupBitfieldMembers("GstVideo-1.NavigationModifierType"),
// the member names do not have a GST prefix, which creates collisions:
FixCutoffEnumMemberNames("GstMpegts-1.DVBTeletextType"),
MiniObjectExtenderBorrows(),
// collides with the base src extenders that actually provide a clock instead of returning the provided one
gir.RenameCallable("Gst-1.Element.provide_clock", "provided_clock"),
// collides with base src set caps
gir.RenameCallable("GstApp-1.AppSrc.set_caps", "app_src_set_caps"),
// collides with extending audio base payloader push
gir.RenameCallable("GstRtp-1.RTPBasePayload.push", "push_buffer"),
// collides with extending audio base payloader push
gir.RenameCallable("GstAllocators-1.DRMDumbAllocator.alloc", "drm_dumb_alloc"),
// otherwise clashes with control binding class extension
gir.RenameCallable("Gst-1.Object.get_control_binding", "current_control_binding"),
// Collides with GstObject:
gir.RenameCallable("Gst-1.ControlBinding.sync_values", "sync_control_binding_values"),
// Collides with method of the same name
gir.TypeRenamer("GstVideo-1.VideoChromaResample", "VideoChromaResampler"),
gir.PreprocessorFunc(func(r gir.Repositories) {
t := r.FindFullType("GstVideo-1.VideoTimeCode").(*gir.Record)
// FIXME: the get_type function requires gst_init(), thus crashes if called
// during init
t.GLibGetType = ""
}),
// PadProbeInfo must not be freed, so we mark it as borrowed.
gir.ModifyCallable("Gst-1.PadProbeCallback", func(c *gir.CallableAttrs) {
for _, p := range c.Parameters.Parameters {
if p.Name == "info" && p.Type.Name == "PadProbeInfo" {
p.TransferOwnership.TransferOwnership = "borrow"
return
}
}
panic("PadProbeCallback does not have an info parameter")
}),
MarkSDPMessageGettersAsBorrowed(),
},
Config: typesystem.Config{
Namespaces: map[string]typesystem.NamespaceConfig{
"Gst-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
ManualTypes: []typesystem.Type{
&typesystem.Alias{
BaseType: typesystem.BaseType{
GirName: "ClockTime",
GoTyp: "ClockTime",
CGoTyp: "C.GstClockTime",
CTyp: "GstClockTime",
},
AliasedType: typesystem.CouldBeForeign[typesystem.Type]{
Type: typesystem.Guint64,
},
},
// We create custom structs so we can call await on a channel instead of
// blocking a thread.
&typesystem.Record{
BaseType: typesystem.BaseType{
GirName: "Promise",
GoTyp: "Promise",
CGoTyp: "C.GstPromise",
CTyp: "GstPromise",
},
BaseConversions: typesystem.BaseConversions{
FromGlibBorrowFunction: "UnsafePromiseFromGlibBorrow",
FromGlibFullFunction: "UnsafePromiseFromGlibFull",
FromGlibNoneFunction: "UnsafePromiseFromGlibNone",
ToGlibNoneFunction: "UnsafePromiseToGlibNone",
ToGlibFullFunction: "UnsafePromiseToGlibFull",
},
},
},
IgnoredDefinitions: []typesystem.IgnoreFunc{
// Collide and use an out array of values. TODO: manually implement
typesystem.IgnoreMatching("Object.get_g_value_array"),
typesystem.IgnoreMatching("ControlBinding.get_g_value_array"),
// Manually implemented:
typesystem.IgnoreMatching("Object.get_value"),
typesystem.IgnoreMatching("ControlBinding.get_value"), // TODO
typesystem.IgnoreMatching("ElementFactory.make_with_properties"),
typesystem.IgnoreMatching("Message.parse_property_notify"),
typesystem.IgnoreMatching("Message.new_property_notify"),
typesystem.IgnoreMatching("Message.get_stream_status_object"),
typesystem.IgnoreMatching("Structure.get_value"),
typesystem.IgnoreMatching("Structure.set_value"),
typesystem.IgnoreMatching("Structure.id_get_value"),
typesystem.IgnoreMatching("Structure.id_take_value"),
typesystem.IgnoreMatching("Structure.take_value"),
typesystem.IgnoreMatching("ChildProxy.set_property"),
typesystem.IgnoreMatching("ChildProxy.get_property"),
typesystem.IgnoreMatching("Iterator.next"),
typesystem.IgnoreMatching("TagList.get_value_index"),
// BusSyncHandler has a borrowed message except when the user returns BUS_DROP.
typesystem.IgnoreMatching("BusSyncHandler"),
// we have bindings for parse_launch(_full), if we need the v variants,
// then manually implement them
typesystem.IgnoreMatching("parse_launchv"),
typesystem.IgnoreMatching("parse_launchv_full"),
// gobject.NewValue handles this already.
typesystem.IgnoreMatching("util_set_value_from_string"),
// Buffer mapping is manually implemented:
typesystem.IgnoreMatching("Buffer.map"),
typesystem.IgnoreMatching("Buffer.unmap"),
typesystem.IgnoreMatching("MapInfo"),
// Requires a gvalue arg, manually implemented:
typesystem.IgnoreMatching("TagSetter.add_tag_value"),
// ParamSpec subclass colliding with constructor:
typesystem.IgnoreMatching("ParamSpecArray"),
typesystem.IgnoreMatching("ParamSpecFraction"),
},
},
"GstApp-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
},
"GstAllocators-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
},
"GstWebRTC-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
},
"GstBase-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
IgnoredDefinitions: []typesystem.IgnoreFunc{
// has unexported free function that crashes the linker when compiling the examples:
typesystem.IgnoreMatching("TypeFindData"),
},
},
"GstVideo-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
IgnoredDefinitions: []typesystem.IgnoreFunc{
// must be implemented manually
typesystem.IgnoreMatching("VideoCodecFrame.set_user_data"),
typesystem.IgnoreMatching("VideoCodecFrame.get_user_data"),
// returns a gconstpointer to an array, manually implemented instead
typesystem.IgnoreMatching("VideoFormat.get_palette"),
},
},
"GstPbutils-1": {
MinVersion: "1.26",
MaxVersion: "1.26",
IgnoredDefinitions: []typesystem.IgnoreFunc{
// Resolve to ObjectClass:
typesystem.IgnoreMatching("DiscovererAudioInfoClass"),
typesystem.IgnoreMatching("DiscovererContainerInfoClass"),
typesystem.IgnoreMatching("DiscovererInfoClass"),
typesystem.IgnoreMatching("DiscovererStreamInfoClass"),
typesystem.IgnoreMatching("DiscovererSubtitleInfoClass"),
typesystem.IgnoreMatching("DiscovererVideoInfoClass"),
typesystem.IgnoreMatching("EncodingTargetClass"),
},
},
},
},
Postprocessors: []typesystem.PostProcessor{
typesystem.MarkAsManuallyExtended("Gst-1", "Object"),
typesystem.MarkAsManuallyExtended("Gst-1", "Element"),
typesystem.MarkAsManuallyExtended("Gst-1", "Bin"),
typesystem.MarkAsManuallyExtended("Gst-1", "Bus"),
typesystem.MarkAsManuallyExtended("Gst-1", "ChildProxy"),
typesystem.MarkAsManuallyExtended("Gst-1", "TagSetter"),
// Virtual methods of BaseTransform collide with Element
func(r *typesystem.Registry) error {
base := r.FindNamespaceByName("GstBase-1")
bt := base.FindLocalTypeByGIRName("BaseTransform").(*typesystem.Class)
bt.FindVirtualMethod("query").ParentName = "ParentQueryBaseTransform"
return nil
},
// Virtual methods of PushSrc collide with BaseSrc
func(r *typesystem.Registry) error {
base := r.FindNamespaceByName("GstBase-1")
pushsrc := base.FindLocalTypeByGIRName("PushSrc").(*typesystem.Class)
pushsrc.FindVirtualMethod("alloc").ParentName = "ParentAllocPushSrc"
pushsrc.FindVirtualMethod("fill").ParentName = "ParentFillPushSrc"
return nil
},
// Virtual methods of GLBaseMemoryAllocator collide with gst.Allocator
func(r *typesystem.Registry) error {
gl := r.FindNamespaceByName("GstGL-1")
glBaseMemoryAllocator := gl.FindLocalTypeByGIRName("GLBaseMemoryAllocator").(*typesystem.Class)
glBaseMemoryAllocator.FindVirtualMethod("alloc").ParentName = "ParentAllocGLBaseMemoryAllocator"
return nil
},
// Virtual methods of AudioSink collide with BaseSink
func(r *typesystem.Registry) error {
audio := r.FindNamespaceByName("GstAudio-1")
audioSink := audio.FindLocalTypeByGIRName("AudioSink").(*typesystem.Class)
audioSink.FindVirtualMethod("prepare").ParentName = "ParentPrepareAudioSink"
audioSink.FindVirtualMethod("stop").ParentName = "ParentStopAudioSink"
return nil
},
// Virtual methods of RTPBasePayload collide with Element
func(r *typesystem.Registry) error {
rtp := r.FindNamespaceByName("GstRtp-1")
rtpBasePayload := rtp.FindLocalTypeByGIRName("RTPBasePayload").(*typesystem.Class)
rtpBasePayload.FindVirtualMethod("query").ParentName = "ParentQueryRTPBasePayload"
return nil
},
},
}
func main() {
genmain.Run(
gendata.Main,
Data,
)
}
var borrowedTypes = []string{
"Gst-1.MiniObject", "Gst-1.Structure", "Gst-1.Caps", "Gst-1.Buffer", "Gst-1.BufferList", "Gst-1.Memory", "Gst-1.Message", "Gst-1.Query", "Gst-1.Sample",
}
// gst.MiniObject extenders must not take a ref on these methods, or they are made readonly
func MiniObjectExtenderBorrows() gir.Preprocessor {
return gir.PreprocessorFunc(func(repos gir.Repositories) {
for _, fulltype := range borrowedTypes {
res := repos.FindFullType(fulltype)
if res == nil {
log.Fatalf("fulltype %s not found", fulltype)
}
switch typ := res.(type) {
case *gir.Record:
for i, m := range typ.Methods {
if m.ReturnValue.TransferOwnership.TransferOwnership == "none" && slices.ContainsFunc(borrowedTypes, func(typ string) bool {
return strings.SplitN(typ, ".", 2)[1] == m.ReturnValue.Type.Name
}) {
log.Printf("marking function as borrowing: %s", m.Name)
typ.Methods[i].ReturnValue.TransferOwnership.TransferOwnership = "borrow"
}
}
default:
log.Fatalf("unhandled type for %s", fulltype)
}
}
})
}
// gstsdp.SDPMessage and family have getters that return borrowed values, so we mark the return value as borrowed
func MarkSDPMessageGettersAsBorrowed() gir.Preprocessor {
return gir.PreprocessorFunc(func(r gir.Repositories) {
funcs := []string{
// message:
// "GstSdp-1.SDPMessage.get_email", // returns string, not borrowed
// "GstSdp-1.SDPMessage.get_phone", // returns string, not borrowed
"GstSdp-1.SDPMessage.get_bandwidth",
"GstSdp-1.SDPMessage.get_time",
"GstSdp-1.SDPMessage.get_zone",
"GstSdp-1.SDPMessage.get_attribute",
"GstSdp-1.SDPMessage.get_media",
// media:
"GstSdp-1.SDPMedia.get_connection",
"GstSdp-1.SDPMedia.get_bandwidth",
"GstSdp-1.SDPMedia.get_attribute",
// "GstSdp-1.SDPMedia.get_format", // returns string, not borrowed
}
for _, fullfunc := range funcs {
f := r.FindFullType(fullfunc).(*gir.Method)
f.ReturnValue.TransferOwnership.TransferOwnership = "borrow"
}
})
}
func MarkReturnAsBorrowed(fulltype string) gir.Preprocessor {
return gir.ModifyCallable(fulltype, func(c *gir.CallableAttrs) {
c.ReturnValue.TransferOwnership.TransferOwnership = "borrow"
})
}
func DedupBitfieldMembers(fulltype string) gir.Preprocessor {
return gir.PreprocessorFunc(func(repos gir.Repositories) {
bf := repos.FindFullType(fulltype).(*gir.Bitfield)
oldmembers := bf.Members
bf.Members = nil
seen := make(map[string]struct{})
for _, m := range oldmembers {
if _, ok := seen[m.CIdentifier]; ok {
continue
}
seen[m.CIdentifier] = struct{}{}
bf.Members = append(bf.Members, m)
}
})
}
// FixCutoffEnumMemberNames adds a namespace prefix, that will later be cut off by FormatMember. It also regenerates the name from the C identifier
func FixCutoffEnumMemberNames(fulltype string) gir.Preprocessor {
return gir.MapMembers(fulltype, func(member *gir.Member) {
newname := strcases.SnakeToGo(true, strings.ToLower(member.CIdentifier))
nameAttr := xml.Name{Local: "name"}
for i, attr := range member.Names {
if attr.Name == nameAttr {
attr.Value = newname
}
member.Names[i] = attr
}
member.CIdentifier = "gst_" + member.CIdentifier
})
}