save
parent
c4844b7ec9
commit
8809fe2ace
|
|
@ -24,7 +24,7 @@
|
|||
此次下载.
|
||||
## TODO:
|
||||
* [x] 如果不是m3u8样子的URL,自动下载html下来、搜索其中的m3u8链接进行下载
|
||||
* [ ] windows、linux、mac都支持ffmpeg合并ts列表为mp4
|
||||
* [x] windows、linux、mac都支持ffmpeg合并ts列表为mp4
|
||||
* [ ] 增加linux的图形界面支持
|
||||
* [ ] 支持保存为mp3格式
|
||||
## 二次开发操作手册:
|
||||
|
|
|
|||
66
cache.go
66
cache.go
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type DbVideoInfo struct {
|
||||
|
|
@ -28,15 +27,15 @@ func (this *RunDownload_Req) getVideoId() (id string, err error) {
|
|||
}
|
||||
|
||||
func cacheRead(dir string, id string) (info *DbVideoInfo, err error) {
|
||||
value, err := dbRead(dir, id)
|
||||
value, err := cdb.FileGetValueString(filepath.Join(dir, "m3u8d_cache.cdb"), id)
|
||||
if err != nil {
|
||||
if err == cdb.ErrNoData {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if len(value) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var obj DbVideoInfo
|
||||
err = json.Unmarshal(value, &obj)
|
||||
err = json.Unmarshal([]byte(value), &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -77,7 +76,7 @@ func cacheWrite(dir string, id string, originReq RunDownload_Req, videoNameFullP
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dbWrite(dir, id, content)
|
||||
return cdb.FileRewriteKeyValue(filepath.Join(dir, "m3u8d_cache.cdb"), id, string(content))
|
||||
}
|
||||
|
||||
func dbRead(dir string, key string) (content []byte, err error) {
|
||||
|
|
@ -98,56 +97,3 @@ func dbRead(dir string, key string) (content []byte, err error) {
|
|||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func dbWrite(dir string, key string, value []byte) (err error) {
|
||||
cdbFileName := filepath.Join(dir, "m3u8d_cache.cdb")
|
||||
|
||||
reader, err := cdb.OpenFile(cdbFileName)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
tmpCdbFileName := cdbFileName + "." + strconv.Itoa(os.Getpid()) + ".tmp"
|
||||
writer, err := cdb.NewFileWriter(tmpCdbFileName)
|
||||
if err != nil {
|
||||
if reader != nil {
|
||||
reader.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if reader != nil {
|
||||
for it := reader.BeginIterator(); it != nil; {
|
||||
tmpKey, tmpValue, err := it.ReadNextKeyValue()
|
||||
if err != nil {
|
||||
if err == cdb.ErrNoData {
|
||||
break
|
||||
}
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
os.Remove(tmpCdbFileName)
|
||||
return err
|
||||
}
|
||||
if string(tmpKey) == key {
|
||||
continue
|
||||
}
|
||||
err = writer.WriteKeyValue(tmpKey, tmpValue)
|
||||
if err != nil {
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
os.Remove(tmpCdbFileName)
|
||||
return err
|
||||
}
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
err = writer.WriteKeyValue([]byte(key), value)
|
||||
if err != nil {
|
||||
writer.Close()
|
||||
return err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpCdbFileName, cdbFileName)
|
||||
}
|
||||
|
|
|
|||
34
download.go
34
download.go
|
|
@ -1,7 +1,3 @@
|
|||
//@author:llychao<lychao_vip@163.com>
|
||||
//@contributor: Junyi<me@junyi.pw>
|
||||
//@date:2020-02-18
|
||||
//@功能:golang m3u8 video Downloader
|
||||
package m3u8d
|
||||
|
||||
import (
|
||||
|
|
@ -12,6 +8,8 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/levigross/grequests"
|
||||
"github.com/orestonce/goffmpeg"
|
||||
"github.com/orestonce/gopool"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
|
@ -24,8 +22,6 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/levigross/grequests"
|
||||
)
|
||||
|
||||
// TsInfo 用于保存 ts 文件的下载地址和文件名
|
||||
|
|
@ -104,7 +100,7 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
|||
}
|
||||
var ffmpegExe string
|
||||
if req.UserFfmpegMerge {
|
||||
ffmpegExe, err = SetupFfmpeg()
|
||||
ffmpegExe, err = goffmpeg.SetupFfmpeg()
|
||||
if err != nil {
|
||||
resp.ErrMsg = "SetupFfmpeg error: " + err.Error()
|
||||
return resp
|
||||
|
|
@ -177,14 +173,28 @@ func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) {
|
|||
var contentHash string
|
||||
if req.UserFfmpegMerge {
|
||||
tmpOutputName = filepath.Join(downloadDir, "all.merge.mp4")
|
||||
contentHash, err = mergeTsFileList_Ffmpeg(ffmpegExe, tsFileList, tmpOutputName)
|
||||
err = goffmpeg.MergeMultiToSingleMp4(goffmpeg.MergeMultiToSingleMp4_Req{
|
||||
FfmpegExePath: ffmpegExe,
|
||||
TsFileList: tsFileList,
|
||||
OutputMp4: tmpOutputName,
|
||||
ProgressCh: nil,
|
||||
})
|
||||
if err != nil {
|
||||
resp.ErrMsg = "合并错误: " + err.Error()
|
||||
return resp
|
||||
}
|
||||
contentHash = getFileSha256(tmpOutputName)
|
||||
if contentHash == "" {
|
||||
resp.ErrMsg = "无法计算摘要信息: " + tmpOutputName
|
||||
return resp
|
||||
}
|
||||
} else {
|
||||
tmpOutputName = filepath.Join(downloadDir, "all.merge.ts")
|
||||
contentHash, err = mergeTsFileList_Raw(tsFileList, tmpOutputName)
|
||||
}
|
||||
if err != nil {
|
||||
resp.ErrMsg = "合并错误: " + err.Error()
|
||||
return resp
|
||||
if err != nil {
|
||||
resp.ErrMsg = "合并错误: " + err.Error()
|
||||
return resp
|
||||
}
|
||||
}
|
||||
var name string
|
||||
for idx := 0; ; idx++ {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
BuildCliBinary() // 编译二进制
|
||||
//BuildCliBinary() // 编译二进制
|
||||
CreateLibForQtUi() // 创建Qt需要使用的.a库文件
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +42,10 @@ func BuildCliBinary() {
|
|||
GOOS: "linux",
|
||||
GOARCH: "arm64",
|
||||
},
|
||||
{
|
||||
GOOS: "linux",
|
||||
GOARCH: "arm",
|
||||
},
|
||||
{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "amd64",
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,9 +0,0 @@
|
|||
//+build !windows
|
||||
|
||||
package m3u8d
|
||||
|
||||
import "errors"
|
||||
|
||||
func UnzipFfmpegToLocal(exeFileDir string) (targetFile string, err error) {
|
||||
return "", errors.New("UnzipFfmpegToLocal not implement.")
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
//+build windows
|
||||
|
||||
package m3u8d
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"github.com/saracen/go7z"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
//go:embed ffmpeg-2022-05-04-git-0914e3a14a-essentials_build.7z
|
||||
var g7zipFileContent []byte
|
||||
|
||||
const FfmpegSha256Value = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
func UnzipFfmpegToLocal(exeFileDir string) (targetFile string, err error) {
|
||||
targetFile = filepath.Join(exeFileDir, "ffmpeg-2022-05-04.exe")
|
||||
if isFileExists(targetFile) && isSha256Match(targetFile) {
|
||||
return targetFile, nil
|
||||
}
|
||||
sz, err := go7z.NewReader(bytes.NewReader(g7zipFileContent), int64(len(g7zipFileContent)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
hdr, err := sz.Next()
|
||||
if err == io.EOF {
|
||||
break // End of archive
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If empty stream (no contents) and isn't specifically an empty file...
|
||||
// then it's a directory.
|
||||
if hdr.IsEmptyStream && !hdr.IsEmptyFile {
|
||||
continue
|
||||
}
|
||||
|
||||
if path.Base(hdr.Name) == "ffmpeg.exe" {
|
||||
if !isDirExists(exeFileDir) {
|
||||
err = os.MkdirAll(exeFileDir, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
// Create file
|
||||
f, err := os.Create(targetFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(f, sz); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !isSha256Match(targetFile) {
|
||||
return "", errors.New("ffmpeg的sha256 校验不通过")
|
||||
}
|
||||
return targetFile, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("7z文件内未找到 ffmpeg.exe")
|
||||
}
|
||||
|
||||
func isSha256Match(targetFile string) bool {
|
||||
return getFileSha256(targetFile) == FfmpegSha256Value
|
||||
}
|
||||
3
go.mod
3
go.mod
|
|
@ -4,8 +4,9 @@ go 1.17
|
|||
|
||||
require (
|
||||
github.com/levigross/grequests v0.0.0-20190908174114-253788527a1a
|
||||
github.com/orestonce/cdb v0.0.0-20210317131130-99d93d11de21
|
||||
github.com/orestonce/cdb v0.0.0-20220528005855-d187c22240e2
|
||||
github.com/orestonce/go2cpp v0.0.0-20220507123906-b66d3600c123
|
||||
github.com/orestonce/goffmpeg v0.0.0-20220528011920-92f3449b6c15
|
||||
github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171
|
||||
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda
|
||||
github.com/spf13/cobra v1.4.0
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -7,8 +7,14 @@ github.com/levigross/grequests v0.0.0-20190908174114-253788527a1a h1:DGFy/362j92
|
|||
github.com/levigross/grequests v0.0.0-20190908174114-253788527a1a/go.mod h1:jVntzcUU+2BtVohZBQmSHWUmh8B55LCNfPhcNCIvvIg=
|
||||
github.com/orestonce/cdb v0.0.0-20210317131130-99d93d11de21 h1:kFAgzquHOv3dmVU2HoUUzu1586bd1DKHfAz6dd5do+I=
|
||||
github.com/orestonce/cdb v0.0.0-20210317131130-99d93d11de21/go.mod h1:HMNNdA1LMQFJRwobtCzVevWcInFSo9rfs1fYeVYwU+c=
|
||||
github.com/orestonce/cdb v0.0.0-20220528005855-d187c22240e2 h1:AmGkuxSIOW0gA/jetY8d1DVV0cyQ08FMCxa1arkI6HQ=
|
||||
github.com/orestonce/cdb v0.0.0-20220528005855-d187c22240e2/go.mod h1:HMNNdA1LMQFJRwobtCzVevWcInFSo9rfs1fYeVYwU+c=
|
||||
github.com/orestonce/go2cpp v0.0.0-20220507123906-b66d3600c123 h1:AyAKqO7kC68OKiZk9GV4bUG+xMe1VQCy2CPbS9hvdY4=
|
||||
github.com/orestonce/go2cpp v0.0.0-20220507123906-b66d3600c123/go.mod h1:1fsOAZftk08/dOTRqlp6f/MVwaEKOhrnPUg0RtWiSdY=
|
||||
github.com/orestonce/goffmpeg v0.0.0-20220519233347-2266c9b171d9 h1:ki+QkZANpELM4WJjjcFwwFmt6r9ni53QxIYeS6OX77Y=
|
||||
github.com/orestonce/goffmpeg v0.0.0-20220519233347-2266c9b171d9/go.mod h1:QHZrzI4ewo7pYfEo8+QzrPxsCixua/TS0aE505m/nBU=
|
||||
github.com/orestonce/goffmpeg v0.0.0-20220528011920-92f3449b6c15 h1:Ix2AmnSHlA2hrI1NkpmXnFeOcxtdCj66fj1KoGFq9tI=
|
||||
github.com/orestonce/goffmpeg v0.0.0-20220528011920-92f3449b6c15/go.mod h1:QHZrzI4ewo7pYfEo8+QzrPxsCixua/TS0aE505m/nBU=
|
||||
github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171 h1:NnOl6HTrhrlTT7aaAybVOtq+fEztGFMoQtegckgwLdk=
|
||||
github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171/go.mod h1:MCQUrAPiG9/PTjHJuGqWLasKbIaG6z62KO6kfp90byM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
|
|
|||
94
merge.go
94
merge.go
|
|
@ -3,104 +3,10 @@ package m3u8d
|
|||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SetupFfmpeg_Resp struct {
|
||||
ErrMsg string
|
||||
FfmpegPath string
|
||||
}
|
||||
|
||||
var gFfmpegExePath string
|
||||
var gFfmpegExePathLocker sync.Mutex
|
||||
|
||||
func SetupFfmpeg() (p string, err error) {
|
||||
gFfmpegExePathLocker.Lock()
|
||||
defer gFfmpegExePathLocker.Unlock()
|
||||
|
||||
if gFfmpegExePath != "" {
|
||||
return gFfmpegExePath, nil
|
||||
}
|
||||
|
||||
ffmpegPath, err := exec.LookPath("ffmpeg")
|
||||
if err != nil {
|
||||
ffmpegPath, err = UnzipFfmpegToLocal(getTempDir())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gFfmpegExePath = ffmpegPath
|
||||
return ffmpegPath, nil
|
||||
}
|
||||
gFfmpegExePath = ffmpegPath
|
||||
return ffmpegPath, nil
|
||||
}
|
||||
|
||||
func getTempDir() string {
|
||||
dir := filepath.Join(os.TempDir(), "m3u8d")
|
||||
if !isDirExists(dir) {
|
||||
_ = os.MkdirAll(dir, 0777)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func mergeTsFileList_Ffmpeg(ffmpegPath string, tsFileList []string, outputMp4 string) (contentHash string, err error) {
|
||||
outputMp4Temp := outputMp4 + ".tmp"
|
||||
cmd := exec.Command(ffmpegPath, "-i", "-", "-acodec", "copy", "-vcodec", "copy", "-f", "mp4", outputMp4Temp)
|
||||
ip, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if isFileExists(outputMp4Temp) {
|
||||
err = os.Remove(outputMp4Temp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
//cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
setupCmd(cmd)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, name := range tsFileList {
|
||||
fin, err := os.Open(name)
|
||||
if err != nil {
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
return "", errors.New("read error: " + err.Error())
|
||||
}
|
||||
_, err = io.Copy(ip, fin)
|
||||
if err != nil {
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
fin.Close()
|
||||
return "", errors.New("write error: " + err.Error())
|
||||
}
|
||||
fin.Close()
|
||||
}
|
||||
err = ip.Close()
|
||||
if err != nil {
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
return "", errors.New("ip.Close error: " + err.Error())
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(outputMp4Temp, outputMp4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getFileSha256(outputMp4), nil
|
||||
}
|
||||
|
||||
func mergeTsFileList_Raw(tsFileList []string, outputTs string) (hash string, err error) {
|
||||
fout, err := os.Create(outputTs)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
//+build !windows
|
||||
|
||||
package m3u8d
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func setupCmd(cmd *exec.Cmd) {
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
//+build windows
|
||||
|
||||
package m3u8d
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setupCmd(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue