use libffmpeg to parse video frame and vid information
This commit is contained in:
		@@ -1,11 +1,7 @@
 | 
			
		||||
package videoparser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func AppendMessage(message string) {
 | 
			
		||||
@@ -33,88 +29,3 @@ func SendEvent(message string) {
 | 
			
		||||
 | 
			
		||||
	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/config"
 | 
			
		||||
	"openmediacenter/apiGo/database"
 | 
			
		||||
	"openmediacenter/apiGo/videoparser/thumbnail"
 | 
			
		||||
	"openmediacenter/apiGo/videoparser/tmdb"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@@ -13,7 +14,6 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var mSettings *types.SettingsType
 | 
			
		||||
var mExtDepsAvailable *ExtDependencySupport
 | 
			
		||||
 | 
			
		||||
// default Tag ids
 | 
			
		||||
const (
 | 
			
		||||
@@ -22,11 +22,6 @@ const (
 | 
			
		||||
	LowQuality = 3
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ExtDependencySupport struct {
 | 
			
		||||
	FFMpeg    bool
 | 
			
		||||
	MediaInfo bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VideoAttributes struct {
 | 
			
		||||
	Duration float32
 | 
			
		||||
	FileSize uint
 | 
			
		||||
@@ -35,10 +30,6 @@ type VideoAttributes struct {
 | 
			
		||||
 | 
			
		||||
func InitDeps(sett *types.SettingsType) {
 | 
			
		||||
	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) {
 | 
			
		||||
@@ -115,58 +106,50 @@ func addVideo(videoName string, fileName string, year int) {
 | 
			
		||||
	var poster *string
 | 
			
		||||
	var tmdbData *tmdb.VideoTMDB
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	// initialize defaults
 | 
			
		||||
	vidAtr := &VideoAttributes{
 | 
			
		||||
		Duration: 0,
 | 
			
		||||
		FileSize: 0,
 | 
			
		||||
		Width:    0,
 | 
			
		||||
	}
 | 
			
		||||
	var insertid int64
 | 
			
		||||
 | 
			
		||||
	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 mSettings.TMDBGrabbing {
 | 
			
		||||
		tmdbData = tmdb.SearchVideo(videoName, year)
 | 
			
		||||
		if tmdbData != nil {
 | 
			
		||||
			// reassign parsed pic as poster
 | 
			
		||||
			poster = ppic
 | 
			
		||||
			// 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 (?,?,?,?,?,?)`
 | 
			
		||||
		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))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Failed to insert video into db: %s\n", err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add default tags
 | 
			
		||||
	if vidAtr.Width != 0 {
 | 
			
		||||
		insertSizeTag(vidAtr.Width, uint(insertId))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add tmdb tags
 | 
			
		||||
	if mSettings.TMDBGrabbing && tmdbData != nil {
 | 
			
		||||
		insertTMDBTags(tmdbData.GenreIds, insertId)
 | 
			
		||||
		insertTMDBTags(tmdbData.GenreIds, insertid)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user