//go:build cgo && !disable_library_libaom package libaom /* #cgo pkg-config: aom #include "libaom.h" */ import "C" import ( "errors" "fmt" "git.gammaspectra.live/S.O.N.G/Ignite/color" "git.gammaspectra.live/S.O.N.G/Ignite/frame" "git.gammaspectra.live/S.O.N.G/Ignite/utilities" "git.gammaspectra.live/S.O.N.G/Ignite/utilities/filmgrain" "git.gammaspectra.live/S.O.N.G/Ignite/utilities/obuwriter" "golang.org/x/exp/constraints" "io" "log" "maps" "os" "runtime" "strconv" "strings" "sync/atomic" "unsafe" ) type Encoder struct { w *obuwriter.Writer cleaned atomic.Bool cfg C.aom_codec_enc_cfg_t codec C.aom_codec_ctx_t raw *C.aom_image_t rawBuffer []byte framesIn, framesOut int frameStatistics frame.FrameStatistics resourcePinner runtime.Pinner logger utilities.Logger free []func() } var libaomVersion = "libaom-av1 " + C.GoString(C.aom_codec_version_str()) + " ABI " + strconv.FormatUint(C.AOM_ENCODER_ABI_VERSION, 10) func Version() string { return libaomVersion } const ( UsageGoodQuality = C.AOM_USAGE_GOOD_QUALITY UsageRealtime = C.AOM_USAGE_REALTIME UsageAllIntra = C.AOM_USAGE_ALL_INTRA ) func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[string]any, logger utilities.Logger) (*Encoder, error) { e := &Encoder{ frameStatistics: make(frame.FrameStatistics), logger: logger, } clonedSettings := make(map[string]any) if modelPath := os.Getenv("VMAF_MODEL_PATH"); modelPath != "" { clonedSettings["vmaf-model-path"] = modelPath } maps.Copy(clonedSettings, settings) photonNoiseIso := getSettingUnsigned[uint](clonedSettings, "photon-noise-iso", 0) photonNoiseTransferFunction := filmgrain.GetTransferFunction(getSettingString(clonedSettings, "photon-noise-transfer", "bt709")) if photonNoiseIso > 0 && photonNoiseTransferFunction != nil { //create table noiseWidth := getSettingUnsigned[uint](clonedSettings, "photon-noise-width", uint(properties.Width)) noiseHeight := getSettingUnsigned[uint](clonedSettings, "photon-noise-height", uint(properties.Height)) table, err := filmgrain.CreatePhotonNoiseTable(int(noiseWidth), int(noiseHeight), float64(photonNoiseIso), photonNoiseTransferFunction) if err != nil { return nil, err } tmpFile, err := os.CreateTemp(os.TempDir(), "photon-table*.tbl") if err != nil { return nil, err } _, err = tmpFile.Write(table) if err != nil { return nil, err } tmpName := tmpFile.Name() tmpFile.Close() e.free = append(e.free, func() { os.Remove(tmpName) }) clonedSettings["film-grain-table"] = tmpName } var aomErr C.aom_codec_err_t encoder := C.aom_codec_av1_cx() if encoder == nil { return nil, errors.New("unsupported codec") } e.cfg.g_usage = C.uint(getSettingUnsigned(clonedSettings, "usage", uint(UsageGoodQuality))) if getSettingBool(clonedSettings, "good", false) { e.cfg.g_usage = UsageGoodQuality } if getSettingBool(clonedSettings, "rt", false) { e.cfg.g_usage = UsageRealtime } if getSettingBool(clonedSettings, "allintra", false) { e.cfg.g_usage = UsageAllIntra } var imageFormat C.aom_img_fmt_t var flags C.aom_codec_flags_t if aomErr = C.aom_codec_enc_config_default(encoder, &e.cfg, e.cfg.g_usage); aomErr != 0 { return nil, errors.New("failed to get default codec config") } var bitsPerSample, decH, decV C.int switch true { case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 4 && properties.ColorSpace.ChromaSampling.B == 4: imageFormat = C.AOM_IMG_FMT_I444 e.cfg.g_profile = 1 switch properties.ColorSpace.BitDepth { case C.AOM_BITS_8: bitsPerSample = 24 case C.AOM_BITS_10: bitsPerSample = 30 case C.AOM_BITS_12: bitsPerSample = 36 } decH = 1 decV = 1 case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 2 && properties.ColorSpace.ChromaSampling.B == 2: imageFormat = C.AOM_IMG_FMT_I422 e.cfg.g_profile = 2 switch properties.ColorSpace.BitDepth { case C.AOM_BITS_8: bitsPerSample = 16 case C.AOM_BITS_10: bitsPerSample = 20 case C.AOM_BITS_12: bitsPerSample = 24 } decH = 2 decV = 1 case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 2 && properties.ColorSpace.ChromaSampling.B == 0: imageFormat = C.AOM_IMG_FMT_I420 e.cfg.g_profile = 0 switch properties.ColorSpace.BitDepth { case C.AOM_BITS_8: bitsPerSample = 12 case C.AOM_BITS_10: bitsPerSample = 15 case C.AOM_BITS_12: bitsPerSample = 18 } decH = 2 decV = 2 case properties.ColorSpace.ChromaSampling.J == 4 && properties.ColorSpace.ChromaSampling.A == 0 && properties.ColorSpace.ChromaSampling.B == 0: //mono is defined as 4:2:0, but monochrome is set on config imageFormat = C.AOM_IMG_FMT_I420 e.cfg.g_profile = 0 e.cfg.monochrome = 1 bitsPerSample = C.int(properties.ColorSpace.BitDepth) decH = 0 decV = 2 default: return nil, errors.New("unsupported input chroma subsampling") } e.cfg.g_input_bit_depth = C.uint(properties.ColorSpace.BitDepth) e.cfg.g_bit_depth = C.aom_bit_depth_t(properties.ColorSpace.BitDepth) if e.cfg.g_bit_depth >= C.AOM_BITS_12 { //only bitdepths up to 12 are supported, see aom_bit_depth_t e.cfg.g_bit_depth = C.AOM_BITS_12 e.cfg.g_profile = 2 } if e.cfg.g_input_bit_depth > C.AOM_BITS_8 { imageFormat |= C.AOM_IMG_FMT_HIGHBITDEPTH } if e.cfg.g_bit_depth > C.AOM_BITS_8 { flags |= C.AOM_CODEC_USE_HIGHBITDEPTH } frameSize, err := properties.ColorSpace.FrameSize(properties.Width, properties.Height) if err != nil { return nil, err } bytesPerSample := 1 if properties.ColorSpace.BitDepth > C.AOM_BITS_8 { bytesPerSample = 2 } e.raw = &C.aom_image_t{} e.raw.bit_depth = C.uint(properties.ColorSpace.BitDepth) //todo: color primaries e.raw.cp = C.AOM_CICP_CP_UNSPECIFIED //todo: transfer characteristics e.raw.tc = C.AOM_CICP_TC_UNSPECIFIED //todo: matrix coefficients e.raw.mc = C.AOM_CICP_MC_UNSPECIFIED e.raw.monochrome = C.int(e.cfg.monochrome) e.raw.fmt = imageFormat switch properties.ColorSpace.ChromaSamplePosition { case color.ChromaSamplePositionLeft: e.raw.csp = C.AOM_CSP_VERTICAL case color.ChromaSamplePositionCenter: e.raw.csp = C.AOM_CSP_UNKNOWN case color.ChromaSamplePositionTopLeft: e.raw.csp = C.AOM_CSP_COLOCATED default: e.raw.csp = C.AOM_CSP_UNKNOWN } if properties.FullColorRange { e.raw._range = C.AOM_CR_FULL_RANGE } else { e.raw._range = C.AOM_CR_STUDIO_RANGE } e.cfg.g_w = C.uint(properties.Width) e.cfg.g_h = C.uint(properties.Height) e.raw.w = e.cfg.g_w e.raw.d_w = e.cfg.g_w e.raw.h = e.cfg.g_h e.raw.d_h = e.cfg.g_h e.raw.bps = bitsPerSample e.raw.x_chroma_shift = C.uint(decH >> 1) e.raw.y_chroma_shift = C.uint(decV >> 1) c_w := (C.int(properties.Width) + decH - 1) / decH c_w *= C.int(bytesPerSample) e.rawBuffer = make([]byte, frameSize) e.resourcePinner.Pin(e.raw) e.resourcePinner.Pin(unsafe.SliceData(e.rawBuffer)) iY := properties.ColorSpace.ChromaSampling.PlaneLumaSamples(properties.Width, properties.Height) iCb := properties.ColorSpace.ChromaSampling.PlaneCbSamples(properties.Width, properties.Height) iCr := properties.ColorSpace.ChromaSampling.PlaneCrSamples(properties.Width, properties.Height) iY *= bytesPerSample iCb *= bytesPerSample iCr *= bytesPerSample e.raw.stride[C.AOM_PLANE_Y] = C.int(properties.Width * bytesPerSample) e.raw.stride[C.AOM_PLANE_U] = c_w e.raw.stride[C.AOM_PLANE_V] = e.raw.stride[C.AOM_PLANE_U] e.raw.planes[C.AOM_PLANE_Y] = (*C.uchar)(unsafe.Pointer(unsafe.SliceData(e.rawBuffer[:iY]))) e.raw.planes[C.AOM_PLANE_U] = (*C.uchar)(unsafe.Pointer(unsafe.SliceData(e.rawBuffer[iY : iY+iCb]))) e.raw.planes[C.AOM_PLANE_V] = (*C.uchar)(unsafe.Pointer(unsafe.SliceData(e.rawBuffer[iY+iCb : iY+iCb+iCr]))) runtime.SetFinalizer(e, func(encoder *Encoder) { encoder.Close() }) /*!\brief Stream timebase units * * Indicates the smallest interval of time, in seconds, used by the stream. * For fixed frame rate material, or variable frame rate material where * frames are timed at a multiple of a given clock (ex: video capture), * the \ref RECOMMENDED method is to set the timebase to the reciprocal * of the frame rate (ex: 1001/30000 for 29.970 Hz NTSC). This allows the * pts to correspond to the frame number, which can be handy. For * re-encoding video from containers with absolute time timestamps, the * \ref RECOMMENDED method is to set the timebase to that of the parent * container or multimedia framework (ex: 1/1000 for ms, as in FLV). */ timeBase := properties.TimeBase() e.cfg.g_timebase.num = C.int(timeBase.Numerator) e.cfg.g_timebase.den = C.int(timeBase.Denominator) // boolean settings if getSettingBool(clonedSettings, "large-scale-tile", e.cfg.large_scale_tile != 0) { e.cfg.large_scale_tile = 1 } if getSettingBool(clonedSettings, "monochrome", e.cfg.monochrome != 0) { e.cfg.monochrome = 1 } if getSettingBool(clonedSettings, "enable-fwd-kf", e.cfg.fwd_kf_enabled != 0) { e.cfg.fwd_kf_enabled = 1 } if getSettingBool(clonedSettings, "kf-disabled", false) { e.cfg.kf_mode = C.AOM_KF_DISABLED } // integer settings type uintSettingPair struct { p *C.uint n string } for _, s := range []uintSettingPair{ {&e.cfg.g_threads, "threads"}, {&e.cfg.g_lag_in_frames, "lag-in-frames"}, {&e.cfg.g_forced_max_frame_width, "forced_max_frame_width"}, {&e.cfg.g_forced_max_frame_height, "forced_max_frame_height"}, {&e.cfg.rc_dropframe_thresh, "drop-frame"}, {&e.cfg.rc_resize_mode, "resize-mode"}, {&e.cfg.rc_resize_denominator, "resize-denominator"}, {&e.cfg.rc_resize_kf_denominator, "resize-kf-denominator"}, {(*C.uint)(&e.cfg.rc_superres_mode), "superres-mode"}, {&e.cfg.rc_superres_denominator, "superres-denominator"}, {&e.cfg.rc_superres_kf_denominator, "superres-kf-denominator"}, {&e.cfg.rc_superres_qthresh, "superres-qthresh"}, {&e.cfg.rc_superres_kf_qthresh, "superres-kf-qthresh"}, {&e.cfg.rc_target_bitrate, "target-bitrate"}, {&e.cfg.rc_min_quantizer, "min-q"}, {&e.cfg.rc_max_quantizer, "max-q"}, {&e.cfg.rc_undershoot_pct, "undershoot-pct"}, {&e.cfg.rc_overshoot_pct, "overshoot-pct"}, {&e.cfg.rc_buf_sz, "buf-sz"}, {&e.cfg.rc_buf_initial_sz, "buf-initial-sz"}, {&e.cfg.rc_buf_optimal_sz, "buf-optimal-sz"}, //{&e.cfg.rc_2pass_vbr_bias_pct, "bias-pct"}, //{&e.cfg.rc_2pass_vbr_minsection_pct, "minsection-pct"}, //{&e.cfg.rc_2pass_vbr_maxsection_pct, "maxsection-pct"}, {&e.cfg.kf_min_dist, "kf-min-dist"}, {&e.cfg.kf_max_dist, "kf-max-dist"}, {&e.cfg.sframe_dist, "sframe-dist"}, {&e.cfg.sframe_mode, "sframe-mode"}, {&e.cfg.use_fixed_qp_offsets, "use-fixed-qp-offsets"}, } { //todo: unset setting from map *s.p = C.uint(getSettingUnsigned(clonedSettings, s.n, uint(*s.p))) } // string/enum settings endUsage := getSettingString(clonedSettings, "end-usage", "vbr") switch endUsage { case "vbr": e.cfg.rc_end_usage = C.AOM_VBR case "cbr": e.cfg.rc_end_usage = C.AOM_CBR case "cq": e.cfg.rc_end_usage = C.AOM_CQ case "q": e.cfg.rc_end_usage = C.AOM_Q default: return nil, errors.New("unknown end-usage setting: " + endUsage) } //TODO: find all settings not set on AV1 encoder and place them on e.cfg if aomErr = C.aom_codec_enc_init_ver(&e.codec, encoder, &e.cfg, flags, C.AOM_ENCODER_ABI_VERSION); aomErr != 0 { return nil, fmt.Errorf("failed to initialize encoder: err %d %s", aomErr, C.GoString(e.codec.err_detail)) } e.free = append(e.free, func() { C.aom_codec_destroy(&e.codec) }) if properties.FullColorRange { if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_COLOR_RANGE, C.AOM_CR_FULL_RANGE); aomErr != 0 { return nil, fmt.Errorf("failed to set color range") } } else { if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_COLOR_RANGE, C.AOM_CR_STUDIO_RANGE); aomErr != 0 { return nil, fmt.Errorf("failed to set color range") } } //??? aomenc does this if properties.ColorSpace.BitDepth == 12 { if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_CHROMA_SUBSAMPLING_X, C.uint(decH>>1)); aomErr != 0 { return nil, fmt.Errorf("failed to set chroma subsampling X") } if aomErr = C.aom_codec_control_uint(&e.codec, C.AV1E_SET_CHROMA_SUBSAMPLING_Y, C.uint(decV>>1)); aomErr != 0 { return nil, fmt.Errorf("failed to set chroma subsampling Y") } } //TODO: fill all controlSettings := map[string]C.int{ "cpu-used": C.AOME_SET_CPUUSED, "auto-alt-ref": C.AOME_SET_ENABLEAUTOALTREF, "sharpness": C.AOME_SET_SHARPNESS, "row-mt": C.AV1E_SET_ROW_MT, //"fp-mt": C.AV1E_SET_FP_MT, "tile-columns": C.AV1E_SET_TILE_COLUMNS, "tile-rows": C.AV1E_SET_TILE_ROWS, "cq-level": C.AOME_SET_CQ_LEVEL, "max-reference-frames": C.AV1E_SET_MAX_REFERENCE_FRAMES, } for key, controlKey := range controlSettings { if _, ok := clonedSettings[key]; ok { val := getSettingUnsigned[uint](clonedSettings, key, 0) if aomErr = C.aom_codec_control_uint(&e.codec, controlKey, C.uint(val)); aomErr != 0 { return nil, fmt.Errorf("error setting parameter %s: %s", key, C.GoString(C.aom_codec_error_detail(&e.codec))) } } } if fgt := getSettingString(clonedSettings, "film-grain-table", ""); fgt != "" { strVal := C.CString(fgt) defer C.free(unsafe.Pointer(strVal)) if aomErr = C.aom_codec_control_charptr(&e.codec, C.AV1E_SET_FILM_GRAIN_TABLE, strVal); aomErr != 0 { return nil, fmt.Errorf("error setting FILM_GRAIN_TABLE parameter: %s", C.GoString(C.aom_codec_error_detail(&e.codec))) } } for k, v := range clonedSettings { if err := func() error { var strVal *C.char if val, ok := v.(string); ok { strVal = C.CString(val) } else if val, ok := v.(int); ok { strVal = C.CString(strconv.FormatInt(int64(val), 10)) } else if val, ok := v.(int64); ok { strVal = C.CString(strconv.FormatInt(val, 10)) } else if val, ok := v.(uint); ok { strVal = C.CString(strconv.FormatUint(uint64(val), 10)) } else if val, ok := v.(uint64); ok { strVal = C.CString(strconv.FormatUint(val, 10)) } else if val, ok := v.(bool); ok { if val { strVal = C.CString("1") } else { strVal = C.CString("0") } } if strVal != nil { defer C.free(unsafe.Pointer(strVal)) } else { return fmt.Errorf("could not get parameter %s", k) } strKey := C.CString(k) defer C.free(unsafe.Pointer(strKey)) if ret := C.aom_codec_set_option(&e.codec, strKey, strVal); ret != 0 { if ret == C.AOM_CODEC_INVALID_PARAM { return fmt.Errorf("bad parameter value %s for %s: %s", C.GoString(strVal), k, C.GoString(C.aom_codec_error_detail(&e.codec))) } else if ret == C.AOM_CODEC_ERROR { return fmt.Errorf("error setting parameter %s: %s", k, C.GoString(C.aom_codec_error_detail(&e.codec))) } else { return fmt.Errorf("error setting parameter %s: %s", k, C.GoString(C.aom_codec_error_detail(&e.codec))) } } return nil }(); err != nil { return nil, err } } if e.w, err = obuwriter.NewWriter(w, properties.Width, properties.Height, 0x31305641, timeBase); err != nil { return nil, err } return e, nil } func (e *Encoder) EncodeStream(stream *frame.Stream) error { for f := range stream.Channel() { if err := e.Encode(f); err != nil { return err } } return e.Flush() } func (e *Encoder) Encode(f frame.Frame) error { /*luma := f.GetLuma() cb := f.GetCb() cr := f.GetCr() copy(unsafe.Slice((*byte)(e.raw.planes[C.AOM_PLANE_Y]), len(luma)), luma) copy(unsafe.Slice((*byte)(e.raw.planes[C.AOM_PLANE_U]), len(cb)), cb) copy(unsafe.Slice((*byte)(e.raw.planes[C.AOM_PLANE_V]), len(cr)), cr) */ copy(e.rawBuffer, f.GetJoint()) if _, err := e.encodeFrame(f.PTS(), f.NextPTS(), e.raw); err != nil { return err } runtime.KeepAlive(f) return nil } func (e *Encoder) encodeFrame(pts, nextPts int64, raw *C.aom_image_t) (pkts int, err error) { var aomErr C.aom_codec_err_t if aomErr = C.aom_codec_encode(&e.codec, raw, C.long(pts), C.ulong(nextPts-pts), 0); aomErr != C.AOM_CODEC_OK { if aomErr == C.AOM_CODEC_INCAPABLE { return 0, errors.New("error encoding frame: AOM_CODEC_INCAPABLE") } else if aomErr == C.AOM_CODEC_INVALID_PARAM { return 0, errors.New("error encoding frame: AOM_CODEC_INVALID_PARAM") } else if aomErr == C.AOM_CODEC_ERROR { return 0, errors.New("error encoding frame: AOM_CODEC_ERROR") } else { return 0, errors.New("error encoding frame") } } if raw != nil { e.framesIn++ } var quant64 C.int if e.framesIn >= int(e.cfg.g_lag_in_frames) { //TODO: maybe call this on new packets? if aomErr = C.aom_codec_control_intptr(&e.codec, C.AOME_GET_LAST_QUANTIZER_64, &quant64); aomErr != C.AOM_CODEC_OK { return 0, errors.New("error getting LAST_QUANTIZER_64") } } var iter C.aom_codec_iter_t for { pkt := C.aom_codec_get_cx_data(&e.codec, &iter) if pkt == nil { break } pkts++ if pkt.kind == C.AOM_CODEC_CX_FRAME_PKT { partitionId := int(C.aom_get_pkt_partition_id(pkt)) if partitionId > 0 { return 0, errors.New("partition id not supported") } packetPts := uint64(C.aom_get_pkt_pts(pkt)) buf := unsafe.Slice((*byte)(C.aom_get_pkt_buf(pkt)), int(C.aom_get_pkt_sz(pkt))) flags := C.aom_get_pkt_flags(pkt) e.frameStatistics[frame.FrameStatisticsKeyNumber] = e.framesOut e.frameStatistics[frame.FrameStatisticsKeyPts] = int64(packetPts) e.frameStatistics[frame.FrameStatisticsKeyQuantizer] = int(quant64) e.frameStatistics[frame.FrameStatisticsKeySize] = len(buf) e.frameStatistics[frame.FrameStatisticsKeyIsKeyFrame] = false e.frameStatistics[frame.FrameStatisticsKeyIsIntraFrame] = false var flagStr []string if flags&C.AOM_FRAME_IS_KEY > 0 { e.frameStatistics[frame.FrameStatisticsKeyIsKeyFrame] = true flagStr = append(flagStr, "K-Frame") } if flags&C.AOM_FRAME_IS_DROPPABLE > 0 { flagStr = append(flagStr, "droppable") } if flags&C.AOM_FRAME_IS_INTRAONLY > 0 { e.frameStatistics[frame.FrameStatisticsKeyIsIntraFrame] = true flagStr = append(flagStr, "I-Frame") } if flags&C.AOM_FRAME_IS_SWITCH > 0 { flagStr = append(flagStr, "S-Frame") } if flags&C.AOM_FRAME_IS_ERROR_RESILIENT > 0 { flagStr = append(flagStr, "resilient") } if flags&C.AOM_FRAME_IS_DELAYED_RANDOM_ACCESS_POINT > 0 { //Forward keyframe e.frameStatistics[frame.FrameStatisticsKeyIsKeyFrame] = true flagStr = append(flagStr, "delayedrandom") } e.logger.Printf("libaom: partition = %d, n = %d, pts = %d, quant64 = %d, bytes = %d, flags = %s", partitionId, e.framesOut, packetPts, quant64, len(buf), strings.Join(flagStr, ", ")) if err = e.w.WriteFrameBytes(packetPts, buf); err != nil { return pkts, err } e.framesOut++ runtime.KeepAlive(buf) } else { log.Printf("kind %d", pkt.kind) } } return pkts, nil } func (e *Encoder) Statistics() frame.Statistics { if len(e.frameStatistics) > 0 { return frame.Statistics{ frame.StatisticsKeyFramesIn: e.framesIn, frame.StatisticsKeyFramesOut: e.framesOut, frame.StatisticsKeyLastFrameOut: maps.Clone(e.frameStatistics), } } else { return frame.Statistics{ frame.StatisticsKeyFramesIn: e.framesIn, frame.StatisticsKeyFramesOut: e.framesOut, } } } func (e *Encoder) Flush() error { var pkts int var err error for { if pkts, err = e.encodeFrame(-1, 0, nil); err != nil { return err } if pkts == 0 { break } } _ = e.w.WriteLength(uint32(e.framesIn)) return nil } func (e *Encoder) Close() { if e.cleaned.Swap(true) == false { e.resourcePinner.Unpin() if e.raw != nil { e.raw = nil } if e.rawBuffer != nil { e.rawBuffer = nil } C.aom_codec_destroy(&e.codec) } } func (e *Encoder) Version() string { return Version() } func getSettingBool(m map[string]any, name string, fallback bool) bool { if v, ok := m[name]; ok { if val, ok := v.(string); ok { delete(m, name) return val == "false" || val == "f" || val == "n" } if val, ok := v.(int); ok { delete(m, name) return val != 0 } if val, ok := v.(int64); ok { delete(m, name) return val != 0 } if val, ok := v.(uint); ok { delete(m, name) return val != 0 } if val, ok := v.(uint64); ok { delete(m, name) return val != 0 } return true } return fallback } func getSettingString(m map[string]any, name string, fallback string) string { if v, ok := m[name]; ok { if val, ok := v.(string); ok { delete(m, name) return val } if val, ok := v.(int); ok { delete(m, name) return strconv.FormatInt(int64(val), 10) } if val, ok := v.(int64); ok { delete(m, name) return strconv.FormatInt(val, 10) } if val, ok := v.(uint); ok { delete(m, name) return strconv.FormatUint(uint64(val), 10) } if val, ok := v.(uint64); ok { delete(m, name) return strconv.FormatUint(val, 10) } if val, ok := v.(bool); ok { delete(m, name) if val { return "1" } else { return "0" } } } return fallback } func getSettingUnsigned[T constraints.Unsigned](m map[string]any, name string, fallback T) T { if v, ok := m[name]; ok { if val, ok := v.(string); ok { if intVal, err := strconv.ParseUint(val, 10, 0); err != nil { delete(m, name) return T(intVal) } else { return fallback } } if val, ok := v.(int); ok { delete(m, name) return T(val) } if val, ok := v.(int64); ok { delete(m, name) return T(val) } if val, ok := v.(uint); ok { delete(m, name) return T(val) } if val, ok := v.(uint64); ok { delete(m, name) return T(val) } if val, ok := v.(C.int); ok { delete(m, name) return T(val) } if val, ok := v.(C.uint); ok { delete(m, name) return T(val) } if val, ok := v.(bool); ok { delete(m, name) if val { return 1 } else { return 0 } } } return fallback }