use libffmpeg to parse video frame and vid information
This commit is contained in:
		@@ -29,7 +29,7 @@ Build_Backend:
 | 
				
			|||||||
  stage: build_backend
 | 
					  stage: build_backend
 | 
				
			||||||
  script:
 | 
					  script:
 | 
				
			||||||
    - cd apiGo
 | 
					    - cd apiGo
 | 
				
			||||||
    - go build -v -o openmediacenter
 | 
					    - go build -v -tags sharedffmpeg -o openmediacenter
 | 
				
			||||||
    - cp -r ../build/ ./static/
 | 
					    - cp -r ../build/ ./static/
 | 
				
			||||||
    - go build -v -tags static -o openmediacenter_full
 | 
					    - go build -v -tags static -o openmediacenter_full
 | 
				
			||||||
    - env GOOS=windows GOARCH=amd64 go build -v -tags static -o openmediacenter.exe
 | 
					    - env GOOS=windows GOARCH=amd64 go build -v -tags static -o openmediacenter.exe
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ module openmediacenter/apiGo
 | 
				
			|||||||
go 1.16
 | 
					go 1.16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						github.com/3d0c/gmf v0.0.0-20210830084021-7b27911659a2 // indirect
 | 
				
			||||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
						github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.5.0
 | 
						github.com/go-sql-driver/mysql v1.5.0
 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.0.0-beta.3
 | 
						github.com/pelletier/go-toml/v2 v2.0.0-beta.3
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
					cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
				
			||||||
 | 
					github.com/3d0c/gmf v0.0.0-20210830084021-7b27911659a2 h1:+wdkl7m6ElGhLyYxHab+uPYf57MQ4XpX6EuG/8kbkLg=
 | 
				
			||||||
 | 
					github.com/3d0c/gmf v0.0.0-20210830084021-7b27911659a2/go.mod h1:0QMRcUq2JsDECeAq7bj4h79k7XbhtTsrPUQf6G7qfPs=
 | 
				
			||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
 | 
					github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
 | 
				
			||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 | 
					github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,7 @@
 | 
				
			|||||||
