golang之媒体处理
[视频]
获取视频封面图:
1) 如果是使用oss的话, 可以添加指定的后缀生成指定图片
视频截帧: https://help.aliyun.com/zh/oss/user-guide/video-snapshots?spm=a2c4g.11186623.0.0.8ea266d4kR5bST
2) 借助第三方包实现: https://github.com/u2takey/ffmpeg-go
注: 他是需要依赖ffmpeg的
package main import (
"bytes"
"fmt"
"github.com/disintegration/imaging"
ffmpeg "github.com/u2takey/ffmpeg-go"
"log"
"os"
"strings"
) func main() {
var videoPath = "./test.mp4"
videoPath = "https://hk-cloud-screen-test.oss-cn-hangzhou.aliyuncs.com/ai_moment/108f17a6-2f94-4b2d-8667-d369058a5455.mp4"
snapshotName, err := GetSnapshot(videoPath, "test", 1)
if err != nil {
fmt.Println(snapshotName, err)
}
} func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName string, err error) {
buf := bytes.NewBuffer(nil)
err = ffmpeg.Input(videoPath).
Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
WithOutput(buf, os.Stdout).
Run() if err != nil {
log.Fatal("生成缩略图失败:", err)
return "", err
} img, err := imaging.Decode(buf)
if err != nil {
log.Fatal("生成缩略图失败:", err)
return "", err
} err = imaging.Save(img, snapshotPath+".png")
if err != nil {
log.Fatal("生成缩略图失败:", err)
return "", err
} names := strings.Split(snapshotPath, "\\")
snapshotName = names[len(names)-1] + ".png"
return
}
还支持更多操作 转GIF,视频格式转换等操作
获取视频长度:
1)若是oss上的视频,则可以使用阿里云的IMM中的提取视频信息的服务
注意这里获取需要使用到签名之后获取对应的数据
这里使用基于阿里云oss包:
client, err := oss.New("oss-cn-hangzhou.aliyuncs.com", "accessKeyID", "accessKeySecret")
if err != nil {
panic(err)
}
bucketName := "buekct名称"
bucket, err := client.Bucket(bucketName)
if err != nil {
panic(err)
}
objectKey := "pp/cbe1655d-9f67-490c-901a-20932f79443c.mp4"
// Get object
options := []oss.Option{
oss.AddParam("x-oss-process", "video/info"),
}
signedURL, err := bucket.SignURL(objectKey, oss.HTTPGet, 10*60, options...)
if err != nil {
panic(err)
}
fmt.Println(signedURL)
// 获取请求信息
resp, err := http.Get(signedURL)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
fmt.Println("data:", string(body))
2)对于在线视频
package main import (
"bytes"
"encoding/binary"
"fmt"
"io"
"os"
) // 获取本地文件视频秒数 // BoxHeader 信息头
type BoxHeader struct {
Size uint32
FourccType [4]byte
Size64 uint64
} func main() {
url := "./video.mp4"
file, err := os.Open(url)
if err != nil {
panic(err)
}
duration, err := GetMP4Duration(file)
if err != nil {
panic(err)
}
fmt.Println(duration)
} // GetMP4Duration 获取视频时长,以秒计
func GetMP4Duration(reader io.ReaderAt) (lengthOfTime uint32, err error) {
var info = make([]byte, 0x10)
var boxHeader BoxHeader
var offset int64 = 0
// 获取moov结构偏移
for {
_, err = reader.ReadAt(info, offset)
if err != nil {
return
}
boxHeader = getHeaderBoxInfo(info)
fourccType := getFourccType(boxHeader)
if fourccType == "moov" {
break
}
// 有一部分mp4 mdat尺寸过大需要特殊处理
if fourccType == "mdat" {
if boxHeader.Size == 1 {
offset += int64(boxHeader.Size64)
continue
}
}
offset += int64(boxHeader.Size)
}
// 获取moov结构开头一部分
moovStartBytes := make([]byte, 0x100)
_, err = reader.ReadAt(moovStartBytes, offset)
if err != nil {
return
}
// 定义timeScale与Duration偏移
timeScaleOffset := 0x1C
durationOffest := 0x20
timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4])
Duration := binary.BigEndian.Uint32(moovStartBytes[durationOffest : durationOffest+4])
lengthOfTime = Duration / timeScale
return
} // getHeaderBoxInfo 获取头信息
func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) {
buf := bytes.NewBuffer(data)
binary.Read(buf, binary.BigEndian, &boxHeader)
return
} // getFourccType 获取信息头类型
func getFourccType(boxHeader BoxHeader) (fourccType string) {
fourccType = string(boxHeader.FourccType[:])
return
}
另外还有其他第三方库可以使用:
go get -u github.com/Stitch-Zhang/gmp4
可以处理非下载模式的在线资源
可以借助ffmpeg, 添加到环境变量中
https://github.com/BtbN/FFmpeg-Builds
package main import (
"context"
"fmt"
"gopkg.in/vansante/go-ffprobe.v2"
"log"
"time"
) func main() { api := "https://www.runoob.com/try/demo_source/movie.mp4"
//api := "https://hk-ai-test.oss-cn-hangzhou.aliyuncs.com/volcVideo/ac6f7816-d0de-4ddd-b57d-590cc5e2e94f_0.mp4"
ctx, cancelFunc := context.WithTimeout(context.Background(), 20*time.Second)
defer cancelFunc() data, err := ffprobe.ProbeURL(ctx, api)
if err != nil {
panic(err)
} if data == nil {
log.Fatal("获取数据为空")
} fmt.Println(data.Format.Duration().Seconds())
}
视频上传oss:
videoUrl := "https://muse.console.volcengine.com/api/storage/objects/media/7307071523758653478_origin.mp4?x-muse-token=ChUIARABGIzen6sGIIyBpasGKgM2MTMSmQEKDgiggI3e06eehGQQl5UBEgAaAhgCIggKAmxmEgJjbioGCNo0EIYDMikKJ3siYml6X2lkIjoiMTAwMDAwMDAxIiwiZGVyaXZlZF91aWQiOiIifUIUChJpY2FyY2guaWFtLmF1dGhycGNKLi9hcGkvc3RvcmFnZS9vYmplY3RzL21lZGlhLzczMDcwNzE1MjM3NTg2NTM0Nzga2AJpSExFbFAzM0RjWFZLSzJDenk5UXVlU3QxZXRidlkwSDZnaFBDdFB2VkZ4YjU3YjFnVlpmdlBkaEFtK3pZeEx1U3N2aWEydmZ1RVBaRStFNmd0aFdJSk5sUmFVM3RvNDkvWTZ1ME1IWUlOeVY5bjdicWxCZFI3ZGhZT2VRd3dZS2dXSEdhTzl5VllxSVRQcTgvdk14M3lXd1hvQ21LWjYwY0tUN2dBMlFjQStIN0cxWnJwRzc0bzU2UEtqd3ZTbVVPYzBLdlhtN1pJVTROOStTVFVoaExZdGpMUzFWVEo2Z3FqalVZeGMxV0FsTkN0WXJ6YitabitJandHMTl1WGMveHJZb2xOeFhZQk9sZ3JwNXBQTmV5QmRBYVdQSHpPdDlTbHpITytpeXJITzR3SmRQc0FWQ1E0cXJPbnpPaGJlWEdTVENZME5KelJISnJzT0ZuZE4xTFE9PQ==&infer_mime=ext"
resp, err := http.Get(videoUrl)
if err != nil {
fmt.Println("网络发生错误")
return
}
if resp.StatusCode == http.StatusNotFound {
fmt.Println("图片资源不存在")
return
}
defer resp.Body.Close() fmt.Println(resp.StatusCode)
// 读取文件内容
body, err := io.ReadAll(resp.Body) // 读取本地文件并上传
//videoPath := "./video.mp4"
//f, err := os.Open(videoPath)
//if err != nil {
// panic(err)
//}
//fmt.Println("-----读取视频文件")
//defer f.Close()
//data := make([]byte, 0)
//n, err := f.Read(data)
//fmt.Println(n, err)
//
err = bucket.PutObject("demo.mp4", bytes.NewReader(body))
fmt.Println("上传结果:", err)
[图片]
上传图片到oss:
//imgPath := "./oss.jpg"
// 本地读取文件后上传 //f, err := os.OpenFile(imgPath, os.O_RDONLY, 0644)
//if err != nil {
// panic(err)
//}
//defer f.Close()
//
//stat, err := f.Stat()
//if err != nil {
// panic(err)
//}
//
//// 创建一个byte切片
//data := make([]byte, stat.Size())
//count, err := f.Read(data)
//fmt.Println("文件长度:", count) // 读取一个文件base64
pictureUrl := "http://pic.netbian.com/uploads/allimg/210423/224716-1619189236e4d9.jpg"
//pictureUrl := "http://pic.netbian.com/uploads/allimg/210423/224716-16191892369.jpg" // 不存在的图片
resp, err := http.Get(pictureUrl)
if err != nil {
fmt.Println("网络发生错误")
return
}
if resp.StatusCode == http.StatusNotFound {
fmt.Println("图片资源不存在")
return
}
defer resp.Body.Close() fmt.Println(resp.StatusCode)
// 读取文件内容
body, err := io.ReadAll(resp.Body)
// 将[]byte数据转换成base64的字符串
imgBase64 := base64.StdEncoding.EncodeToString(body)
fmt.Println("img base64:", len(imgBase64))
// 解析base64字符串到[]byte
decode, err := base64.StdEncoding.DecodeString(imgBase64) err = bucket.PutObject("test222.jpg", bytes.NewReader(decode)) // 从本地文件上传
//err = bucket.PutObjectFromFile("test.jpg", imgPath)
if err != nil {
// HandleError(err)
panic(err)
}
fmt.Println("上传图片成功")
获取远程图片信息方法:
func GetImageInfo(imgUrl string) (*types.ImgInfo, error) {
resp, err := http.Get(imgUrl)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
logx.Infof("[获取图片信息]失败:%v 状态码", err, resp.StatusCode)
return nil, errors.New("获取远程图片失败")
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
resp.Body = io.NopCloser(bytes.NewReader(body))
m, _, err := image.Decode(resp.Body)
if err != nil {
return nil, err
}
imgBase64 := base64.StdEncoding.EncodeToString(body)
return &types.ImgInfo{
Width: m.Bounds().Dx(),
Heigh: m.Bounds().Dy(),
Base64Str: imgBase64,
}, nil
}
注:
1. 对于上传到oss的资源,如果使用未绑定域名方式的话, 他会默认附件下载的方式, 如果绑定了域名之后,可以在线查看资源
golang之媒体处理的更多相关文章
- TODO:这是一个我的自媒体
TODO:这是一个我的自媒体 自媒体(外文名:We Media)又称"公民媒体"或"个人媒体",是指私人化.平民化.普泛化.自主化的传播者,以现代化.电子化的手 ...
- GoLang几种读文件方式的比较
GoLang提供了很多读文件的方式,一般来说常用的有三种.使用Read加上buffer,使用bufio库和ioutil 库. 那他们的效率如何呢?用一个简单的程序来评测一下: package main ...
- 经济学人使用Golang构建微服务历程回顾
关键点 经济学人内容分发系统需要更大的灵活性,将内容传递给日益多样化的数字渠道.为了实现这一灵活性目标并保持高水平的性能和可靠性,平台从一个单体结构过渡到微服务体系结构. 用Go编写的服务是新系统的一 ...
- Golang源码分析之目录详解
开源项目「go home」聚焦Go语言技术栈与面试题,以协助Gopher登上更大的舞台,欢迎go home~ 导读 学习Go语言源码的第一步就是了解先了解它的目录结构,你对它的源码目录了解多少呢? 目 ...
- Golang 实现 RTP
在 Coding 之前我们先来简单介绍一下 RTP(Real-time Transport Protocol), 正如它的名字所说,用于互联网的实时传输协议,通过 IP 网络传输音频和视频的网络协议. ...
- Golang, 以17个简短代码片段,切底弄懂 channel 基础
(原创出处为本博客:http://www.cnblogs.com/linguanh/) 前序: 因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程.管道等高并发编程知识 ...
- 说说Golang的使用心得
13年上半年接触了Golang,对Golang十分喜爱.现在是2015年,离春节还有几天,从开始学习到现在的一年半时间里,前前后后也用Golang写了些代码,其中包括业余时间的,也有产品项目中的.一直 ...
- TODO:Golang指针使用注意事项
TODO:Golang指针使用注意事项 先来看简单的例子1: 输出: 1 1 例子2: 输出: 1 3 例子1是使用值传递,Add方法不会做任何改变:例子2是使用指针传递,会改变地址,从而改变地址. ...
- JavaScript自定义媒体播放器
使用<audio>和<video>元素的play()和pause()方法,可以手工控制媒体文件的播放.组合使用属性.事件和这两个方法,很容易创建一个自定义的媒体播放器,如下面的 ...
- Golang 编写的图片压缩程序,质量、尺寸压缩,批量、单张压缩
目录: 前序 效果图 简介 全部代码 前序: 接触 golang 不久,一直是边学边做,边总结,深深感到这门语言的魅力,等下要跟大家分享是最近项目 服务端 用到的图片压缩程序,我单独分离了出来,做成了 ...
随机推荐
- hook拼多多客服软件发消息,拼多多客服机器人代码,拼多多发消息代码
最近由于工作需要,逆向了拼多多客服平台,能调用消息监听.消息发送.根据订单号发起会话,是纯hook实现的,demo包括c++调用demo,c#调用demo.hook 的dll是c++编写的,有需要的人 ...
- ipv6 知识
ref: 网络编程懒人入门(十一):一文读懂什么是IPv6 https://cloud.tencent.com/developer/article/1551346 IT知识大全:IPv6详解
- Innodb对表加锁的过程分析
本节我们通过一些具体的案例来分析Innodb对表上锁的过程.具体场景如下图所示. 在这里我们将语句分为4类:普通select(快照读).锁定读.半一致性读 和 insert语句. 普通读 普通的sel ...
- Angular 18+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)
前言 在 Attribute Directives 属性型指令 文章中,我们学习过了指令.指令是没有 HTML 和 CSS 的组件,它单纯用于封装 JS 的部分. 这一篇我们将继续学习另一种指令 -- ...
- C# – class, filed, property, const, readonly, get, set, init, required 使用基础
前言 心血来潮,这篇讲点基础的东西. Field 比起 Property,Field 很不起眼,你若问 JavaScript,它甚至都没有 Field. 但在 C#,class 里头真正装 value ...
- x64汇编——汇编指令
汇编指令 mov dest, src mov move的简称 将src的内容赋值给dest,类似于dest = src [地址值] 中扩号 [ ]里面放的都是内存地址 一个变量的地址值,是它所有字节地 ...
- Nginx UI:全新的 Nginx 在线管理平台
前言 Nginx在程序部署中扮演着至关重要的角色,其高性能.高安全性.易于配置和管理的特点,使得它成为现代Web应用部署中不可或缺的一部分.今天大姚给大家分享一款实用的 Nginx Web UI 工具 ...
- 如何修改word默认模板(Normal.dotm)
背景描述:平时有大量的文字编辑工作要做,其中最繁琐的就是格式问题:为了排版工整.符合要求,在每个word中都要进行大量的更改,如:字体.大小.行距.段前段后间距等......但这其中有很多的重复性工作 ...
- ToDesk云电脑游戏数量?高性能显卡云桌面
玩游戏最怕遇到电脑配置跟不上,操作卡成狗不说,画面还一卡卡的,游戏体验极差. 最近被人安利了ToDesk的云电脑,可能是刚推出的,配置价格都很能打,浅用了一波拿来打APEX和荒野大镖客,体验有点惊喜到 ...
- Java统计list集合中重复的元素
本题目能够从宏观上理解list.set.map三大集合的特点: 解决思路是:使用一个map,key用来记录list中的数据,我们知道set集合不允许元素重复,正好在map的jdk8的api中有一个ke ...