Skip to content

Commit 60e0e42

Browse files
committed
feat: alternative mpdecimate modet (slow)
1 parent 425598b commit 60e0e42

File tree

1 file changed

+86
-31
lines changed

1 file changed

+86
-31
lines changed

main.go

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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

941942
func 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

Comments
 (0)