NewComicDownloader/src/common/naming.py
2025-02-04 17:40:05 +08:00

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