视频下载和格式转换

通过下载m3u8文件,下载所有ts文件,并合并成视频文件

音视频文件各种格式的转换工作

主要使用扩展包 #

github.com/grafov/m3u8
github.com/spf13/cobra

代码 #

下载视频文件 #

package download

import (
	"bufio"
	"crypto/md5"
	"fmt"
	"github.com/grafov/m3u8"
	"github.com/spf13/cobra"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"
)


var (
	file string
	path string
	Command = &cobra.Command{
		Use:     "download",
		Short:   "download 视频下载",
		Example: "tool download",
		Run: func(cmd *cobra.Command, args []string) {
			 execute()
		},
	}
)

func init() {
	current,_ := os.Getwd()
	Command.PersistentFlags().StringVarP(&file, "file", "f", "index.m3u8", "要解析的m3u8文件")
	Command.PersistentFlags().StringVarP(&path, "path", "p", current, "路径地址")
}

var urls = make(map[string]int,1000)

func execute()  {
	wg := &sync.WaitGroup{}
	p := createDateDir(path)
	plists := getUrls(file)
	for idx, url := range plists {
		filename := p + "/" + strconv.Itoa(idx + 1) + ".ts"
		wg.Add(1)
		go download(wg,url,filename)
	}
	wg.Wait()

	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
	}()

}


func md5Check(u string)  bool  {
	key := fmt.Sprintf("%x",md5.Sum([]byte(u)))
	if _,ok := urls[key]; ok == true {
		return true
	}
	urls[key] = 1
	return false
}


func download(wg *sync.WaitGroup,url string,filename string)  {
	fmt.Println("start download url:",url)
	resp, err := http.Get(url)
	if err != nil {
		panic("不能下载文件" + err.Error())
	}
	defer func(wg *sync.WaitGroup) {
		resp.Body.Close()
		wg.Done()
	}(wg)
	//读取内容弄
	content, err := ioutil.ReadAll(resp.Body)

	if err == io.EOF {
		goto End
	}

	if err != nil {
		panic("读取文件失败"+err.Error())
	}
	err = ioutil.WriteFile(filename, content, 0666)

	if err != nil {
		panic("写入文件错误" + err.Error())
	}
End:
	fmt.Println("save url to file sucess",filename)
}

func createDateDir(Path string) string {
	folderName := time.Now().Format("200601021504")
	folderPath := filepath.Join(Path, folderName)
	if _, err := os.Stat(folderPath); os.IsNotExist(err) {
		// 必须分成两步:先创建文件夹、再修改权限
		_ = os.Mkdir(folderPath, 0777) //0777也可以os.ModePerm
		_ = os.Chmod(folderPath, 0777)
	}
	return folderPath
}


/**
 * 解析m3u8文件,获取url列表
 */
func getUrls(filename string) (urls []string)  {
	file,err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	p, listType, err := m3u8.DecodeFrom(bufio.NewReader(file), true)
	if err != nil {
		log.Println(err)
	}

	if listType != m3u8.MEDIA {
		panic("不能获取视频类型ts")
	}

	mediapl := p.(*m3u8.MediaPlaylist)

	if mediapl.Segments == nil ||  len(mediapl.Segments) == 0 {
		panic("不能获取视频列表")
	}

	for _,seg := range mediapl.Segments {
		if seg != nil && seg.URI != "" {
			if !md5Check(seg.URI) {
				urls = append(urls,seg.URI)
			}

		}
	}

	return
}

合并ts文件到mp4 #

package merge

import (
	"fmt"
	"github.com/spf13/cobra"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	path string
	delete bool
	Command = &cobra.Command{
		Use:     "merge",
		Short:   "merge 合并文件",
		Example: "tool merge -p=202202232344",
		Run: func(cmd *cobra.Command, args []string) {
			execute()
		},
	}
)
func init() {
	current,_ := os.Getwd()
	Command.PersistentFlags().BoolVarP(&delete,"delete","d",false,"是否删除下载路径")
	Command.PersistentFlags().StringVarP(&path, "path", "p", current, "路径地址")
}

func execute(){
	files, err := filepath.Glob(path + "/*.ts")
	if err != nil {
		panic(err)
	}
	p,_ := os.Getwd()
	videofile,_ := filepath.Abs( p+ "/" + strings.Trim(path,"/") + ".mp4")

	fii, err := os.OpenFile(videofile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm)
	if err != nil {
		panic(err)
	}
	for i := 1; i <= len(files); i++ {
		tsfile,_ := filepath.Abs(path + "/" + strconv.Itoa(i) + ".ts")
		f, err := os.OpenFile(tsfile, os.O_RDONLY, os.ModePerm)
		if err != nil {
			fmt.Println(err)
		}
		b, err := ioutil.ReadAll(f)
		if err != nil {
			fmt.Println(err)
			return
		}
		_, _ = fii.Write(b)
		_ = f.Close()
	}

	defer func() {
		if delete == true {
			delpath,_ := filepath.Abs(path)
			os.RemoveAll(delpath)
		}
		fii.Close()
	}()
}

