Unbewohnte
4 years ago
committed by
GitHub
5 changed files with 514 additions and 0 deletions
@ -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 |
||||
} |
@ -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) |
||||
} |
||||
} |
@ -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 |
||||
} |
@ -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() |
||||
} |
@ -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…
Reference in new issue