Compare commits

...
12 Commits
Author SHA1 Message Date
Simon Kellet e0d4d2d989 error handling for zero bytes 2025-01-22 19:19:00 +00:00
Simon Kellet 9871e574d5 better glitching algorithm 2025-01-22 19:18:49 +00:00
Simon Kellet 24b11a4033 png support 2025-01-22 18:51:01 +00:00
Simon Kellet 25cd72190d fixed usage 2025-01-22 17:23:56 +00:00
Simon Kellet d2e7e11e59 removed binary 2025-01-22 17:20:40 +00:00
Simon Kellet 23585f58a8 now can add chance to glitch 2025-01-22 17:19:48 +00:00
Simon Kellet 2f3c65ae9f changes 2025-01-14 00:40:22 +00:00
Simon Kellet 61400ce031 ignore changes 2025-01-14 00:11:13 +00:00
Simon Kellet 689231d94a ignore update 2025-01-14 00:10:39 +00:00
Simon Kellet 51d7b25cba clean up and guard checks 2025-01-14 00:08:36 +00:00
Simon Kellet bb6177811b ignore mp4 2025-01-09 19:49:36 +00:00
Simon Kellet e4a5771e18 added new functionality 2025-01-09 19:48:57 +00:00
7 changed files with 180 additions and 34 deletions
+4
View File
@@ -1,4 +1,8 @@
*.gif
*.jpeg
*.png
*.mp4
./imgs/*
imgs/
glitch_img
.DS_Store
+1 -1
View File
@@ -24,7 +24,7 @@ go build
### Example
```bash
./glitch*img img.jpeg 100 30 2
./glitch_img img.jpeg 100 30 2
```
Outputs 100 images into a destination folder _output_ with 30 random bytes of data, running through the file twice
+50
View File
@@ -0,0 +1,50 @@
package main
import (
"fmt"
"os/exec"
"time"
)
func ffmpegGenerateMP4(fileExt string) {
inputPattern := "./output/img_glitched_%d." + fileExt
outputVideo := "output.mp4"
cmd := exec.Command(
"ffmpeg",
"-y",
"-framerate", "30",
"-i", inputPattern,
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2,setpts=PTS*2.0", // Scale and slow down playback
"-vcodec", "libx264", // Use H.264 codec
"-pix_fmt", "yuv420p",
"-crf", "23", // Compression level: lower is higher quality (range: 18-28)
"-preset", "fast",
outputVideo,
)
done := make(chan bool)
go func() {
err := cmd.Run()
check(err, "cannot run ffmpeg command!")
done <- true
}()
go func() {
msg := "Generating MP4"
c := 0
for {
select {
case <-done:
return
default:
fmt.Printf("\r%s%s", msg, dots(c))
c = (c + 1)
time.Sleep(500 * time.Millisecond)
}
}
}()
<-done
fmt.Println("\nGIF successfully created in ./output.mp4!")
}
+57 -9
View File
@@ -1,22 +1,22 @@
package main
import (
"fmt"
"math/rand/v2"
"os"
"path/filepath"
)
func makeOutputDir() {
// Default to just "output"
err := os.Mkdir("output", 0755)
check(err, "cannot make directory")
func fillSliceWithASCII(slice []byte) {
const asciiMin = 16
const asciiMax = 150
for i := range slice {
slice[i] = byte(rand.Int32N(asciiMax-asciiMin+1) + asciiMin)
}
}
func glitchImage(inputFile string, outputFile string, shitSize int64, iterations int64) error {
// Ensure the output directory exists
makeOutputDir()
// Prepend the "output" directory to the output file
outputFilePath := filepath.Join("output", outputFile)
// Open the source file for reading
@@ -39,6 +39,10 @@ func glitchImage(inputFile string, outputFile string, shitSize int64, iterations
// Get the size of the destination file
fileSize := fileInfo.Size()
if fileSize == 0 {
fmt.Println("Cannot glitch file with size of zero bytes!")
return fmt.Errorf("input file is zero bytes")
}
// Generate a random offset within the file
var i int64
@@ -51,10 +55,54 @@ func glitchImage(inputFile string, outputFile string, shitSize int64, iterations
// Generate random data and write it in
shit := make([]byte, shitSize)
fillSliceWithASCII(shit)
_, err = destination.Write(shit)
check(err, "failed to write shit data: %w")
// fmt.Printf("Successfully wrote %d bytes of shit at offset %d in file %s\n", shitSize, offset, outputFilePath)
}
return nil
}
func copyImage(inputFile string, outputFile string) error {
// Prepend the "output" directory to the output file
outputFilePath := filepath.Join("output", outputFile)
// Open the source file for reading
source, err := os.Open(inputFile)
check(err, "failed to open source file: %w")
defer source.Close()
// Create a new file for writing the copy
destination, err := os.Create(outputFilePath)
check(err, "failed to create output file: %w")
defer destination.Close()
// Copy the contents of the source file to the destination
_, err = destination.ReadFrom(source)
check(err, "failed to copy file contents: %w")
// no errors
return nil
}
func generateGlitchedSequence(inputFile string, fileExt string, totalImages int, shitSize int64, iterations int64, chanceToRandom int32) error {
for i := 1; i <= totalImages; i++ {
outputFileName := fmt.Sprintf("img_glitched_%d.%s", i, fileExt)
if rand.Int32N(100) < chanceToRandom {
// Glitched image
err := glitchImage(inputFile, outputFileName, shitSize, iterations)
if err != nil {
return fmt.Errorf("Cannot glitch image")
}
} else {
// Normal image
err := copyImage(inputFile, outputFileName)
if err != nil {
return fmt.Errorf("Cannot glitch image")
}
}
}
// no errors
return nil
}
BIN
View File
Binary file not shown.
+34 -19
View File
@@ -3,18 +3,28 @@ package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
)
// TODO:
// - Flags instead of os.Args
// - Interactive UI
// - Check compatability with other image types (PNG etc)
// - better ffmpeg bindings (go package?)
// - Update README
const MAX_SIZE = 1000000
func main() {
if len(os.Args) < 5 {
fmt.Println("Usage: go run main.go <source-file> <number-of-copies> <shit-size> <iterations>")
if len(os.Args) < 6 {
fmt.Println("Usage: ./glitch.go <source-file> <number-of-copies> <shit-size> <iterations> <chance-to-random % (0-100)>")
return
}
sourceFile := os.Args[1]
fileExt := filepath.Ext(sourceFile)
numCopies, err := strconv.Atoi(os.Args[2])
if err != nil || numCopies <= 0 {
fmt.Println("Invalid number of copies. Must be a positive integer.")
@@ -25,27 +35,32 @@ func main() {
if err != nil || shitSize <= 0 {
fmt.Println("Invalid shit size. Must be a positive integer.")
return
} else if shitSize > MAX_SIZE {
fmt.Printf("shitSize exceeds maximum size (%d)\n", MAX_SIZE)
return
}
iterations, err := strconv.ParseInt(os.Args[4], 10, 8)
check(err, "Cannot parse iterations. Must be a positive integer")
for i := 1; i <= numCopies; i++ {
outputFile := fmt.Sprintf("%s_glitched_%d%s",
stripExtension(sourceFile),
i,
filepath.Ext(sourceFile),
)
chanceToRandom, err := strconv.ParseInt(os.Args[5], 10, 32)
if err != nil || chanceToRandom < 0 {
fmt.Println("Invalid random chance value. Must be a positive integer (0-100).")
return
} else if chanceToRandom > 100 {
fmt.Println("Invalid random chance value. Must range between 0 to 100.")
return
}
err := glitchImage(sourceFile, outputFile, shitSize, iterations)
fmt.Fprintf(os.Stdout, "Generating:\n %d copies of %s\n %d random bytes of data\n %d interations(s)\n Chance to random %d%%\n File Extention: %s\n\n",
numCopies, sourceFile, shitSize, iterations, chanceToRandom, fileExt)
makeOutputDir()
defer clearOutputDir()
err = generateGlitchedSequence(sourceFile, fileExt, numCopies, shitSize, iterations, int32(chanceToRandom))
if err != nil {
fmt.Printf("Error glitching file %d: %v\n", i, err)
return
}
}
fmt.Println("GENREATING GIF YEAHH ")
// now run ffmpeg to make a gif!
cmd := exec.Command("ffmpeg", "-y", "-framerate 60", "-i", "./output/img_glitched_%d.jpeg output.gif")
err = cmd.Run()
check(err, "cannot run ffmpeg command!")
//
ffmpegGenerateMP4(fileExt)
}
+29
View File
@@ -2,6 +2,7 @@ package main
import (
"fmt"
"os"
"path/filepath"
)
@@ -11,7 +12,35 @@ func check(e error, s string) {
}
}
func dots(count int) string {
dotStr := ""
for i := 0; i < count; i++ {
dotStr += "."
}
return dotStr
}
func stripExtension(filename string) string {
ext := filepath.Ext(filename)
return filename[:len(filename)-len(ext)]
}
func makeOutputDir() {
// Default to just "output"
err := os.Mkdir("output", 0755)
check(err, "cannot make directory")
return
}
func clearOutputDir() {
err := os.RemoveAll("./output")
check(err, "cannot remove directory")
return
}
func printUsage() {
fmt.Println("Usage: go run main.go <source-file> <number-of-copies> <shit-size> <iterations>")
fmt.Println(`
source-file:
`)
}