Browse Source

Add files via upload

main
Unbewohnte 3 years ago committed by GitHub
parent
commit
a54ec85867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      audio/audio.go
  2. 41
      extractor/extractor.go
  3. 144
      jsonData/jsonData.go
  4. 209
      main.go
  5. 94
      processor/processor.go

26
audio/audio.go

@ -0,0 +1,26 @@
package audio
import (
"os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
)
func PlayAudio(audioPath string) {
f, err := os.Open(audioPath)
if err != nil {
panic(err)
}
streamer, format, err := mp3.Decode(f)
defer streamer.Close()
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
}

41
extractor/extractor.go

@ -0,0 +1,41 @@
package extractor
import (
"fmt"
"path/filepath"
"github.com/lijo-jose/gffmpeg/pkg/gffmpeg"
)
const (
ImageFileExtention string = "png"
)
func GetFFMPEG(binPath string) gffmpeg.GFFmpeg {
GFFMPEG, err := gffmpeg.NewGFFmpeg(binPath)
if err != nil {
panic(err)
}
return GFFMPEG
}
func ExtractFrames(gff gffmpeg.GFFmpeg, videoPath, outputPath string, fps int) {
builder := gffmpeg.NewBuilder()
builder = builder.SrcPath(videoPath).VideoFilters(fmt.Sprintf("fps=%v", fps)).DestPath(outputPath + "%10d." + ImageFileExtention)
gff.Set(builder)
output := gff.Start(nil)
if output.Err != nil {
panic(output.Err)
}
}
func ExtractAudio(gff gffmpeg.GFFmpeg, videoPath, outputPath string) {
builder := gffmpeg.NewBuilder()
builder = builder.SrcPath(videoPath).VideoFilters("-q:a -map a").DestPath(filepath.Join(outputPath, "extractedAudio.mp3"))
gff.Set(builder)
output := gff.Start(nil)
if output.Err != nil {
panic(output.Err)
}
}

144
jsonData/jsonData.go

@ -0,0 +1,144 @@
package jsonData
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
)
type Data struct {
MaxGOROUTINES uint
WIDTH uint
HEIGHT uint
ExtractionFPS int
AudioFilePath string
AudioPlayback bool
FFMPEGbin string
VideoFramesOutputPath string
InputVideo string
AsciiFilesPath string
AsciiChars []string
}
const (
jsonFilename string = "VideoToAsciiSettings.json"
)
var (
defaults = Data{
MaxGOROUTINES: 200,
WIDTH: 210,
HEIGHT: 60,
ExtractionFPS: 30,
AudioFilePath: ``,
AudioPlayback: false,
FFMPEGbin: `(must have)`,
VideoFramesOutputPath: ``,
InputVideo: `(must have)`,
AsciiFilesPath: ``,
AsciiChars: []string{" ", "░", "▒", "▓", "█"},
}
currentDir, _ = os.Getwd()
)
func checkIfJsonExist() bool {
files, err := ioutil.ReadDir(currentDir)
if err != nil {
panic(err)
}
for _, file := range files {
if file.Name() == jsonFilename {
return true
}
}
return false
}
// This Json MUST be contained in the same directory with a binary
func createJson() *os.File {
json, err := os.Create(filepath.Join(currentDir, jsonFilename))
if err != nil {
panic(err)
}
return json
}
func writeDefaults(jsonSettings *os.File) {
marshalledDefaults, err := json.MarshalIndent(defaults, "", " ")
if err != nil {
panic(err)
}
jsonSettings.Write(marshalledDefaults)
}
func readFromJson() *Data {
file, err := ioutil.ReadFile(filepath.Join(currentDir, jsonFilename))
if err != nil {
panic(err)
}
var data Data
json.Unmarshal(file, &data)
videoFrames := filepath.Join(data.VideoFramesOutputPath)
asciiTxtPath := filepath.Join(data.AsciiFilesPath)
audioPath := filepath.Join(data.AudioFilePath)
if videoFrames == "" || videoFrames == "." || videoFrames == " " {
data.VideoFramesOutputPath = currentDir
}
if asciiTxtPath == "" || asciiTxtPath == "." || asciiTxtPath == " " {
data.AsciiFilesPath = currentDir
}
if audioPath == "" || audioPath == "." || audioPath == " " {
data.AudioFilePath = currentDir
}
return &data
}
func createDirs(data *Data) {
videoFrames := filepath.Join(data.VideoFramesOutputPath)
asciiTxtPath := filepath.Join(data.AsciiFilesPath)
audioPath := filepath.Join(data.AudioFilePath)
if videoFrames == "" || videoFrames == "." || videoFrames == " " {
videoFrames = currentDir
}
if asciiTxtPath == "" || asciiTxtPath == "." || asciiTxtPath == " " {
asciiTxtPath = currentDir
}
if audioPath == "" || audioPath == "." || audioPath == " " {
audioPath = currentDir
}
err := os.MkdirAll(videoFrames, os.ModePerm)
if err != nil {
panic(err)
}
err = os.MkdirAll(asciiTxtPath, os.ModePerm)
if err != nil {
panic(err)
}
err = os.MkdirAll(audioPath, os.ModePerm)
if err != nil {
panic(err)
}
}
func GetSettings() (*Data, bool) {
if checkIfJsonExist() == false {
jsonFile := createJson()
defer jsonFile.Close()
writeDefaults(jsonFile)
data := readFromJson()
createDirs(data)
return data, true
}
data := readFromJson()
createDirs(data)
return data, false
}

