deal_image.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import os,re
  2. from natsort import natsorted,ns
  3. import shutil
  4. import exifread
  5. import time
  6. import datetime
  7. from databases import PhotoRecord,SqlQuery,CRUD
  8. import settings
  9. import copy
  10. import json
  11. import requests
  12. from service.pic_deal import Picture
  13. import xlsxwriter
  14. from PIL import Image
  15. from utils.utils_func import check_path
  16. from .base_deal import BaseDealImage
  17. from middleware import UnicornException
  18. _Type = ['.png', '.PNG', '.jpg', '.JPG', '.gif', '.GIF', ".jpge", ".JPGE"]
  19. class DealImage(BaseDealImage):
  20. def __init__(self, image_dir=None):
  21. super().__init__()
  22. self.image_dir = image_dir
  23. self.header = None
  24. def check_path(self, image_dir: str):
  25. if not os.path.exists(image_dir):
  26. os.mkdir(image_dir)
  27. return True
  28. def list_dir(self, path):
  29. listdir = os.listdir(path)
  30. sort_result = sorted(listdir, key=lambda x: int(re.findall(r'^(\d+)_', x)[0]) if re.findall(r'^(\d+)_', x) else 0)
  31. # print("listdir 排序排序排序排序排序排序", sort_result)
  32. # return natsorted(listdir, alg=ns.PATH)
  33. return sort_result
  34. def get_date_time_original(self, file_path):
  35. with open(file_path, 'rb') as file_data:
  36. tags = exifread.process_file(file_data)
  37. if "EXIF DateTimeOriginal" in tags:
  38. return str(tags["EXIF DateTimeOriginal"])
  39. else:
  40. return False
  41. def get_goods_art_no(self, goods_art_no):
  42. # time_array = time.strptime(date_time_original, "%Y:%m:%d %H:%M:%S")
  43. # time_array = time.mktime(time_array)
  44. # datetime_obj = datetime.datetime.fromtimestamp(time_array)
  45. session = SqlQuery()
  46. configModel = CRUD(PhotoRecord)
  47. result = configModel.read(
  48. session,
  49. conditions={"goods_art_no": goods_art_no,"delete_time": None},
  50. order_by="id",
  51. ascending=True,
  52. )
  53. session.close()
  54. if result:
  55. return result.goods_art_no, result.image_index, result.image_deal_mode
  56. else:
  57. return None
  58. def get_goods_art_no_files(self, goods_art_no):
  59. # time_array = time.strptime(date_time_original, "%Y:%m:%d %H:%M:%S")
  60. # time_array = time.mktime(time_array)
  61. # datetime_obj = datetime.datetime.fromtimestamp(time_array)
  62. session = SqlQuery()
  63. configModel = CRUD(PhotoRecord)
  64. result = configModel.read_all(
  65. session,
  66. conditions={"goods_art_no": goods_art_no,"delete_time": None},
  67. order_by="id",
  68. ascending=True,
  69. )
  70. session.close()
  71. if result:
  72. return [{"image_path":item.image_path,"id":item.id,"image_index":item.image_index} for item in result]
  73. else:
  74. return []
  75. def get_data_from_hqt_with_goods_art_no(self, goods_art_no_list):
  76. _goods_art_no_list = copy.deepcopy(goods_art_no_list)
  77. _list = []
  78. # 单次请求数少于20个
  79. goods_art_no_dict = {}
  80. while _goods_art_no_list:
  81. goods_art_no = _goods_art_no_list.pop()
  82. _list.append(goods_art_no)
  83. if len(_list) == 20 or len(_goods_art_no_list) == 0:
  84. online_goods_art_data = self.get_goods_art_no_info(
  85. goods_art_list=_list
  86. )
  87. if online_goods_art_data:
  88. for _goods_art_no in online_goods_art_data:
  89. goods_art_no_dict[_goods_art_no] = online_goods_art_data[
  90. _goods_art_no
  91. ]
  92. _list = []
  93. return goods_art_no_dict
  94. def get_goods_art_no_info(self, numbers_list=None, goods_art_list=None):
  95. # 获取商品基础信息,入参为商品的编号
  96. url = "{domain}/api/backend/goods_client/goods_query".format(
  97. domain=settings.DOMAIN
  98. )
  99. data = {
  100. 'goods_art_list': goods_art_list
  101. }
  102. data = json.dumps(data)
  103. _s = requests.session().post(url=url, data=data, headers=self.header)
  104. response_data = _s.json()
  105. goods_number_data = {}
  106. # ["", "", "", "", "", "", "", "", "", "", "", ]
  107. if "data" not in response_data:
  108. return {}
  109. for data in response_data["data"]:
  110. goods_number_data[data["goods_art_no"]] = {}
  111. goods_number_data[data["goods_art_no"]]["商品货号"] = data["goods_art_no"].upper()
  112. goods_number_data[data["goods_art_no"]]["款号"] = data["goods_number"].upper()
  113. goods_number_data[data["goods_art_no"]]["商品面料"] = data["fabric"]
  114. goods_number_data[data["goods_art_no"]]["商品内里"] = data["lining"]
  115. goods_number_data[data["goods_art_no"]]["商品鞋底"] = data["sole"]
  116. goods_number_data[data["goods_art_no"]]["鞋垫"] = data["insole"]
  117. goods_number_data[data["goods_art_no"]]["颜色名称"] = data["color"]
  118. return goods_number_data
  119. def get_data_from_hqt(self, goods_number_list):
  120. _goods_number_list = copy.deepcopy(goods_number_list)
  121. _list = []
  122. # 单次请求数少于20个
  123. goods_number_dict = {}
  124. while _goods_number_list:
  125. goods_art_no = _goods_number_list.pop()
  126. if "NUM" in goods_art_no:
  127. goods_art_no = goods_art_no.replace("NUM", "")
  128. _list.append(goods_art_no)
  129. if len(_list) == 20 or len(_goods_number_list) == 0:
  130. online_goods_art_data = self.get_goods_art_no_info(
  131. numbers_list=_list
  132. )
  133. if online_goods_art_data:
  134. for number in online_goods_art_data:
  135. goods_number_dict["NUM" + number] = online_goods_art_data[
  136. number
  137. ]
  138. _list = []
  139. return goods_number_dict
  140. def create_folder(self, path):
  141. def check_folder(__path):
  142. if not os.path.exists(__path):
  143. os.makedirs(__path)
  144. return False
  145. return True
  146. # 文件夹不存在,创建货号子集文件夹
  147. if not check_folder(path):
  148. for name in ["原始图", "原始图_已抠图", "800x800", "200images"]:
  149. other_path = path + "/" + name
  150. check_folder(other_path)
  151. def move_images(self, goods_art_no, goods_art_no_path, old_image_path):
  152. """
  153. 步骤:
  154. 1、移动到原始图
  155. Args:
  156. goods_art_no:
  157. goods_art_no_path:
  158. old_image_path:
  159. Returns:
  160. """
  161. # 移动到原始图
  162. file = os.path.split(old_image_path)[1]
  163. # 扩展名
  164. e = os.path.splitext(file)[1]
  165. print("self.goods_images_count_dict", self.goods_images_count_dict)
  166. # 获取图片序列
  167. self.goods_images_count_dict[goods_art_no] += 1
  168. # A9999(1).jpg
  169. new_file_name = "{}({})".format(goods_art_no, self.goods_images_count_dict[goods_art_no])
  170. original_image_path = "{}/原始图/{}{}".format(goods_art_no_path, new_file_name, e)
  171. try:
  172. shutil.move(old_image_path, original_image_path)
  173. except Exception as e:
  174. print(f"文件操作异常:{e}")
  175. def copy_images(self, goods_art_no, goods_art_no_path, old_image_path):
  176. """
  177. 步骤:
  178. 1、移动到原始图
  179. Args:
  180. goods_art_no:
  181. goods_art_no_path:
  182. old_image_path:
  183. Returns:
  184. """
  185. # 移动到原始图
  186. file = os.path.split(old_image_path)[1]
  187. # 扩展名
  188. e = os.path.splitext(file)[1]
  189. print("self.goods_images_count_dict", self.goods_images_count_dict)
  190. # 获取图片序列
  191. self.goods_images_count_dict[goods_art_no] += 1
  192. # A9999(1).jpg
  193. new_file_name = "{}({})".format(goods_art_no, self.goods_images_count_dict[goods_art_no])
  194. check_path("{}/原始图".format(goods_art_no_path))
  195. original_image_path = "{}/原始图/{}{}".format(goods_art_no_path, new_file_name, e)
  196. try:
  197. shutil.copy2(old_image_path, original_image_path)
  198. return True,"移动成功"
  199. except Exception as e:
  200. print(f"文件操作异常:{e}")
  201. return False,f"货号[{goods_art_no}]原始图{old_image_path}不存在或已被删除"
  202. def dealMoveImage(self, image_dir: str, callback_func=None,goods_art_no=None) -> dict:
  203. if not self.check_path(image_dir=image_dir + "/历史"):
  204. raise UnicornException("文件夹创建失败")
  205. # 遍历目标文件夹,获取有拍摄信息的图片,并按拍摄时间排序
  206. files = self.list_dir(image_dir)
  207. original_photo_list = [] # 原始图片列表
  208. for file_idx,file in enumerate(files):
  209. # -----图片清洗
  210. file_path = image_dir + "/" + file
  211. if os.path.isdir(file_path): # 忽略文件夹
  212. continue
  213. file_name, suffix = os.path.splitext(file)
  214. if suffix not in _Type: # 非图片进行移除
  215. shutil.move(file_path, image_dir + "/历史/" + file)
  216. continue
  217. date_time_original = self.get_date_time_original(file_path) # 获取照片拍照时间
  218. if date_time_original:
  219. # 基于照片的时间,与数据库匹配goods_art_no
  220. _data = self.get_goods_art_no(goods_art_no)
  221. if _data:
  222. # 能匹配上数据库
  223. goods_art_no, image_index, image_deal_mode = _data
  224. print("832 与数据库匹配goods_art_no", file_path,file_name, date_time_original, goods_art_no)
  225. original_photo_list.append({"file_path": file_path,
  226. "file": file,
  227. "date_time_original": date_time_original,
  228. "goods_art_no": goods_art_no,
  229. "image_index": file_idx,
  230. "real_goods_art_no": "",
  231. "real_goods_number": "",
  232. })
  233. else:
  234. # 匹配不上报错
  235. # self.show_progress_detail("图片:{} 无法对应货号,不做处理".format(file))
  236. if callback_func:
  237. callback_func("图片:{} 无对应货号".format(file))
  238. # shutil.move(photo_dict["file_path"], self.image_dir + "/历史/" + photo_dict["file"])
  239. continue
  240. else:
  241. shutil.move(file_path, image_dir + "/历史/" + file)
  242. if not original_photo_list:
  243. raise UnicornException("没有任何匹配的图片")
  244. return False, "没有任何匹配的图片"
  245. # 排序需要基于拍照的文件序号进行处理
  246. # original_photo_list.sort(
  247. # key=lambda x: "{}-{}-{}".format(x["goods_art_no"], x["image_index"], x["file"]))
  248. # print(original_photo_list)
  249. # 对有拍摄信息的图片进行数据库比对,如有比对上,则移动至货号文件夹,否则移入历史文件夹
  250. total_num = len(original_photo_list)
  251. # 当天日期作为文件夹
  252. seconds = time.time()
  253. output_path = "{}/{f_name}".format(settings.OUTPUT_DIR,f_name=time.strftime("%Y-%m-%d", time.localtime(seconds)))
  254. # 遍历每个匹配好的数据进行处理
  255. print(f"original_photo_list:",original_photo_list)
  256. n = 0
  257. for photo_dict in original_photo_list:
  258. n += 1
  259. print("处理dict:",photo_dict)
  260. # 进度条
  261. goods_art_no = photo_dict["goods_art_no"]
  262. original_image_path = photo_dict["file_path"]
  263. # 输出货号文件夹
  264. if photo_dict["real_goods_art_no"]:
  265. goods_art_no = "{}@{}".format(photo_dict["real_goods_art_no"], photo_dict["real_goods_number"])
  266. goods_art_no_path = "{output_path}/{goods_art_no}".format(output_path=output_path,
  267. goods_art_no=goods_art_no)
  268. # 创建货号下的一系列文件夹
  269. self.create_folder(goods_art_no_path)
  270. # 重命名并进行移动
  271. print("开始移动:{} {} 命名为:{}".format(goods_art_no, original_image_path, goods_art_no_path))
  272. self.move_images(goods_art_no, goods_art_no_path, original_image_path) # 货号、货号文件路径、原始图路径
  273. time.sleep(0.2)
  274. # self.progress_sign.emit({"type": "移动原始图片", "progress_bar_value": int(n / total_num * 100)})
  275. # self.show_progress_detail("货号{} 相关文件夹创建完成,已移动原图~".format(goods_art_no))
  276. if callback_func:
  277. callback_func("货号{} 相关文件夹创建完成,已移动原图~".format(goods_art_no))
  278. print("已完成移动处理")
  279. if n != 0:
  280. # if settings.MattingPics:
  281. # # 检查所有未处理的货号文件夹,查看是否有完成图片加工处理
  282. # self.deal_images()
  283. # 自动生成一个货号表
  284. print("output_path", output_path)
  285. self.deal(output_path)
  286. # 完成处理
  287. # self.set_state(state_value=2)
  288. return True, output_path
  289. def dealMoveImageV2(self,goods_art_no=None) -> dict:
  290. # 遍历目标文件夹,获取有拍摄信息的图片,并按拍摄时间排序
  291. original_photo_list = [] # 原始图片列表
  292. _data = self.get_goods_art_no_files(goods_art_no)
  293. if _data:
  294. for file_idx, dataItem in enumerate(_data):
  295. # 能匹配上数据库
  296. file_path = dataItem['image_path']
  297. original_photo_list.append({"file_path": file_path,
  298. "goods_art_no": goods_art_no,
  299. "image_index": file_idx,
  300. })
  301. if not original_photo_list:
  302. raise UnicornException("没有任何匹配的图片")
  303. # 当天日期作为文件夹
  304. seconds = time.time()
  305. output_path = "{}/{f_name}".format(settings.OUTPUT_DIR,f_name=time.strftime("%Y-%m-%d", time.localtime(seconds)))
  306. # 遍历每个匹配好的数据进行处理
  307. print(f"original_photo_list:",original_photo_list)
  308. n = 0
  309. for photo_dict in original_photo_list:
  310. n += 1
  311. print("处理dict:",photo_dict)
  312. # 进度条
  313. goods_art_no = photo_dict["goods_art_no"]
  314. original_image_path = photo_dict["file_path"]
  315. goods_art_no_path = "{output_path}/{goods_art_no}".format(output_path=output_path,
  316. goods_art_no=goods_art_no)
  317. # 创建货号下的一系列文件夹
  318. self.create_folder(goods_art_no_path)
  319. # 重命名并进行移动
  320. print("开始移动:{} {} 命名为:{}".format(goods_art_no, original_image_path, goods_art_no_path))
  321. result, message = self.copy_images(goods_art_no, goods_art_no_path, original_image_path) # 货号、货号文件路径、原始图路径
  322. if not result:
  323. return result, message
  324. time.sleep(0.1)
  325. print("已完成移动处理")
  326. if n != 0:
  327. # 自动生成一个货号表
  328. print("output_path", output_path)
  329. self.deal(output_path)
  330. # 完成处理
  331. # self.set_state(state_value=2)
  332. return True, output_path
  333. def deal(cls, dir_path):
  334. # print("dir_path", dir_path)
  335. out_excel_data = []
  336. for goods_art_no_folder in os.listdir(dir_path): # 遍历货号文件夹集合
  337. if not os.path.isdir(
  338. "{}/{}".format(dir_path, goods_art_no_folder)
  339. ): # 非文件夹进行过滤
  340. continue
  341. if "软件" in goods_art_no_folder:
  342. continue
  343. # print("goods_art_no_folder", goods_art_no_folder)
  344. # 如果存在800的主图,则优先进行使用
  345. big_image_folder_path = "{}/{}/800x800".format(
  346. dir_path, goods_art_no_folder
  347. )
  348. if not os.path.exists(big_image_folder_path):
  349. os.mkdir(big_image_folder_path)
  350. all_big_images = os.listdir(big_image_folder_path)
  351. goods_pic_total = len(all_big_images)
  352. _Type = [".png", ".PNG", ".jpg", ".JPG", ".gif", ".GIF", ".jpge", ".JPGE"]
  353. thumb_image_file_path = None
  354. # print("all_big_images",all_big_images)
  355. if all_big_images:
  356. for _file in all_big_images:
  357. # print(_file)
  358. file_name, e = os.path.splitext(_file)
  359. # print(file_name, e)
  360. if e in _Type:
  361. thumb_image_file_path = "{}/{}/800x800/{}".format(
  362. dir_path, goods_art_no_folder, _file
  363. )
  364. break
  365. # 如果不存在主图则进行使用原始图
  366. if thumb_image_file_path is None:
  367. _path = "{}/{}/原始图".format(dir_path, goods_art_no_folder)
  368. if not os.path.exists(_path):
  369. continue
  370. all_original_images = os.listdir(_path) # 遍历货号原始图文件夹
  371. sort_result = sorted(all_original_images, key=lambda x: int(re.findall(r'((\d+))', x)[0]) if re.findall(r'((\d+))', x) else 0)
  372. goods_pic_total = len(sort_result)
  373. if not sort_result:
  374. continue
  375. image_file = sort_result[0] # 取第一个货号图
  376. image_file_path = "{}/{}/原始图/{}".format(
  377. dir_path, goods_art_no_folder, image_file
  378. )
  379. # print("\033[1;32;40m 需要处理的200images,路径: \033[0m",image_file_path,all_original_images,sort_result)
  380. if not os.path.exists(
  381. "{}/{}/200images".format(dir_path, goods_art_no_folder)
  382. ):
  383. os.mkdir("{}/{}/200images".format(dir_path, goods_art_no_folder))
  384. thumb_image_file_path = "{}/{}/200images/{}".format(
  385. dir_path, goods_art_no_folder, image_file
  386. )
  387. if not os.path.exists(thumb_image_file_path):
  388. # 开始触发进行压缩生成文件
  389. shutil.copy(image_file_path, thumb_image_file_path) # 复制文件
  390. pic = Picture(thumb_image_file_path)
  391. pic.resize(width=600)
  392. pic.save_img(thumb_image_file_path)
  393. # print("thumb_image_file_path", thumb_image_file_path)
  394. goods_number = ""
  395. if "@" in goods_art_no_folder:
  396. _ = goods_art_no_folder.split("@")
  397. goods_art_no_folder = _[0]
  398. goods_number = _[1].replace("NUM", "")
  399. out_excel_data.append(
  400. [
  401. goods_number,
  402. goods_art_no_folder,
  403. thumb_image_file_path,
  404. goods_pic_total,
  405. ]
  406. )
  407. if out_excel_data:
  408. out_excel_path = "{}/货号表-{}.xlsx".format(dir_path, time.time())
  409. options = {
  410. "default_format_properties": {
  411. "align": "left",
  412. "valign": "vcenter",
  413. "text_wrap": True,
  414. }
  415. }
  416. book = xlsxwriter.Workbook(filename=out_excel_path, options=options)
  417. sheet = book.add_worksheet("sheet1")
  418. # sheet.freeze_panes(1, 2)
  419. sheet.set_column("B:B", 17)
  420. sheet.set_column("D:D", 20)
  421. sheet.write_row("A1", ["编号", "原货号", "新货号", "缩略图", "原始图张数"])
  422. for index, data in enumerate(out_excel_data):
  423. # print(data)
  424. goods_number, goods_art_no, image_file, goods_pic_total = data
  425. try:
  426. im = Image.open(image_file)
  427. im_x, im_y = im.size
  428. image_width = 100
  429. image_height = int(im_y * image_width / im_x)
  430. sheet.set_row(index + 1, 95)
  431. x_scale = round(
  432. image_width / im_x, 2
  433. ) # 固定宽度/要插入的原始图片宽
  434. y_scale = round(
  435. image_height / im_y, 2
  436. ) # 固定高度/要插入的原始图片高
  437. sheet.insert_image(
  438. index + 1,
  439. 3,
  440. image_file,
  441. {
  442. "x_scale": x_scale,
  443. "y_scale": y_scale,
  444. "x_offset": 5,
  445. "y_offset": 5,
  446. "object_position":1,
  447. },
  448. )
  449. except:
  450. pass
  451. sheet.write_row("A{}".format(index + 2), [goods_number])
  452. sheet.write_row("B{}".format(index + 2), [goods_art_no])
  453. sheet.write_row("E{}".format(index + 2), [goods_pic_total])