Options.Audio adds all streams
This commit is contained in:
parent
c9bc141f27
commit
bbbb23041a
4 changed files with 81 additions and 107 deletions
17
README.md
17
README.md
|
@ -28,11 +28,11 @@ Height() int
|
|||
Depth() int
|
||||
Bitrate() int
|
||||
Frames() int
|
||||
Stream() int
|
||||
Duration() float64
|
||||
FPS() float64
|
||||
Codec() string
|
||||
AudioCodec() string
|
||||
Stream() int
|
||||
HasAudio() bool
|
||||
FrameBuffer() []byte
|
||||
MetaData() map[string]string
|
||||
SetFrameBuffer(buffer []byte) error
|
||||
|
@ -71,6 +71,7 @@ The `VideoWriter` is used to write frames to a video file. The only required par
|
|||
vidio.NewVideoWriter(filename string, width, height int, options *vidio.Options) (*vidio.VideoWriter, error)
|
||||
|
||||
FileName() string
|
||||
Audio() string
|
||||
Width() int
|
||||
Height() int
|
||||
Bitrate() int
|
||||
|
@ -81,7 +82,6 @@ FPS() float64
|
|||
Quality() float64
|
||||
Codec() string
|
||||
Format() string
|
||||
AudioCodec() string
|
||||
|
||||
Write(frame []byte) error
|
||||
Close()
|
||||
|
@ -97,11 +97,14 @@ type Options struct {
|
|||
Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
|
||||
Codec string // Codec for video.
|
||||
Format string // Pixel Format for video. Default "rgb24".
|
||||
Audio string // File path for audio. If no audio, audio="".
|
||||
AudioCodec string // Codec for audio.
|
||||
Audio string // File path for extra stream data.
|
||||
}
|
||||
```
|
||||
|
||||
The `Options.Audio` parameter is intended for users who wish to process a video stream and keep the audio. Instead of having to process the video and store in a file and then combine with the original audio later, the user can simply pass in the original file path via the `Options.Video` parameter. This will combine the video with all other streams in the given file (Audio, Subtitle, Data, and Attachments Streams) and will cut all streams to be the same length. Note that `vidio` is not a audio/video editing library.
|
||||
|
||||
Note that this means that adding extra stream data from a file will only work if the filename being written to is a container format.
|
||||
|
||||
## Images
|
||||
|
||||
Vidio provides some convenience functions for reading and writing to images using an array of bytes. Currently, only `png` and `jpeg` formats are supported. When reading images, an optional `buffer` can be passed in to avoid array reallocation.
|
||||
|
@ -121,8 +124,8 @@ options := vidio.Options{
|
|||
FPS: video.FPS(),
|
||||
Bitrate: video.Bitrate(),
|
||||
}
|
||||
if video.AudioCodec() != "" {
|
||||
options.Audio = "input.mp4"
|
||||
if video.HasAudio() {
|
||||
options.Audio = video.FileName()
|
||||
}
|
||||
|
||||
writer, _ := vidio.NewVideoWriter("output.mp4", video.Width(), video.Height(), &options)
|
||||
|
|
30
video.go
30
video.go
|
@ -17,11 +17,11 @@ type Video struct {
|
|||
depth int // Depth of frames.
|
||||
bitrate int // Bitrate for video encoding.
|
||||
frames int // Total number of frames.
|
||||
stream int // Stream Index.
|
||||
duration float64 // Duration of video in seconds.
|
||||
fps float64 // Frames per second.
|
||||
codec string // Codec used for video encoding.
|
||||
audioCodec string // Codec used for audio encoding.
|
||||
stream int // Stream Index.
|
||||
hasaudio bool // Flag storing whether file has Audio.
|
||||
framebuffer []byte // Raw frame data.
|
||||
metadata map[string]string // Video metadata.
|
||||
pipe *io.ReadCloser // Stdout pipe for ffmpeg process.
|
||||
|
@ -55,6 +55,11 @@ func (video *Video) Frames() int {
|
|||
return video.frames
|
||||
}
|
||||
|
||||
// Returns the zero-indexed video stream index.
|
||||
func (video *Video) Stream() int {
|
||||
return video.stream
|
||||
}
|
||||
|
||||
func (video *Video) Duration() float64 {
|
||||
return video.duration
|
||||
}
|
||||
|
@ -67,15 +72,8 @@ func (video *Video) Codec() string {
|
|||
return video.codec
|
||||
}
|
||||
|
||||
// Returns the audio codec of the first audio track (if present).
|
||||
// Can be used to check if a video has audio.
|
||||
func (video *Video) AudioCodec() string {
|
||||
return video.audioCodec
|
||||
}
|
||||
|
||||
// Returns the zero-indexed video stream index.
|
||||
func (video *Video) Stream() int {
|
||||
return video.stream
|
||||
func (video *Video) HasAudio() bool {
|
||||
return video.hasaudio
|
||||
}
|
||||
|
||||
func (video *Video) FrameBuffer() []byte {
|
||||
|
@ -132,21 +130,13 @@ func NewVideoStreams(filename string) ([]*Video, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
audioCodec := ""
|
||||
if len(audioData) > 0 {
|
||||
// Look at the first audio stream only.
|
||||
if ac, ok := audioData[0]["codec_name"]; ok {
|
||||
audioCodec = ac
|
||||
}
|
||||
}
|
||||
|
||||
streams := make([]*Video, len(videoData))
|
||||
for i, data := range videoData {
|
||||
video := &Video{
|
||||
filename: filename,
|
||||
depth: 3,
|
||||
audioCodec: audioCodec,
|
||||
stream: i,
|
||||
hasaudio: len(audioData) > 0,
|
||||
metadata: data,
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
type VideoWriter struct {
|
||||
filename string // Output filename.
|
||||
audio string // Audio filename.
|
||||
audio string // Extra stream data filename.
|
||||
width int // Frame width.
|
||||
height int // Frame height.
|
||||
bitrate int // Output video bitrate.
|
||||
|
@ -24,7 +24,6 @@ type VideoWriter struct {
|
|||
quality float64 // Used if bitrate not given. Default 0.5.
|
||||
codec string // Codec to encode video with. Default libx264.
|
||||
format string // Output format. Default rgb24.
|
||||
audioCodec string // Codec to encode audio with. Default aac.
|
||||
pipe *io.WriteCloser // Stdout pipe of ffmpeg process.
|
||||
cmd *exec.Cmd // ffmpeg command.
|
||||
}
|
||||
|
@ -39,14 +38,14 @@ type Options struct {
|
|||
Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
|
||||
Codec string // Codec for video.
|
||||
Format string // Pixel Format for video. Default "rgb24".
|
||||
Audio string // File path for audio. If no audio, audio="".
|
||||
AudioCodec string // Codec for audio.
|
||||
Audio string // File path for extra stream data.
|
||||
}
|
||||
|
||||
func (writer *VideoWriter) FileName() string {
|
||||
return writer.filename
|
||||
}
|
||||
|
||||
// File used to fill in extra stream data.
|
||||
func (writer *VideoWriter) Audio() string {
|
||||
return writer.audio
|
||||
}
|
||||
|
@ -91,10 +90,6 @@ func (writer *VideoWriter) Format() string {
|
|||
return writer.format
|
||||
}
|
||||
|
||||
func (writer *VideoWriter) AudioCodec() string {
|
||||
return writer.audioCodec
|
||||
}
|
||||
|
||||
// Creates a new VideoWriter struct with default values from the Options struct.
|
||||
func NewVideoWriter(filename string, width, height int, options *Options) (*VideoWriter, error) {
|
||||
// Check if ffmpeg is installed on the users machine.
|
||||
|
@ -102,15 +97,16 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide
|
|||
return nil, err
|
||||
}
|
||||
|
||||
writer := &VideoWriter{filename: filename}
|
||||
|
||||
if options == nil {
|
||||
options = &Options{}
|
||||
}
|
||||
|
||||
writer.width = width
|
||||
writer.height = height
|
||||
writer.bitrate = options.Bitrate
|
||||
writer := &VideoWriter{
|
||||
filename: filename,
|
||||
width: width,
|
||||
height: height,
|
||||
bitrate: options.Bitrate,
|
||||
}
|
||||
|
||||
// Default Parameter options logic from:
|
||||
// https://github.com/imageio/imageio-ffmpeg/blob/master/imageio_ffmpeg/_io.py#L268.
|
||||
|
@ -161,23 +157,9 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide
|
|||
|
||||
if options.Audio != "" {
|
||||
if !exists(options.Audio) {
|
||||
return nil, fmt.Errorf("audio file %s does not exist", options.Audio)
|
||||
return nil, fmt.Errorf("file %s does not exist", options.Audio)
|
||||
}
|
||||
|
||||
audioData, err := ffprobe(options.Audio, "a")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(audioData) == 0 {
|
||||
return nil, fmt.Errorf("given audio file %s has no audio", options.Audio)
|
||||
}
|
||||
|
||||
writer.audio = options.Audio
|
||||
|
||||
if options.AudioCodec == "" {
|
||||
writer.audioCodec = "aac"
|
||||
} else {
|
||||
writer.audioCodec = options.AudioCodec
|
||||
}
|
||||
}
|
||||
|
||||
return writer, nil
|
||||
|
@ -202,12 +184,22 @@ func (writer *VideoWriter) init() error {
|
|||
|
||||
gif := strings.HasSuffix(strings.ToLower(writer.filename), ".gif")
|
||||
|
||||
if writer.audio == "" || gif {
|
||||
command = append(command, "-an") // No audio.
|
||||
} else {
|
||||
// Assumes "writer.file" is a container format.
|
||||
// gif check is included since they are a common format.
|
||||
if writer.audio != "" && !gif {
|
||||
command = append(
|
||||
command,
|
||||
"-i", writer.audio,
|
||||
"-map", "0:v:0",
|
||||
"-map", "1:a?", // Add Audio streams if present.
|
||||
"-c:a", "copy",
|
||||
"-map", "1:s?", // Add Subtitle streams if present.
|
||||
"-c:s", "copy",
|
||||
"-map", "1:d?", // Add Data streams if present.
|
||||
"-c:d", "copy",
|
||||
"-map", "1:t?", // Add Attachments streams if present.
|
||||
"-c:t", "copy",
|
||||
"-shortest", // Cut longest streams to match audio duration.
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -261,16 +253,6 @@ func (writer *VideoWriter) init() error {
|
|||
}
|
||||
}
|
||||
|
||||
// If audio was included, then specify video and audio channels.
|
||||
if writer.audio != "" && !gif {
|
||||
command = append(
|
||||
command,
|
||||
"-acodec", writer.audioCodec,
|
||||
"-map", "0:v:0",
|
||||
"-map", "1:a:0",
|
||||
)
|
||||
}
|
||||
|
||||
command = append(command, writer.filename)
|
||||
cmd := exec.Command("ffmpeg", command...)
|
||||
writer.cmd = cmd
|
||||
|
|
|
@ -45,7 +45,6 @@ func TestVideoMetaData(t *testing.T) {
|
|||
assertEquals(video.duration, 3.366667)
|
||||
assertEquals(video.fps, float64(30))
|
||||
assertEquals(video.codec, "h264")
|
||||
assertEquals(video.audioCodec, "aac")
|
||||
assertEquals(video.stream, 0)
|
||||
assertEquals(len(video.framebuffer), 0)
|
||||
|
||||
|
@ -83,7 +82,7 @@ func TestVideoFrame(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestVideoWriting(t *testing.T) {
|
||||
testWriting := func(input, output string, audio bool) {
|
||||
testWriting := func(input, output string) {
|
||||
video, err := NewVideo(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -93,8 +92,8 @@ func TestVideoWriting(t *testing.T) {
|
|||
Bitrate: video.Bitrate(),
|
||||
Codec: video.Codec(),
|
||||
}
|
||||
if audio {
|
||||
options.Audio = input
|
||||
if video.HasAudio() {
|
||||
options.Audio = video.FileName()
|
||||
}
|
||||
|
||||
writer, err := NewVideoWriter(output, video.width, video.height, &options)
|
||||
|
@ -112,9 +111,9 @@ func TestVideoWriting(t *testing.T) {
|
|||
os.Remove(output)
|
||||
}
|
||||
|
||||
testWriting("test/koala.mp4", "test/koala-out.mp4", true)
|
||||
testWriting("test/koala.mp4", "test/koala-out.mp4")
|
||||
fmt.Println("Video Writing (with Audio) Test Passed")
|
||||
testWriting("test/koala-noaudio.mp4", "test/koala-noaudio-out.mp4", false)
|
||||
testWriting("test/koala-noaudio.mp4", "test/koala-noaudio-out.mp4")
|
||||
fmt.Println("Video Writing (without Audio) Test Passed")
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue