repochecker/videos: Also check grammar in subtitles
This commit is contained in:
parent
acdf0a6261
commit
5d716106c4
6 changed files with 192 additions and 105 deletions
|
|
@ -1,120 +1,24 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
type VideoInfo struct {
|
||||
Streams []struct {
|
||||
CodecType string `json:"codec_type"`
|
||||
CodecName string `json:"codec_name"`
|
||||
CodecLongName string `json:"codec_long_name"`
|
||||
Duration string
|
||||
NbFrames string `json:"nb_frames"`
|
||||
Width int
|
||||
Height int
|
||||
} `json:"streams"`
|
||||
var hooks *sync.CheckHooks
|
||||
|
||||
func RegisterChecksHooks(h *sync.CheckHooks) {
|
||||
hooks = h
|
||||
|
||||
h.RegisterExerciceHook(CheckResolutionVideo)
|
||||
}
|
||||
|
||||
func CheckResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
i, ok := sync.GlobalImporter.(sync.LocalImporter)
|
||||
if !ok {
|
||||
log.Printf("Unable to load `videos-rules.so` as the current Importer is not a LocalImporter (%T).", sync.GlobalImporter)
|
||||
return
|
||||
}
|
||||
|
||||
if len(e.VideoURI) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
path, err := url.PathUnescape(e.VideoURI[9:])
|
||||
if err != nil {
|
||||
path = e.VideoURI
|
||||
}
|
||||
|
||||
data, err := ffmpeg.Probe(i.GetLocalPath(path))
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("unable to open %q: %w", path, err))
|
||||
return
|
||||
}
|
||||
|
||||
vInfo := &VideoInfo{}
|
||||
err = json.Unmarshal([]byte(data), vInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
video_seen := false
|
||||
subtitles_seen := false
|
||||
for _, s := range vInfo.Streams {
|
||||
if s.CodecType == "video" {
|
||||
video_seen = true
|
||||
if (s.Width > 1920 || s.Height > 1080) && !exceptions.HasException(":size:above_maximum") {
|
||||
errs = append(errs, fmt.Errorf("video track is too wide: %dx%d (maximum allowed: 1920x1080)", s.Width, s.Height))
|
||||
}
|
||||
|
||||
if s.CodecName != "h264" {
|
||||
errs = append(errs, fmt.Errorf("video codec has to be H264 (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
|
||||
duration, err := strconv.ParseFloat(s.Duration, 64)
|
||||
if err == nil {
|
||||
if duration < 45 && !exceptions.HasException(":duration:too_short") {
|
||||
errs = append(errs, fmt.Errorf("video is too short"))
|
||||
}
|
||||
if duration > 450 && !exceptions.HasException(":duration:too_long") {
|
||||
errs = append(errs, fmt.Errorf("video is too long"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("invalid track duration: %q", s.Duration))
|
||||
}
|
||||
} else if s.CodecType == "subtitle" {
|
||||
subtitles_seen = true
|
||||
|
||||
if s.CodecName != "mov_text" {
|
||||
errs = append(errs, fmt.Errorf("subtitle format has to be MOV text/3GPP Timed Text (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
|
||||
nbframes, err := strconv.ParseInt(s.NbFrames, 10, 64)
|
||||
if err == nil {
|
||||
if nbframes < 5 && !exceptions.HasException(":subtitle:tiny") {
|
||||
errs = append(errs, fmt.Errorf("too few subtitles"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("invalid number of frame: %q", s.NbFrames))
|
||||
}
|
||||
} else if s.CodecType == "audio" {
|
||||
if !exceptions.HasException(":audio:allowed") {
|
||||
errs = append(errs, fmt.Errorf("an audio track is present, use subtitle for explainations"))
|
||||
}
|
||||
|
||||
if s.CodecName != "aac" {
|
||||
errs = append(errs, fmt.Errorf("audio codec has to be AAC (Advanced Audio Coding) (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("unknown track found of type %q", s.CodecType))
|
||||
}
|
||||
}
|
||||
|
||||
if !video_seen {
|
||||
errs = append(errs, fmt.Errorf("no video track found"))
|
||||
}
|
||||
if !subtitles_seen && !exceptions.HasException(":subtitle:no_track") {
|
||||
errs = append(errs, fmt.Errorf("no subtitles track found"))
|
||||
for _, err := range checkResolutionVideo(e, exceptions) {
|
||||
errs = append(errs, fmt.Errorf("resolution.mp4: %w", err))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func RegisterChecksHooks(h *sync.CheckHooks) {
|
||||
h.RegisterExerciceHook(CheckResolutionVideo)
|
||||
}
|
||||
|
|
|
|||
47
repochecker/videos/subtitles.go
Normal file
47
repochecker/videos/subtitles.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/asticode/go-astisub"
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
)
|
||||
|
||||
func CheckGrammarSubtitleTrack(path string, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
tmpfile, err := ioutil.TempFile("", "resolution-*.srt")
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("unable to create a temporary file: %w", err))
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
// Extract subtitles
|
||||
err = ffmpeg.Input(path).
|
||||
Output(tmpfile.Name(), ffmpeg.KwArgs{"map": "0:s:0"}).
|
||||
OverWriteOutput().Run()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("ffmpeg returns an error when extracting subtitles track: %w", err))
|
||||
}
|
||||
|
||||
subtitles, err := astisub.OpenFile(tmpfile.Name())
|
||||
if err != nil {
|
||||
log.Println("Unable to open subtitles file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
var lines []string
|
||||
for _, item := range subtitles.Items {
|
||||
lines = append(lines, item.String())
|
||||
}
|
||||
for _, e := range hooks.CallCustomHook("CheckGrammar", strings.Join(lines, "\n"), exceptions) {
|
||||
errs = append(errs, fmt.Errorf("subtitle-track: %w", e))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
122
repochecker/videos/video.go
Normal file
122
repochecker/videos/video.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
type VideoInfo struct {
|
||||
Streams []struct {
|
||||
CodecType string `json:"codec_type"`
|
||||
CodecName string `json:"codec_name"`
|
||||
CodecLongName string `json:"codec_long_name"`
|
||||
Duration string
|
||||
NbFrames string `json:"nb_frames"`
|
||||
Width int
|
||||
Height int
|
||||
} `json:"streams"`
|
||||
}
|
||||
|
||||
func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
i, ok := sync.GlobalImporter.(sync.LocalImporter)
|
||||
if !ok {
|
||||
log.Printf("Unable to load `videos-rules.so` as the current Importer is not a LocalImporter (%T).", sync.GlobalImporter)
|
||||
return
|
||||
}
|
||||
|
||||
if len(e.VideoURI) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Filter exceptions to only keep related to resolution.mp4
|
||||
exceptions = exceptions.GetFileExceptions("resolution.mp4")
|
||||
|
||||
path, err := url.PathUnescape(e.VideoURI[9:])
|
||||
if err != nil {
|
||||
path = e.VideoURI
|
||||
}
|
||||
path = i.GetLocalPath(path)
|
||||
|
||||
data, err := ffmpeg.Probe(path)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("unable to open %q: %w", path, err))
|
||||
return
|
||||
}
|
||||
|
||||
vInfo := &VideoInfo{}
|
||||
err = json.Unmarshal([]byte(data), vInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
video_seen := false
|
||||
subtitles_seen := false
|
||||
for _, s := range vInfo.Streams {
|
||||
if s.CodecType == "video" {
|
||||
video_seen = true
|
||||
if (s.Width > 1920 || s.Height > 1080) && !exceptions.HasException(":size:above_maximum") {
|
||||
errs = append(errs, fmt.Errorf("video track is too wide: %dx%d (maximum allowed: 1920x1080)", s.Width, s.Height))
|
||||
}
|
||||
|
||||
if s.CodecName != "h264" {
|
||||
errs = append(errs, fmt.Errorf("video codec has to be H264 (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
|
||||
duration, err := strconv.ParseFloat(s.Duration, 64)
|
||||
if err == nil {
|
||||
if duration < 45 && !exceptions.HasException(":duration:too_short") {
|
||||
errs = append(errs, fmt.Errorf("video is too short"))
|
||||
}
|
||||
if duration > 450 && !exceptions.HasException(":duration:too_long") {
|
||||
errs = append(errs, fmt.Errorf("video is too long"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("invalid track duration: %q", s.Duration))
|
||||
}
|
||||
} else if s.CodecType == "subtitle" {
|
||||
subtitles_seen = true
|
||||
|
||||
if s.CodecName != "mov_text" {
|
||||
errs = append(errs, fmt.Errorf("subtitle format has to be MOV text/3GPP Timed Text (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
|
||||
nbframes, err := strconv.ParseInt(s.NbFrames, 10, 64)
|
||||
if err == nil {
|
||||
if nbframes < 5 && !exceptions.HasException(":subtitle:tiny") {
|
||||
errs = append(errs, fmt.Errorf("too few subtitles"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("invalid number of frame: %q", s.NbFrames))
|
||||
}
|
||||
} else if s.CodecType == "audio" {
|
||||
if !exceptions.HasException(":audio:allowed") {
|
||||
errs = append(errs, fmt.Errorf("an audio track is present, use subtitle for explainations"))
|
||||
}
|
||||
|
||||
if s.CodecName != "aac" {
|
||||
errs = append(errs, fmt.Errorf("audio codec has to be AAC (Advanced Audio Coding) (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("unknown track found of type %q", s.CodecType))
|
||||
}
|
||||
}
|
||||
|
||||
if !video_seen {
|
||||
errs = append(errs, fmt.Errorf("no video track found"))
|
||||
}
|
||||
if !subtitles_seen && !exceptions.HasException(":subtitle:no_track") {
|
||||
errs = append(errs, fmt.Errorf("no subtitles track found"))
|
||||
} else if subtitles_seen {
|
||||
errs = append(errs, CheckGrammarSubtitleTrack(path, exceptions)...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in a new issue