| Frankiezafe  (Talk | contribs)  (→process) | Frankiezafe  (Talk | contribs)   (→screenshots) | ||
| (16 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 148: | Line 163: | ||
|   	movie_path += '.mkv' |   	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 ]) |   	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:python]] | ||
| + | [[category:video]] | ||
| + | [[category:codec]] | ||
| + | [[category:compression]] | ||
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]]