@@ -935,8 +935,9 @@ type motion struct {
935935 Score int // 0-100
936936}
937937
938- var motionTimeRegex = regexp .MustCompile (`frame:(\d+) +pts:(\d+) +pts_time:([\d\.]+)` )
939- var motionScoreRegex = regexp .MustCompile (`lavfi.scene_score=([\d\.]+)` )
938+ // var motionTimeRegex = regexp.MustCompile(`frame:(\d+) +pts:(\d+) +pts_time:([\d\.]+)`)
939+ // var motionScoreRegex = regexp.MustCompile(`lavfi.scene_score=([\d\.]+)`)
940+ var motionTimeRegexMpDecimate = regexp .MustCompile (`showinfo.+ pts_time:([\d\.]+)` )
940941
941942func motionTimeline (ctx context.Context , fpath string ) ([]motion , error ) {
942943 probeCtx , probeCancel := context .WithTimeout (ctx , time .Minute )
@@ -998,13 +999,70 @@ func motionTimeline(ctx context.Context, fpath string) ([]motion, error) {
998999 // todo: if ~1 value per second is too many, we can re-evaluate, or merge/avg/downsample them later here
9991000 // todo: configurable scene motion value
10001001
1002+ // motionCtx, motionCancel := context.WithTimeout(ctx, time.Minute)
1003+ // defer motionCancel()
1004+ // ffmpeg := exec.CommandContext(
1005+ // motionCtx,
1006+ // "ffmpeg",
1007+ // "-i", fpath,
1008+ // "-vf", fmt.Sprintf(`select='not(mod(n\,%v))',select='gte(scene\,0.02)',metadata=print`, fps),
1009+ // "-an",
1010+ // "-f", "null",
1011+ // "-",
1012+ // )
1013+ // data, err = ffmpeg.CombinedOutput()
1014+ // if err != nil {
1015+ // return nil, fmt.Errorf("failed to motion ffmpeg %v: %v (%v)", fpath, err, ffmpeg.Args)
1016+ // }
1017+
1018+ // ms := []motion{}
1019+
1020+ // scanner := bufio.NewScanner(bytes.NewReader(data))
1021+ // var (
1022+ // m motion
1023+ // hasTime bool
1024+ // hasScore bool
1025+ // )
1026+ // for scanner.Scan() {
1027+ // line := scanner.Text()
1028+ // timeMatch := motionTimeRegex.FindStringSubmatch(line)
1029+ // if timeMatch != nil {
1030+ // timeMatchF, err := strconv.ParseFloat(timeMatch[3], 64)
1031+ // if err != nil {
1032+ // // todo: warn
1033+ // continue
1034+ // }
1035+ // m.Time = int(timeMatchF)
1036+ // hasTime = true
1037+ // continue
1038+ // }
1039+ // scoreMatch := motionScoreRegex.FindStringSubmatch(line)
1040+ // if scoreMatch != nil {
1041+ // scoreMatchF, err := strconv.ParseFloat(scoreMatch[1], 64)
1042+ // if err != nil {
1043+ // // todo: warn
1044+ // continue
1045+ // }
1046+ // m.Score = int(scoreMatchF * 100)
1047+ // hasScore = true
1048+ // }
1049+
1050+ // if hasTime && hasScore {
1051+ // hasTime = false
1052+ // hasScore = false
1053+ // ms = append(ms, m)
1054+ // m = motion{}
1055+ // }
1056+ // }
1057+
1058+ // todo: this works better for motion detection imo, but it is heavy
10011059 motionCtx , motionCancel := context .WithTimeout (ctx , time .Minute )
10021060 defer motionCancel ()
10031061 ffmpeg := exec .CommandContext (
10041062 motionCtx ,
10051063 "ffmpeg" ,
10061064 "-i" , fpath ,
1007- "-vf" , fmt . Sprintf ( `select='not(mod(n\,%v))',select='gte(scene\,0.02)',metadata=print` , fps ),
1065+ "-vf" , "mpdecimate=hi=2000:lo=1000:frac=0.33,showinfo" , // todo: tune
10081066 "-an" ,
10091067 "-f" , "null" ,
10101068 "-" ,
@@ -1016,43 +1074,40 @@ func motionTimeline(ctx context.Context, fpath string) ([]motion, error) {
10161074
10171075 ms := []motion {}
10181076
1077+ secondMotionCounters := map [int ]int {}
1078+
10191079 scanner := bufio .NewScanner (bytes .NewReader (data ))
1020- var (
1021- m motion
1022- hasTime bool
1023- hasScore bool
1024- )
10251080 for scanner .Scan () {
10261081 line := scanner .Text ()
1027- timeMatch := motionTimeRegex .FindStringSubmatch (line )
1028- if timeMatch != nil {
1029- timeMatchF , err := strconv .ParseFloat (timeMatch [3 ], 64 )
1030- if err != nil {
1031- // todo: warn
1032- continue
1033- }
1034- m .Time = int (timeMatchF )
1035- hasTime = true
1082+ timeMatch := motionTimeRegexMpDecimate .FindStringSubmatch (line )
1083+ if timeMatch == nil {
10361084 continue
10371085 }
1038- scoreMatch := motionScoreRegex .FindStringSubmatch (line )
1039- if scoreMatch != nil {
1040- scoreMatchF , err := strconv .ParseFloat (scoreMatch [1 ], 64 )
1041- if err != nil {
1042- // todo: warn
1043- continue
1044- }
1045- m .Score = int (scoreMatchF * 100 )
1046- hasScore = true
1086+ timeMatchF , err := strconv .ParseFloat (timeMatch [1 ], 64 )
1087+ if err != nil {
1088+ // todo: warn
1089+ continue
10471090 }
1091+ timeMatchI := int (timeMatchF )
10481092
1049- if hasTime && hasScore {
1050- hasTime = false
1051- hasScore = false
1052- ms = append (ms , m )
1053- m = motion {}
1093+ ctr := secondMotionCounters [timeMatchI ]
1094+ ctr ++
1095+ secondMotionCounters [timeMatchI ] = ctr
1096+ }
1097+
1098+ for second , ctr := range secondMotionCounters {
1099+ score := int ((float32 (ctr ) / float32 (fps )) * 100 )
1100+ if score > 100 {
1101+ score = 100
10541102 }
1103+ ms = append (ms , motion {
1104+ Time : second ,
1105+ Score : score ,
1106+ })
10551107 }
1108+ sort .Slice (ms , func (i , j int ) bool {
1109+ return ms [i ].Time < ms [j ].Time
1110+ })
10561111
10571112 return ms , nil
10581113}
0 commit comments