Skip to content

Commit

Permalink
Better handling of whatsapp stickers
Browse files Browse the repository at this point in the history
  • Loading branch information
laggykiller committed Dec 16, 2022
1 parent 400547d commit 823b553
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 40 deletions.
2 changes: 1 addition & 1 deletion sticker_convert/preset.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"vid_size_max": 500000,
"img_size_max": 100000,
"vid_format": ".webp",
"img_format": ".png",
"img_format": ".webp",
"fps_min": 1,
"fps_max": 30,
"res_min": 512,
Expand Down
30 changes: 19 additions & 11 deletions sticker_convert/uploaders/compress_wastickers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from utils.converter import StickerConvert
from utils.format_verify import FormatVerify
from utils.metadata_handler import MetadataHandler
import time

def clean_dir(dir):
for i in os.listdir(dir):
Expand All @@ -17,44 +18,51 @@ def compress_wastickers(in_dir, out_dir, author='Me', title='My sticker pack', q
packs = MetadataHandler.split_sticker_packs(in_dir, title=title, file_per_pack=30, separate_image_anim=True)

for pack_title, stickers in packs.items():
num = 0 # Originally the Sticker Maker application name the files with int(time.time())
with tempfile.TemporaryDirectory() as tempdir:
for src in stickers:
print('Verifying', src, 'for compressing into .wastickers')

src_full_name = os.path.split(src)[-1]
src_name = os.path.splitext(src_full_name)[0]

if FormatVerify.is_anim(src):
extension = '.webp'
else:
extension = '.png'
# WhatsApp does not care about a static image in webp anyway
# if FormatVerify.is_anim(src):
# extension = '.webp'
# else:
# extension = '.png'

extension = '.webp'

dst = os.path.join(tempdir, src_name + extension)
dst = os.path.join(tempdir, str(num) + extension)
num += 1

if FormatVerify.check_file(src, res_min=512, res_max=512, square=True, size_max=500000, animated=True, format='.webp') or FormatVerify.check_file(src, res=512, square=True, size_max=100000, animated=False, format='.png'):
# WhatsApp does not care about a static image in webp anyway
# if FormatVerify.check_file(src, res_min=512, res_max=512, square=True, size_max=500000, animated=True, format='.webp') or FormatVerify.check_file(src, res_min=512, res_max=512, square=True, size_max=100000, animated=False, format='.png'):
if FormatVerify.check_file(src, res_min=512, res_max=512, square=True, size_max=500000, format='.webp') or FormatVerify.check_file(src, res_min=512, res_max=512, square=True, size_max=100000, format='.png'):
shutil.copy(src, dst)
else:
StickerConvert.convert_and_compress_to_size(src, dst, vid_size_max=500000, img_size_max=100000, res_min=512, res_max=512, quality_max=quality_max, quality_min=quality_min, fps_max=fps_max, fps_min=fps_min, steps=steps)

out_f = os.path.join(out_dir, pack_title + '.wastickers')

CompressWastickers.add_metadata(in_dir, tempdir, author)
CompressWastickers.add_metadata(in_dir, tempdir, author, title)
CompressWastickers.compress(out_f, tempdir)

@staticmethod
def add_metadata(in_dir, tmp_dir, author):
cover_path = os.path.join(tmp_dir, 'cover.png')
def add_metadata(in_dir, tmp_dir, author, title):
cover_path = os.path.join(tmp_dir, '100.png')
if 'cover.png' in os.listdir(in_dir):
if FormatVerify.check_file(cover_path, res=96, size_max=50000):
shutil.copy(os.path.join(in_dir, 'cover.png'), cover_path)
else:
StickerConvert.convert_and_compress_to_size(os.path.join(in_dir, 'cover.png'), cover_path, img_size_max=50000, vid_size_max=50000, res_min=96, res_max=96)
StickerConvert.convert_and_compress_to_size(os.path.join(in_dir, f'cover.png'), cover_path, img_size_max=50000, vid_size_max=50000, res_min=96, res_max=96)
else:
# First image in the directory, extracting first frame
first_image = [i for i in os.listdir(in_dir) if not i.endswith('.txt')][0]
StickerConvert.compress_to_size(StickerConvert.convert_generic_image, os.path.join(in_dir, f'{first_image}[0]'), cover_path, img_size_max=50000, vid_size_max=50000, res_min=96, res_max=96)

MetadataHandler.set_metadata(tmp_dir, author=author)
MetadataHandler.set_metadata(tmp_dir, author=author, title=title)

@staticmethod
def compress(out_f, in_dir):
Expand Down
52 changes: 31 additions & 21 deletions sticker_convert/utils/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ def get_convert_method(in_f, out_f):
return StickerConvert.convert_tgs

else:
if in_f_ext == '.webp':
return StickerConvert.convert_from_webp_anim

if FormatVerify.is_anim(in_f):
if in_f_ext == '.webp':
return StickerConvert.convert_from_webp_anim
elif out_f_ext == '.png' or out_f_ext == '.apng':
if out_f_ext == '.png' or out_f_ext == '.apng':
return StickerConvert.convert_to_apng_anim
else:
return StickerConvert.convert_generic_anim
Expand Down Expand Up @@ -83,7 +84,7 @@ def get_step_value(max, min, step, steps):
return True

size = os.path.getsize(tmp_f)
if FormatVerify.is_anim(tmp_f):
if FormatVerify.is_anim(in_f):
size_max = vid_size_max
else:
size_max = img_size_max
Expand Down Expand Up @@ -232,7 +233,7 @@ def convert_generic_anim(in_f, out_f, res=512, quality=90, fps=30, fps_in=None,
stream = ffmpeg.filter(stream, 'pad', res, res, '(ow-iw)/2', '(ow-ih)/2', color='black@0')
stream = ffmpeg.filter(stream, 'setsar', 1)
if out_f_ext == '.apng' or out_f_ext == '.png':
stream = ffmpeg.output(stream, out_f, vcodec='apng', pix_fmt='rgba', quality=quality, plays=0)
stream = ffmpeg.output(stream, out_f, vcodec='apng', pix_fmt='rgba', quality=95, plays=0)
elif out_f_ext == '.webp':
stream = ffmpeg.output(stream, out_f, vcodec='webp', pix_fmt='yuva420p', quality=quality, lossless=0, loop=0)
else:
Expand All @@ -242,22 +243,32 @@ def convert_generic_anim(in_f, out_f, res=512, quality=90, fps=30, fps_in=None,
# RunBin.run_cmd(['ffmpeg', '-y', '-i', in_f, '-r', str(fps), '-vf', f'scale={res}:-1:flags=neighbor:sws_dither=none,pad={res}:{res}:(ow-iw)/2:(oh-ih)/2:color=black@0,setsar=1', '-pix_fmt', 'yuva420p', '-quality', str(quality), '-lossless', '0', str(out_f)])

@staticmethod
def convert_from_webp_anim(in_f, out_f, res=512, quality=90, fps=30, **kwargs):
# ffmpeg do not support webp decoding (yet)
# Converting animated .webp to image of the frames or .webp directly can result in broken frames
# .mp4 does not like odd number of width / height
# Converting to .webm first is safe way of handling .webp

def convert_from_webp_anim(in_f, out_f, res=512, quality=90, fps=30, color=90, **kwargs):
with tempfile.TemporaryDirectory() as tempdir:
tmp_f = os.path.join(tempdir, 'temp.webm')

if RunBin.get_bin('magick', silent=True) == None:
with Image(filename=in_f) as img:
img.save(filename=tmp_f)
if FormatVerify.is_anim(in_f):
# ffmpeg do not support webp decoding (yet)
# Converting animated .webp to image of the frames or .webp directly can result in broken frames
# .mp4 does not like odd number of width / height
# Converting to .webm first is safe way of handling .webp

tmp_f = os.path.join(tempdir, 'tmp.webm')
StickerConvert.convert_generic_image(in_f, tmp_f, quality=quality)
StickerConvert.convert_generic_anim(tmp_f, out_f, res=res, quality=quality, fps=fps)
else:
RunBin.run_cmd(['magick', in_f, '-quality', str(quality), tmp_f])

StickerConvert.convert_generic_anim(tmp_f, out_f, res=res, quality=quality, fps=fps)
extension = os.path.splitext(out_f)[-1].lower()
tmp_f = os.path.join(tempdir, f'tmp{extension}')
StickerConvert.convert_generic_image(in_f, tmp_f, quality=quality)
# Need more compression for .png
if os.path.splitext(out_f)[-1] == '.png':
tmp1_f = os.path.join(tempdir, 'tmp.1.png')
RunBin.run_cmd(['pngnq-s9', '-L', '-Qn', '-T15', '-n', str(color), '-e', '.1.png', tmp_f])

tmp2_f = os.path.join(tempdir, 'tmp.1.2.png')
RunBin.run_cmd(['pngquant', '--nofs', '--quality', f'0-{quality}', '--strip', '--ext', '.2.png', tmp1_f])

shutil.move(tmp2_f, out_f)
else:
shutil.move(tmp_f, out_f)

@staticmethod
def convert_tgs(in_f, out_f, res=512, quality=90, fps=30, **kwargs):
Expand Down Expand Up @@ -305,8 +316,7 @@ def convert_to_apng_anim(in_f, out_f, res=512, quality=90, fps=30, color=90):

# pngnq-s9 optimization
tmp3_f = os.path.join(tempdir1, 'tmp1_strip.1.png')
number_of_colors = color # 1-256 number of colors
RunBin.run_cmd(['pngnq-s9', '-L', '-Qn', '-T15', '-n', str(number_of_colors), '-e', '.1.png', tmp2_f])
RunBin.run_cmd(['pngnq-s9', '-L', '-Qn', '-T15', '-n', str(color), '-e', '.1.png', tmp2_f])

# pngquant optimization
tmp4_f = os.path.join(tempdir1, 'tmp1_strip.1.2.png')
Expand Down
15 changes: 8 additions & 7 deletions sticker_convert/utils/format_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,14 @@ def is_anim(file):

else:
if os.path.isfile(file):
try:
file_f_ffprobe = ffmpeg.probe(file)
codec_type = file_f_ffprobe['streams'][0]['codec_type']
if codec_type == 'video':
return True
except ffmpeg.Error:
pass
if file_ext not in ('.webp', '.webm', '.png', '.apng'):
try:
file_f_ffprobe = ffmpeg.probe(file)
codec_type = file_f_ffprobe['streams'][0]['codec_type']
if codec_type == 'video':
return True
except ffmpeg.Error:
pass

if RunBin.get_bin('magick', silent=True) == None:
with Image(filename=file) as img:
Expand Down

0 comments on commit 823b553

Please sign in to comment.