209
main.go

@ -0,0 +1,209 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"sort"
"sync"
"github.com/nsf/termbox-go"
"./audio"
"./extractor"
"./jsonData"
"./processor"
)
var (
settings, firstLaunch = jsonData.GetSettings()
WG sync.WaitGroup
// how many goroutines you want to be working on processing frames
MaxGOROUTINES uint = settings.MaxGOROUTINES
// width and height of an ascii text file
WIDTH uint = settings.WIDTH
HEIGHT uint = settings.HEIGHT
// the amount of frames/second you extract from video with ffmpeg
ExtractionFPS int = settings.ExtractionFPS
// where the audio file will go when you extract it from a video
AudioFilePath string = settings.AudioFilePath
// shows if you want to play audio or not
AudioPlayback bool = settings.AudioPlayback
// path to ffmpeg
FFMPEGbin = settings.FFMPEGbin
// this is where the frames from a video will be
VideoFramesOutputPath = settings.VideoFramesOutputPath
// path to a video
InputVideo = settings.InputVideo
// this is where the processed ascii textfiles will be
AsciiFilesPath = settings.AsciiFilesPath
// character set for "asciifying" images
asciiChars = settings.AsciiChars
)
func main() {
// 1) extract frames && audio (optional) from a video
// 2) process videoframes into Ascii
// 3) it`s ready to play
if firstLaunch {
fmt.Println("Created settings file; closing in 10 seconds...")
time.Sleep(time.Second * 10)
os.Exit(0)
}
fmt.Print("Extraction mode (0), playback mode (1), processing images mode (2), Extract audio (3) ? (0,1,2,3) : ")
var input string
fmt.Scanln(&input)
if input == "0" || input == "0 " {
t0 := time.Now()
fmt.Println("Extracting images...")
gff := extractor.GetFFMPEG(FFMPEGbin)
extractor.ExtractFrames(gff, InputVideo, VideoFramesOutputPath, ExtractionFPS)
FinishTime := time.Now().Sub(t0)
fmt.Printf("Done in %v", FinishTime)
fmt.Scanln()
} else if input == "1" || input == "1 " {
asciiFiles, err := ioutil.ReadDir(AsciiFilesPath)
if err != nil {
panic(err)
}
var Sequence []string
for _, file := range asciiFiles {
if file.Name()[len(file.Name())-3:] == "txt" {
frame, readErr := ioutil.ReadFile(filepath.Join(AsciiFilesPath, file.Name()))
if readErr != nil {
panic(readErr)
}
Sequence = append(Sequence, string(frame))
}
}
err = termbox.Init()
if err != nil {
panic(err)
}
timeForEachFrame := time.Duration(time.Second / time.Duration(ExtractionFPS))
t0 := time.Now()
if AudioPlayback == true {
go audio.PlayAudio(filepath.Join(AudioFilePath, "extractedAudio.mp3"))
}
var counter uint64 = 0
var nextFrameTime time.Time = time.Now()
for {
if counter < uint64(len(Sequence)) {
now := time.Now()
if now.After(nextFrameTime) {
nextFrameTime = now.Add(timeForEachFrame)
showFrame(Sequence[counter])
counter++
}
} else {
termbox.Close()
break
}
}
fmt.Printf("Took %v", time.Now().Sub(t0))
fmt.Scanln()
} else if input == "2" || input == "2 " {
t0 := time.Now()
fmt.Println("Processing images...")
files, err := ioutil.ReadDir(VideoFramesOutputPath)
if err != nil {
panic(err)
}
var sortedFilenames []string
for _, f := range files {
if f.Name()[len(f.Name())-3:] == extractor.ImageFileExtention {
sortedFilenames = append(sortedFilenames, f.Name())
}
}
sort.Strings(sortedFilenames)
jobs := make(chan *processor.DataForAscii, len(sortedFilenames))
for i := 0; i < int(MaxGOROUTINES); i++ {
WG.Add(1)
go Worker(jobs, &WG)
}
var counter uint64 = 0
for {
if counter == uint64(len(sortedFilenames)) {
break
}
if len(jobs) < int(MaxGOROUTINES) {
img, err := processor.GetImage(filepath.Join(VideoFramesOutputPath, sortedFilenames[counter]))
if err != nil {
panic(err)
}
jobs <- &processor.DataForAscii{
Img: img,
Width: WIDTH,
Height: HEIGHT,
Filename: fmt.Sprintf("%010d_ascii.txt", counter),
}
img = nil
counter++
}
}
close(jobs)
WG.Wait()
FinishTime := time.Now().Sub(t0)
fmt.Printf("Done in %v", FinishTime)
fmt.Scanln()
} else if input == "3" || input == "3 " {
t0 := time.Now()
fmt.Println("Extracting audio...")
gff := extractor.GetFFMPEG(FFMPEGbin)
extractor.ExtractAudio(gff, InputVideo, AudioFilePath)
FinishTime := time.Now().Sub(t0)
fmt.Printf("Done in %v", FinishTime)
fmt.Scanln()
}
}
func Worker(jobs <-chan *processor.DataForAscii, WG *sync.WaitGroup) {
defer WG.Done()
for data := range jobs {
processor.ASCIIfy(asciiChars, data.Img, data.Width, data.Height, filepath.Join(AsciiFilesPath, data.Filename))
data = nil
}
}
func showFrame(frame string) {
termbox.SetCursor(0, 0)
var x, y int = 0, 0
for _, char := range frame {
termbox.HideCursor()
if string(char) == "\n" {
y++
x = 0
} else {
termbox.SetCell(x, y, char, termbox.ColorWhite, termbox.ColorBlack)
x++
}
}
termbox.Flush()
}

