diff --git a/.gitignore b/.gitignore index ea6600d..7c3d97b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ __pycache__/ metadata_1.json metadata_mi.json main.spec +.vscode/settings.json diff --git a/main.py b/main.py index add149b..8fa9558 100755 --- a/main.py +++ b/main.py @@ -39,6 +39,7 @@ def main(argc, argv): parser.add_argument('--print', dest='bPrint', action='store_true', help='comicinfo.xml 출력') parser.add_argument('--metadata', dest='bMetadata', action='store_true', help='.metadata 파일로부터 ComicInfo.xml 생성 및 CBZ에 추가') parser.add_argument('--verbose', dest='bVerbose', action='store_true', help='상세 로그 출력') + parser.add_argument('--fix-arc-path', dest='bFixArcPath', action='store_true', help='CBZ 아카이브의 내부 폴더 구조를 제거합니다.') parser.add_argument('-v', '--version', action='version', version='ComicInfoXMLConv 1.0') args = parser.parse_args(argv[1:]) # argv[0]은 스크립트 이름 제외 @@ -52,68 +53,13 @@ def main(argc, argv): # 태그 파싱 if args.info_tags is not None and len(args.info_tags) > 0: util.DbgOut(f"입력된 태그: {args.info_tags}", True) - listTags = args.info_tags.split(',') - util.DbgOut(f"태그 리스트: {listTags}", True) + # listTags = args.info_tags.split(',') # This variable is not used + # util.DbgOut(f"태그 리스트: {listTags}", True) # 단일 파일 처리 (--in 옵션) if True == util.IsExistsPath(args.arc_file): util.DbgOut(f"대상 파일: {args.arc_file}", True) - - # Path 변수 확장 - arc_title = ExpandPathVars(args.info_title, args.arc_file) - arc_writer = ExpandPathVars(args.info_writer, args.arc_file) - arc_series = ExpandPathVars(args.info_series, args.arc_file) - arc_volume = ExpandPathVars(args.info_volume, args.arc_file) - arc_number = ExpandPathVars(args.info_number, args.arc_file) - arc_tags = args.info_tags.split(',') if args.info_tags else [] - - # .metadata 옵션이 활성화된 경우 - if True == args.bMetadata: - ProcessMetadataToCBZ(args.arc_file, args.bPrint, - arc_volume, arc_number, args.info_count, - arc_title, arc_writer, arc_series, - arc_tags if arc_tags else None) - return - - listArcContents = util.GetZipContentList(args.arc_file) - - if True == any(item.lower() == "comicinfo.xml" for item in listArcContents): - util.DbgOut(f"comicinfo.xml 발견됨: {args.arc_file}", True) - - comicinfo_xml_bytes = util.GetZippedFileByte(args.arc_file, "ComicInfo.xml") - if None != comicinfo_xml_bytes: - modified = ModComicInfoXML(comicinfo_xml_bytes, arc_title, arc_writer, arc_series, arc_tags, - info_number=arc_number, info_volume=arc_volume, info_count=args.info_count) - - if True == args.bPrint: - if modified is not None: - print(modified.decode('utf-8')) - else: - print(comicinfo_xml_bytes.decode('utf-8')) - elif modified is not None: - util.AddFileToZip(args.arc_file, "ComicInfo.xml", modified) - util.DbgOut(f"ComicInfo.xml 업데이트됨: {args.arc_file}", True) - - else: - util.DbgOut(f"comicinfo.xml 없음: {args.arc_file}", True) - if arc_volume or arc_number or arc_title or arc_writer or arc_series or arc_tags or args.info_count: - new_xml = CreateComicInfoXML( - info_title=arc_title if arc_title else "", - info_writer=arc_writer if arc_writer else "", - info_series=arc_series if arc_series else "", - tags=arc_tags if arc_tags else [], - pages=[], - volume=arc_volume if arc_volume else "", - number=arc_number if arc_number else "", - count=int(args.info_count) if args.info_count is not None else -1 - ) - if new_xml: - if True == args.bPrint: - print(new_xml) - else: - util.AddFileToZip(args.arc_file, "ComicInfo.xml", new_xml.encode('utf-8')) - util.DbgOut(f"ComicInfo.xml 생성됨: {args.arc_file}", True) - + process_archive_file(args.arc_file, args) return # 폴더 처리 (--root 옵션) - 하위 폴더 포함, CBZ 파일만 처리 @@ -121,72 +67,82 @@ def main(argc, argv): util.DbgOut(f"입력 폴더: {args.root_dir}", True) # CBZ 확장자 파일만 리커시브로 검색 listArcFiles = util.ListFileExtRcr(args.root_dir, ['cbz']) - + for strFullPath in listArcFiles: if False == os.path.exists(strFullPath): continue util.DbgOut(f"파일 발견: {strFullPath}", True) - - # Path 변수 확장 (파일마다 달라짐) - arc_title = ExpandPathVars(args.info_title, strFullPath) - arc_writer = ExpandPathVars(args.info_writer, strFullPath) - arc_series = ExpandPathVars(args.info_series, strFullPath) - arc_volume = ExpandPathVars(args.info_volume, strFullPath) - arc_number = ExpandPathVars(args.info_number, strFullPath) - arc_tags = args.info_tags.split(',') if args.info_tags else [] - - # .metadata 옵션이 활성화된 경우 - if True == args.bMetadata: - ProcessMetadataToCBZ(strFullPath, args.bPrint, - arc_volume, arc_number, args.info_count, - arc_title, arc_writer, arc_series, - arc_tags if arc_tags else None) - continue + process_archive_file(strFullPath, args) - listArcContents = util.GetZipContentList(strFullPath) - - if True == any(item.lower() == "comicinfo.xml" for item in listArcContents): - util.DbgOut(f"comicinfo.xml 발견됨: {strFullPath}", True) - - comicinfo_xml_bytes = util.GetZippedFileByte(strFullPath, "ComicInfo.xml") - if None != comicinfo_xml_bytes: - modified = ModComicInfoXML(comicinfo_xml_bytes, arc_title, arc_writer, arc_series, arc_tags, - info_number=arc_number, info_volume=arc_volume, info_count=args.info_count) - - if True == args.bPrint: - if modified is not None: - print(modified.decode('utf-8')) - else: - print(comicinfo_xml_bytes.decode('utf-8')) - elif modified is not None: - util.AddFileToZip(strFullPath, "ComicInfo.xml", modified) - util.DbgOut(f"ComicInfo.xml 업데이트됨: {strFullPath}", True) - - else: - util.DbgOut(f"comicinfo.xml 없음: {strFullPath}", True) - if arc_volume or arc_number or arc_title or arc_writer or arc_series or arc_tags or args.info_count: - new_xml = CreateComicInfoXML( - info_title=arc_title if arc_title else "", - info_writer=arc_writer if arc_writer else "", - info_series=arc_series if arc_series else "", - tags=arc_tags if arc_tags else [], - pages=[], - volume=arc_volume if arc_volume else "", - number=arc_number if arc_number else "", - count=int(args.info_count) if args.info_count is not None else -1 - ) - if new_xml: - if True == args.bPrint: - print(new_xml) - else: - util.AddFileToZip(strFullPath, "ComicInfo.xml", new_xml.encode('utf-8')) - util.DbgOut(f"ComicInfo.xml 생성됨: {strFullPath}", True) - - return + +def process_archive_file(archive_path: str, args: argparse.Namespace): + """지정된 아카이브 파일에 대해 ComicInfo.xml을 처리합니다.""" + # --fix-arc-path 옵션 처리 + if args.bFixArcPath: + if FlattenZipArchive(archive_path): + util.DbgOut(f"아카이브 폴더 구조 제거 완료: {archive_path}", True) + + # Path 변수 확장 + arc_title = ExpandPathVars(args.info_title, archive_path) + arc_writer = ExpandPathVars(args.info_writer, archive_path) + arc_series = ExpandPathVars(args.info_series, archive_path) + arc_volume = ExpandPathVars(args.info_volume, archive_path) + arc_number = ExpandPathVars(args.info_number, archive_path) + arc_tags = args.info_tags.split(',') if args.info_tags else [] + + # .metadata 옵션이 활성화된 경우 + if args.bMetadata: + ProcessMetadataToCBZ(archive_path, args.bPrint, + arc_volume, arc_number, args.info_count, + arc_title, arc_writer, arc_series, + arc_tags if arc_tags else None) + return + + listArcContents = util.GetZipContentList(archive_path) -# + comicinfo_path_in_zip = None + for item in listArcContents: + # os.path.basename은 '/' 또는 '\' 구분자를 사용하여 경로의 마지막 부분을 반환합니다. + if os.path.basename(item.lower()) == "comicinfo.xml": + comicinfo_path_in_zip = item + break + has_comicinfo = comicinfo_path_in_zip is not None + + if has_comicinfo: + util.DbgOut(f"comicinfo.xml 발견됨: {archive_path} (경로: {comicinfo_path_in_zip})", True) + comicinfo_xml_bytes = util.GetZippedFileByte(archive_path, comicinfo_path_in_zip) + if comicinfo_xml_bytes: + modified = ModComicInfoXML(comicinfo_xml_bytes, arc_title, arc_writer, arc_series, arc_tags, + info_number=arc_number, info_volume=arc_volume, info_count=args.info_count) + + if args.bPrint: + output = modified.decode('utf-8') if modified else comicinfo_xml_bytes.decode('utf-8') + print(output) + elif modified: + util.AddFileToZip(archive_path, comicinfo_path_in_zip, modified) + util.DbgOut(f"ComicInfo.xml 업데이트됨: {archive_path} (경로: {comicinfo_path_in_zip})", True) + else: + util.DbgOut(f"comicinfo.xml 없음: {archive_path}", True) + if arc_volume or arc_number or arc_title or arc_writer or arc_series or arc_tags or args.info_count: + new_xml = CreateComicInfoXML( + info_title=arc_title or "", + info_writer=arc_writer or "", + info_series=arc_series or "", + tags=arc_tags, + pages=[], + volume=arc_volume or "", + number=arc_number or "", + count=int(args.info_count) if args.info_count is not None else -1 + ) + if new_xml: + if args.bPrint: + print(new_xml) + else: + util.AddFileToZip(archive_path, "ComicInfo.xml", new_xml.encode('utf-8')) + util.DbgOut(f"ComicInfo.xml 생성됨: {archive_path}", True) + def ExpandPathVars(text: str | None, filepath: str) -> str | None: """Replace path template variables (, , etc.) with actual values. After variable expansion, if the result has a :s/pattern/replacement/ suffix, @@ -235,6 +191,48 @@ def ExpandPathVars(text: str | None, filepath: str) -> str | None: return result +def FlattenZipArchive(zip_path: str) -> bool: + """ + ZIP 아카이브의 내부 폴더 구조를 제거합니다. + 모든 파일을 루트 디렉토리로 이동시킵니다. + + Args: + zip_path: ZIP 파일 경로 + + Returns: + 구조가 변경되었으면 True, 아니면 False + """ + try: + needs_flattening = False + with util.zipfile.ZipFile(zip_path, 'r') as zin: + for item in zin.infolist(): + if '/' in item.filename or '\\' in item.filename: + needs_flattening = True + break + + if not needs_flattening: + return False + + temp_zip = zip_path + '.tmp' + + with util.zipfile.ZipFile(zip_path, 'r') as zin: + with util.zipfile.ZipFile(temp_zip, 'w') as zout: + for item in zin.infolist(): + if item.is_dir(): + continue + data = zin.read(item.filename) + new_name = os.path.basename(item.filename) + zout.writestr(new_name, data) + + util.shutil.move(temp_zip, zip_path) + return True + + except Exception as e: + util.DbgOut(f"아카이브 구조 변경 중 오류 발생: {e}", True) + if 'temp_zip' in locals() and os.path.exists(temp_zip): + os.remove(temp_zip) + return False + def ResolveRegexTransform(text: str | None, current_value: str | None) -> str | None: """Apply regex substitution if text uses s/pattern/replacement/ syntax.