277 lines
10 KiB
Python
277 lines
10 KiB
Python
from pathlib import Path
|
|
from datetime import datetime
|
|
from typing import Callable
|
|
import base64,hashlib,os,re
|
|
from src.config import BASE_IMAGES_DIR,CBZ_DIR,OLD_CBZ_DIR
|
|
from src.common.item import MangaInfo,MangaItem
|
|
from typing import Generator, Union, List, Optional
|
|
from datetime import datetime
|
|
|
|
PREFIX_SCRAMBLE = "scramble="
|
|
|
|
class DirectoryNaming:
|
|
"""目录命名策略类"""
|
|
def ensure_dir(directory: Path):
|
|
"""确保目录存在"""
|
|
directory.mkdir(parents=True, exist_ok=True)
|
|
|
|
@classmethod
|
|
def chapter_images_dir(cls, manga_info: MangaInfo, chapter: str, filename: str = None) -> Path:
|
|
"""生成章节目录"""
|
|
if filename:
|
|
return Path(BASE_IMAGES_DIR,f"{manga_info.project}","images",f"{manga_info.title}",chapter.title, filename)
|
|
else:
|
|
return Path(BASE_IMAGES_DIR,f"{manga_info.project}","images",f"{manga_info.title}",chapter.title)
|
|
|
|
@classmethod
|
|
def chapter_cbz_dir(cls, manga_info: MangaInfo) -> Path:
|
|
"""生成章节CBZ文件目录"""
|
|
return Path(CBZ_DIR,f"{manga_info.project}",f"{manga_info.title}")
|
|
|
|
@classmethod
|
|
def manga_cover_dir(cls, manga_item: MangaItem) -> Path:
|
|
"""生成漫画封面目录"""
|
|
return Path(BASE_IMAGES_DIR,f"{manga_item.info.project}","icons",f"{manga_item.info.title}",f"{manga_item.info.title}.jpg")
|
|
|
|
@classmethod
|
|
def manga_cover_dir(cls, manga_info: MangaInfo, cache: bool = True, is_dir: bool = False) -> Path:
|
|
"""生成漫画封面目录"""
|
|
path = ""
|
|
if cache:
|
|
path = Path(BASE_IMAGES_DIR,f"{manga_info.project}","icons",".cache")
|
|
else:
|
|
path = Path(BASE_IMAGES_DIR,f"{manga_info.project}","icons",f"{manga_info.title}")
|
|
if not is_dir:
|
|
path = os.path.join(path, f"{manga_info.title}.jpg")
|
|
return Path(path)
|
|
|
|
class FileNaming:
|
|
"""文件命名策略类"""
|
|
PREFIX_SCRAMBLE = "scramble="
|
|
ext = ".jpg"
|
|
|
|
@classmethod
|
|
def chapter_cbz(cls, manga_info: MangaInfo, chapter: str) -> Path:
|
|
"""生成章节CBZ文件目录"""
|
|
return Path(CBZ_DIR,f"{manga_info.project}",f"{manga_info.title}",f"{chapter.title}.CBZ")
|
|
|
|
@classmethod
|
|
def old_chapter_cbz(cls, manga_info: MangaInfo, chapter: str) -> Path:
|
|
"""生成章节CBZ文件目录"""
|
|
return Path(OLD_CBZ_DIR,f"{manga_info.project}",f"{manga_info.title}",f"{chapter.title}.CBZ")
|
|
|
|
#处理成符合规定的文件名
|
|
@classmethod
|
|
def fix_file_name(cls, filename, replace=None):
|
|
if not isinstance(filename, str):
|
|
return filename
|
|
in_tab = r'[?*/\|.:><]'
|
|
str_replace = ""
|
|
if replace is not None:
|
|
str_replace = replace
|
|
filename = re.sub(in_tab, str_replace, filename)
|
|
count = 1
|
|
while True:
|
|
str_file = filename[0-count]
|
|
if str_file == " ":
|
|
count += 1
|
|
else:
|
|
filename = filename[0:len(filename)+1-count]
|
|
break
|
|
return filename
|
|
|
|
@classmethod
|
|
def file_update_by_date(cls, path, day: int = 7, remove: bool = False) -> bool:
|
|
is_update = False
|
|
if not os.path.exists(path): return False
|
|
now = datetime.now()
|
|
str_strftime = '%Y%m%d'
|
|
now_time = now.strftime(str_strftime)
|
|
m_time = datetime.fromtimestamp(os.path.getmtime(path))
|
|
file_time = m_time.strftime(str_strftime)
|
|
if int(now_time) - int(file_time) > int(day):
|
|
is_update = True
|
|
if is_update and remove:
|
|
try:
|
|
os.remove(path)
|
|
is_update = False
|
|
except:
|
|
raise exit(f"file_update_by_date() {path} 删除失败")
|
|
return is_update
|
|
|
|
@classmethod
|
|
def default_filename(cls,url: str, idx: int) -> str:
|
|
"""默认文件名生成器:使用数字序号"""
|
|
#from ..utils import get_file_extension
|
|
#ext = get_file_extension(url)
|
|
return f"{idx:03d}{cls.ext}"
|
|
|
|
@staticmethod
|
|
def default_path(base_dir: Path, chapter_name: str, filename: str) -> Path:
|
|
"""默认路径生成器:直接在章节目录下"""
|
|
return base_dir / chapter_name / filename
|
|
|
|
@classmethod
|
|
def getFileScrambleImageName(cls,count,block=None,suffix=".jpg"):
|
|
if block:
|
|
return cls.PREFIX_SCRAMBLE+str(block)+"_"+"{:0>3d}".format(count)+suffix
|
|
else:
|
|
return "{:0>3d}".format(count)+suffix
|
|
|
|
@classmethod
|
|
def getFileScrambleImageSave(cls,img_path):
|
|
base_dir = os.path.dirname(img_path)
|
|
file_name = os.path.basename(img_path)
|
|
if file_name.startswith(cls.PREFIX_SCRAMBLE):
|
|
file_name = file_name.split("_")[-1]
|
|
return os.path.join(base_dir,file_name)
|
|
|
|
# 解密切片
|
|
@classmethod
|
|
def encodeImage(cls,str_en):
|
|
#print("en",str_en)
|
|
enc = base64.b64decode(str_en)
|
|
#print("解密:",enc)
|
|
m = hashlib.md5()
|
|
m.update(enc)
|
|
md5 = m.digest()
|
|
d = md5[-1]
|
|
#print(md5)
|
|
try:
|
|
blocks = d % 10 + 5
|
|
except:
|
|
blocks = 0 %10 + 5
|
|
#print("blocks=",blocks)
|
|
return blocks
|
|
|
|
@classmethod
|
|
def cover_format_path(cls, path, count=0):
|
|
if count != 0:
|
|
name, suffix = os.path.splitext(path)
|
|
new_path = name+"-"+str(count)+suffix
|
|
return new_path
|
|
if not os.path.exists(path): return path
|
|
count = 1
|
|
while count:
|
|
name, suffix = os.path.splitext(path)
|
|
new_path = name+"-"+str(count)+suffix
|
|
if not os.path.exists(new_path): return new_path
|
|
else: count += 1
|
|
|
|
@classmethod
|
|
def get_filenames_optimized(cls,
|
|
folder_path: Union[str, Path],
|
|
recursive: bool = False,
|
|
ext_filter: Optional[List[str]] = None,
|
|
include_hidden: bool = False,
|
|
full_path: bool = True,
|
|
min_size: Optional[int] = None,
|
|
max_size: Optional[int] = None
|
|
) -> Generator[str, None, None]:
|
|
"""
|
|
高性能文件名获取函数(优化版)
|
|
|
|
:param folder_path: 目标文件夹路径
|
|
:param recursive: 是否递归子目录
|
|
:param ext_filter: 扩展名过滤列表(如 ['.jpg', '.png']),不区分大小写
|
|
:param include_hidden: 是否包含隐藏文件
|
|
:param full_path: 是否返回完整路径
|
|
:param min_size: 最小文件大小(单位:字节)
|
|
:param max_size: 最大文件大小(单位:字节)
|
|
|
|
:return: 生成器,按需生成符合条件的文件路径
|
|
"""
|
|
# 路径标准化处理
|
|
folder_path = Path(folder_path).resolve()
|
|
if not folder_path.is_dir():
|
|
raise ValueError(f"无效的目录路径: {folder_path}")
|
|
|
|
# 预处理扩展名过滤条件
|
|
ext_tuple = tuple(ext.lower() for ext in ext_filter) if ext_filter else None
|
|
|
|
# 主扫描逻辑
|
|
def _scandir(path: Path):
|
|
with os.scandir(path) as entries:
|
|
for entry in entries:
|
|
# 跳过无效条目
|
|
if not entry.name:
|
|
continue
|
|
|
|
# 处理目录
|
|
if entry.is_dir():
|
|
if recursive:
|
|
# 隐藏目录处理
|
|
if not include_hidden and entry.name.startswith('.'):
|
|
continue
|
|
yield from _scandir(Path(entry.path))
|
|
continue
|
|
|
|
# 处理文件
|
|
if not entry.is_file():
|
|
continue
|
|
|
|
# 过滤隐藏文件
|
|
if not include_hidden:
|
|
if entry.name.startswith('.') or (os.name == 'nt' and entry.is_system()):
|
|
continue
|
|
|
|
# 扩展名过滤
|
|
if ext_tuple:
|
|
file_ext = Path(entry.name).suffix.lower()
|
|
if file_ext not in ext_tuple:
|
|
continue
|
|
|
|
# 文件大小过滤
|
|
try:
|
|
stat = entry.stat(follow_symlinks=False)
|
|
except OSError:
|
|
continue
|
|
|
|
if min_size is not None and stat.st_size < min_size:
|
|
continue
|
|
if max_size is not None and stat.st_size > max_size:
|
|
continue
|
|
|
|
# 生成结果
|
|
yield entry.path if full_path else entry.name
|
|
|
|
return _scandir(folder_path)
|
|
|
|
|
|
class NamingStrategy:
|
|
"""命名策略集合类"""
|
|
|
|
@staticmethod
|
|
def original_filename(url: str, idx: int) -> str:
|
|
"""保留原始文件名的生成器"""
|
|
from ..utils import get_file_extension
|
|
ext = get_file_extension(url)
|
|
return f"image_{idx}_original{ext}"
|
|
|
|
@staticmethod
|
|
def date_based_path(base_dir: Path, chapter_name: str, filename: str) -> Path:
|
|
"""按日期组织的路径生成器"""
|
|
today = datetime.now()
|
|
return base_dir / str(today.year) / f"{today.month:02d}" / chapter_name / filename
|
|
|
|
@staticmethod
|
|
def manga_volume_path(
|
|
manga_name: str,
|
|
volume_num: int
|
|
) -> Callable[[Path, str, str], Path]:
|
|
"""生成按漫画名和卷号组织的路径生成器"""
|
|
def path_generator(base_dir: Path, chapter_name: str, filename: str) -> Path:
|
|
return base_dir / manga_name / f"第{volume_num:02d}卷" / chapter_name / filename
|
|
return path_generator
|
|
|
|
@staticmethod
|
|
def custom_manga_filename(
|
|
prefix: str = "page",
|
|
digits: int = 4
|
|
) -> Callable[[str, int], str]:
|
|
"""生成自定义漫画页面文件名生成器"""
|
|
def filename_generator(url: str, idx: int) -> str:
|
|
from ..utils import get_file_extension
|
|
ext = get_file_extension(url)
|
|
return f"{prefix}_{idx:0{digits}d}{ext}"
|
|
return filename_generator |