diff --git a/sticker_convert/preset.json b/sticker_convert/preset.json index 7d9fad9..83d4bf8 100755 --- a/sticker_convert/preset.json +++ b/sticker_convert/preset.json @@ -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, diff --git a/sticker_convert/uploaders/compress_wastickers.py b/sticker_convert/uploaders/compress_wastickers.py index e9538fe..6dc25f2 100755 --- a/sticker_convert/uploaders/compress_wastickers.py +++ b/sticker_convert/uploaders/compress_wastickers.py @@ -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): @@ -17,6 +18,7 @@ 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') @@ -24,37 +26,43 @@ def compress_wastickers(in_dir, out_dir, author='Me', title='My sticker pack', q 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): diff --git a/sticker_convert/utils/converter.py b/sticker_convert/utils/converter.py index 8ebc48d..4bc3442 100755 --- a/sticker_convert/utils/converter.py +++ b/sticker_convert/utils/converter.py @@ -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 @@ -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 @@ -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: @@ -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): @@ -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') diff --git a/sticker_convert/utils/format_verify.py b/sticker_convert/utils/format_verify.py index ceb9e20..ab4d042 100755 --- a/sticker_convert/utils/format_verify.py +++ b/sticker_convert/utils/format_verify.py @@ -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: