main
orestonce 2022-05-28 10:11:38 +08:00
parent c4844b7ec9
commit 8809fe2ace
12 changed files with 42 additions and 279 deletions

View File

@ -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格式
## 二次开发操作手册:

View File

@ -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)
}

View File

@ -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++ {

View File

@ -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",

View File

@ -1,9 +0,0 @@
//+build !windows
package m3u8d
import "errors"
func UnzipFfmpegToLocal(exeFileDir string) (targetFile string, err error) {
return "", errors.New("UnzipFfmpegToLocal not implement.")
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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 {

View File

@ -1,9 +0,0 @@
//+build !windows
package m3u8d
import "os/exec"
func setupCmd(cmd *exec.Cmd) {
}

View File

@ -1,14 +0,0 @@
//+build windows
package m3u8d
import (
"os/exec"
"syscall"
)
func setupCmd(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
}
}