Skip to content

Instantly share code, notes, and snippets.

@mmstick
Last active August 29, 2015 14:06
Show Gist options
  • Save mmstick/33f2a3f7c10abc43dfee to your computer and use it in GitHub Desktop.
Save mmstick/33f2a3f7c10abc43dfee to your computer and use it in GitHub Desktop.
// Package first obtains a list of episodes in all subdirectories and stores
// data about each episode into an episode struct. After the data is collected,
// it is stored inside of the Episodes type. After all episodes are gathered,
// the program will transcode all of the episodes with H.265 10-bit video and
// Opus audio -- storing the transcoded video into a MKV container. Information
// is printed as soon as the episode is finished transcoding.
package main
import "flag"
import "fmt"
import "io/ioutil"
import "os"
import "os/exec"
import "time"
const (
CRF_HELP = "Set the CRF value: 0-51. Higher CRF gives lower quality."
STATIC_PARAMS = "me=star:subme=7:bframes=16:b-adapt=2:ref=16:rc-lookahead=60:max-merge=5:tu-intra-depth=4:tu-inter-depth=4"
MB = 1000000
)
var (
crf = flag.Int("crf", 24, CRF_HELP)
sprintf = fmt.Sprintf
)
// scanDir returns a list of files in the current directory.
func scanDir(path string) []os.FileInfo {
directory, _ := ioutil.ReadDir(path)
return directory
}
// Episode contains information about each episode.
type Episode struct {
name string
directory string
originalSize float32
transcodedSize float32
sizeDifference float32
time time.Duration
stat error
}
// Episodes contains a list of episodes
type Episodes []Episode
// appendEpisode will append the current episode to the episode list.
func (list *Episodes) appendEpisode(file os.FileInfo, directory string) {
*list = append(*list, Episode{
name: file.Name(),
directory: directory,
originalSize: float32(file.Size()) / MB,
})
}
// scanEpisodes will recurse through each subdirectory discovered and adds
// each episode found to the episode list.
func (list *Episodes) scanEpisodes(directoryList []os.FileInfo, dir string) {
for _, file := range directoryList {
if file.IsDir() {
subdir := sprintf("%s/%s", dir, file.Name())
list.scanEpisodes(scanDir(subdir), subdir)
} else {
list.appendEpisode(file, dir)
}
}
}
// convertToMKV replaces the file extension from the filename to mkv.
func convertToMKV(input string) string {
for index := len(input) - 1; index >= 0; index-- {
if input[index] == '.' {
input = input[:index]
}
}
return input + ".mkv"
}
// getFileNames returns the input and output names for the file to be
// encoded.
func getFileNames(directory, name *string) (string, string) {
inputName := sprintf("%s/%s", *directory, *name)
outputName := convertToMKV(sprintf("%s/encoded_%s", *directory, *name))
return inputName, outputName
}
// encodeParameters returns the encode parameters for the episode.
func encodeParameters(inputName, outputName string) *exec.Cmd {
x265Parameters := sprintf("crf=%d:%s", *crf, STATIC_PARAMS)
cmd := exec.Command("ffmpeg", "-i", inputName, "-c:a", "libopus",
"-c:v", "libx265", "-x265-params", x265Parameters, outputName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// transcodeSize returns the size of the newly transcoded episode, if it
// exists.
func transcodeSize(transcodedEpisode string) float32 {
if file, err := os.Open(transcodedEpisode); err == nil {
stat, _ := file.Stat()
return float32(stat.Size()) / MB
} else {
fmt.Println("err")
return 0
}
}
// transcode the current episode.
func (ep *Episode) transcode(index, files int) {
inputName, outputName := getFileNames(&ep.directory, &ep.name)
fmt.Printf("%d/%d: %s\n", index, files, ep.name)
startTime := time.Now()
ep.stat = encodeParameters(inputName, outputName).Run()
ep.time = time.Since(startTime)
ep.transcodedSize = transcodeSize(outputName)
ep.sizeDifference = ep.originalSize - ep.transcodedSize
ep.status()
}
// status prints the status of the transcoded episode.
func (ep *Episode) status() {
if ep.stat != nil {
fmt.Println("--> Failed to transcode", ep.name)
} else {
fmt.Println("--> Transcoded", ep.name)
fmt.Printf("--> Original Size: %.2fMB\n", ep.originalSize)
fmt.Printf("--> New Size: %.2fMB\n", ep.transcodedSize)
fmt.Printf("--> Difference: %.2fMB\n", ep.sizeDifference)
fmt.Println("--> Time:", ep.time)
}
}
// transcodeEpisodes will transcode all episodes in the episode list
func (list *Episodes) transcodeEpisodes() {
files := len(*list)
for index, ep := range *list {
ep.transcode(index+1, files)
}
}
// Creates a new list of episodes
func newEpisodeList(directory string) *Episodes {
var episodeList Episodes
episodeList.scanEpisodes(scanDir(directory), directory)
return &episodeList
}
func main() {
flag.Parse()
directory, _ := os.Getwd()
startTime := time.Now()
episodeList := newEpisodeList(directory)
episodeList.transcodeEpisodes()
fmt.Printf("Transcoding completed in %s\n", time.Since(startTime))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment