use libffmpeg to parse video frame and vid information
This commit is contained in:
		
							
								
								
									
										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