音视频转换 #

通过ffmpeg可执行命令进行封装

package format

import (
	"bytes"
	"fmt"
	"github.com/spf13/cobra"
	"log"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	input    string
	output    string
	voice  bool
	start int
	end int
	cmds map[string]func(input,output string)
	Command = &cobra.Command{
		Use:     "format",
		Short:   "format 格式化文件",
		Example: "tool format 1.flv 1.mp4",
		PreRun: func(cmd *cobra.Command, args []string) {
			usage()
			cmds = map[string]func(input string, output string){}
			cmds["flv2mp4"] =flv2mp4
			cmds["mkv2mp4"] = mkv2mp4
			cmds["tovoice"] = tovoice
		},

		Run: func(cmd *cobra.Command, args []string) {
			execute()
		},
	}
)

func init() {
	Command.PersistentFlags().IntVarP(&start,"start","s",0,"截取视频开始mp42git使用")
	Command.PersistentFlags().IntVarP(&end,"end","e",10,"截取视频长度mp42git使用")
	Command.PersistentFlags().BoolVarP(&voice, "voice", "v", false, "音频文件")
	Command.PersistentFlags().StringVarP(&input, "input", "i", "1.flv", "输入视频文件")
	Command.PersistentFlags().StringVarP(&output, "output", "o", "1.mp4", "输出视频文件")
}

func usage()  {
	fmt.Println("+--------------------------------------------------+")
	fmt.Println("|            list command                          |")
	fmt.Println("| tool format -i=1.flv -o=1.mp4                    |")
	fmt.Println("| tool format -i=1.mkv -o1.mp4                     |")
	fmt.Println("| tool format -i=1.mp4 -o=1.gif -s=1 -e=10         |")
	fmt.Println("| tool format -i=1.mp4 -o=1.mp3 -v=true            |")
	fmt.Println("+--------------------------------------------------+")
}

func execute() {
	var fname string
	iext := filepath.Ext(input)
	oext := filepath.Ext(output)

	if voice {
		fname = "tovoice"
	} else {
		fname = strings.ToLower(iext[1:]) + "2"+ strings.ToLower(oext[1:])
	}

	if strings.ToLower(oext[1:]) == "gif" {
		mp42gif(input,output,start,end)
		return
	}
	fn,ok := cmds[fname];
	 if !ok {
		log.Panic("没有转化函数")
	}
	fn(input,output)

	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
	}()

}


func flv2mp4(input,output string)  {
		//
		cmd := exec.Command("bin/ffmpeg.exe","-i",input,"-c","copy",output)
		fmt.Println(cmd.String())
		template(cmd)
}

func mkv2mp4(input,output string)  {
	//ffmpeg -i 1.mkv -c:v copy -c:a aac  1.mp4
	cmd := exec.Command("bin/ffmpeg.exe","-i",input,"-c:v","copy","-c:a","aac",output)
	fmt.Println(cmd.String())
	template(cmd)
}
func mp42gif(input,output string,start,end int)  {
	cmd := exec.Command("bin/ffmpeg.exe","-ss",strconv.Itoa(start),"-i",input,"-t",strconv.Itoa(end),output)
	fmt.Println(cmd.String())
	template(cmd)
}
func tovoice(input,output string)  {
	//ffmpeg  -i input  -vn output
	cmd := exec.Command("bin/ffmpeg.exe","-i",input,"-vn",output)
	fmt.Println(cmd.String())
	template(cmd)
}


func template(cmd *exec.Cmd) {
	 out := new(bytes.Buffer)
	cmd.Stdout = out
	if err := cmd.Start(); err != nil {
		log.Panic(err)
	}

	if err := cmd.Wait(); err != nil {
		log.Panic(err)
	}

	fmt.Println(out.String())
}


操作命令 #

  1. 去视频网站下载m3u8文件
  2. 执行下载命令

视频处理小工具 1、下载m3m8文件

tool.exe download [-f 执行*.m3u8文件 -p 下载目录]

2、合并ts文件到mp4

tool.exe merge [-p 下载后的ts地址 --d 生成视频后是否删除ts目录] 

3、格式转化

tool.exe format -h 查看帮助