Frankiezafe (Talk | contribs) |
Frankiezafe (Talk | contribs) (→screenshots) |
||
(13 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | [[File:Igotit 1000-1500jpg movie.png| | + | [[File:Igotit 1000-1500jpg movie.png|400px]] |
Automatised post-processing of video using image compression algorithm glitches. | Automatised post-processing of video using image compression algorithm glitches. | ||
− | == | + | == screenshots == |
+ | |||
+ | '''Die Antwoord''', Banana brain | ||
+ | |||
+ | <script> { global $wgGallerier; $wgGallerier->addGallery( array( "path" => "compressions/die-antwoord-banana-brain/", "width" => 300, "height" => 126 )); } </script> | ||
+ | |||
+ | '''Lorn''', Acid Rain | ||
+ | |||
+ | <script> { global $wgGallerier; $wgGallerier->addGallery( array( "path" => "compressions/lorn-acid-rain/", "width" => 300, "height" => 126 )); } </script> | ||
+ | |||
+ | |||
+ | '''Bhad Bhabie''', I got it | ||
+ | |||
+ | <script> { global $wgGallerier; $wgGallerier->addGallery( array( "path" => "compressions/bhad-babie-i-got-it/", "width" => 300, "height" => 126 )); } </script> | ||
+ | |||
+ | == video == | ||
<html><iframe src="https://player.vimeo.com/video/272100688?title=0&byline=0&portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></html> | <html><iframe src="https://player.vimeo.com/video/272100688?title=0&byline=0&portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></html> | ||
Line 9: | Line 24: | ||
<html><iframe src="https://player.vimeo.com/video/272102866?title=0&byline=0&portrait=0" width="640" height="338" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></html> | <html><iframe src="https://player.vimeo.com/video/272102866?title=0&byline=0&portrait=0" width="640" height="338" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></html> | ||
− | == process == | + | === process === |
For '''jpg''' codec, the process is recompressing each frame with 1% less quality than previous compression. In the videos above, we start at 40% and gradually decrease the quality to 1%. | For '''jpg''' codec, the process is recompressing each frame with 1% less quality than previous compression. In the videos above, we start at 40% and gradually decrease the quality to 1%. | ||
Line 149: | Line 164: | ||
subprocess.call(['ffmpeg', '-f', 'image2', '-framerate', str(fps), '-r', str(fps), '-i', im_path, '-an', '-vcodec', 'mjpeg', '-q:v', '1', folder_compressed + '/' + movie_path ]) | subprocess.call(['ffmpeg', '-f', 'image2', '-framerate', str(fps), '-r', str(fps), '-i', im_path, '-an', '-vcodec', 'mjpeg', '-q:v', '1', folder_compressed + '/' + movie_path ]) | ||
+ | == sound == | ||
+ | |||
+ | First attempt using [http://lame.sourceforge.net/ lame], [[File:I_got_it_3.mp3]] | ||
+ | |||
+ | <html> | ||
+ | <audio controls style="width:100%"> | ||
+ | <source src="https://frankiezafe.org/images/0/00/I_got_it_3.mp3"> | ||
+ | </audio> | ||
+ | </html> | ||
+ | |||
+ | === script === | ||
+ | |||
+ | try: | ||
+ | from cStringIO import StringIO as BytesIO | ||
+ | except ImportError: | ||
+ | from io import BytesIO | ||
+ | from PIL import Image | ||
+ | import os | ||
+ | import shutil | ||
+ | import subprocess | ||
+ | |||
+ | source = 'source.wav' | ||
+ | |||
+ | swap_file1 = 'output.mp3' | ||
+ | swap_file2 = 'output2.wav' | ||
+ | |||
+ | passes = 27 | ||
+ | freq = 20001 | ||
+ | freq_gap = 300 | ||
+ | scale = 1.03 | ||
+ | compression = 20 | ||
+ | compression_gap = 1.3 | ||
+ | |||
+ | FNULL = open(os.devnull, 'w') | ||
+ | |||
+ | subprocess.call(['lame', '--quiet', '-q', '9', source, swap_file1 ]) | ||
+ | |||
+ | for i in range( 0, passes ): | ||
+ | print( '>>>>>>>>>>> pass: ' + str( i ) + ', freq: ' + str(freq) + ', compression: ' + str(compression) ) | ||
+ | subprocess.call(['ffmpeg', '-i', swap_file1, '-ar', str(freq), '-loglevel', 'panic', '-y', swap_file2 ]) | ||
+ | subprocess.call(['lame', '--scale', str(scale), '--quiet', '-q', '9', '--comp', str(compression), swap_file2, swap_file1 ]) | ||
+ | if i%2 == 1: | ||
+ | freq -= freq_gap | ||
+ | else: | ||
+ | freq += freq_gap | ||
+ | compression += compression_gap | ||
+ | |||
+ | # preview | ||
+ | subprocess.call(['xplayer', swap_file1], stdout=FNULL, stderr=subprocess.STDOUT) | ||
+ | |||
+ | == References == | ||
+ | |||
+ | * https://arstechnica.com/features/2007/10/the-audiofile-understanding-mp3-compression/ | ||
+ | * https://en.wikipedia.org/wiki/MP3#Bit_rate | ||
+ | * http://sox.sourceforge.net/sox.html | ||
+ | * http://lame.sourceforge.net/ | ||
[[category:glitch]] | [[category:glitch]] |
Automatised post-processing of video using image compression algorithm glitches.
Die Antwoord, Banana brain
Lorn, Acid Rain
Bhad Bhabie, I got it
For jpg codec, the process is recompressing each frame with 1% less quality than previous compression. In the videos above, we start at 40% and gradually decrease the quality to 1%.
For gif codec, it is much more straight-forward, as we do the process in one pass, specifying the number of colors in the palette.
Python script
try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO from PIL import Image import os import shutil import subprocess ------------- GLOBAL ------------- # set to true if the source video has not been extracted already regenarate_video_frames = False recompress_video_frames = True regenarate_output_video = True # source video path video = 'source.mv' # folder path to export source video frames folder_frames = 'frames' # folder path to store compressed video frames folder_compressed = 'compressed' # type of compressor - JPG or GIF compressor = 'JPG' # jpg compressor settings jpg_from = 40 jpg_to = 1 jpg_steps = 1 # gif compressor settings gif_colors = 4 # output frames limits limit_from = 1800 limit_to = 3125 # output frame rate fps = 25 fprefix = 'output_' ------------- FUNCTIONS ------------- def create_folder(p): if not os.path.exists(p): os.makedirs(p) else: clear_folder(p) def clear_folder(p): for root, dirs, files in os.walk( p ): for f in files: os.unlink(os.path.join( root, f )) for d in dirs: shutil.rmtree(os.path.join( root, d )) def jpg_compress( src_path, dst_path ): jpg_q = jpg_from while jpg_q >= jpg_to: src = Image.open( src_path ) buffer = BytesIO() src.save( buffer, "JPEG", quality = jpg_q, optimize=True, progressive=True ) jpg_q -= jpg_steps buffer.seek(0) with open( dst_path, "w") as handle: handle.write(buffer.read()) src_path = dst_path def gif_compress( src_path, dst_path ): src = Image.open( src_path ) buffer = BytesIO() src = src.convert('P', palette=Image.ADAPTIVE, colors=gif_colors) src.save( buffer, "PNG" ) buffer.seek(0) with open( dst_path, "w") as handle: handle.write(buffer.read()) ------------- PROCESS ------------- if regenarate_video_frames == True: create_folder( folder_frames ) subprocess.call(['ffmpeg', '-i', video, '-vcodec', 'png', folder_frames + '/' + fprefix + '%05d.png' ]) if recompress_video_frames == True: create_folder( folder_compressed ) ext = '.jpg' if compressor == 'GIF': ext = '.png' for root, dirs, files in os.walk( folder_frames ): files.sort() i = 0 outi = 0 for f in files: if limit_from > -1 and i >= limit_from and i < limit_to: outf = folder_compressed + '/' + fprefix if outi < 10: outf += '0000' elif outi < 100: outf += '000' elif outi < 1000: outf += '00' elif outi < 10000: outf += '0' outf += str(outi) + ext outi += 1 if compressor == 'JPG': jpg_compress( folder_frames + '/' + f, outf ) elif compressor == 'GIF': gif_compress( folder_frames + '/' + f, outf ) print( str( outi ) + '/' + str( ( limit_to - limit_from ) ) + ' frame (' + ext + ')' ) i += 1 if regenarate_output_video == True: movie_path = 'movie_' + fps + 'fps_' + limit_from + '-' + limit_to im_path = folder_compressed + '/' + fprefix + '%5d' if compressor == 'JPG': movie_path += '_' + jpg_from + '-' + jpg_to + '-' + jpg_steps + 'jpg' im_path += '.jpg' elif compressor == 'GIF': movie_path += '_' + gif_colors + 'gif' im_path += '.png' else: pass movie_path += '.mkv' subprocess.call(['ffmpeg', '-f', 'image2', '-framerate', str(fps), '-r', str(fps), '-i', im_path, '-an', '-vcodec', 'mjpeg', '-q:v', '1', folder_compressed + '/' + movie_path ])
First attempt using lame, File:I got it 3.mp3
try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO from PIL import Image import os import shutil import subprocess source = 'source.wav' swap_file1 = 'output.mp3' swap_file2 = 'output2.wav' passes = 27 freq = 20001 freq_gap = 300 scale = 1.03 compression = 20 compression_gap = 1.3 FNULL = open(os.devnull, 'w') subprocess.call(['lame', '--quiet', '-q', '9', source, swap_file1 ]) for i in range( 0, passes ): print( '>>>>>>>>>>> pass: ' + str( i ) + ', freq: ' + str(freq) + ', compression: ' + str(compression) ) subprocess.call(['ffmpeg', '-i', swap_file1, '-ar', str(freq), '-loglevel', 'panic', '-y', swap_file2 ]) subprocess.call(['lame', '--scale', str(scale), '--quiet', '-q', '9', '--comp', str(compression), swap_file2, swap_file1 ]) if i%2 == 1: freq -= freq_gap else: freq += freq_gap compression += compression_gap # preview subprocess.call(['xplayer', swap_file1], stdout=FNULL, stderr=subprocess.STDOUT)
online identity ∋ [ social ∋ [mastodon♥, twitter®, facebook®, diaspora, linkedin®]
∥ repos ∋ [github®, gitlab♥, bitbucket®, sourceforge] ∥ media ∋ [itch.io®, vimeo®, peertube♥, twitch.tv®, tumblr®] ∥ communities ∋ [godotengine♥, openprocessing, stackoverflow, threejs]]