@@ -799,6 +799,101 @@ def ensure_make_pdf_dir(pdf_dir: str):
799
799
return pdf_dir
800
800
801
801
802
+ class LongImgPlugin (JmOptionPlugin ):
803
+ plugin_key = 'long_img'
804
+
805
+ def invoke (self ,
806
+ photo : JmPhotoDetail = None ,
807
+ album : JmAlbumDetail = None ,
808
+ downloader = None ,
809
+ img_dir = None ,
810
+ filename_rule = 'Pid' ,
811
+ delete_original_file = False ,
812
+ ** kwargs ,
813
+ ):
814
+ if photo is None and album is None :
815
+ jm_log ('wrong_usage' , 'long_img必须运行在after_photo或after_album时' )
816
+
817
+ try :
818
+ from PIL import Image
819
+ except ImportError :
820
+ self .warning_lib_not_install ('PIL' )
821
+ return
822
+
823
+ self .delete_original_file = delete_original_file
824
+
825
+ # 处理文件夹配置
826
+ img_dir = self .get_img_dir (img_dir )
827
+
828
+ # 处理生成的长图文件的路径
829
+ filename = DirRule .apply_rule_directly (album , photo , filename_rule )
830
+
831
+ # 长图路径
832
+ long_img_path = os .path .join (img_dir , f'{ filename } .png' )
833
+
834
+ # 调用 PIL 把 photo_dir 下的所有图片合并为长图
835
+ img_path_ls = self .write_img_2_long_img (long_img_path , album , photo )
836
+ self .log (f'Convert Successfully: JM{ album or photo } → { long_img_path } ' )
837
+
838
+ # 执行删除
839
+ self .execute_deletion (img_path_ls )
840
+
841
+ def write_img_2_long_img (self , long_img_path , album : JmAlbumDetail , photo : JmPhotoDetail ) -> List [str ]:
842
+ import itertools
843
+ from PIL import Image
844
+
845
+ if album is None :
846
+ img_dir_items = [self .option .decide_image_save_dir (photo )]
847
+ else :
848
+ img_dir_items = [self .option .decide_image_save_dir (photo ) for photo in album ]
849
+
850
+ img_paths = itertools .chain (* map (files_of_dir , img_dir_items ))
851
+ img_paths = filter (lambda x : not x .startswith ('.' ), img_paths ) # 过滤系统文件
852
+
853
+ images = self .open_images (img_paths )
854
+
855
+ try :
856
+ resample_method = Image .Resampling .LANCZOS
857
+ except AttributeError :
858
+ resample_method = Image .LANCZOS
859
+
860
+ min_img_width = min (img .width for img in images )
861
+ total_height = 0
862
+ for i , img in enumerate (images ):
863
+ if img .width > min_img_width :
864
+ images [i ] = img .resize ((min_img_width , int (img .height * min_img_width / img .width )),
865
+ resample = resample_method )
866
+ total_height += images [i ].height
867
+
868
+ long_img = Image .new ('RGB' , (min_img_width , total_height ))
869
+ y_offset = 0
870
+ for img in images :
871
+ long_img .paste (img , (0 , y_offset ))
872
+ y_offset += img .height
873
+
874
+ long_img .save (long_img_path )
875
+ for img in images :
876
+ img .close ()
877
+
878
+ return img_paths
879
+
880
+ def open_images (self , img_paths : List [str ]):
881
+ images = []
882
+ for img_path in img_paths :
883
+ try :
884
+ img = Image .open (img_path )
885
+ images .append (img )
886
+ except IOError as e :
887
+ self .log (f"Failed to open image { img_path } : { e } " , 'error' )
888
+ return images
889
+
890
+ @staticmethod
891
+ def get_img_dir (img_dir : Optional [str ]) -> str :
892
+ img_dir = fix_filepath (img_dir or os .getcwd ())
893
+ mkdir_if_not_exists (img_dir )
894
+ return img_dir
895
+
896
+
802
897
class JmServerPlugin (JmOptionPlugin ):
803
898
plugin_key = 'jm_server'
804
899
0 commit comments