├── LICENSE ├── README.md ├── muybridge0001.png ├── muybridge0002.png ├── muybridge0003.png ├── muybridge0004.png ├── muybridge0005.png ├── muybridge0006.png ├── muybridge0007.png ├── muybridge0008.png ├── muybridge0009.png ├── muybridge0010.png ├── muybridge0011.png ├── muybridge0012.png ├── muybridge0013.png ├── muybridge0014.png ├── muybridge0015.png └── tomato.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kaspar RAVEL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tomato 2 | 3 | **tomato** is a python script to glitch AVI files 4 | - utilities inspired by [Way Spurr-Chen](https://github.com/wayspurrchen)'s [moshy](https://github.com/wayspurrchen/moshy). 5 | - functionality based off of [Tomasz Sulej](https://github.com/tsulej)'s research on AVI file structure. 6 | 7 | It was designed to operate video frame ordering, substraction and duplication. 8 | 9 | Modes called through -mode [mode] 10 | 11 | - `void` - does nothing 12 | - `random` - randomizes frame order 13 | - `reverse` - reverse frame order 14 | - `invert` - switches each consecutive frame witch each other 15 | - `bloom` - duplicates `c` times p-frame number `n` 16 | - `pulse` - duplicates groups of `c` p-frames every `n` frames 17 | - `overlap` - copy group of `c` frames taken from every `n`th position 18 | - `jiggle` - take frame from around current position. `n` parameter is spread size [broken] 19 | 20 | Other parameters : 21 | 22 | - `-c and -n` - reserved for the modes 23 | - `-ff [0 or 1]` - ignore first frame (default 1) 24 | - `-a [0 or 1]` - activate audio (default 0) 25 | - `-k [0 to 1]` - kill frames with too much data (default 0.7) 26 | 27 | ## Examples of usage 28 | 29 | Takes out iframes: 30 | >python tomato.py -i input.avi 31 | 32 | Duplicate 50 times the 100th frame: 33 | >python tomato.py -i input.avi -m bloom -c 50 -n 100 34 | 35 | Duplicates 5 times a frame every 10 frame: 36 | >python tomato.py -i input.avi -m pulse -c 5 -n 10 37 | 38 | Shuffles all of the frames in the video: 39 | >python tomato.py -i input.avi -m random 40 | 41 | Copy 4 frames taken starting from every 2nd frame. [1 2 3 4 3 4 5 6 5 6 7 8 7 8...]: 42 | >python tomato.py -i input.avi -m overlap -c 4 -n 2 43 | 44 | 45 | ## Why tomato ? 46 | 47 | I made tomato because I wanted to be able to glitch avi files regardless of the contained codec, the resolution and the file size while still being super duper fast and not needing to encode anything. 48 | 49 | ## How does it work ? 50 | 51 | It reorders the frames inside the movi tag of your AVI file. 52 | 53 | ## How should you use it 54 | 55 | Libraries used : numpy, argparse, os, re, random, struct, itertools 56 | 57 | I recommend preparing your AVI files with ffmpeg and the codec library of your choice. To read your glitched files I recommend VLC or Xine if you're under Linux. Both are great for visualizing content (especially xine for the random mode) but keep in mind you should always be experimenting and using different visualizers or tools to bake your files. 58 | 59 | If you have any questions or ideas feel free to send me an email at kaspar.ravel@gmail.com 60 | 61 | For more info on development : https://www.kaspar.wtf/blog/tomato-v2-0-avi-breaker 62 | -------------------------------------------------------------------------------- /muybridge0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0001.png -------------------------------------------------------------------------------- /muybridge0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0002.png -------------------------------------------------------------------------------- /muybridge0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0003.png -------------------------------------------------------------------------------- /muybridge0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0004.png -------------------------------------------------------------------------------- /muybridge0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0005.png -------------------------------------------------------------------------------- /muybridge0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0006.png -------------------------------------------------------------------------------- /muybridge0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0007.png -------------------------------------------------------------------------------- /muybridge0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0008.png -------------------------------------------------------------------------------- /muybridge0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0009.png -------------------------------------------------------------------------------- /muybridge0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0010.png -------------------------------------------------------------------------------- /muybridge0011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0011.png -------------------------------------------------------------------------------- /muybridge0012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0012.png -------------------------------------------------------------------------------- /muybridge0013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0013.png -------------------------------------------------------------------------------- /muybridge0014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0014.png -------------------------------------------------------------------------------- /muybridge0015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsKaspar/tomato/bac10c756109498d19198c35fcc488cf915c4fac/muybridge0015.png -------------------------------------------------------------------------------- /tomato.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import argparse, os, re, random, struct 4 | from itertools import chain 5 | from itertools import repeat 6 | 7 | print (" _ _ ") 8 | print ("| | | | ") 9 | print ("| |_ ___ _ __ ___ __ _| |_ ___ ") 10 | print ("| __/ _ \| '_ ` _ \ / _` | __/ _ \ ") 11 | print ("| || (_) | | | | | | (_| | || (_) |") 12 | print (" \__\___/|_| |_| |_|\__,_|\__\___/ ") 13 | print ("tomato.py v2.0 last update 21.03.2020") 14 | print ("\\\\ Audio Video Interleave breaker") 15 | print (" ") 16 | print ("glitch tool made with love for the glitch art community <3") 17 | print ("if you have any questions, would like to contact me") 18 | print ("or even hire me for performance / research / education") 19 | print ("you can shoot me an email at kaspar.ravel@gmail.com") 20 | print ("___________________________________") 21 | print (" ") 22 | print ("wb. https://www.kaspar.wtf ") 23 | print ("fb. https://www.facebook.com/kaspar.wtf ") 24 | print ("ig. https://www.instagram.com/kaspar.wtf ") 25 | print ("___________________________________") 26 | print (" ") 27 | 28 | #parse arguments 29 | parser = argparse.ArgumentParser(add_help=True) 30 | parser.add_argument("-i", "--input", help="input file") 31 | parser.add_argument('-m', "--mode", action='store', dest='modevalue',help='choose mode - void, random, reverse, invert, bloom, pulse, jiggle, overlap', default='void') 32 | parser.add_argument('-c', action='store', dest='countframes', default=1,help="how often to glitch (for modes that support it)") 33 | parser.add_argument('-n', action='store', dest='positframes', default=1,help="how many frames in the glitch (for modes that support it)") 34 | parser.add_argument('-a', action='store', dest='audio', default=0,help="attempt to preserve audio") 35 | parser.add_argument('-ff', action='store', dest='firstframe', default=1,help="whether to keep first video frame") 36 | parser.add_argument('-k', action='store', dest='kill', default=0.7, type=float,help="max framesize to kill while cleaning") 37 | 38 | args = parser.parse_args() 39 | 40 | filein = args.input 41 | mode = args.modevalue 42 | countframes = args.countframes 43 | positframes = args.positframes 44 | audio = args.audio 45 | firstframe = args.firstframe 46 | kill = args.kill 47 | 48 | if filein is None or os.path.exists(filein) == False: 49 | print("> step 0/5: valid input file required!") 50 | print("use -h to see help") 51 | exit() 52 | 53 | 54 | #define temp directory and files 55 | temp_nb = random.randint(10000, 99999) 56 | temp_dir = "temp-" + str(temp_nb) 57 | temp_hdrl = temp_dir +"/hdrl.bin" 58 | temp_movi = temp_dir +"/movi.bin" 59 | temp_idx1 = temp_dir +"/idx1.bin" 60 | 61 | os.mkdir(temp_dir) 62 | 63 | #Define constrain function for jiggle :3 64 | def constrain(val, min_val, max_val): 65 | return min(max_val, max(min_val, val)) 66 | 67 | ###################################### 68 | ### STREAM FILE INTO WORK DIR BINS ### 69 | ###################################### 70 | 71 | print("> step 1/5 : streaming into binary files") 72 | 73 | def bstream_until_marker(bfilein, bfileout, marker=0, startpos=0): 74 | chunk = 1024 75 | filesize = os.path.getsize(bfilein) 76 | if marker : 77 | marker = str.encode(marker) 78 | 79 | with open(bfilein,'rb') as rd: 80 | with open(bfileout,'ab') as wr: 81 | for pos in range(startpos, filesize, chunk): 82 | rd.seek(pos) 83 | buffer = rd.read(chunk) 84 | 85 | if marker: 86 | if buffer.find(marker) > 0 : 87 | marker_pos = re.search(marker, buffer).start() # position is relative to buffer glitchedframes 88 | marker_pos = marker_pos + pos # position should be absolute now 89 | split = buffer.split(marker, 1) 90 | wr.write(split[0]) 91 | return marker_pos 92 | else: 93 | wr.write(buffer) 94 | else: 95 | wr.write(buffer) 96 | 97 | #make 3 files, 1 for each chunk 98 | movi_marker_pos = bstream_until_marker(filein, temp_hdrl, "movi") 99 | idx1_marker_pos = bstream_until_marker(filein, temp_movi, "idx1", movi_marker_pos) 100 | bstream_until_marker(filein, temp_idx1, 0, idx1_marker_pos) 101 | 102 | #################################### 103 | ### FUN STUFF WITH VIDEO CONTENT ### 104 | #################################### 105 | 106 | print("> step 2/5 : constructing frame index") 107 | 108 | with open(temp_movi,'rb') as rd: 109 | chunk = 1024 110 | filesize = os.path.getsize(temp_movi) 111 | frame_table = [] 112 | 113 | for pos in range(0, filesize, chunk): 114 | rd.seek(pos) 115 | buffer = rd.read(chunk) 116 | 117 | #build first list with all adresses 118 | for m in (re.finditer(b'\x30\x31\x77\x62', buffer)): # find iframes 119 | if audio : frame_table.append([m.start() + pos, 'sound']) 120 | for m in (re.finditer(b'\x30\x30\x64\x63', buffer)): # find b frames 121 | frame_table.append([m.start() + pos, 'video']) 122 | 123 | #then remember to sort the list 124 | frame_table.sort(key=lambda tup: tup[0]) 125 | 126 | l = [] 127 | l.append([0,0, 'void']) 128 | max_frame_size = 0 129 | 130 | #build tuples for each frame index with frame sizes 131 | for n in range(len(frame_table)): 132 | if n + 1 < len(frame_table): 133 | frame_size = frame_table[n + 1][0] - frame_table[n][0] 134 | else: 135 | frame_size = filesize - frame_table[n][0] 136 | max_frame_size = max(max_frame_size, frame_size) 137 | l.append([frame_table[n][0],frame_size, frame_table[n][1]]) 138 | 139 | 140 | ######################## 141 | ### TIME FOR SOME FX ### 142 | ######################## 143 | 144 | # variables that make shit work 145 | clean = [] 146 | final = [] 147 | 148 | # keep first video frame or not 149 | if firstframe : 150 | for x in l : 151 | if x[2] == 'video': 152 | clean.append(x) 153 | break 154 | 155 | # clean the list by killing "big" frames 156 | for x in l: 157 | if x[1] <= (max_frame_size * kill) : 158 | clean.append(x) 159 | 160 | # FX modes 161 | 162 | if mode == "void": 163 | print('> step 3/5 : mode void') 164 | final = clean 165 | 166 | if mode == "random": 167 | print('> step 3/5 : mode random') 168 | final = random.sample(clean,len(clean)) 169 | 170 | if mode == "reverse": 171 | print('> step 3/5 : mode reverse') 172 | final = clean[::-1] 173 | 174 | if mode == "invert": 175 | print('> step 3/5 : mode invert') 176 | final = sum(zip(clean[1::2], clean[::2]), ()) 177 | 178 | if mode == 'bloom': 179 | print('> step 3/5 : mode bloom') 180 | repeat = int(countframes) 181 | frame = int(positframes) 182 | ## split list 183 | lista = clean[:frame] 184 | listb = clean[frame:] 185 | ## rejoin list with bloom 186 | final = lista + ([clean[frame]]*repeat) + listb 187 | 188 | if mode == 'pulse': 189 | print('> step 3/5 : mode pulse') 190 | pulselen = int(countframes) 191 | pulseryt = int(positframes) 192 | j = 0 193 | for x in clean: 194 | i = 0 195 | if(j % pulselen == 0): 196 | while i < pulselen : 197 | final.append(x) 198 | i = i + 1 199 | else: 200 | final.append(x) 201 | j = j + 1 202 | 203 | if mode == "jiggle": 204 | print('> step 3/5 : mode jiggle') 205 | #print('*needs debugging lol help thx*') # didn't pandy's branch fix this? 206 | amount = int(positframes) 207 | final = [clean[constrain(x+int(random.gauss(0,amount)),0,len(clean)-1)] for x in range(0,len(clean))] 208 | 209 | if mode == "overlap": 210 | print('> step 3/5 : mode overlap') 211 | pulselen = int(countframes) 212 | pulseryt = int(positframes) 213 | 214 | clean = [clean[i:i+pulselen] for i in range(0,len(clean),pulseryt)] 215 | final = [item for sublist in clean for item in sublist] 216 | 217 | if mode == 'exponential': 218 | print('> step 3/5 : mode exponential') 219 | print('sorry, currently not implemented. using void..') 220 | 221 | if mode == 'swap': 222 | print('> step 3/5 : mode swap') 223 | print('sorry, currently not implemented. using void..') 224 | 225 | 226 | #################################### 227 | ### PUT VIDEO FILE BACK TOGETHER ### 228 | #################################### 229 | 230 | print("> step 4/5 : putting things back together") 231 | 232 | #name new file 233 | cname = '-c' + str(countframes) if int(countframes) > 1 else '' 234 | pname = '-n' + str(positframes) if int(positframes) > 1 else '' 235 | fileout = filein[:-4] + '-' + mode + cname + pname + '.avi' 236 | 237 | #delete old file 238 | if os.path.exists(fileout): 239 | os.remove(fileout) 240 | 241 | bstream_until_marker(temp_hdrl, fileout) 242 | 243 | with open(temp_movi,'rb') as rd: 244 | filesize = os.path.getsize(temp_movi) 245 | with open(fileout,'ab') as wr: 246 | wr.write(struct.pack('<4s', b'movi')) 247 | for x in final: 248 | if x[0] != 0 and x[1] != 0: 249 | rd.seek(x[0]) 250 | wr.write(rd.read(x[1])) 251 | 252 | bstream_until_marker(temp_idx1, fileout) 253 | 254 | #remove unnecessary temporary files and folders 255 | os.remove(temp_hdrl) 256 | os.remove(temp_movi) 257 | os.remove(temp_idx1) 258 | os.rmdir(temp_dir) 259 | 260 | print("> step 5/5 : done - final idx size : " + str(len(final))) 261 | --------------------------------------------------------------------------------