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