Last active
August 29, 2015 14:06
-
-
Save mmstick/33f2a3f7c10abc43dfee to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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