package videoparser
 | 
					package videoparser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/base64"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func AppendMessage(message string) {
 | 
					func AppendMessage(message string) {
 | 
				
			||||||
@@ -33,88 +29,3 @@ func SendEvent(message string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	IndexSender.Publish(marshal)
 | 
						IndexSender.Publish(marshal)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// ext dependency support check
 | 
					 | 
				
			||||||
func checkExtDependencySupport() *ExtDependencySupport {
 | 
					 | 
				
			||||||
	var extDepsAvailable ExtDependencySupport
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	extDepsAvailable.FFMpeg = commandExists("ffmpeg")
 | 
					 | 
				
			||||||
	extDepsAvailable.MediaInfo = commandExists("mediainfo")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &extDepsAvailable
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// check if a specific system command is available
 | 
					 | 
				
			||||||
func commandExists(cmd string) bool {
 | 
					 | 
				
			||||||
	_, err := exec.LookPath(cmd)
 | 
					 | 
				
			||||||
	return err == nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// parse the thumbail picture from video file
 | 
					 | 
				
			||||||
func parseFFmpegPic(path string) (*string, error) {
 | 
					 | 
				
			||||||
	app := "ffmpeg"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cmd := exec.Command(app,
 | 
					 | 
				
			||||||
		"-hide_banner",
 | 
					 | 
				
			||||||
		"-loglevel", "panic",
 | 
					 | 
				
			||||||
		"-ss", "00:04:00",
 | 
					 | 
				
			||||||
		"-i", path,
 | 
					 | 
				
			||||||
		"-vframes", "1",
 | 
					 | 
				
			||||||
		"-q:v", "2",
 | 
					 | 
				
			||||||
		"-f", "singlejpeg",
 | 
					 | 
				
			||||||
		"pipe:1")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	stdout, err := cmd.Output()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		fmt.Println(err.Error())
 | 
					 | 
				
			||||||
		fmt.Println(string(err.(*exec.ExitError).Stderr))
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	strEncPic := base64.StdEncoding.EncodeToString(stdout)
 | 
					 | 
				
			||||||
	if strEncPic == "" {
 | 
					 | 
				
			||||||
		return nil, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	backpic64 := fmt.Sprintf("data:image/jpeg;base64,%s", strEncPic)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &backpic64, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getVideoAttributes(path string) *VideoAttributes {
 | 
					 | 
				
			||||||
	app := "mediainfo"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	arg0 := path
 | 
					 | 
				
			||||||
	arg1 := "--Output=JSON"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cmd := exec.Command(app, arg1, "-f", arg0)
 | 
					 | 
				
			||||||
	stdout, err := cmd.Output()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var t struct {
 | 
					 | 
				
			||||||
		Media struct {
 | 
					 | 
				
			||||||
			Track []struct {
 | 
					 | 
				
			||||||
				Duration string
 | 
					 | 
				
			||||||
				FileSize string
 | 
					 | 
				
			||||||
				Width    string
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = json.Unmarshal(stdout, &t)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		fmt.Println(err.Error())
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	duration, err := strconv.ParseFloat(t.Media.Track[0].Duration, 32)
 | 
					 | 
				
			||||||
	filesize, err := strconv.Atoi(t.Media.Track[0].FileSize)
 | 
					 | 
				
			||||||
	width, err := strconv.Atoi(t.Media.Track[1].Width)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ret := VideoAttributes{
 | 
					 | 
				
			||||||
		Duration: float32(duration),
 | 
					 | 
				
			||||||
		FileSize: uint(filesize),
 | 
					 | 
				
			||||||
		Width:    uint(width),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &ret
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"openmediacenter/apiGo/api/types"
 | 
						"openmediacenter/apiGo/api/types"
 | 
				
			||||||
	"openmediacenter/apiGo/config"
 | 
						"openmediacenter/apiGo/config"
 | 
				
			||||||
	"openmediacenter/apiGo/database"
 | 
						"openmediacenter/apiGo/database"
 | 
				
			||||||
 | 
						"openmediacenter/apiGo/videoparser/thumbnail"
 | 
				
			||||||
	"openmediacenter/apiGo/videoparser/tmdb"
 | 
						"openmediacenter/apiGo/videoparser/tmdb"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@@ -13,7 +14,6 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var mSettings *types.SettingsType
 | 
					var mSettings *types.SettingsType
 | 
				
			||||||
var mExtDepsAvailable *ExtDependencySupport
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// default Tag ids
 | 
					// default Tag ids
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -22,11 +22,6 @@ const (
 | 
				
			|||||||
	LowQuality = 3
 | 
						LowQuality = 3
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ExtDependencySupport struct {
 | 
					 | 
				
			||||||
	FFMpeg    bool
 | 
					 | 
				
			||||||
	MediaInfo bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type VideoAttributes struct {
 | 
					type VideoAttributes struct {
 | 
				
			||||||
	Duration float32
 | 
						Duration float32
 | 
				
			||||||
	FileSize uint
 | 
						FileSize uint
 | 
				
			||||||
@@ -35,10 +30,6 @@ type VideoAttributes struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func InitDeps(sett *types.SettingsType) {
 | 
					func InitDeps(sett *types.SettingsType) {
 | 
				
			||||||
	mSettings = sett
 | 
						mSettings = sett
 | 
				
			||||||
	// check if the extern dependencies are available
 | 
					 | 
				
			||||||
	mExtDepsAvailable = checkExtDependencySupport()
 | 
					 | 
				
			||||||
	fmt.Printf("FFMPEG support: %t\n", mExtDepsAvailable.FFMpeg)
 | 
					 | 
				
			||||||
	fmt.Printf("MediaInfo support: %t\n", mExtDepsAvailable.MediaInfo)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ReIndexVideos(path []string) {
 | 
					func ReIndexVideos(path []string) {
 | 
				
			||||||
@@ -115,58 +106,50 @@ func addVideo(videoName string, fileName string, year int) {
 | 
				
			|||||||
	var poster *string
 | 
						var poster *string
 | 
				
			||||||
	var tmdbData *tmdb.VideoTMDB
 | 
						var tmdbData *tmdb.VideoTMDB
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
 | 
						var insertid int64
 | 
				
			||||||
	// initialize defaults
 | 
					 | 
				
			||||||
	vidAtr := &VideoAttributes{
 | 
					 | 
				
			||||||
		Duration: 0,
 | 
					 | 
				
			||||||
		FileSize: 0,
 | 
					 | 
				
			||||||
		Width:    0,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	vidFolder := config.GetConfig().General.ReindexPrefix + mSettings.VideoPath
 | 
						vidFolder := config.GetConfig().General.ReindexPrefix + mSettings.VideoPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if mExtDepsAvailable.FFMpeg {
 | 
					 | 
				
			||||||
		ppic, err = parseFFmpegPic(vidFolder + fileName)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			fmt.Printf("FFmpeg error occured: %s\n", err.Error())
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			fmt.Println("successfully extracted thumbnail!!")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if mExtDepsAvailable.MediaInfo {
 | 
					 | 
				
			||||||
		atr := getVideoAttributes(vidFolder + fileName)
 | 
					 | 
				
			||||||
		if atr != nil {
 | 
					 | 
				
			||||||
			vidAtr = atr
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// if TMDB grabbing is enabled serach in api for video...
 | 
						// if TMDB grabbing is enabled serach in api for video...
 | 
				
			||||||
	if mSettings.TMDBGrabbing {
 | 
						if mSettings.TMDBGrabbing {
 | 
				
			||||||
		tmdbData = tmdb.SearchVideo(videoName, year)
 | 
							tmdbData = tmdb.SearchVideo(videoName, year)
 | 
				
			||||||
		if tmdbData != nil {
 | 
							if tmdbData != nil {
 | 
				
			||||||
			// reassign parsed pic as poster
 | 
					 | 
				
			||||||
			poster = ppic
 | 
					 | 
				
			||||||
			// and tmdb pic as thumbnail
 | 
								// and tmdb pic as thumbnail
 | 
				
			||||||
			ppic = &tmdbData.Thumbnail
 | 
								poster = &tmdbData.Thumbnail
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// parse pic from 4min frame
 | 
				
			||||||
 | 
						ppic, vinfo, err := thumbnail.Parse(vidFolder+fileName, 240)
 | 
				
			||||||
 | 
						// use parsed pic also for poster pic
 | 
				
			||||||
 | 
						if poster == nil {
 | 
				
			||||||
 | 
							poster = ppic
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Printf("FFmpeg error occured: %s\n", err.Error())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							query := `INSERT INTO videos(movie_name,movie_url) VALUES (?,?)`
 | 
				
			||||||
 | 
							err, insertid = database.Insert(query, videoName, fileName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		query := `INSERT INTO videos(movie_name,movie_url,poster,thumbnail,quality,length) VALUES (?,?,?,?,?,?)`
 | 
							query := `INSERT INTO videos(movie_name,movie_url,poster,thumbnail,quality,length) VALUES (?,?,?,?,?,?)`
 | 
				
			||||||
	err, insertId := database.Insert(query, videoName, fileName, poster, ppic, vidAtr.Width, vidAtr.Duration)
 | 
							err, insertid = database.Insert(query, videoName, fileName, ppic, poster, vinfo.Width, vinfo.Length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// add default tags
 | 
				
			||||||
 | 
							if vinfo.Width != 0 && err == nil {
 | 
				
			||||||
 | 
								insertSizeTag(uint(vinfo.Width), uint(insertid))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		fmt.Printf("Failed to insert video into db: %s\n", err.Error())
 | 
							fmt.Printf("Failed to insert video into db: %s\n", err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// add default tags
 | 
					 | 
				
			||||||
	if vidAtr.Width != 0 {
 | 
					 | 
				
			||||||
		insertSizeTag(vidAtr.Width, uint(insertId))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// add tmdb tags
 | 
						// add tmdb tags
 | 
				
			||||||
	if mSettings.TMDBGrabbing && tmdbData != nil {
 | 
						if mSettings.TMDBGrabbing && tmdbData != nil {
 | 
				
			||||||
		insertTMDBTags(tmdbData.GenreIds, insertId)
 | 
							insertTMDBTags(tmdbData.GenreIds, insertid)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	AppendMessage(fmt.Sprintf("%s - added!", videoName))
 | 
						AppendMessage(fmt.Sprintf("%s - added!", videoName))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										22
									
								
								apiGo/videoparser/thumbnail/Thumbnailparser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								apiGo/videoparser/thumbnail/Thumbnailparser.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package thumbnail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/base64"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type VidInfo struct {
 | 
				
			||||||
 | 
						Width     uint32
 | 
				
			||||||
 | 
						Height    uint32
 | 
				
			||||||
 | 
						Length    uint64
 | 
				
			||||||
 | 
						FrameRate float32
 | 
				
			||||||
 | 
						Size      int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EncodeBase64(data *[]byte, mimetype string) *string {
 | 
				
			||||||
 | 
						strEncPic := base64.StdEncoding.EncodeToString(*data)
 | 
				
			||||||
 | 
						backpic64 := fmt.Sprintf("data:%s;base64,%s", mimetype, strEncPic)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &backpic64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										183
									
								
								apiGo/videoparser/thumbnail/Thumbnailparser_shared_ffmpeg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								apiGo/videoparser/thumbnail/Thumbnailparser_shared_ffmpeg.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
				
			|||||||
 | 
					// +build sharedffmpeg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package thumbnail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/3d0c/gmf"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Parse(filename string, time uint64) (*string, *VidInfo, error) {
 | 
				
			||||||
 | 
						dta, inf, err := decodePic(filename, "png", time)
 | 
				
			||||||
 | 
						if len(dta) > 0 && err == nil {
 | 
				
			||||||
 | 
							// base64 encode picture
 | 
				
			||||||
 | 
							enc := EncodeBase64(dta, "image/png")
 | 
				
			||||||
 | 
							return enc, inf, nil
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func decodePic(srcFileName string, decodeExtension string, time uint64) (pic *[]byte, info *VidInfo, err error) {
 | 
				
			||||||
 | 
						var swsctx *gmf.SwsCtx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stat, err := os.Stat(srcFileName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// file seems to not even exist
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fileSize := stat.Size()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inputCtx, err := gmf.NewInputCtx(srcFileName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Error creating context - %s\n", err)
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer inputCtx.Free()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						srcVideoStream, err := inputCtx.GetBestStream(gmf.AVMEDIA_TYPE_VIDEO)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("No video stream found in '%s'\n", srcFileName)
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						codec, err := gmf.FindEncoder(decodeExtension)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("%s\n", err)
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cc := gmf.NewCodecCtx(codec)
 | 
				
			||||||
 | 
						defer gmf.Release(cc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cc.SetTimeBase(gmf.AVR{Num: 1, Den: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cc.SetPixFmt(gmf.AV_PIX_FMT_RGBA).SetWidth(srcVideoStream.CodecPar().Width()).SetHeight(srcVideoStream.CodecPar().Height())
 | 
				
			||||||
 | 
						if codec.IsExperimental() {
 | 
				
			||||||
 | 
							cc.SetStrictCompliance(gmf.FF_COMPLIANCE_EXPERIMENTAL)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := cc.Open(nil); err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer cc.Free()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ist, err := inputCtx.GetStream(srcVideoStream.Index())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Error getting stream - %s\n", err)
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer ist.Free()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = inputCtx.SeekFrameAt(int64(time), 0)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Error while seeking file: %s\n", err.Error())
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// convert source pix_fmt into AV_PIX_FMT_RGBA
 | 
				
			||||||
 | 
						// which is set up by codec context above
 | 
				
			||||||
 | 
						icc := srcVideoStream.CodecCtx()
 | 
				
			||||||
 | 
						if swsctx, err = gmf.NewSwsCtx(icc.Width(), icc.Height(), icc.PixFmt(), cc.Width(), cc.Height(), cc.PixFmt(), gmf.SWS_BICUBIC); err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer swsctx.Free()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						frameRate := float32(ist.GetRFrameRate().AVR().Num) / float32(ist.GetRFrameRate().AVR().Den)
 | 
				
			||||||
 | 
						inf := VidInfo{
 | 
				
			||||||
 | 
							Width:     uint32(icc.Width()),
 | 
				
			||||||
 | 
							Height:    uint32(icc.Height()),
 | 
				
			||||||
 | 
							FrameRate: frameRate,
 | 
				
			||||||
 | 
							Length:    uint64(inputCtx.Duration()),
 | 
				
			||||||
 | 
							Size:      fileSize,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info = &inf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							pkt        *gmf.Packet
 | 
				
			||||||
 | 
							frames     []*gmf.Frame
 | 
				
			||||||
 | 
							drain      int = -1
 | 
				
			||||||
 | 
							frameCount int = 0
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							if drain >= 0 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pkt, err = inputCtx.GetNextPacket()
 | 
				
			||||||
 | 
							if err != nil && err != io.EOF {
 | 
				
			||||||
 | 
								if pkt != nil {
 | 
				
			||||||
 | 
									pkt.Free()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								log.Printf("error getting next packet - %s", err)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							} else if err != nil && pkt == nil {
 | 
				
			||||||
 | 
								drain = 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pkt != nil && pkt.StreamIndex() != srcVideoStream.Index() {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							frames, err = ist.CodecCtx().Decode(pkt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("Fatal error during decoding - %s\n", err)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Decode() method doesn't treat EAGAIN and EOF as errors
 | 
				
			||||||
 | 
							// it returns empty frames slice instead. Countinue until
 | 
				
			||||||
 | 
							// input EOF or frames received.
 | 
				
			||||||
 | 
							if len(frames) == 0 && drain < 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if frames, err = gmf.DefaultRescaler(swsctx, frames); err != nil {
 | 
				
			||||||
 | 
								panic(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							packets, err := cc.Encode(frames, drain)
 | 
				
			||||||
 | 
							if len(packets) == 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							picdata := packets[0].Data()
 | 
				
			||||||
 | 
							pic = &picdata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// cleanup here
 | 
				
			||||||
 | 
							for _, p := range packets {
 | 
				
			||||||
 | 
								p.Free()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i, _ := range frames {
 | 
				
			||||||
 | 
								frames[i].Free()
 | 
				
			||||||
 | 
								frameCount++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pkt != nil {
 | 
				
			||||||
 | 
								pkt.Free()
 | 
				
			||||||
 | 
								pkt = nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// we only want to encode first picture then exit
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < inputCtx.StreamsCnt(); i++ {
 | 
				
			||||||
 | 
							st, _ := inputCtx.GetStream(i)
 | 
				
			||||||
 | 
							st.CodecCtx().Free()
 | 
				
			||||||
 | 
							st.Free()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										130
									
								
								apiGo/videoparser/thumbnail/Thumbnailparser_syscall.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								apiGo/videoparser/thumbnail/Thumbnailparser_syscall.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					// +build !sharedffmpeg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package thumbnail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ExtDependencySupport struct {
 | 
				
			||||||
 | 
						FFMpeg    bool
 | 
				
			||||||
 | 
						MediaInfo bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Parse(filename string, time uint64) (*string, *VidInfo, error) {
 | 
				
			||||||
 | 
						// check if the extern dependencies are available
 | 
				
			||||||
 | 
						mExtDepsAvailable := checkExtDependencySupport()
 | 
				
			||||||
 | 
						fmt.Printf("FFMPEG support: %t\n", mExtDepsAvailable.FFMpeg)
 | 
				
			||||||
 | 
						fmt.Printf("MediaInfo support: %t\n", mExtDepsAvailable.MediaInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var pic *string = nil
 | 
				
			||||||
 | 
						var info *VidInfo = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if mExtDepsAvailable.FFMpeg {
 | 
				
			||||||
 | 
							p, err := parseFFmpegPic(filename, time)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pic = EncodeBase64(p, "image/jpeg")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if mExtDepsAvailable.MediaInfo {
 | 
				
			||||||
 | 
							i, err := getVideoAttributes(filename)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							info = i
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return pic, info, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// check if a specific system command is available
 | 
				
			||||||
 | 
					func commandExists(cmd string) bool {
 | 
				
			||||||
 | 
						_, err := exec.LookPath(cmd)
 | 
				
			||||||
 | 
						return err == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ext dependency support check
 | 
				
			||||||
 | 
					func checkExtDependencySupport() *ExtDependencySupport {
 | 
				
			||||||
 | 
						var extDepsAvailable ExtDependencySupport
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						extDepsAvailable.FFMpeg = commandExists("ffmpeg")
 | 
				
			||||||
 | 
						extDepsAvailable.MediaInfo = commandExists("mediainfo")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &extDepsAvailable
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func secToString(time uint64) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%02d:%02d:%02d", time/3600, (time/60)%60, time%60)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parse the thumbail picture from video file
 | 
				
			||||||
 | 
					func parseFFmpegPic(path string, time uint64) (*[]byte, error) {
 | 
				
			||||||
 | 
						app := "ffmpeg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := exec.Command(app,
 | 
				
			||||||
 | 
							"-hide_banner",
 | 
				
			||||||
 | 
							"-loglevel", "panic",
 | 
				
			||||||
 | 
							"-ss", secToString(time),
 | 
				
			||||||
 | 
							"-i", path,
 | 
				
			||||||
 | 
							"-vframes", "1",
 | 
				
			||||||
 | 
							"-q:v", "2",
 | 
				
			||||||
 | 
							"-f", "singlejpeg",
 | 
				
			||||||
 | 
							"pipe:1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdout, err := cmd.Output()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err.Error())
 | 
				
			||||||
 | 
							fmt.Println(string(err.(*exec.ExitError).Stderr))
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &stdout, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getVideoAttributes(path string) (*VidInfo, error) {
 | 
				
			||||||
 | 
						app := "mediainfo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						arg0 := path
 | 
				
			||||||
 | 
						arg1 := "--Output=JSON"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := exec.Command(app, arg1, "-f", arg0)
 | 
				
			||||||
 | 
						stdout, err := cmd.Output()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var t struct {
 | 
				
			||||||
 | 
							Media struct {
 | 
				
			||||||
 | 
								Track []struct {
 | 
				
			||||||
 | 
									Duration string
 | 
				
			||||||
 | 
									FileSize string
 | 
				
			||||||
 | 
									Width    string
 | 
				
			||||||
 | 
									Height   string
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = json.Unmarshal(stdout, &t)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						duration, err := strconv.ParseFloat(t.Media.Track[0].Duration, 32)
 | 
				
			||||||
 | 
						filesize, err := strconv.Atoi(t.Media.Track[0].FileSize)
 | 
				
			||||||
 | 
						width, err := strconv.Atoi(t.Media.Track[1].Width)
 | 
				
			||||||
 | 
						height, err := strconv.Atoi(t.Media.Track[1].Height)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret := VidInfo{
 | 
				
			||||||
 | 
							Length:    uint64(duration),
 | 
				
			||||||
 | 
							Size:      int64(filesize),
 | 
				
			||||||
 | 
							Width:     uint32(width),
 | 
				
			||||||
 | 
							Height:    uint32(height),
 | 
				
			||||||
 | 
							FrameRate: 0,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &ret, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
Package: OpenMediaCenter
 | 
					Package: OpenMediaCenter
 | 
				
			||||||
Depends: nginx, mariadb-server, mediainfo
 | 
					Depends: nginx, mariadb-server, libffmpeg-ocaml
 | 
				
			||||||
Section: web
 | 
					Section: web
 | 
				
			||||||
Priority: optional
 | 
					Priority: optional
 | 
				
			||||||
Architecture: all
 | 
					Architecture: all
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user