implement libav in some methods
This commit is contained in:
parent
9489689dbf
commit
d92f02d38e
7 changed files with 259 additions and 56 deletions
|
@ -126,12 +126,10 @@ func MediaListFromAPI(
|
||||||
}
|
}
|
||||||
var caption string
|
var caption string
|
||||||
if !stories {
|
if !stories {
|
||||||
// caption, err = GetPostCaption(postURL)
|
caption, err = GetPostCaption(postURL)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return nil, fmt.Errorf("failed to get caption: %w", err)
|
return nil, fmt.Errorf("failed to get caption: %w", err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// todo: fix this (429 error)
|
|
||||||
}
|
}
|
||||||
for _, item := range details.Items {
|
for _, item := range details.Items {
|
||||||
media := ctx.Extractor.NewMedia(
|
media := ctx.Extractor.NewMedia(
|
||||||
|
|
|
@ -57,8 +57,6 @@ func ParseIGramResponse(body []byte) (*IGramResponse, error) {
|
||||||
return nil, fmt.Errorf("failed to decode response1: %w", err)
|
return nil, fmt.Errorf("failed to decode response1: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
switch rawResponse.(type) {
|
switch rawResponse.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
// array of IGramMedia
|
// array of IGramMedia
|
||||||
|
@ -113,7 +111,6 @@ func GetPostCaption(
|
||||||
req.Header.Set("Referer", "https://www.instagram.com/accounts/onetap/?next=%2F")
|
req.Header.Set("Referer", "https://www.instagram.com/accounts/onetap/?next=%2F")
|
||||||
req.Header.Set("Alt-Used", "www.instagram.com")
|
req.Header.Set("Alt-Used", "www.instagram.com")
|
||||||
req.Header.Set("Connection", "keep-alive")
|
req.Header.Set("Connection", "keep-alive")
|
||||||
req.Header.Set("Cookie", `csrftoken=Ib2Zuvf1y9HkDwXFxkdang; sessionid=8569455296%3AIFQiov2eYfTdSd%3A19%3AAYfVHnaxecWGWhyzxvz60vu5qLn05DyKgN_tTZUXTA; ds_user_id=8569455296; mid=Z_j1vQAEAAGVUE3KuxMR7vBonGBw; ig_did=BC48C8B7-D71B-49EF-8195-F9DE37A57B49; rur="CLN\0548569455296\0541775905137:01f7ebda5b896815e9279bb86a572db6bdc8ebccf3e1f8d5327e2bc5ca187fd5cd932b66"; wd=513x594; datr=x_X4Z_CHqpwtjaRKq7PtCNu3`)
|
|
||||||
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
||||||
req.Header.Set("Sec-Fetch-Dest", "document")
|
req.Header.Set("Sec-Fetch-Dest", "document")
|
||||||
req.Header.Set("Sec-Fetch-Mode", "navigate")
|
req.Header.Set("Sec-Fetch-Mode", "navigate")
|
||||||
|
@ -130,7 +127,9 @@ func GetPostCaption(
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return "", fmt.Errorf("failed to get response: %s", resp.Status)
|
// return an empty caption
|
||||||
|
// probably 429 error
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -18,6 +18,7 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/asticode/go-astikit v0.42.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
@ -29,6 +30,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aki237/nscjar v0.0.0-20210417074043-bbb606196143
|
github.com/aki237/nscjar v0.0.0-20210417074043-bbb606196143
|
||||||
|
github.com/asticode/go-astiav v0.35.1
|
||||||
github.com/aws/aws-sdk-go v1.55.6 // indirect
|
github.com/aws/aws-sdk-go v1.55.6 // indirect
|
||||||
github.com/grafov/m3u8 v0.12.1
|
github.com/grafov/m3u8 v0.12.1
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -2,6 +2,10 @@ github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.31 h1:SIkzqC6Nv+znY4NGbWlJceWdns8Q
|
||||||
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.31/go.mod h1:kL1v4iIjlalwm3gCYGvF4NLa3hs+aKEfRkNJvj4aoDU=
|
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.31/go.mod h1:kL1v4iIjlalwm3gCYGvF4NLa3hs+aKEfRkNJvj4aoDU=
|
||||||
github.com/aki237/nscjar v0.0.0-20210417074043-bbb606196143 h1:PqRkQZW8lAlK2DnH9iSBfISmDxSChaoNJHwP0p7SD2Y=
|
github.com/aki237/nscjar v0.0.0-20210417074043-bbb606196143 h1:PqRkQZW8lAlK2DnH9iSBfISmDxSChaoNJHwP0p7SD2Y=
|
||||||
github.com/aki237/nscjar v0.0.0-20210417074043-bbb606196143/go.mod h1:l0r3UsMujHR1bAYL7R0+6NXkHo/vIe+ja3xLZbUZNb8=
|
github.com/aki237/nscjar v0.0.0-20210417074043-bbb606196143/go.mod h1:l0r3UsMujHR1bAYL7R0+6NXkHo/vIe+ja3xLZbUZNb8=
|
||||||
|
github.com/asticode/go-astiav v0.35.1 h1:jq27Ihf+GXtOTnhzNTcpKrW1iLNRAuPSoarh7/SapYc=
|
||||||
|
github.com/asticode/go-astiav v0.35.1/go.mod h1:K7D8UC6GeQt85FUxk2KVwYxHnotrxuEnp5evkkudc2s=
|
||||||
|
github.com/asticode/go-astikit v0.42.0 h1:pnir/2KLUSr0527Tv908iAH6EGYYrYta132vvjXsH5w=
|
||||||
|
github.com/asticode/go-astikit v0.42.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||||
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||||
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||||
|
|
126
util/av/remux.go
126
util/av/remux.go
|
@ -2,31 +2,117 @@ package av
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
"github.com/asticode/go-astiav"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RemuxFile(
|
func RemuxFile(inputFile string) error {
|
||||||
inputFile string,
|
ext := strings.ToLower(filepath.Ext(inputFile))
|
||||||
) error {
|
var muxerName string
|
||||||
tempFileName := inputFile + ".temp"
|
switch ext {
|
||||||
outputFile := inputFile
|
case ".mp4":
|
||||||
err := os.Rename(inputFile, tempFileName)
|
muxerName = "mp4"
|
||||||
if err != nil {
|
case ".mkv":
|
||||||
return fmt.Errorf("failed to rename file: %w", err)
|
muxerName = "matroska"
|
||||||
|
case ".mov":
|
||||||
|
muxerName = "mov"
|
||||||
|
case ".avi":
|
||||||
|
muxerName = "avi"
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported output container for extension: %s", ext)
|
||||||
}
|
}
|
||||||
defer os.Remove(tempFileName)
|
outputFile := strings.TrimSuffix(inputFile, ext) + ".remuxed" + ext
|
||||||
err = ffmpeg.
|
|
||||||
Input(tempFileName).
|
inputCtx := astiav.AllocFormatContext()
|
||||||
Output(outputFile, ffmpeg.KwArgs{
|
if inputCtx == nil {
|
||||||
"c": "copy",
|
return fmt.Errorf("failed to alloc input format context")
|
||||||
}).
|
}
|
||||||
Silent(true).
|
defer inputCtx.Free()
|
||||||
OverWriteOutput().
|
|
||||||
Run()
|
if err := inputCtx.OpenInput(inputFile, nil, nil); err != nil {
|
||||||
|
return fmt.Errorf("failed to open input: %w", err)
|
||||||
|
}
|
||||||
|
defer inputCtx.CloseInput()
|
||||||
|
|
||||||
|
if err := inputCtx.FindStreamInfo(nil); err != nil {
|
||||||
|
return fmt.Errorf("failed to find stream info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outCtx, err := astiav.AllocOutputFormatContext(nil, muxerName, outputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to remux file: %w", err)
|
return fmt.Errorf("failed to alloc output format context: %w", err)
|
||||||
|
}
|
||||||
|
defer outCtx.Free()
|
||||||
|
|
||||||
|
inToOutIdx := make(map[int]int)
|
||||||
|
for inIdx, inStream := range inputCtx.Streams() {
|
||||||
|
inCP := inStream.CodecParameters()
|
||||||
|
if inCP.CodecID() == astiav.CodecIDNone {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mt := inCP.MediaType(); mt != astiav.MediaTypeVideo && mt != astiav.MediaTypeAudio && mt != astiav.MediaTypeSubtitle {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
outStream := outCtx.NewStream(nil)
|
||||||
|
if outStream == nil {
|
||||||
|
return fmt.Errorf("failed to create new stream in output context")
|
||||||
|
}
|
||||||
|
if err := inCP.Copy(outStream.CodecParameters()); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy codec parameters: %w", err)
|
||||||
|
}
|
||||||
|
outStream.CodecParameters().SetCodecTag(0)
|
||||||
|
outStream.SetTimeBase(inStream.TimeBase())
|
||||||
|
inToOutIdx[inIdx] = len(inToOutIdx)
|
||||||
|
}
|
||||||
|
if len(inToOutIdx) == 0 {
|
||||||
|
return fmt.Errorf("no supported streams to remux")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !outCtx.OutputFormat().Flags().Has(astiav.IOFormatFlagNofile) {
|
||||||
|
ioCtx, err := astiav.OpenIOContext(outputFile, astiav.NewIOContextFlags(astiav.IOContextFlagWrite), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open output IO context: %w", err)
|
||||||
|
}
|
||||||
|
defer ioCtx.Close()
|
||||||
|
outCtx.SetPb(ioCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := outCtx.WriteHeader(nil); err != nil {
|
||||||
|
return fmt.Errorf("failed to write output header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := astiav.AllocPacket()
|
||||||
|
defer packet.Free()
|
||||||
|
for {
|
||||||
|
if err := inputCtx.ReadFrame(packet); err != nil {
|
||||||
|
if err == astiav.ErrEof {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error reading frame: %w", err)
|
||||||
|
}
|
||||||
|
outIdx, ok := inToOutIdx[packet.StreamIndex()]
|
||||||
|
if !ok {
|
||||||
|
packet.Unref()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inStream := inputCtx.Streams()[packet.StreamIndex()]
|
||||||
|
outStream := outCtx.Streams()[outIdx]
|
||||||
|
packet.SetPts(astiav.RescaleQRnd(packet.Pts(), inStream.TimeBase(), outStream.TimeBase(), astiav.RoundingNearInf))
|
||||||
|
packet.SetDts(astiav.RescaleQRnd(packet.Dts(), inStream.TimeBase(), outStream.TimeBase(), astiav.RoundingNearInf))
|
||||||
|
packet.SetDuration(astiav.RescaleQ(packet.Duration(), inStream.TimeBase(), outStream.TimeBase()))
|
||||||
|
packet.SetStreamIndex(outIdx)
|
||||||
|
packet.SetPos(-1)
|
||||||
|
if err := outCtx.WriteInterleavedFrame(packet); err != nil {
|
||||||
|
packet.Unref()
|
||||||
|
return fmt.Errorf("error writing frame: %w", err)
|
||||||
|
}
|
||||||
|
packet.Unref()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := outCtx.WriteTrailer(); err != nil {
|
||||||
|
return fmt.Errorf("failed to write trailer: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,115 @@
|
||||||
package av
|
package av
|
||||||
|
|
||||||
import (
|
import (
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image/jpeg"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/asticode/go-astiav"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExtractVideoThumbnail(
|
func ExtractVideoThumbnail(videoPath string, imagePath string) error {
|
||||||
videoPath string,
|
formatCtx := astiav.AllocFormatContext()
|
||||||
thumbnailPath string,
|
defer formatCtx.Free()
|
||||||
) error {
|
|
||||||
err := ffmpeg.
|
err := formatCtx.OpenInput(videoPath, nil, nil)
|
||||||
Input(videoPath).
|
|
||||||
Output(thumbnailPath, ffmpeg.KwArgs{
|
|
||||||
"vframes": 1,
|
|
||||||
"ss": "00:00:01",
|
|
||||||
}).
|
|
||||||
Silent(true).
|
|
||||||
OverWriteOutput().
|
|
||||||
Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed opening input: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = formatCtx.FindStreamInfo(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed finding stream info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, codec, err := formatCtx.FindBestStream(
|
||||||
|
astiav.MediaTypeVideo, -1, -1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed finding best video stream: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
codecParameters := stream.CodecParameters()
|
||||||
|
|
||||||
|
decoder := astiav.FindDecoder(codec.ID())
|
||||||
|
if decoder == nil {
|
||||||
|
return fmt.Errorf("no decoder found for codec %s", codec.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
codecCtx := astiav.AllocCodecContext(decoder)
|
||||||
|
defer codecCtx.Free()
|
||||||
|
|
||||||
|
err = codecParameters.ToCodecContext(codecCtx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed setting codec parameters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = codecCtx.Open(decoder, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed opening codec: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := astiav.AllocPacket()
|
||||||
|
defer packet.Free()
|
||||||
|
frame := astiav.AllocFrame()
|
||||||
|
defer frame.Free()
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
timeout := 5 * time.Second
|
||||||
|
|
||||||
|
// read frames until we find a video frame or timeout
|
||||||
|
for time.Since(startTime) < timeout {
|
||||||
|
err := formatCtx.ReadFrame(packet)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, astiav.ErrEof) {
|
||||||
|
return fmt.Errorf("end of file reached before finding video frame")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed reading frame: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.StreamIndex() != stream.Index() {
|
||||||
|
packet.Unref()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = codecCtx.SendPacket(packet)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed sending packet to decoder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = codecCtx.ReceiveFrame(frame)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, astiav.ErrEagain) || errors.Is(err, astiav.ErrEof) {
|
||||||
|
packet.Unref()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed receiving frame from decoder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := frame.Data().GuessImageFormat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed guessing image format: %w", err)
|
||||||
|
}
|
||||||
|
err = frame.Data().ToImage(img)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed converting frame to image: %w", err)
|
||||||
|
}
|
||||||
|
packet.Unref()
|
||||||
|
|
||||||
|
file, err := os.Create(imagePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating image file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = jpeg.Encode(file, img, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed encoding image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("timeout while waiting for video frame")
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,41 @@
|
||||||
package av
|
package av
|
||||||
|
|
||||||
import (
|
import "github.com/asticode/go-astiav"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetVideoInfo(filePath string) (int64, int64, int64) {
|
func GetVideoInfo(filePath string) (int64, int64, int64) {
|
||||||
probeData, err := ffmpeg.Probe(filePath)
|
formatCtx := astiav.AllocFormatContext()
|
||||||
if err != nil {
|
if formatCtx == nil {
|
||||||
return 0, 0, 0
|
return 0, 0, 0
|
||||||
}
|
}
|
||||||
duration := gjson.Get(probeData, "format.duration").Float()
|
defer formatCtx.Free()
|
||||||
width := gjson.Get(probeData, "streams.0.width").Int()
|
|
||||||
height := gjson.Get(probeData, "streams.0.height").Int()
|
|
||||||
|
|
||||||
return int64(duration), width, height
|
if err := formatCtx.OpenInput(filePath, nil, nil); err != nil {
|
||||||
|
return 0, 0, 0
|
||||||
|
}
|
||||||
|
defer formatCtx.CloseInput()
|
||||||
|
|
||||||
|
if err := formatCtx.FindStreamInfo(nil); err != nil {
|
||||||
|
return 0, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var width, height int64
|
||||||
|
found := false
|
||||||
|
for _, stream := range formatCtx.Streams() {
|
||||||
|
if stream.CodecParameters().MediaType() == astiav.MediaTypeVideo {
|
||||||
|
width = int64(stream.CodecParameters().Width())
|
||||||
|
height = int64(stream.CodecParameters().Height())
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return 0, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// get duration in seconds
|
||||||
|
duration := formatCtx.Duration()
|
||||||
|
durationSeconds := duration / int64(astiav.TimeBase)
|
||||||
|
|
||||||
|
return durationSeconds, width, height
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue