forked from Niek/superview
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsuperview.go
More file actions
156 lines (126 loc) · 4.46 KB
/
Copy pathsuperview.go
File metadata and controls
156 lines (126 loc) · 4.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"math"
"os"
"os/exec"
"os/signal"
"strconv"
"strings"
"syscall"
"github.com/jessevdk/go-flags"
)
var opts struct {
Input string `short:"i" long:"input" description:"The input video filename" value-name:"FILE" required:"true"`
Output string `short:"o" long:"output" description:"The output video filename" value-name:"FILE" required:"false" default:"output.mp4"`
Bitrate int `short:"b" long:"bitrate" description:"The bitrate in bytes/second to encode in. If not specified, take the same bitrate as the input file" value-name:"BITRATE" required:"false"`
}
func main() {
// Parse flags
flags.Parse(&opts)
_, err := os.Stat(opts.Input)
if err != nil {
log.Fatal(err)
}
// Check for available codecs
codecs, err := exec.Command("ffmpeg", "-codecs").CombinedOutput()
codecsString := string(codecs)
if err != nil {
log.Fatal("Cannot find ffmpeg/ffprobe on your system. Make sure to install it first: https://github.com/Niek/superview/#requirements")
}
fmt.Printf("ffmpeg version: %s\n", codecsString[strings.Index(codecsString, "ffmpeg version ")+15:20])
fmt.Printf("H.264 support: %t\n", strings.Contains(codecsString, "H.264"))
fmt.Printf("H.265/HEVC support: %t\n", strings.Contains(codecsString, "H.265"))
// Check specs of the input video (codec, dimensions, duration, bitrate)
out, err := exec.Command("ffprobe", "-i", opts.Input, "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=codec_name,width,height,duration,bit_rate", "-print_format", "json").CombinedOutput()
if err != nil {
log.Fatal(err)
}
// Parse into struct
var specs struct {
Streams []struct {
Codec string `json:"codec_name"`
Width int
Height int
Duration string
Bitrate string `json:"bit_rate"`
}
}
json.Unmarshal(out, &specs)
// Parse duration to float
duration, _ := strconv.ParseFloat(specs.Streams[0].Duration, 64)
// Parse bitrate to int
if opts.Bitrate == 0 {
opts.Bitrate, _ = strconv.Atoi(specs.Streams[0].Bitrate)
}
outX := int(float64(specs.Streams[0].Width)/(4.0/3.0)*(16.0/9.0)) / 2 * 2 // multiplier of 2
outY := specs.Streams[0].Height
fmt.Printf("Scaling input file %s (codec: %s, duration: %d secs) from %d*%d to %d*%d using superview scaling\n", opts.Input, specs.Streams[0].Codec, int(duration), specs.Streams[0].Width, specs.Streams[0].Height, outX, outY)
// Generate filter files
fX, err := os.Create("x.pgm")
fY, err := os.Create("y.pgm")
defer fX.Close()
defer fY.Close()
wX := bufio.NewWriter(fX)
wY := bufio.NewWriter(fY)
wX.WriteString(fmt.Sprintf("P2 %d %d 65535\n", outX, outY))
wY.WriteString(fmt.Sprintf("P2 %d %d 65535\n", outX, outY))
for y := 0; y < outY; y++ {
for x := 0; x < outX; x++ {
tx := (float64(x)/float64(outX) - 0.5) * 2.0
sx := float64(x) - float64(outX-specs.Streams[0].Width)/2.0
offset := math.Pow(tx, 2) * (float64(outX-specs.Streams[0].Width) / 2.0)
if tx < 0 {
offset *= -1
}
wX.WriteString(strconv.Itoa(int(sx - offset)))
wX.WriteString(" ")
wY.WriteString(strconv.Itoa(y))
wY.WriteString(" ")
}
wX.WriteString("\n")
wY.WriteString("\n")
}
wX.Flush()
wY.Flush()
fmt.Printf("Filter files generated, re-encoding video at bitrate %d MB/s\n", opts.Bitrate/1024/1024)
// Starting encoder, write progress to stdout pipe
cmd := exec.Command("ffmpeg", "-hide_banner", "-progress", "pipe:1", "-loglevel", "panic", "-y", "-re", "-i", opts.Input, "-i", "x.pgm", "-i", "y.pgm", "-filter_complex", "remap,format=yuv444p,format=yuv420p", "-c:v", specs.Streams[0].Codec, "-b:v", strconv.Itoa(opts.Bitrate), "-c:a", "copy", "-x265-params", "log-level=error", opts.Output)
stdout, err := cmd.StdoutPipe()
rd := bufio.NewReader(stdout)
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// Kill encoder process on Ctrl+C
sigC := make(chan os.Signal, 1)
signal.Notify(sigC, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigC
cmd.Process.Kill()
}()
// Read and parse progress
for {
line, _, err := rd.ReadLine()
if err == io.EOF {
fmt.Printf("\r")
break
}
if bytes.Contains(line, []byte("out_time_ms=")) {
time := bytes.Replace(line, []byte("out_time_ms="), nil, 1)
timeF, _ := strconv.ParseFloat(string(time), 64)
fmt.Printf("\rEncoding progress: %.2f%%", timeF/(duration*10000))
}
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("Done! You can open the output file %s to see the result\n", opts.Output)
}