{"id":492223,"date":"2026-08-26T03:46:51","date_gmt":"2026-08-26T03:46:51","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=492223"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=492223","title":{"rendered":"\u041a\u0430\u043a \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043d\u0430 Mac \u0437\u0430 10\u201315 \u043c\u0438\u043d\u0443\u0442 \u0438 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u0443\u0431\u043b\u0438 \u0444\u043e\u0442\u043e, \u0432\u0438\u0434\u0435\u043e \u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 + \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u0441\u043a\u0440\u0438\u043f\u0442"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440\u0430\u0432\u0447\u0430\u043d\u0435!<\/p>\n<p>\u041d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u0441 \u0431\u0435\u0441\u043f\u043e\u0440\u044f\u0434\u043a\u043e\u043c \u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430 \u041f\u041a \u0437\u043d\u0430\u043a\u043e\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u0430\u0436\u0434\u044b\u0439: \u043a\u0442\u043e-\u0442\u043e \u0441\u0430\u043c \u0433\u043e\u0434\u0430\u043c\u0438 \u043d\u0435 \u0441\u043b\u0435\u0434\u0438\u0442 \u0442\u0430\u043a \u043a\u0430\u043a \u043b\u0435\u043d\u044c, \u0430 \u043a\u043e\u0433\u043e-\u0442\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u043e\u0441\u044f\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043f\u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0438\u043b\u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0447\u0443\u0436\u043e\u0439 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440 \u2014 \u00ab\u043d\u0443 \u0442\u044b \u0436\u0435 \u0430\u0439\u0442\u0438\u0448\u043d\u0438\u043a, \u0442\u044b \u0432\u0441\u0451 \u043c\u043e\u0436\u0435\u0448\u044c!\u00bb. \u0414\u0430, \u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c, \u0438 \u043a\u043e\u0444\u0435\u043c\u0430\u0448\u0438\u043d\u0443 \u043f\u043e\u0447\u0438\u043d\u0438\u0442\u044c! \u0412\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043c\u0438\u043b\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0441\u0438\u043c \ud83d\ude42<\/p>\n<p>\u042f \u043d\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435. \u0412\u043e\u043e\u0431\u0449\u0435, \u00ab\u0430\u0439\u0442\u0438\u0448\u043d\u0438\u043a\u00bb \u0432 XXI \u0432\u0435\u043a\u0435 \u0437\u0432\u0443\u0447\u0438\u0442 \u0433\u043e\u0440\u0434\u043e, \u043f\u043e\u0447\u0442\u0438 \u043a\u0430\u043a \u00abSuperMAN\u00bb. \u041a\u0442\u043e \u0431\u044b \u043c\u043e\u0433 \u043f\u043e\u0434\u0443\u043c\u0430\u0442\u044c \u0435\u0449\u0451 \u043b\u0435\u0442 \u0434\u0432\u0430\u0434\u0446\u0430\u0442\u044c \u043d\u0430\u0437\u0430\u0434, \u0447\u0442\u043e \u043b\u044e\u0431\u043e\u0439 \u043c\u0430\u043b\u043e-\u043c\u0430\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0436\u0435\u043d\u0435\u0440 \u0438\u043b\u0438 \u0432\u0435\u0440\u0441\u0442\u0430\u043b\u044c\u0449\u0438\u043a, \u0434\u0430 \u0443\u0436\u0435 \u0438 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u0441 \u043a\u043e\u0434\u043e\u043c \u0438\u043b\u0438 \u0436\u0435\u043b\u0435\u0437\u043e\u043c, \u0441\u0442\u0430\u043d\u0435\u0442 \u043d\u043e\u0441\u0438\u0442\u044c \u044d\u0442\u043e \u0432\u0441\u0435\u043c\u043e\u0433\u0443\u0449\u0435\u0435 \u0437\u0432\u0430\u043d\u0438\u0435!<\/p>\n<h3>\u0411\u043b\u0438\u0436\u0435 \u043a \u0434\u0435\u043b\u0443<\/h3>\n<p>\u0417\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u0435\u0442 \u043d\u0430 \u043d\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u043c Mac \u0443 \u043c\u0435\u043d\u044f \u043d\u0430\u043a\u043e\u043f\u0438\u043b\u043e\u0441\u044c \u043e\u043a\u043e\u043b\u043e <strong>860 \u0413\u0411 \u0444\u0430\u0439\u043b\u043e\u0432<\/strong>: \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0438 \u0432\u0438\u0434\u0435\u043e \u0441 \u0440\u0430\u0437\u043d\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, PSD, \u0437\u0430\u043f\u0438\u0441\u0438 \u044d\u043a\u0440\u0430\u043d\u0430, \u0441\u0442\u0430\u0440\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u0438 \u0434\u0430\u0436\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0435 \u043a\u043e\u043f\u0438\u0438.<\/p>\n<p>\u0420\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0430\u0440\u0445\u0438\u0432 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u0435\u0441\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e, \u0430 \u0434\u0430\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u043e\u0435\u043c\u0443 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0443 \u044f \u043d\u0435 \u0445\u043e\u0442\u0435\u043b. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0444\u0430\u0439\u043b \u043c\u043e\u0433 \u043b\u0435\u0436\u0430\u0442\u044c \u0432 \u043f\u044f\u0442\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u0430\u043f\u043a\u0430\u0445 \u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043f\u043e-\u0440\u0430\u0437\u043d\u043e\u043c\u0443.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0440\u0430 \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 Python-\u0441\u043a\u0440\u0438\u043f\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0418\u0418 + \u0434\u043e\u0432\u0451\u043b \u0440\u0443\u043a\u0430\u043c\u0438 \u0434\u043e \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438.<\/p>\n<p><strong>\u0421\u0443\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442\u0430: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043d \u0438\u0449\u0435\u0442 \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438, \u0430 \u0437\u0430\u0442\u0435\u043c \u0433\u043e\u0442\u043e\u0432\u0438\u0442 \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0435\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435 \u0438, \u0435\u0441\u043b\u0438 \u0432\u0441\u0451 \u043e\u043a, \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442. \u0415\u0441\u043b\u0438 \u043d\u0435\u0442 \u2014 \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c.<\/strong><\/p>\n<blockquote>\n<p>\u0414\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0442\u043e\u0447\u043d\u044b\u0445 \u0434\u0443\u0431\u043b\u0435\u0439 \u0432 \u0441\u043a\u0440\u0438\u043f\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f SHA-256 (\u0446\u0438\u0444\u0440\u043e\u0432\u043e\u0439 \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0430). <\/p>\n<p>\u0418\u043c\u044f \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0432\u0430\u0436\u043d\u043e: <code>IMG_4382.jpg<\/code> \u0438 <code>photo_2022.jpg<\/code> \u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0443\u0433\u043e\u0434\u043d\u043e \u0438 \u043b\u0435\u0436\u0430\u0442\u044c \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u0430\u043f\u043a\u0430\u0445, \u043d\u043e \u0435\u0441\u043b\u0438 \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442, SHA-256 \u0431\u0443\u0434\u0435\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c. \u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0432 \u0444\u0430\u0439\u043b\u0435 \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u0438\u043d \u0431\u0430\u0439\u0442 \u0438 \u0445\u0435\u0448 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0441\u0442\u0430\u043d\u0435\u0442 \u0434\u0440\u0443\u0433\u0438\u043c.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0441\u043a\u0440\u0438\u043f\u0442\u0435 \u0441\u0432\u044f\u0437\u043a\u0430 <strong>\u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 + \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 SHA-256<\/strong> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0442\u043e\u0447\u043d\u0443\u044e \u043a\u043e\u043f\u0438\u044e. \u0421\u0430\u043c SHA-256 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0448\u0438\u0444\u0440\u0443\u0435\u0442 \u0438 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0444\u0430\u0439\u043b\u0430, \u043e\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043e\u043a.<\/p>\n<\/blockquote>\n<h3>\u041a\u0430\u043a \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c: 6 \u0448\u0430\u0433\u043e\u0432<\/h3>\n<h4>\u0428\u0430\u0433 1. \u041e\u0442\u043a\u0440\u044b\u0442\u044c Terminal<\/h4>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d Python 3 (\u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0438\u0436\u0435):<\/p>\n<pre><code class=\"python\">python3 --version<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2b6\/fdf\/cfe\/2b6fdfcfeea9b9e0043bd6d1cacd0154.png\" width=\"1448\" height=\"1086\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/2b6\/fdf\/cfe\/2b6fdfcfeea9b9e0043bd6d1cacd0154.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2b6\/fdf\/cfe\/2b6fdfcfeea9b9e0043bd6d1cacd0154.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h4>\u0428\u0430\u0433 2. \u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434<\/h4>\n<p>\u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0432\u0435\u0441\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0438\u0436\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">TMP_SCRIPT=\"$(mktemp \/tmp\/mac_file_organizer.XXXXXX)\"cat &gt; \"$TMP_SCRIPT\" &lt;&lt;'PY'from __future__ import annotationsimport csv, datetime as dt, hashlib, json, os, re, shutil, subprocess, sys, unicodedata, zipfilefrom collections import Counter, defaultdictfrom pathlib import PathPHOTO={\".jpg\",\".jpeg\",\".png\",\".heic\",\".heif\",\".webp\",\".gif\",\".tif\",\".tiff\",\".bmp\",\".dng\",\".raw\",\".cr2\",\".cr3\",\".nef\",\".arw\"}VIDEO={\".mov\",\".mp4\",\".m4v\",\".avi\",\".mkv\",\".webm\",\".mts\",\".m2ts\",\".3gp\"}AUDIO={\".mp3\",\".m4a\",\".aac\",\".wav\",\".flac\",\".ogg\",\".aiff\"}DOCS={\".pdf\",\".doc\",\".docx\",\".rtf\",\".txt\",\".odt\",\".pages\"}SHEETS={\".xls\",\".xlsx\",\".xlsm\",\".csv\",\".tsv\",\".ods\",\".numbers\"}PRESENTATIONS={\".ppt\",\".pptx\",\".key\",\".odp\"}DESIGN={\".psd\",\".ai\",\".eps\",\".svg\",\".sketch\",\".fig\"}ARCHIVES={\".zip\",\".rar\",\".7z\",\".tar\",\".gz\"}CODE={\".py\",\".js\",\".ts\",\".tsx\",\".jsx\",\".php\",\".html\",\".htm\",\".css\",\".sql\",\".json\",\".xml\",\".yaml\",\".yml\",\".sh\",\".command\"}MEDIA=PHOTO|VIDEO|AUDIOWORK=DOCS|SHEETS|PRESENTATIONS|DESIGN|ARCHIVESSKIP_NAMES={\".git\",\"node_modules\",\".Trash\",\"__pycache__\",\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\"}SKIP_PREFIXES=(\"_FILE_AUDIT_\",\"_FILE_QUARANTINE_\",\"_ORGANIZER_PLAN_\",\"_ORGANIZER_RUN_\")PROJECT_MARKERS={\".git\",\"package.json\",\"pyproject.toml\",\"requirements.txt\",\"Cargo.toml\",\"go.mod\",\"composer.json\"}GENERIC={\"\u0440\u0430\u0437\u043d\u043e\u0435\",\"\u0441\u0442\u0430\u0440\u043e\u0435\",\"\u043a\u043e\u043f\u0438\u044f\",\"\u0444\u043e\u0442\u043e \u0441 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430\",\"\u043d\u043e\u0432\u0430\u044f \u043f\u0430\u043f\u043a\u0430\",\"backup\",\"old\",\"temp\",\"photos\",\"pictures\",\"documents\",\"downloads\"}PRIVATE={\"\u043f\u0430\u0441\u043f\u043e\u0440\u0442\",\"\u043f\u0440\u0430\u0432\u0430\",\"\u0441\u0432\u0438\u0434\u0435\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e\",\"\u043b\u0438\u0447\u043d\u043e\u0435\"}MONTHS={\"\u044f\u043d\u0432\u0430\u0440\":1,\"\u0444\u0435\u0432\u0440\u0430\u043b\":2,\"\u043c\u0430\u0440\u0442\":3,\"\u0430\u043f\u0440\u0435\u043b\":4,\"\u043c\u0430\u0439\":5,\"\u043c\u0430\u044f\":5,\"\u0438\u044e\u043d\":6,\"\u0438\u044e\u043b\":7,\"\u0430\u0432\u0433\u0443\u0441\u0442\":8,\"\u0441\u0435\u043d\u0442\u044f\u0431\u0440\":9,\"\u043e\u043a\u0442\u044f\u0431\u0440\":10,\"\u043d\u043e\u044f\u0431\u0440\":11,\"\u0434\u0435\u043a\u0430\u0431\u0440\":12}def stamp(): return dt.datetime.now().strftime(\"%Y%m%d_%H%M%S\")def norm(s): return unicodedata.normalize(\"NFC\",str(s))def clean(s):    s=re.sub(r\"\\s+\",\" \",norm(s).replace(\"\/\",\" - \").replace(\":\",\" - \")).strip(\" ._-\")    return (s or \"\u0411\u0435\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f\")[:120]def human(n):    x=float(n)    for u in (\"\u0411\",\"\u041a\u0411\",\"\u041c\u0411\",\"\u0413\u0411\",\"\u0422\u0411\"):        if x&lt;1024 or u==\"\u0422\u0411\": return f\"{int(x)} {u}\" if u==\"\u0411\" else f\"{x:.1f} {u}\"        x\/=1024def choose_folder():    r=subprocess.run([\"osascript\",\"-e\",'POSIX path of (choose folder with prompt \"\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u043f\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\")'],                     capture_output=True,text=True)    if not r.stdout.strip(): print(\"\u041f\u0430\u043f\u043a\u0430 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u0430.\"); raise SystemExit    return Path(r.stdout.strip()).expanduser().resolve()def is_project_dir(p):    try: return bool({x.name for x in p.iterdir()} &amp; PROJECT_MARKERS)    except Exception: return Falsedef iter_files(root):    for dp,dirs,files in os.walk(root):        cur=Path(dp)        if cur!=root and is_project_dir(cur):            dirs[:]=[]            continue        dirs[:]=[d for d in dirs if d not in SKIP_NAMES and not any(d.startswith(p) for p in SKIP_PREFIXES)]        for name in files:            p=cur\/name            try:                if not p.is_symlink(): yield p            except Exception: passdef sha256_file(p,block=1024*1024):    h=hashlib.sha256()    try:        with p.open(\"rb\") as f:            while True:                b=f.read(block)                if not b: break                h.update(b)        return h.hexdigest()    except Exception: return Nonedef category(p):    e=p.suffix.lower()    for exts,name in ((PHOTO,\"\u0424\u043e\u0442\u043e\"),(VIDEO,\"\u0412\u0438\u0434\u0435\u043e\"),(AUDIO,\"\u0410\u0443\u0434\u0438\u043e\"),(DOCS,\"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\"),                      (SHEETS,\"\u0422\u0430\u0431\u043b\u0438\u0446\u044b\"),(PRESENTATIONS,\"\u041f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438\"),(DESIGN,\"\u0414\u0438\u0437\u0430\u0439\u043d\"),                      (ARCHIVES,\"\u0410\u0440\u0445\u0438\u0432\u044b\"),(CODE,\"\u041a\u043e\u0434\")):        if e in exts: return name    return \"\u041f\u0440\u043e\u0447\u0435\u0435\"def parse_date(text):    s=norm(text).lower()    patterns=[        (r\"(?&lt;!\\d)(20\\d{2}|19\\d{2})[-_. \/](0?[1-9]|1[0-2])[-_. \/](0?[1-9]|[12]\\d|3[01])(?!\\d)\",\"ymd\"),        (r\"(?&lt;!\\d)(20\\d{2}|19\\d{2})(0[1-9]|1[0-2])([0-2]\\d|3[01])(?!\\d)\",\"ymd\"),        (r\"(?&lt;!\\d)(0?[1-9]|[12]\\d|3[01])[-_. \/](0?[1-9]|1[0-2])[-_. \/](20\\d{2}|19\\d{2})(?!\\d)\",\"dmy\"),    ]    for rx,order in patterns:        m=re.search(rx,s)        if not m: continue        a,b,c=map(int,m.groups())        try: return (dt.datetime(a,b,c),100) if order==\"ymd\" else (dt.datetime(c,b,a),100)        except Exception: pass    for word,month in MONTHS.items():        if word in s:            y=re.search(r\"(?&lt;!\\d)(20\\d{2}|19\\d{2})(?!\\d)\",s)            if y: return dt.datetime(int(y.group(1)),month,1),85    y=re.search(r\"(?&lt;!\\d)(20\\d{2}|19\\d{2})(?!\\d)\",s)    return (dt.datetime(int(y.group(1)),1,1),60) if y else (None,0)def exif_dates(paths):    exe=shutil.which(\"exiftool\")    if not exe: return {}    out={}    print(\"ExifTool \u043d\u0430\u0439\u0434\u0435\u043d \u2014 \u0447\u0438\u0442\u0430\u044e \u0434\u0430\u0442\u044b \u0444\u043e\u0442\u043e \u0438 \u0432\u0438\u0434\u0435\u043e...\")    for start in range(0,len(paths),200):        chunk=paths[start:start+200]        try:            r=subprocess.run([exe,\"-j\",\"-DateTimeOriginal\",\"-MediaCreateDate\",\"-CreateDate\",\"-TrackCreateDate\",*[str(p) for p in chunk]],                             capture_output=True,text=True,timeout=120)            for item in json.loads(r.stdout or \"[]\"):                src=item.get(\"SourceFile\")                if not src: continue                for key in (\"DateTimeOriginal\",\"MediaCreateDate\",\"CreateDate\",\"TrackCreateDate\"):                    m=re.match(r\"(\\d{4}):(\\d{2}):(\\d{2})\",str(item.get(key,\"\")))                    if m:                        out[os.path.abspath(src)]=dt.datetime(*map(int,m.groups()))                        break        except Exception: pass    return outdef spotlight_date(p):    try:        r=subprocess.run([\"mdls\",\"-raw\",\"-name\",\"kMDItemContentCreationDate\",str(p)],capture_output=True,text=True,timeout=5)        m=re.search(r\"(20\\d{2}|19\\d{2})-(\\d{2})-(\\d{2})\",r.stdout)        return dt.datetime(*map(int,m.groups())) if m else None    except Exception: return Nonedef filesystem_date(p):    try:        st=p.stat()        return dt.datetime.fromtimestamp(getattr(st,\"st_birthtime\",None) or st.st_mtime)    except Exception: return dt.datetime.now()def infer_date(p,root,exif):    ap=os.path.abspath(str(p))    if ap in exif: return exif[ap],100,\"EXIF \/ QuickTime\"    value,conf=parse_date(p.name)    if value and conf&gt;=85: return value,conf,\"\u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430\"    best=(value,conf,\"\u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430\" if value else \"\")    for part in reversed(p.relative_to(root).parts[:-1]):        v,c=parse_date(part)        if v and c&gt;best[1]: best=(v,c,f\"\u043f\u0430\u043f\u043a\u0430: {part}\")    if best[0] and best[1]&gt;=85: return best    if p.suffix.lower() in MEDIA:        v=spotlight_date(p)        if v: return v,75,\"Spotlight\"    return best if best[0] else (filesystem_date(p),30,\"\u0434\u0430\u0442\u0430 \u0444\u0430\u0439\u043b\u0430 macOS\")def extract_text(p,limit=50000):    e=p.suffix.lower()    try:        if e in {\".txt\",\".csv\",\".tsv\",\".html\",\".htm\",\".rtf\"}:            return p.read_text(encoding=\"utf-8\",errors=\"ignore\")[:limit]        if e==\".docx\":            with zipfile.ZipFile(p) as z: raw=z.read(\"word\/document.xml\")            return re.sub(r\"\\s+\",\" \",re.sub(r\"&lt;[^&gt;]+&gt;\",\" \",raw.decode(\"utf-8\",\"ignore\")))[:limit]        if e in {\".xlsx\",\".xlsm\"}:            chunks=[]            with zipfile.ZipFile(p) as z:                for name in (\"xl\/sharedStrings.xml\",\"xl\/workbook.xml\"):                    if name in z.namelist():                        chunks.append(re.sub(r\"&lt;[^&gt;]+&gt;\",\" \",z.read(name).decode(\"utf-8\",\"ignore\")))            return re.sub(r\"\\s+\",\" \",\" \".join(chunks))[:limit]        if e==\".pdf\":            r=subprocess.run([\"mdls\",\"-raw\",\"-name\",\"kMDItemTextContent\",str(p)],capture_output=True,text=True,timeout=5)            return \"\" if r.stdout.strip()==\"(null)\" else r.stdout[:limit]    except Exception: pass    return \"\"def document_type(ctx,e):    ctx=ctx.lower()    rules=[        ((\"\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\",\"\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435\",\"\u0434\u043e\u043f. \u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435\"),\"\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\"),        ((\"\u0434\u043e\u0433\u043e\u0432\u043e\u0440\",\"contract\"),\"\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u044b\"),        ((\"\u0430\u043a\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\",\"\u0430\u043a\u0442 \u2116\"),\"\u0410\u043a\u0442\u044b\"),        ((\"\u0441\u0447\u0435\u0442 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442\",\"\u0441\u0447\u0451\u0442 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442\",\"invoice\"),\"\u0421\u0447\u0435\u0442\u0430\"),        ((\"\u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435\",\"\u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\",\"\u043a\u043f \"),\"\u041a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f\"),        ((\"\u0431\u0440\u0438\u0444\",\"brief\"),\"\u0411\u0440\u0438\u0444\u044b\"),        ((\"\u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0437\u0430\u0434\u0430\u043d\u0438\u0435\",\"\u0442\u0437 \"),\"\u0422\u0417\"),        ((\"\u043e\u0442\u0447\u0435\u0442\",\"\u043e\u0442\u0447\u0451\u0442\",\"report\"),\"\u041e\u0442\u0447\u0451\u0442\u044b\"),        ((\"\u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\",),\"\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b\"),        ((\"seo\",\"\u0441\u0435\u043c\u0430\u043d\u0442\u0438\u043a\",\"\u043c\u0435\u0442\u0440\u0438\u043a\u0430\",\"\u0434\u0438\u0440\u0435\u043a\u0442\",\"\u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\"),\"SEO \u0438 \u0440\u0435\u043a\u043b\u0430\u043c\u0430\"),    ]    for words,name in rules:        if any(w in ctx for w in words): return name    if e in SHEETS: return \"\u0422\u0430\u0431\u043b\u0438\u0446\u044b\"    if e in PRESENTATIONS: return \"\u041f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438\"    if e in DESIGN: return \"\u0414\u0438\u0437\u0430\u0439\u043d\"    if e in ARCHIVES: return \"\u0410\u0440\u0445\u0438\u0432\u044b\"    return \"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\"def unique_path(dst,reserved=None):    reserved=reserved if reserved is not None else set()    if not dst.exists() and str(dst) not in reserved:        reserved.add(str(dst)); return dst    for i in range(2,10000):        c=dst.with_name(f\"{dst.stem}__{i}{dst.suffix}\")        if not c.exists() and str(c) not in reserved:            reserved.add(str(c)); return c    raise RuntimeError(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043e\u0431\u0440\u0430\u0442\u044c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0438\u043c\u044f\")def write_csv(path,fields,rows):    with path.open(\"w\",encoding=\"utf-8-sig\",newline=\"\") as f:        w=csv.DictWriter(f,fieldnames=fields); w.writeheader(); w.writerows(rows)def audit(root):    print(f\"\\n\u0421\u043a\u0430\u043d\u0438\u0440\u0443\u044e:\\n{root}\")    files=list(iter_files(root)); cats=Counter(); sizes=defaultdict(list); total=0    for i,p in enumerate(files,1):        try: size=p.stat().st_size        except Exception: size=0        total+=size; cats[category(p)]+=1        if size&gt;0: sizes[size].append(p)        if i%1000==0: print(f\"\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e {i}\/{len(files)}\")    print(\"\\n\u0418\u0449\u0443 \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438...\")    groups=[]    for size,paths in sizes.items():        if len(paths)&lt;2: continue        hashes=defaultdict(list)        for p in paths:            h=sha256_file(p)            if h: hashes[h].append(p)        groups += [{\"hash\":h,\"size\":size,\"paths\":ps} for h,ps in hashes.items() if len(ps)&gt;1]    dup_files=sum(len(g[\"paths\"])-1 for g in groups)    dup_bytes=sum(g[\"size\"]*(len(g[\"paths\"])-1) for g in groups)    out=root\/f\"_FILE_AUDIT_{stamp()}\"; out.mkdir()    (out\/\"summary.json\").write_text(json.dumps({        \"files\":len(files),\"size\":human(total),\"categories\":dict(cats),        \"duplicate_groups\":len(groups),\"duplicate_files\":dup_files,\"duplicate_size\":human(dup_bytes)    },ensure_ascii=False,indent=2),encoding=\"utf-8\")    print(\"\\n\"+\"=\"*48+\"\\n\u0410\u0423\u0414\u0418\u0422 \u0413\u041e\u0422\u041e\u0412\\n\"+\"=\"*48)    print(f\"\\n\u0424\u0430\u0439\u043b\u043e\u0432: {len(files)}\\n\u0420\u0430\u0437\u043c\u0435\u0440:  {human(total)}\")    for name,count in cats.most_common(): print(f\"{name:&lt;14} {count}\")    print(f\"\\n\u0413\u0440\u0443\u043f\u043f \u0434\u0443\u0431\u043b\u0435\u0439: {len(groups)}\\n\u041b\u0438\u0448\u043d\u0438\u0445 \u043a\u043e\u043f\u0438\u0439: {dup_files}\\n\u041e\u0431\u044a\u0451\u043c \u0434\u0443\u0431\u043b\u0435\u0439: {human(dup_bytes)}\")    return groupsdef keeper_score(p,root):    rel=p.relative_to(root); text=str(rel).lower()    return len(rel.parts)*10 + (1000 if \"\u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\" in text else 0) - (500 if re.search(r\"(\u043a\u043e\u043f\u0438\u044f|copy|\\(\\d+\\))\",p.name,re.I) else 0) + (100 if re.search(r\"(19|20)\\d{2}\",text) else 0)def quarantine(root,groups):    n=sum(len(g[\"paths\"])-1 for g in groups); b=sum(g[\"size\"]*(len(g[\"paths\"])-1) for g in groups)    print(f\"\\n\u0411\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e: {n} \u0444\u0430\u0439\u043b\u043e\u0432 \/ {human(b)}\\n\u0424\u0430\u0439\u043b\u044b \u041d\u0415 \u0431\u0443\u0434\u0443\u0442 \u0443\u0434\u0430\u043b\u0435\u043d\u044b.\")    if input(\"\\n\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u041a\u0410\u0420\u0410\u041d\u0422\u0418\u041d: \").strip()!=\"\u041a\u0410\u0420\u0410\u041d\u0422\u0418\u041d\":        print(\"\u041e\u0442\u043c\u0435\u043d\u0435\u043d\u043e.\"); return    q=root\/f\"_FILE_QUARANTINE_{stamp()}\"; q.mkdir()    undo=[]; moved=skipped=0    for g in groups:        paths=sorted(g[\"paths\"],key=lambda p:keeper_score(p,root),reverse=True)        expected=g[\"hash\"]; keeper=paths[0]        if any((not p.exists()) or sha256_file(p)!=expected for p in paths):            skipped+=len(paths)-1; continue        for p in paths[1:]:            dst=q\/p.relative_to(root); dst.parent.mkdir(parents=True,exist_ok=True)            try:                shutil.move(str(p),str(dst))                undo.append({\"original\":str(p),\"quarantine\":str(dst),\"keeper\":str(keeper),\"sha256\":expected}); moved+=1            except Exception: skipped+=1    write_csv(q\/\"undo.csv\",[\"original\",\"quarantine\",\"keeper\",\"sha256\"],undo)    print(f\"\\n\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e: {moved}\\n\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e: {skipped}\\n\u041a\u0430\u0440\u0430\u043d\u0442\u0438\u043d:\\n{q}\")def latest(root,prefix):    xs=sorted((p for p in root.iterdir() if p.is_dir() and p.name.startswith(prefix)),reverse=True)    return xs[0] if xs else Nonedef restore_quarantine(root):    q=latest(root,\"_FILE_QUARANTINE_\")    if not q or not (q\/\"undo.csv\").exists(): print(\"\u041a\u0430\u0440\u0430\u043d\u0442\u0438\u043d \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d.\"); return    rows=list(csv.DictReader((q\/\"undo.csv\").open(encoding=\"utf-8-sig\")))    ok=skip=0    for r in reversed(rows):        src,dst=Path(r[\"quarantine\"]),Path(r[\"original\"])        if not src.exists() or dst.exists(): skip+=1; continue        try:            dst.parent.mkdir(parents=True,exist_ok=True); shutil.move(str(src),str(dst)); ok+=1        except Exception: skip+=1    print(f\"\\n\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u043e: {ok}\\n\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e: {skip}\")def ask_rules():    print(\"\\n\u041c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u044b\/\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e. \u0415\u0441\u043b\u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u2014 Enter.\")    projects=[clean(x.strip()) for x in input(\"\u041f\u0440\u043e\u0435\u043a\u0442\u044b: \").split(\",\") if x.strip()]    generic=set(GENERIC)    print(\"\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f: \u0440\u0430\u0437\u043d\u043e\u0435, \u0441\u0442\u0430\u0440\u043e\u0435, \u043a\u043e\u043f\u0438\u044f, backup, old, temp, \u043d\u043e\u0432\u0430\u044f \u043f\u0430\u043f\u043a\u0430\")    generic.update(x.strip().lower() for x in input(\"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u0438\u043b\u0438 Enter: \").split(\",\") if x.strip())    return projects,genericdef detect_project(rel,text,projects,generic):    ctx=(\" \".join(rel.parts)+\" \"+text[:5000]).lower()    for project in projects:        if project.lower() in ctx: return project    if len(rel.parts)&gt;1 and not rel.parts[0].startswith(\"_\"):        first=clean(rel.parts[0])        if first.lower() not in generic: return first    return \"\u0411\u0435\u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\"def build_plan(root,projects,generic):    print(\"\\n\u0421\u0442\u0440\u043e\u044e \u0431\u0443\u0434\u0443\u0449\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443...\")    files=list(iter_files(root)); exif=exif_dates([p for p in files if p.suffix.lower() in MEDIA])    reserved=set(); plan=[]; summary=Counter(); confidence=Counter()    for i,p in enumerate(files,1):        rel=p.relative_to(root); e=p.suffix.lower()        date,conf,source=infer_date(p,root,exif); year=str(date.year); month=f\"{date.year:04d}-{date.month:02d}\"        if e in PHOTO|VIDEO:            dst=root\/\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\"\/\"02_\u0424\u041e\u0422\u041e_\u0418_\u0412\u0418\u0414\u0415\u041e\"\/year\/month            if len(rel.parts)&gt;1:                first=clean(rel.parts[0])                if first.lower() not in generic and not first.startswith(\"_\"): dst\/=first            dst\/=p.name; group=\"\u0424\u043e\u0442\u043e\/\u0432\u0438\u0434\u0435\u043e\"        elif e in AUDIO:            dst=root\/\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\"\/\"03_\u0410\u0423\u0414\u0418\u041e\"\/year\/p.name; group=\"\u0410\u0443\u0434\u0438\u043e\"        elif e in WORK:            text=extract_text(p); ctx=(\" \".join(rel.parts)+\" \"+text)            dtype=document_type(ctx,e)            if any(w in ctx.lower() for w in PRIVATE):                dst=root\/\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\"\/\"04_\u041b\u0418\u0427\u041d\u041e\u0415\"\/dtype\/year\/p.name; group=\"\u041b\u0438\u0447\u043d\u043e\u0435\"            else:                dst=root\/\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\"\/\"01_\u0420\u0410\u0411\u041e\u0422\u0410\"\/detect_project(rel,text,projects,generic)\/dtype\/year\/p.name; group=\"\u0420\u0430\u0431\u043e\u0442\u0430\"        elif e in CODE:            continue        else:            dst=root\/\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\"\/\"90_\u041f\u0420\u041e\u0427\u0415\u0415\"\/year\/p.name; group=\"\u041f\u0440\u043e\u0447\u0435\u0435\"        dst=unique_path(dst,reserved)        try: size=p.stat().st_size        except Exception: size=0        plan.append({\"source\":str(p),\"destination\":str(dst),\"group\":group,\"date\":date.isoformat(sep=\" \",timespec=\"seconds\"),                     \"date_source\":source,\"confidence\":conf,\"size_bytes\":size})        summary[group]+=1        confidence[\"\u0412\u044b\u0441\u043e\u043a\u0430\u044f\" if conf&gt;=85 else \"\u0421\u0440\u0435\u0434\u043d\u044f\u044f\" if conf&gt;=60 else \"\u041d\u0438\u0437\u043a\u0430\u044f\"]+=1        if i%500==0: print(f\"\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043e {i}\/{len(files)}\")    out=root\/f\"_ORGANIZER_PLAN_{stamp()}\"; out.mkdir()    write_csv(out\/\"plan.csv\",[\"source\",\"destination\",\"group\",\"date\",\"date_source\",\"confidence\",\"size_bytes\"],plan)    print(\"\\n\"+\"=\"*48+\"\\n\u041f\u041b\u0410\u041d \u0413\u041e\u0422\u041e\u0412\\n\"+\"=\"*48)    print(f\"\\n\u0424\u0430\u0439\u043b\u043e\u0432: {len(plan)}\")    for k,v in summary.items(): print(f\"{k:&lt;16} {v}\")    print(\"\\n\u0423\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u0442\u044b:\")    for k in (\"\u0412\u044b\u0441\u043e\u043a\u0430\u044f\",\"\u0421\u0440\u0435\u0434\u043d\u044f\u044f\",\"\u041d\u0438\u0437\u043a\u0430\u044f\"): print(f\"{k:&lt;16} {confidence[k]}\")    print(f\"\\n\u041f\u043e\u043b\u043d\u044b\u0439 \u043f\u043b\u0430\u043d:\\n{out\/'plan.csv'}\")    return plandef apply_plan(root,plan):    if input(\"\\n\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0420\u0410\u0417\u041e\u0411\u0420\u0410\u0422\u042c \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430: \").strip()!=\"\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u0422\u042c\":        print(\"\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e.\"); return    run=root\/f\"_ORGANIZER_RUN_{stamp()}\"; run.mkdir()    undo=[]; moved=skipped=0    for i,r in enumerate(plan,1):        src,dst=Path(r[\"source\"]),Path(r[\"destination\"])        if not src.exists(): skipped+=1; continue        dst.parent.mkdir(parents=True,exist_ok=True)        if dst.exists(): dst=unique_path(dst)        try:            shutil.move(str(src),str(dst)); undo.append({\"original\":str(src),\"current\":str(dst)}); moved+=1        except Exception: skipped+=1        if i%500==0: print(f\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e {i}\/{len(plan)}\")    write_csv(run\/\"undo.csv\",[\"original\",\"current\"],undo)    print(f\"\\n\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e: {moved}\\n\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e: {skipped}\\n\u041d\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430:\\n{root\/'_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e'}\")    subprocess.run([\"open\",str(root\/\"_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\")],check=False)def undo_sort(root):    run=latest(root,\"_ORGANIZER_RUN_\")    if not run or not (run\/\"undo.csv\").exists(): print(\"\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430.\"); return    rows=list(csv.DictReader((run\/\"undo.csv\").open(encoding=\"utf-8-sig\")))    ok=skip=0    for r in reversed(rows):        src,dst=Path(r[\"current\"]),Path(r[\"original\"])        if not src.exists() or dst.exists(): skip+=1; continue        try:            dst.parent.mkdir(parents=True,exist_ok=True); shutil.move(str(src),str(dst)); ok+=1        except Exception: skip+=1    print(f\"\\n\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u043e: {ok}\\n\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043e: {skip}\")def main():    if sys.platform!=\"darwin\": print(\"\u042d\u0442\u043e\u0442 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d \u043d\u0430 macOS.\"); return    root=choose_folder()    print(f\"\\n\u0412\u044b\u0431\u0440\u0430\u043d\u0430 \u043f\u0430\u043f\u043a\u0430:\\n{root}\\n\\n1 \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\\n2 \u2014 \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443\\n3 \u2014 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d\\n4 \u2014 \u0432\u044b\u0439\u0442\u0438\")    choice=input(\"\\n\u0412\u0432\u0435\u0434\u0438\u0442\u0435 1, 2, 3 \u0438\u043b\u0438 4: \").strip()    if choice==\"2\": undo_sort(root); return    if choice==\"3\": restore_quarantine(root); return    if choice!=\"1\": return    groups=audit(root)    print(\"\\n1 \u2014 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c\\n2 \u2014 \u0443\u0431\u0440\u0430\u0442\u044c \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438 \u0432 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d\\n3 \u2014 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435\")    choice=input(\"\\n\u0412\u0432\u0435\u0434\u0438\u0442\u0435 1, 2 \u0438\u043b\u0438 3: \").strip()    if choice==\"1\": return    if choice==\"2\":        quarantine(root,groups)        if input(\"\\n\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443? \u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0414\u0410 \u0438\u043b\u0438 Enter: \").strip()!=\"\u0414\u0410\": return    projects,generic=ask_rules()    plan=build_plan(root,projects,generic)    print(\"\\n1 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u043b\u0430\u043d\\n2 \u2014 \u0440\u0430\u0437\u043b\u043e\u0436\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b\")    if input(\"\\n\u0412\u0432\u0435\u0434\u0438\u0442\u0435 1 \u0438\u043b\u0438 2: \").strip()==\"2\": apply_plan(root,plan)    else: print(\"\\n\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e.\")if __name__==\"__main__\":    main()PYif ! command -v python3 &gt;\/dev\/null 2&gt;&amp;1; then    echo    echo \"Python 3 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 Python 3 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u043a.\"    rm -f \"$TMP_SCRIPT\"    exit 1fipython3 \"$TMP_SCRIPT\"STATUS=$?rm -f \"$TMP_SCRIPT\"if [ \"$STATUS\" -ne 0 ]; then    echo    echo \"\u0421\u043a\u0440\u0438\u043f\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439: $STATUS\"fi<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<\/div>\n<\/details>\n<p>\u0412\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u0432 Terminal \u0438 \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u0415\u043d\u0442\u0435\u0440! (\u0442\u043e \u0435\u0441\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c).<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4ec\/478\/ecb\/4ec478ecbab9efa459a47298107677e0.png\" width=\"1448\" height=\"1086\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/4ec\/478\/ecb\/4ec478ecbab9efa459a47298107677e0.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4ec\/478\/ecb\/4ec478ecbab9efa459a47298107677e0.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h4>\u0428\u0430\u0433 3. \u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043f\u0430\u043f\u043a\u0443<\/h4>\n<p>\u041e\u0442\u043a\u0440\u043e\u0435\u0442\u0441\u044f \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0435 \u043e\u043a\u043d\u043e macOS. \u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c (\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u0432\u0441\u0435 \u0441\u043a\u0438\u043d\u0443\u0442\u044c \u0432 1 \u043c\u0435\u0441\u0442\u043e \u0438 \u0441 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430 \u0438 \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0438 \u0441 \u043f\u0430\u043f\u043e\u043a \u0438 \u0442\u0434, \u0432\u0441\u0435 \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430 \u0434\u0443\u0431\u043b\u0438 \u0438 \u0442\u0434).<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/24d\/bda\/349\/24dbda3495f0d2bee190c35ea64d5474.png\" alt=\"\" title=\"\" width=\"1448\" height=\"1086\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/24d\/bda\/349\/24dbda3495f0d2bee190c35ea64d5474.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/24d\/bda\/349\/24dbda3495f0d2bee190c35ea64d5474.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u043c\u0435\u043d\u044e:<\/p>\n<pre><code class=\"python\">1 \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c2 \u2014 \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u04433 \u2014 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d4 \u2014 \u0432\u044b\u0439\u0442\u0438<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c <code>1<\/code>.<\/p>\n<hr\/>\n<h4>\u0428\u0430\u0433 4. \u0414\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430<\/h4>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043a\u0440\u0438\u043f\u0442 <strong>\u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0435\u0442<\/strong>. \u041e\u043d \u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u043a\u0430\u0442\u0430\u043b\u043e\u0433, \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u0443\u0435\u0442 \u0444\u0430\u0439\u043b\u044b \u043f\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0443 \u0438 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 SHA-256 \u0434\u043b\u044f \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0443\u0431\u043b\u0435\u0439.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f30\/7ff\/de3\/f307ffde3a6386fed7417e2c2f7c4377.png\" width=\"1448\" height=\"1086\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/f30\/7ff\/de3\/f307ffde3a6386fed7417e2c2f7c4377.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f30\/7ff\/de3\/f307ffde3a6386fed7417e2c2f7c4377.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0412 \u043c\u043e\u0451\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0431\u044b\u043b \u0442\u0430\u043a\u0438\u043c:<\/p>\n<pre><code class=\"python\">\u0424\u0430\u0439\u043b\u043e\u0432: 14 422\u0420\u0430\u0437\u043c\u0435\u0440: 108,5 \u0413\u0411\u0413\u0440\u0443\u043f\u043f \u0434\u0443\u0431\u043b\u0435\u0439: 2 982\u041b\u0438\u0448\u043d\u0438\u0445 \u043a\u043e\u043f\u0438\u0439: 3 203\u041e\u0431\u044a\u0451\u043c \u0434\u0443\u0431\u043b\u0435\u0439: 26,8 \u0413\u0411<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0440\u0430\u0442\u043a\u0438\u0439 \u043e\u0442\u0447\u0451\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 <code><em>FILE<\/em>AUDIT_...\/summary.json<\/code>.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 Terminal \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u0442:<\/p>\n<pre><code class=\"python\">1 \u2014 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c2 \u2014 \u0443\u0431\u0440\u0430\u0442\u044c \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438 \u0432 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d3 \u2014 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h4>\u0428\u0430\u0433 5. \u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438 \u0432 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d<\/h4>\n<p>\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c <code>2<\/code> \u0438 \u0432\u0432\u043e\u0434\u0438\u043c:<\/p>\n<pre><code class=\"python\">\u041a\u0410\u0420\u0410\u041d\u0422\u0418\u041d<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0e8\/566\/067\/0e85660679bd00d7ae0e9e33acfc5d7b.png\" width=\"1448\" height=\"1086\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/0e8\/566\/067\/0e85660679bd00d7ae0e9e33acfc5d7b.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0e8\/566\/067\/0e85660679bd00d7ae0e9e33acfc5d7b.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0424\u0430\u0439\u043b\u044b <strong>\u043d\u0435 \u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f<\/strong>. \u041b\u0438\u0448\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0443 <code><em>FILE<\/em>QUARANTINE_...<\/code> \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u0432.<\/p>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u043e\u043c SHA-256 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0435\u0449\u0451 \u0440\u0430\u0437, \u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f <code>undo.csv<\/code> \u0434\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0444\u0430\u0439\u043b\u043e\u0432.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0438\u043c <code>\u0414\u0410<\/code>, \u0435\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u043c \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435. \u041f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u044f\u0442\u0443\u044e \u2014 \u043b\u0438\u0431\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u0436\u0430\u0442\u044c Enter.<\/p>\n<hr\/>\n<h4>\u0428\u0430\u0433 6. \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c plan.csv \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443<\/h4>\n<p>\u0421\u043a\u0440\u0438\u043f\u0442 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043f\u0430\u043f\u043a\u0443 <code><em>ORGANIZER<\/em>PLAN_...<\/code> \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 <code>plan.csv<\/code>.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/414\/0ba\/d21\/4140bad21b4e88a3416a3335fab5b7b6.png\" width=\"1448\" height=\"1086\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/414\/0ba\/d21\/4140bad21b4e88a3416a3335fab5b7b6.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/414\/0ba\/d21\/4140bad21b4e88a3416a3335fab5b7b6.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0412 <code>plan.csv<\/code> \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>\u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043f\u0443\u0442\u044c;<\/p>\n<\/li>\n<li>\n<p>\u0431\u0443\u0434\u0443\u0449\u0438\u0439 \u043f\u0443\u0442\u044c;<\/p>\n<\/li>\n<li>\n<p>\u0433\u0440\u0443\u043f\u043f\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u0443\u044e \u0434\u0430\u0442\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0434\u0430\u0442\u044b;<\/p>\n<\/li>\n<li>\n<p>\u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0443\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f:<\/p>\n<pre><code class=\"python\">1 \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u043b\u0430\u043d2 \u2014 \u0440\u0430\u0437\u043b\u043e\u0436\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0441\u0451 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442, \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c <code>2<\/code> \u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u043c \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"python\">\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u0422\u042c<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u044b \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f. \u0418\u0442\u043e\u0433\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u043e\u0439:<\/p>\n<pre><code>_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e\/\u251c\u2500\u2500 01_\u0420\u0410\u0411\u041e\u0422\u0410\/\u2502   \u2514\u2500\u2500 \u041f\u0440\u043e\u0435\u043a\u0442\/\u2502       \u2514\u2500\u2500 \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\/\u2502           \u2514\u2500\u2500 2026\/\u251c\u2500\u2500 02_\u0424\u041e\u0422\u041e_\u0418_\u0412\u0418\u0414\u0415\u041e\/\u2502   \u2514\u2500\u2500 2026\/\u2502       \u2514\u2500\u2500 2026-08\/\u251c\u2500\u2500 03_\u0410\u0423\u0414\u0418\u041e\/\u2502   \u2514\u2500\u2500 2026\/\u251c\u2500\u2500 04_\u041b\u0418\u0427\u041d\u041e\u0415\/\u2502   \u2514\u2500\u2500 \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\/\u2502       \u2514\u2500\u2500 2026\/\u2514\u2500\u2500 90_\u041f\u0420\u041e\u0427\u0415\u0415\/    \u2514\u2500\u2500 2026\/<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 \u0432\u0440\u043e\u0434\u0435 <code>\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u044b<\/code>, <code>\u0410\u043a\u0442\u044b<\/code>, <code>\u0421\u0447\u0435\u0442\u0430<\/code>, <code>\u0422\u0417<\/code>, <code>\u041e\u0442\u0447\u0451\u0442\u044b<\/code>, <code>SEO \u0438 \u0440\u0435\u043a\u043b\u0430\u043c\u0430<\/code>, <code>\u0422\u0430\u0431\u043b\u0438\u0446\u044b<\/code>, <code>\u041f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438<\/code>, <code>\u0414\u0438\u0437\u0430\u0439\u043d<\/code>.<\/p>\n<p>\u041e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u0438\u043c\u0435\u043d\u0430 \u043d\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f: \u043f\u043e\u044f\u0432\u044f\u0442\u0441\u044f <code>file__2.pdf<\/code>, <code>file__3.pdf<\/code> \u0438 \u0442. \u0434.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043d\u0435 \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0441\u044f, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043a\u043e\u0434 \u0435\u0449\u0451 \u0440\u0430\u0437 \u0438 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c <code>2 \u2014 \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443<\/code>. \u0414\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d\u0430 \u2014 <code>3 \u2014 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043a\u0430\u0440\u0430\u043d\u0442\u0438\u043d<\/code>.<\/p>\n<blockquote>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong> \u0434\u043b\u044f \u0444\u043e\u0442\u043e \u0438 \u0432\u0438\u0434\u0435\u043e \u0434\u0430\u0442\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435 EXIF\/QuickTime \u2192 \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430 \u2192 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u043f\u043a\u0438 \u2192 Spotlight \u2192 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u0430\u044f \u0434\u0430\u0442\u0430 macOS. \u0415\u0441\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d ExifTool, \u043e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438:<\/p>\n<\/blockquote>\n<pre><code>brew install exiftool<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>plan.csv<\/code> \u043b\u0443\u0447\u0448\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c <strong>\u0434\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u044b <\/strong><code><strong>\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u0422\u042c<\/strong><\/code>.<\/p>\n<p>\u0415\u0441\u043b\u0438 Terminal \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 <code>Operation not permitted<\/code>, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435: <code>\u0421\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u2192 \u041a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c \u2192 \u041f\u043e\u043b\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0434\u0438\u0441\u043a\u0443<\/code>\u0438 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f Terminal.<\/p>\n<blockquote>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0432\u044b\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u044f \u0432\u0441\u0451 \u0436\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u0443\u044e \u043a\u043e\u043f\u0438\u044e \u0432\u0430\u0436\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<\/blockquote>\n<h3>\u0427\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0443 \u043c\u0435\u043d\u044f \u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b<\/h3>\n<p>\u0414\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u044f \u0432\u0437\u044f\u043b \u043d\u0435 \u0432\u0441\u0435 860 \u0413\u0411, \u0430 \u043e\u0434\u043d\u0443 \u043a\u0440\u0443\u043f\u043d\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u043e\u0431\u044a\u0451\u043c\u043e\u043c <strong>108,5 \u0413\u0411<\/strong>.<\/p>\n<p><strong>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>14 422 \u0444\u0430\u0439\u043b\u0430<\/strong>; <\/p>\n<\/li>\n<li>\n<p><strong>2 982 \u0433\u0440\u0443\u043f\u043f\u044b \u0442\u043e\u0447\u043d\u044b\u0445 \u0434\u0443\u0431\u043b\u0435\u0439<\/strong>; <\/p>\n<\/li>\n<li>\n<p><strong>3 203 \u043b\u0438\u0448\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438<\/strong>; <\/p>\n<\/li>\n<li>\n<p><strong>26,8 \u0413\u0411 \u0434\u0443\u0431\u043b\u0435\u0439<\/strong>. <\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f, \u0442\u043e\u0447\u043d\u044b\u0435 \u043a\u043e\u043f\u0438\u0438 \u0437\u0430\u043d\u044f\u043b\u0438 \u043e\u043a\u043e\u043b\u043e <strong>24,7%<\/strong> \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u0451\u043c\u0430.<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043d\u0430 \u0432\u0435\u0441\u044c \u0430\u0440\u0445\u0438\u0432<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041e\u0431\u044a\u0451\u043c<\/p>\n<\/td>\n<td>\n<p align=\"left\">108,5 \u0413\u0411<\/p>\n<\/td>\n<td>\n<p align=\"left\">860 \u0413\u0411<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0424\u0430\u0439\u043b\u044b<\/p>\n<\/td>\n<td>\n<p align=\"left\">14 422<\/p>\n<\/td>\n<td>\n<p align=\"left\">~114 313<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041b\u0438\u0448\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">3 203<\/p>\n<\/td>\n<td>\n<p align=\"left\">~25 388<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0422\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">26,8 \u0413\u0411<\/p>\n<\/td>\n<td>\n<p align=\"left\">~212,4 \u0413\u0411<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u043e\u0441\u043b\u0435 \u043e\u0447\u0438\u0441\u0442\u043a\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">81,7 \u0413\u0411<\/p>\n<\/td>\n<td>\n<p align=\"left\">~647,6 \u0413\u0411<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p><strong>212,4 \u0413\u0411 \u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0433\u043d\u043e\u0437, \u0430 \u043d\u0435 \u0443\u0436\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438.<\/strong> \u041f\u043e\u043a\u0430 \u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 108,5 \u0413\u0411.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u0430\u044f \u0436\u0435 \u0434\u043e\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430\u0445, \u043f\u043e\u0441\u043b\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u0447\u043d\u044b\u0445 \u043a\u043e\u043f\u0438\u0439 \u0430\u0440\u0445\u0438\u0432 \u043c\u043e\u0436\u0435\u0442 \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c\u0441\u044f \u0441 <strong>860 \u0434\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 648 \u0413\u0411<\/strong>.<\/p>\n<h3>\u0427\u0442\u043e \u0441\u043a\u0440\u0438\u043f\u0442 \u043f\u043e\u043a\u0430 \u043d\u0435 \u0443\u043c\u0435\u0435\u0442<\/h3>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435, \u043e\u043d \u0438\u0449\u0435\u0442 \u0444\u0430\u0439\u043b\u044b \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c SHA-256, \u0442\u043e \u0435\u0441\u0442\u044c \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438 \u0441 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u0447\u0442\u043e\u0436\u043d\u043e\u0439 \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0433\u043e \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f.<\/p>\n<p><strong>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f:<\/strong><\/p>\n<ul>\n<li>\n<p> \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438; <\/p>\n<\/li>\n<li>\n<p> \u043e\u0434\u043d\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\u0445; <\/p>\n<\/li>\n<li>\n<p> \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e; <\/p>\n<\/li>\n<li>\n<p> \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432; <\/p>\n<\/li>\n<li>\n<p> \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 PSD; <\/p>\n<\/li>\n<li>\n<p> \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u044b\u0435 \u0438\u043b\u0438 \u043d\u0435\u043d\u0443\u0436\u043d\u044b\u0435 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438. <\/p>\n<\/li>\n<\/ul>\n<p>\u042d\u0442\u043e \u0443\u0436\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u044d\u0442\u0430\u043f, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u044e \u0441\u0442\u0430\u0442\u044c\u044e \u043f\u043e\u0437\u0436\u0435. \u0415\u0441\u043b\u0438 \u0431\u0443\u0434\u0443\u0442 \u043a\u0430\u043a\u0438\u0435 \u0442\u043e \u043f\u043e\u0436\u0435\u043b\u0430\u043d\u0438\u044f \u0438\u043b\u0438 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u044f, \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445. <\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1074554\/\">https:\/\/habr.com\/ru\/articles\/1074554\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440\u0430\u0432\u0447\u0430\u043d\u0435!\u041d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u0441 \u0431\u0435\u0441\u043f\u043e\u0440\u044f\u0434\u043a\u043e\u043c \u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430 \u041f\u041a \u0437\u043d\u0430\u043a\u043e\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u0430\u0436\u0434\u044b\u0439: \u043a\u0442\u043e-\u0442\u043e \u0441\u0430\u043c \u0433\u043e\u0434\u0430\u043c\u0438 \u043d\u0435 \u0441\u043b\u0435\u0434\u0438\u0442 \u0442\u0430\u043a \u043a\u0430\u043a \u043b\u0435\u043d\u044c, \u0430 \u043a\u043e\u0433\u043e-\u0442\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u043e\u0441\u044f\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043f\u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0438\u043b\u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u0447\u0443\u0436\u043e\u0439 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440 \u2014 \u00ab\u043d\u0443 \u0442\u044b \u0436\u0435 \u0430\u0439\u0442\u0438\u0448\u043d\u0438\u043a, \u0442\u044b \u0432\u0441\u0451 \u043c\u043e\u0436\u0435\u0448\u044c!\u00bb. \u0414\u0430, \u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c, \u0438 \u043a\u043e\u0444\u0435\u043c\u0430\u0448\u0438\u043d\u0443 \u043f\u043e\u0447\u0438\u043d\u0438\u0442\u044c! \u0412\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043c\u0438\u043b\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0441\u0438\u043c :)\u042f \u043d\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435. \u0412\u043e\u043e\u0431\u0449\u0435, \u00ab\u0430\u0439\u0442\u0438\u0448\u043d\u0438\u043a\u00bb \u0432 XXI \u0432\u0435\u043a\u0435 \u0437\u0432\u0443\u0447\u0438\u0442 \u0433\u043e\u0440\u0434\u043e, \u043f\u043e\u0447\u0442\u0438 \u043a\u0430\u043a \u00abSuperMAN\u00bb. \u041a\u0442\u043e \u0431\u044b \u043c\u043e\u0433 \u043f\u043e\u0434\u0443\u043c\u0430\u0442\u044c \u0435\u0449\u0451 \u043b\u0435\u0442 \u0434\u0432\u0430\u0434\u0446\u0430\u0442\u044c \u043d\u0430\u0437\u0430\u0434, \u0447\u0442\u043e \u043b\u044e\u0431\u043e\u0439 \u043c\u0430\u043b\u043e-\u043c\u0430\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0436\u0435\u043d\u0435\u0440 \u0438\u043b\u0438 \u0432\u0435\u0440\u0441\u0442\u0430\u043b\u044c\u0449\u0438\u043a, \u0434\u0430 \u0443\u0436\u0435 \u0438 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u0441 \u043a\u043e\u0434\u043e\u043c \u0438\u043b\u0438 \u0436\u0435\u043b\u0435\u0437\u043e\u043c, \u0441\u0442\u0430\u043d\u0435\u0442 \u043d\u043e\u0441\u0438\u0442\u044c \u044d\u0442\u043e \u0432\u0441\u0435\u043c\u043e\u0433\u0443\u0449\u0435\u0435 \u0437\u0432\u0430\u043d\u0438\u0435!\u0411\u043b\u0438\u0436\u0435 \u043a \u0434\u0435\u043b\u0443\u0417\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u0435\u0442 \u043d\u0430 \u043d\u0435\u0440\u0430\u0431\u043e\u0447\u0435\u043c Mac \u0443 \u043c\u0435\u043d\u044f \u043d\u0430\u043a\u043e\u043f\u0438\u043b\u043e\u0441\u044c \u043e\u043a\u043e\u043b\u043e 860 \u0413\u0411 \u0444\u0430\u0439\u043b\u043e\u0432: \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0438 \u0432\u0438\u0434\u0435\u043e \u0441 \u0440\u0430\u0437\u043d\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, PSD, \u0437\u0430\u043f\u0438\u0441\u0438 \u044d\u043a\u0440\u0430\u043d\u0430, \u0441\u0442\u0430\u0440\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u0438 \u0434\u0430\u0436\u0435 \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0435 \u043a\u043e\u043f\u0438\u0438.\u0420\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0430\u0440\u0445\u0438\u0432 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u0435\u0441\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e, \u0430 \u0434\u0430\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u043e\u0435\u043c\u0443 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0443 \u044f \u043d\u0435 \u0445\u043e\u0442\u0435\u043b. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0444\u0430\u0439\u043b \u043c\u043e\u0433 \u043b\u0435\u0436\u0430\u0442\u044c \u0432 \u043f\u044f\u0442\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u0430\u043f\u043a\u0430\u0445 \u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043f\u043e-\u0440\u0430\u0437\u043d\u043e\u043c\u0443.\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0440\u0430 \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 Python-\u0441\u043a\u0440\u0438\u043f\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0418\u0418 + \u0434\u043e\u0432\u0451\u043b \u0440\u0443\u043a\u0430\u043c\u0438 \u0434\u043e \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438.\u0421\u0443\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442\u0430: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043d \u0438\u0449\u0435\u0442 \u0442\u043e\u0447\u043d\u044b\u0435 \u0434\u0443\u0431\u043b\u0438, \u0430 \u0437\u0430\u0442\u0435\u043c \u0433\u043e\u0442\u043e\u0432\u0438\u0442 \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0435\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435 \u0438, \u0435\u0441\u043b\u0438 \u0432\u0441\u0451 \u043e\u043a, \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442. \u0415\u0441\u043b\u0438 \u043d\u0435\u0442 \u2014 \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c.\u0414\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0442\u043e\u0447\u043d\u044b\u0445 \u0434\u0443\u0431\u043b\u0435\u0439 \u0432 \u0441\u043a\u0440\u0438\u043f\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f SHA-256 (\u0446\u0438\u0444\u0440\u043e\u0432\u043e\u0439 \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0430). \u0418\u043c\u044f \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0432\u0430\u0436\u043d\u043e: IMG_4382.jpg \u0438 photo_2022.jpg \u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0443\u0433\u043e\u0434\u043d\u043e \u0438 \u043b\u0435\u0436\u0430\u0442\u044c \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u0430\u043f\u043a\u0430\u0445, \u043d\u043e \u0435\u0441\u043b\u0438 \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442, SHA-256 \u0431\u0443\u0434\u0435\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c. \u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0432 \u0444\u0430\u0439\u043b\u0435 \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u0438\u043d \u0431\u0430\u0439\u0442 \u0438 \u0445\u0435\u0448 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0441\u0442\u0430\u043d\u0435\u0442 \u0434\u0440\u0443\u0433\u0438\u043c.\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0441\u043a\u0440\u0438\u043f\u0442\u0435 \u0441\u0432\u044f\u0437\u043a\u0430 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 + \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 SHA-256 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0442\u043e\u0447\u043d\u0443\u044e \u043a\u043e\u043f\u0438\u044e. \u0421\u0430\u043c SHA-256 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0448\u0438\u0444\u0440\u0443\u0435\u0442 \u0438 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0444\u0430\u0439\u043b\u0430, \u043e\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043e\u043a.\u041a\u0430\u043a \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c: 6 \u0448\u0430\u0433\u043e\u0432\u0428\u0430\u0433 1. \u041e\u0442\u043a\u0440\u044b\u0442\u044c Terminal\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d Python 3 (\u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0438\u0436\u0435):python3 &#8212;version\u0428\u0430\u0433 2. \u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434\u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0432\u0435\u0441\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0438\u0436\u0435:\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442TMP_SCRIPT=&#187;$(mktemp \/tmp\/mac_file_organizer.XXXXXX)&#187;cat &gt; &#171;$TMP_SCRIPT&#187; &lt;&lt;&#8216;PY&#8217;from __future__ import annotationsimport csv, datetime as dt, hashlib, json, os, re, shutil, subprocess, sys, unicodedata, zipfilefrom collections import Counter, defaultdictfrom pathlib import PathPHOTO={&#171;.jpg&#187;,&#187;.jpeg&#187;,&#187;.png&#187;,&#187;.heic&#187;,&#187;.heif&#187;,&#187;.webp&#187;,&#187;.gif&#187;,&#187;.tif&#187;,&#187;.tiff&#187;,&#187;.bmp&#187;,&#187;.dng&#187;,&#187;.raw&#187;,&#187;.cr2&#8243;,&#187;.cr3&#8243;,&#187;.nef&#187;,&#187;.arw&#187;}VIDEO={&#171;.mov&#187;,&#187;.mp4&#8243;,&#187;.m4v&#187;,&#187;.avi&#187;,&#187;.mkv&#187;,&#187;.webm&#187;,&#187;.mts&#187;,&#187;.m2ts&#187;,&#187;.3gp&#187;}AUDIO={&#171;.mp3&#8243;,&#187;.m4a&#187;,&#187;.aac&#187;,&#187;.wav&#187;,&#187;.flac&#187;,&#187;.ogg&#187;,&#187;.aiff&#187;}DOCS={&#171;.pdf&#187;,&#187;.doc&#187;,&#187;.docx&#187;,&#187;.rtf&#187;,&#187;.txt&#187;,&#187;.odt&#187;,&#187;.pages&#187;}SHEETS={&#171;.xls&#187;,&#187;.xlsx&#187;,&#187;.xlsm&#187;,&#187;.csv&#187;,&#187;.tsv&#187;,&#187;.ods&#187;,&#187;.numbers&#187;}PRESENTATIONS={&#171;.ppt&#187;,&#187;.pptx&#187;,&#187;.key&#187;,&#187;.odp&#187;}DESIGN={&#171;.psd&#187;,&#187;.ai&#187;,&#187;.eps&#187;,&#187;.svg&#187;,&#187;.sketch&#187;,&#187;.fig&#187;}ARCHIVES={&#171;.zip&#187;,&#187;.rar&#187;,&#187;.7z&#187;,&#187;.tar&#187;,&#187;.gz&#187;}CODE={&#171;.py&#187;,&#187;.js&#187;,&#187;.ts&#187;,&#187;.tsx&#187;,&#187;.jsx&#187;,&#187;.php&#187;,&#187;.html&#187;,&#187;.htm&#187;,&#187;.css&#187;,&#187;.sql&#187;,&#187;.json&#187;,&#187;.xml&#187;,&#187;.yaml&#187;,&#187;.yml&#187;,&#187;.sh&#187;,&#187;.command&#187;}MEDIA=PHOTO|VIDEO|AUDIOWORK=DOCS|SHEETS|PRESENTATIONS|DESIGN|ARCHIVESSKIP_NAMES={&#171;.git&#187;,&#187;node_modules&#187;,&#187;.Trash&#187;,&#187;__pycache__&#187;,&#187;_\u0420\u0410\u0417\u041e\u0411\u0420\u0410\u041d\u041e&#187;}SKIP_PREFIXES=(&#171;_FILE_AUDIT_&#187;,&#187;_FILE_QUARANTINE_&#187;,&#187;_ORGANIZER_PLAN_&#187;,&#187;_ORGANIZER_RUN_&#187;)PROJECT_MARKERS={&#171;.git&#187;,&#187;package.json&#187;,&#187;pyproject.toml&#187;,&#187;requirements.txt&#187;,&#187;Cargo.toml&#187;,&#187;go.mod&#187;,&#187;composer.json&#187;}GENERIC={&#171;\u0440\u0430\u0437\u043d\u043e\u0435&#187;,&#187;\u0441\u0442\u0430\u0440\u043e\u0435&#187;,&#187;\u043a\u043e\u043f\u0438\u044f&#187;,&#187;\u0444\u043e\u0442\u043e \u0441 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430&#187;,&#187;\u043d\u043e\u0432\u0430\u044f \u043f\u0430\u043f\u043a\u0430&#187;,&#187;backup&#187;,&#187;old&#187;,&#187;temp&#187;,&#187;photos&#187;,&#187;pictures&#187;,&#187;documents&#187;,&#187;downloads&#187;}PRIVATE={&#171;\u043f\u0430\u0441\u043f\u043e\u0440\u0442&#187;,&#187;\u043f\u0440\u0430\u0432\u0430&#187;,&#187;\u0441\u0432\u0438\u0434\u0435\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e&#187;,&#187;\u043b\u0438\u0447\u043d\u043e\u0435&#187;}MONTHS={&#171;\u044f\u043d\u0432\u0430\u0440&#187;:1,&#187;\u0444\u0435\u0432\u0440\u0430\u043b&#187;:2,&#187;\u043c\u0430\u0440\u0442&#187;:3,&#187;\u0430\u043f\u0440\u0435\u043b&#187;:4,&#187;\u043c\u0430\u0439&#187;:5,&#187;\u043c\u0430\u044f&#187;:5,&#187;\u0438\u044e\u043d&#187;:6,&#187;\u0438\u044e\u043b&#187;:7,&#187;\u0430\u0432\u0433\u0443\u0441\u0442&#187;:8,&#187;\u0441\u0435\u043d\u0442\u044f\u0431\u0440&#187;:9,&#187;\u043e\u043a\u0442\u044f\u0431\u0440&#187;:10,&#187;\u043d\u043e\u044f\u0431\u0440&#187;:11,&#187;\u0434\u0435\u043a\u0430\u0431\u0440&#187;:12}def stamp(): return dt.datetime.now().strftime(&#171;%Y%m%d_%H%M%S&#187;)def norm(s): return unicodedata.normalize(&#171;NFC&#187;,str(s))def clean(s):    s=re.sub(r&#187;\\s+&#187;,&#187; &#171;,norm(s).replace(&#171;\/&#187;,&#187; &#8212; &#171;).replace(&#171;:&#187;,&#187; &#8212; &#171;)).strip(&#187; ._-&#171;)    return (s or &#171;\u0411\u0435\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f&#187;)[:120]def human(n):    x=float(n)    for u in (&#171;\u0411&#187;,&#187;\u041a\u0411&#187;,&#187;\u041c\u0411&#187;,&#187;\u0413\u0411&#187;,&#187;\u0422\u0411&#187;):        if x&lt;1024 or u==&#187;\u0422\u0411&#187;: return f&#187;{int(x)} {u}&#187; if u==&#187;\u0411&#187; else f&#187;{x:.1f} {u}&#187;        x\/=1024def choose_folder():    r=subprocess.run([&#171;osascript&#187;,&#187;-e&#187;,&#8217;POSIX path of (choose folder with prompt &#171;\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u043f\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0443\u0436\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c&#187;)&#8217;],                     capture_output=True,text=True)    if not r.stdout.strip(): print(&#171;\u041f\u0430\u043f\u043a\u0430 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u0430.&#187;); raise SystemExit    return Path(r.stdout.strip()).expanduser().resolve()def is_project_dir(p):    try: return bool({x.name for x in p.iterdir()} &amp; PROJECT_MARKERS)    except Exception: return Falsedef iter_files(root):    for dp,dirs,files in os.walk(root):        cur=Path(dp)        if cur!=root and is_project_dir(cur):            dirs[:]=[]            continue        dirs[:]=[d for d in dirs if d not in SKIP_NAMES and not any(d.startswith(p) for p in SKIP_PREFIXES)]        for name in files:            p=cur\/name            try:                if not p.is_symlink(): yield p            except Exception: passdef sha256_file(p,block=1024*1024):    h=hashlib.sha256()    try:        with p.open(&#171;rb&#187;) as f:            while True:                b=f.read(block)                if not b: break                h.update(b)        return h.hexdigest()    except Exception: return Nonedef category(p):    e=p.suffix.lower()    for exts,name in ((PHOTO,&#187;\u0424\u043e\u0442\u043e&#187;),(VIDEO,&#187;\u0412\u0438\u0434\u0435\u043e&#187;),(AUDIO,&#187;\u0410\u0443\u0434\u0438\u043e&#187;),(DOCS,&#187;\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b&#187;),                      (SHEETS,&#187;\u0422\u0430\u0431\u043b\u0438\u0446\u044b&#187;),(PRESENTATIONS,&#187;\u041f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438&#187;),(DESIGN,&#187;\u0414\u0438\u0437\u0430\u0439\u043d&#187;),                      (ARCHIVES,&#187;\u0410\u0440\u0445\u0438\u0432\u044b&#187;),(CODE,&#187;\u041a\u043e\u0434&#187;)):        if e in exts: return name    return &#171;\u041f\u0440\u043e\u0447\u0435\u0435&#187;def parse_date(text):    s=norm(text).lower()    patterns=[        (r&#187;(?&lt;!\\d)(20\\d{2}|19\\d{2})[-_. \/](0?[1-9]|1[0-2])[-_. \/](0?[1-9]|[12]\\d|3[01])(?!\\d)&#187;,&#187;ymd&#187;),        (r&#187;(?&lt;!\\d)(20\\d{2}|19\\d{2})(0[1-9]|1[0-2])([0-2]\\d|3[01])(?!\\d)&#187;,&#187;ymd&#187;),        (r&#187;(?&lt;!\\d)(0?[1-9]|[12]\\d|3[01])[-_. \/](0?[1-9]|1[0-2])[-_. \/](20\\d{2}|19\\d{2})(?!\\d)&#187;,&#187;dmy&#187;),    ]    for rx,order in patterns:        m=re.search(rx,s)        if not m: continue        a,b,c=map(int,m.groups())        try: return (dt.datetime(a,b,c),100) if order==&#187;ymd&#187; else (dt.datetime(c,b,a),100)        except Exception: pass    for word,month in MONTHS.items():        if word in s:            y=re.search(r&#187;(?&lt;!\\d)(20\\d{2}|19\\d{2})(?!\\d)&#187;,s)            if y: return dt.datetime(int(y.group(1)),month,1),85    y=re.search(r&#187;(?&lt;!\\d)(20\\d{2}|19\\d{2})(?!\\d)&#187;,s)    return (dt.datetime(int(y.group(1)),1,1),60) if y else (None,0)def exif_dates(paths):    exe=shutil.which(&#171;exiftool&#187;)    if not exe: return {}    out={}    print(&#171;ExifTool \u043d\u0430\u0439\u0434\u0435\u043d \u2014 \u0447\u0438\u0442\u0430\u044e \u0434\u0430\u0442\u044b \u0444\u043e\u0442\u043e \u0438 \u0432\u0438\u0434\u0435\u043e&#8230;&#187;)    for start in range(0,len(paths),200):        chunk=paths[start:start+200]        try:            r=subprocess.run([exe,&#187;-j&#187;,&#187;-DateTimeOriginal&#187;,&#187;-MediaCreateDate&#187;,&#187;-CreateDate&#187;,&#187;-TrackCreateDate&#187;,*[str(p) for p in chunk]],                             capture_output=True,text=True,timeout=120)            for item in json.loads(r.stdout or &#171;[]&#187;):                src=item.get(&#171;SourceFile&#187;)                if not src: continue                for key in (&#171;DateTimeOriginal&#187;,&#187;MediaCreateDate&#187;,&#187;CreateDate&#187;,&#187;TrackCreateDate&#187;):                    m=re.match(r&#187;(\\d{4}):(\\d{2}):(\\d{2})&#187;,str(item.get(key,&#187;&#187;)))                    if m:                        out[os.path.abspath(src)]=dt.datetime(*map(int,m.groups()))                        break        except Exception: pass    return outdef spotlight_date(p):    try:        r=subprocess.run([&#171;mdls&#187;,&#187;-raw&#187;,&#187;-name&#187;,&#187;kMDItemContentCreationDate&#187;,str(p)],capture_output=True,text=True,timeout=5)        m=re.search(r&#187;(20\\d{2}|19\\d{2})-(\\d{2})-(\\d{2})&#187;,r.stdout)        return dt.datetime(*map(int,m.groups())) if m else None    except Exception: return Nonedef filesystem_date(p):    try:        st=p.stat()        return dt.datetime.fromtimestamp(getattr(st,&#187;st_birthtime&#187;,None) or st.st_mtime)    except Exception: return dt.datetime.now()def infer_date(p,root,exif):    ap=os.path.abspath(str(p))    if ap in exif: return exif[ap],100,&#187;EXIF \/ QuickTime&#187;    value,conf=parse_date(p.name)    if value and conf&gt;=85: return value,conf,&#187;\u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430&#187;    best=(value,conf,&#187;\u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430&#187; if value else &#171;&#187;)    for part in reversed(p.relative_to(root).parts[:-1]):        v,c=parse_date(part)        if v and c&gt;best[1]: best=(v,c,f&#187;\u043f\u0430\u043f\u043a\u0430: {part}&#187;)    if best[0] and best[1]&gt;=85: return best    if p.suffix.lower() in MEDIA:        v=spotlight_date(p)        if v: return v,75,&#187;Spotlight&#187;    return best if best[0] else (filesystem_date(p),30,&#187;\u0434\u0430\u0442\u0430 \u0444\u0430\u0439\u043b\u0430 macOS&#187;)def extract_text(p,limit=50000):    e=p.suffix.lower()    try:        if e in {&#171;.txt&#187;,&#187;.csv&#187;,&#187;.tsv&#187;,&#187;.html&#187;,&#187;.htm&#187;,&#187;.rtf&#187;}:            return p.read_text(encoding=&#187;utf-8&#8243;,errors=&#187;ignore&#187;)[:limit]        if e==&#187;.docx&#187;:            with zipfile.ZipFile(p) as z: raw=z.read(&#171;word\/document.xml&#187;)            return re.sub(r&#187;\\s+&#187;,&#187; &#171;,re.sub(r&#187;&lt;[^&gt;]+&gt;&#187;,&#187; &#171;,raw.decode(&#171;utf-8&#8243;,&#187;ignore&#187;)))[:limit]        if e in {&#171;.xlsx&#187;,&#187;.xlsm&#187;}:            chunks=[]            with zipfile.ZipFile(p) as z:                for name in (&#171;xl\/sharedStrings.xml&#187;,&#187;xl\/workbook.xml&#187;):                    if name in z.namelist():                        chunks.append(re.sub(r&#187;&lt;[^&gt;]+&gt;&#187;,&#187; &#171;,z.read(name).decode(&#171;utf-8&#8243;,&#187;ignore&#187;)))            return re.sub(r&#187;\\s+&#187;,&#187; &#171;,&#187; &#171;.join(chunks))[:limit]        if e==&#187;.pdf&#187;:            r=subprocess.run([&#171;mdls&#187;,&#187;-raw&#187;,&#187;-name&#187;,&#187;kMDItemTextContent&#187;,str(p)],capture_output=True,text=True,timeout=5)            return &#171;&#187; if r.stdout.strip()==&#187;(null)&#187; else r.stdout[:limit]    except Exception: pass    return &#171;&#187;def document_type(ctx,e):    ctx=ctx.lower()    rules=[        ((&#171;\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435&#187;,&#187;\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435&#187;,&#187;\u0434\u043e\u043f. \u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435&#187;),&#187;\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f&#187;),        ((&#171;\u0434\u043e\u0433\u043e\u0432\u043e\u0440&#187;,&#187;contract&#187;),&#187;\u0414\u043e\u0433\u043e\u0432\u043e\u0440\u044b&#187;),        ((&#171;\u0430\u043a\u0442 \u0432\u044b\u043f\u043e\u043b\u043d&#187;,&#187;\u0430\u043a\u0442 \u2116&#187;),&#187;\u0410\u043a\u0442\u044b&#187;),        ((&#171;\u0441\u0447\u0435\u0442 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442&#187;,&#187;\u0441\u0447\u0451\u0442 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442&#187;,&#187;invoice&#187;),&#187;\u0421\u0447\u0435\u0442\u0430&#187;),        ((&#171;\u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435&#187;,&#187;\u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a&#187;,&#187;\u043a\u043f &#171;),&#187;\u041a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f&#187;),        ((&#171;\u0431\u0440\u0438\u0444&#187;,&#187;brief&#187;),&#187;\u0411\u0440\u0438\u0444\u044b&#187;),        ((&#171;\u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0437\u0430\u0434\u0430\u043d\u0438\u0435&#187;,&#187;\u0442\u0437 &#171;),&#187;\u0422\u0417&#187;),        ((&#171;\u043e\u0442\u0447\u0435\u0442&#187;,&#187;\u043e\u0442\u0447\u0451\u0442&#187;,&#187;report&#187;),&#187;\u041e\u0442\u0447\u0451\u0442\u044b&#187;),&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-492223","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/492223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=492223"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/492223\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=492223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=492223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=492223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}