1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package ffmpeg
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"[project]/logger"
"[project]/util"
)
func (b *baseMpeg) GetVttThumb(src string, dstDir string, interval float64, height int) (string, error) {
// first videot track
vtracks, err := b.GetVideoTracks(src)
if err != nil {
return "", fmt.Errorf("Error getting video tracks: %v", err)
}
if len(vtracks) == 0 {
return "", fmt.Errorf("No valid video tracks found")
}
videoTrack := vtracks[0]
logger.INFO.Printf("Using video track %d for thumbnail extraction", videoTrack.Index)
// Get video duration to calculate number of thumbnails
duration, err := b.GetVidLen(src)
if err != nil {
return "", fmt.Errorf("Error getting video duration: %v", err)
}
// Calculate grid dimensions
cols := 5 // Standard grid width
perSheet := 100 // Max thumbs per sprite sheet
rows := perSheet / cols
// Calculate thumbnail FPS (1 thumbnail every 'interval' seconds = 1/interval fps)
fps := 1.0 / interval
// ffmpeg -i input.mkv -vf "fps=1/interval,scale=-2:100,tile=5x20" sprite_%03d.jpg
spritePattern := filepath.Join(dstDir, "sprite_%03d.jpg")
vf := fmt.Sprintf("fps=%.6f,scale=-2:%d,tile=%dx%d", fps, height, cols, rows)
_, err = util.RunCmd(*exec.Command("ffmpeg",
"-i", src,
"-map", fmt.Sprintf("0:%d", videoTrack.Index),
"-vf", vf,
"-y",
spritePattern,
))
if err != nil {
return "", fmt.Errorf("Error creating sprite sheets: %v", err)
}
// Get list of generated sprite sheets
spriteFiles, err := filepath.Glob(filepath.Join(dstDir, "sprite_*.jpg"))
if err != nil {
return "", fmt.Errorf("Error listing sprite sheets: %v", err)
}
if len(spriteFiles) == 0 {
return "", fmt.Errorf("No sprite sheets were generated")
}
// Overestimated total number of thumbnails (loop will break early if exceeding duration)
numThumbs := len(spriteFiles) * perSheet
// Generate VTT file
vttBody := "WEBVTT\n\n"
// Calculate individual thumbnail dimensions
sw, sh, err := util.GetImageDimensions(spriteFiles[0])
if err != nil {
return "", fmt.Errorf("Error getting sprite dimensions: %v", err)
}
thumbWidth := sw / cols
thumbHeight := sh / rows
// Generate VTT entries for each thumbnail
for i := range numThumbs {
sheetIdx := i / perSheet
gridIdx := i % perSheet
row := gridIdx / cols
col := gridIdx % cols
startTime := float64(i) * interval
endTime := startTime + interval
if endTime > duration {
endTime = duration
}
// Stop if exceeded the video duration
if startTime >= duration {
break
}
spriteName := filepath.Base(spriteFiles[sheetIdx])
x := col * thumbWidth
y := row * thumbHeight
vttBody += fmt.Sprintf("Thumb %d\n", i+1)
vttBody += fmt.Sprintf("%s --> %s\n", util.FormatVttTime(startTime), util.FormatVttTime(endTime))
vttBody += fmt.Sprintf("%s#xywh=%d,%d,%d,%d\n\n", spriteName, x, y, thumbWidth, thumbHeight)
}
// Write VTT file
vtt := filepath.Join(dstDir, "thumbnails.vtt")
if err := os.WriteFile(vtt, []byte(vttBody), 0644); err != nil {
return "", fmt.Errorf("Error writing VTT file: %v", err)
}
return vtt, nil
}
|