DataHoarder
6ea3e971bb
All checks were successful
continuous-integration/drone/push Build is passing
254 lines
5.3 KiB
Go
254 lines
5.3 KiB
Go
package frame
|
|
|
|
import (
|
|
"git.gammaspectra.live/S.O.N.G/Ignite/color"
|
|
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type Stream struct {
|
|
properties StreamProperties
|
|
channel <-chan Frame
|
|
lock atomic.Bool
|
|
}
|
|
|
|
func NewStream(properties StreamProperties) (*Stream, chan<- Frame) {
|
|
c := make(chan Frame)
|
|
s := &Stream{
|
|
properties: properties,
|
|
channel: c,
|
|
}
|
|
return s, c
|
|
}
|
|
|
|
type StreamProperties struct {
|
|
// Width could be not populated until the first frame is read. Frame can contain different settings.
|
|
Width int `json:"width" yaml:"width"`
|
|
// Height could be not populated until the first frame is read. Frame can contain different settings.
|
|
Height int `json:"height" yaml:"height"`
|
|
// PixelAspectRatio could be not populated until the first frame is read. Frame can contain different settings.
|
|
PixelAspectRatio utilities.Ratio `json:"par" yaml:"par"`
|
|
// ColorSpace could be not populated until the first frame is read. Frame can contain different settings.
|
|
ColorSpace color.Space `json:"colorspace" yaml:"colorspace"`
|
|
FrameRate utilities.Ratio `json:"framerate" yaml:"framerate"`
|
|
FullColorRange bool `json:"fullrange" yaml:"fullrange"`
|
|
VFR bool `json:"vfr" yaml:"vfr"`
|
|
}
|
|
|
|
func (p StreamProperties) TimeBase() utilities.Ratio {
|
|
timeBase := p.FrameRate.Reciprocal()
|
|
if p.VFR {
|
|
timeBase = utilities.NewRatio(1, 1000)
|
|
}
|
|
|
|
return timeBase
|
|
}
|
|
|
|
func (p StreamProperties) FrameProperties() Properties {
|
|
return Properties{
|
|
Width: p.Width,
|
|
Height: p.Height,
|
|
PixelAspectRatio: p.PixelAspectRatio,
|
|
ColorSpace: p.ColorSpace,
|
|
FullColorRange: p.FullColorRange,
|
|
}
|
|
}
|
|
|
|
func (s *Stream) Properties() StreamProperties {
|
|
return s.properties
|
|
}
|
|
|
|
// Channel gets the Frame channel, and locks the input
|
|
func (s *Stream) Channel() <-chan Frame {
|
|
if s.Lock() {
|
|
return s.channel
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Lock locks the input, and returns success
|
|
func (s *Stream) Lock() bool {
|
|
return !s.lock.Swap(true)
|
|
}
|
|
|
|
// Unlock unlocks the input, and returns the old value
|
|
func (s *Stream) Unlock() bool {
|
|
return s.lock.Swap(false)
|
|
}
|
|
|
|
// Copy copies the stream n times
|
|
func (s *Stream) Copy(n int) []*Stream {
|
|
if !s.Lock() {
|
|
return nil
|
|
}
|
|
slice := make([]*Stream, n)
|
|
sliceChannels := make([]chan<- Frame, n)
|
|
for i := range slice {
|
|
slice[i], sliceChannels[i] = NewStream(s.Properties())
|
|
}
|
|
|
|
go func() {
|
|
defer func() {
|
|
for _, sliceItem := range sliceChannels {
|
|
close(sliceItem)
|
|
}
|
|
}()
|
|
|
|
for f := range s.channel {
|
|
for _, sliceItem := range sliceChannels {
|
|
sliceItem <- f
|
|
}
|
|
}
|
|
}()
|
|
|
|
return slice
|
|
}
|
|
|
|
// Slice produces a slice of the stream, and locks the input
|
|
func (s *Stream) Slice(start, end int) *Stream {
|
|
if end < start {
|
|
return nil
|
|
}
|
|
if !s.Lock() {
|
|
return nil
|
|
}
|
|
slice, channel := NewStream(s.Properties())
|
|
|
|
var index int
|
|
go func() {
|
|
defer func() {
|
|
//empty source channel
|
|
for range s.channel {
|
|
|
|
}
|
|
}()
|
|
defer close(channel)
|
|
for f := range s.channel {
|
|
if index >= end {
|
|
break
|
|
}
|
|
|
|
if index >= start {
|
|
channel <- f
|
|
}
|
|
index++
|
|
}
|
|
}()
|
|
|
|
return slice
|
|
}
|
|
|
|
// Split produces several Stream across each split point, and locks the input.
|
|
// For example, splits = [2, 5, 20] will produce 4 Stream, [0-1], [2-4], [5-19], [20-...]
|
|
func (s *Stream) Split(splits ...int) []*Stream {
|
|
if len(splits) == 0 {
|
|
return []*Stream{s}
|
|
} else if !s.Lock() {
|
|
return nil
|
|
}
|
|
slice := make([]*Stream, len(splits)+1)
|
|
sliceChannels := make([]chan<- Frame, len(splits)+1)
|
|
for i := range slice {
|
|
slice[i], sliceChannels[i] = NewStream(s.Properties())
|
|
}
|
|
|
|
var index int
|
|
go func() {
|
|
defer func() {
|
|
//empty source channel
|
|
for range s.channel {
|
|
|
|
}
|
|
}()
|
|
defer func() {
|
|
for _, sC := range sliceChannels {
|
|
close(sC)
|
|
}
|
|
}()
|
|
|
|
for f := range s.channel {
|
|
for len(splits) > 0 && index >= splits[0] {
|
|
close(sliceChannels[0])
|
|
splits = splits[1:]
|
|
sliceChannels = sliceChannels[1:]
|
|
slice = slice[1:]
|
|
}
|
|
|
|
sliceChannels[0] <- f
|
|
|
|
index++
|
|
}
|
|
}()
|
|
|
|
return slice
|
|
}
|
|
|
|
// Sample samples frames every each Frame, and locks the input
|
|
func (s *Stream) Sample(each int) *Stream {
|
|
if !s.Lock() {
|
|
return nil
|
|
}
|
|
slice, channel := NewStream(s.Properties())
|
|
|
|
var index int
|
|
go func() {
|
|
defer close(channel)
|
|
for f := range s.channel {
|
|
if index%each == 0 {
|
|
channel <- f
|
|
}
|
|
index++
|
|
}
|
|
}()
|
|
|
|
return slice
|
|
}
|
|
|
|
// Monochrome makes stream luma-only
|
|
func (s *Stream) Monochrome() *Stream {
|
|
if !s.Lock() {
|
|
return nil
|
|
}
|
|
|
|
props := s.Properties()
|
|
props.ColorSpace.ChromaSampling.A = 0
|
|
props.ColorSpace.ChromaSampling.B = 0
|
|
|
|
slice, channel := NewStream(props)
|
|
|
|
go func() {
|
|
defer close(channel)
|
|
for f := range s.channel {
|
|
frameProps := f.Properties()
|
|
frameProps.ColorSpace.ChromaSampling.A = 0
|
|
frameProps.ColorSpace.ChromaSampling.B = 0
|
|
|
|
switch typedFrame := f.(type) {
|
|
case TypedFrame[uint8]:
|
|
channel <- &fUint8{
|
|
properties: frameProps,
|
|
Pts: typedFrame.PTS(),
|
|
NextPts: typedFrame.NextPTS(),
|
|
Y: typedFrame.GetNativeLuma(),
|
|
Cb: nil,
|
|
Cr: nil,
|
|
}
|
|
case TypedFrame[uint16]:
|
|
channel <- &fUint16{
|
|
properties: frameProps,
|
|
Pts: typedFrame.PTS(),
|
|
NextPts: typedFrame.NextPTS(),
|
|
Y: typedFrame.GetNativeLuma(),
|
|
Cb: nil,
|
|
Cr: nil,
|
|
}
|
|
default:
|
|
panic("unknown frame type")
|
|
}
|
|
}
|
|
}()
|
|
|
|
return slice
|
|
}
|