94
processor/processor.go

@ -0,0 +1,94 @@
package processor
import (
"image"
_ "image/jpeg"
"image/png"
_ "image/png"
"os"
"path/filepath"
"github.com/nfnt/resize"
)
type DataForAscii struct {
Img *image.Image
Width uint
Height uint
Filename string
}
// GetImage returns image.Image from a filepath
func GetImage(pathToFile string) (*image.Image, error) {
file, err := os.Open(filepath.Join(pathToFile))
if err != nil {
return nil, err
}
defer file.Close()
image, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return &image, nil
}
// SaveImage takes an image.Image and saves it to a file
func SaveImage(filename string, img *image.Image) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
err = png.Encode(f, *img)
if err != nil {
return err
}
return nil
}
// ResizeImage takes an image.Image and returns a resized one using https://github.com/nfnt/resize
func ResizeImage(img image.Image, newWidth uint, newHeight uint) image.Image {
resizedImage := resize.Resize(newWidth, newHeight, img, resize.Lanczos3)
return resizedImage
}
// GetChar returns a char from chars corresponding to the pixel brightness
func GetChar(chars []string, pixelBrightness int) string {
charsLen := len(chars)
return chars[int((charsLen*pixelBrightness)/256)]
}
// ASCIIfy converts and image.Image into ASCII art
func ASCIIfy(ASCIIchars []string, img *image.Image, cols, rows uint, filename string) {
var resized image.Image
if cols == uint(0) || rows == uint(0) {
resized = *img
} else {
resized = ResizeImage(*img, cols, rows)
}
imgBounds := resized.Bounds()
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer f.Close()
for y := 0; y < imgBounds.Max.Y; y++ {
for x := 0; x < imgBounds.Max.X; x++ {
r, g, b, _ := resized.At(x, y).RGBA()
r = r / 257
g = g / 257
b = b / 257
currentPixelBrightness := int((float64(0.2126)*float64(r) + float64(0.7152)*float64(g) + float64(0.0722)*float64(b)))
f.Write([]byte(GetChar(ASCIIchars, currentPixelBrightness)))
}
f.Write([]byte("\n"))
}
}
Loading…
Cancel
Save