initialize repository

This commit is contained in:
2026-06-28 17:22:25 +09:00
commit f771d0cb8e
6 changed files with 1731 additions and 0 deletions

767
main.py Executable file
View File

@@ -0,0 +1,767 @@
#!/usr/bin/env python3
import os
import sys
import time
import argparse
import xml.etree.ElementTree as ET
from io import BytesIO
import re
import json
import UtilPack as util
# ComicInfo.xsd에서 파싱한 default 값 딕셔너리
DEFAULT_VALUES = {
'Count': '-1',
'Volume': '',
'Number': '',
'Series': '',
'Title': '',
'Writer': '',
'LanguageISO': '',
'Manga': 'Yes',
'Pages': None, # 특수 처리
}
#
def main(argc, argv):
parser = argparse.ArgumentParser(description="ComicInfo.XML 생성기")
parser.add_argument('--root', dest='root_dir', required=False, help='입력 폴더 지정')
parser.add_argument('--in', dest='arc_file', required=False, help='대상 파일 지정')
parser.add_argument('--title', dest='info_title', required=False, help='제목')
parser.add_argument('--author', dest='info_writer', required=False, help='작가')
parser.add_argument('--series', dest='info_series', required=False, help='시리즈')
parser.add_argument('--tags', dest='info_tags', required=False, help='태그 (쉼표로 구분)')
parser.add_argument('--volume', dest='info_volume', required=False, help='권/화 묶음 번호 (예: 3)')
parser.add_argument('--number', dest='info_number', required=False, help='개별 권/화 번호 (예: 1)')
parser.add_argument('--count', dest='info_count', required=False, help='시리즈 총 권수/화수 (기본값: -1)')
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('-v', '--version', action='version', version='ComicInfoXMLConv 1.0')
args = parser.parse_args(argv[1:]) # argv[0]은 스크립트 이름 제외
# verbose 모드 설정
util.SetVerbose(args.bVerbose)
if True == args.bVerbose:
util.DbgOut("환영합니다! ComicInfo.XML 생성기를 시작합니다.", True)
# 태그 파싱
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)
# 단일 파일 처리 (--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)
return
# 폴더 처리 (--root 옵션) - 하위 폴더 포함, CBZ 파일만 처리
if True == util.IsExistsPath(args.root_dir):
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
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 ExpandPathVars(text: str | None, filepath: str) -> str | None:
"""Replace path template variables (<filename>, <parentfolder>, etc.) with actual values.
After variable expansion, if the result has a :s/pattern/replacement/ suffix,
the regex is applied to the expanded text.
"""
if not text:
return text
dirpath = os.path.dirname(filepath)
cur_folder = os.path.basename(dirpath) if dirpath else ""
par_folder = os.path.basename(os.path.dirname(dirpath)) if dirpath and os.path.dirname(dirpath) else ""
filename_no_ext = os.path.splitext(os.path.basename(filepath))[0]
ext = os.path.splitext(filepath)[1]
var_map = {
"filename": filename_no_ext,
"parentfolder": cur_folder,
"currentfolder": cur_folder,
"parentfolder": par_folder,
"fullpath": filepath,
"ext": ext,
}
result = text
# Plain variable replacement
for key, value in var_map.items():
result = result.replace(f"<{key}>", value)
# After variable expansion, check for :s/pattern/replacement/ suffix
m = re.match(r'^(.*):s(.)(.+?)\2(.*?)(?:\2([ig]*))?$', result, re.DOTALL)
if m:
source = m.group(1)
delim = m.group(2)
pattern = m.group(3)
replacement = m.group(4)
flags_str = m.group(5) or ''
replacement_safe = re.sub(r'\$(\d+)', r'\\\1', replacement)
flags = 0
if 'i' in flags_str:
flags |= re.IGNORECASE
count = 0 if 'g' in flags_str else 1
try:
result = re.sub(pattern, replacement_safe, source, count=count, flags=flags)
except re.error:
pass
return result
def ResolveRegexTransform(text: str | None, current_value: str | None) -> str | None:
"""Apply regex substitution if text uses s/pattern/replacement/ syntax.
Returns the transformed value, or the original text if no regex pattern detected."""
if not text or current_value is None:
return text
m = re.match(r'^s(.)(.+?)\1(.*?)(?:\1([ig]*))?$', text, re.DOTALL)
if m:
pattern = m.group(2)
replacement = m.group(3)
replacement = re.sub(r'\$(\d+)', r'\\\1', replacement)
flags_str = m.group(4) or ''
flags = 0
if 'i' in flags_str:
flags |= re.IGNORECASE
count = 0 if 'g' in flags_str else 1
try:
return re.sub(pattern, replacement, current_value, count=count, flags=flags)
except re.error:
return text
return text
def ParsePathForComicInfo(strFullPath: str) -> dict:
"""
파일 경로에서 작가, 제목, 번호를 추출합니다.
예: /some/path/[Kim] Some Comic/blablabla_01.cbz
반환: {'author': 'Kim', 'title': 'Some Comic', 'number': '01'}
Args:
strFullPath: 파일 전체 경로
Returns:
{'author': str, 'title': str, 'number': str} 딕셔너리
"""
result = {
'author': None,
'title': None,
'number': None
}
try:
# 경로를 / 로 분리
path_parts = strFullPath.replace('\\', '/').split('/')
# [] 가 포함된 부분 찾기
folder_with_bracket = None
for part in path_parts:
if '[' in part and ']' in part:
folder_with_bracket = part
break
# [] 로 분리해서 작가와 제목 추출
if folder_with_bracket:
# [작가] 제목 형식으로 분리
match = re.match(r'\[([^\]]+)\]\s*(.+)', folder_with_bracket)
if match:
result['author'] = match.group(1).strip()
result['title'] = match.group(2).strip()
# 파일명에서 번호 추출 (마지막 _ 다음 숫자)
filename = path_parts[-1] # 파일명 (blablabla_01.cbz)
# _숫자 형식 찾기
number_match = re.search(r'_(\d+)', filename)
if number_match:
result['number'] = number_match.group(1)
return result
except Exception as e:
util.DbgOut(f"경로 파싱 오류: {e}", True)
return result
#
def GetComicInfoXMLfromARC(strArcPath) -> bytes:
# zip 파일에서 comicinfo.xml을 추출하는 로직을 여기에 구현
# 예시로, zip 파일에서 comicinfo.xml을 읽어와서 bytes 형태로 반환하는 과정을 생각할 수 있습니다.
listContents = util.GetZipContentList(strArcPath)
for item in listContents:
if item.lower() == "comicinfo.xml":
return util.GetZippedFileByte(strArcPath, item)
return None
#
def ModComicInfoXMLFile(strXMLPath, info_title, info_writer, info_series, tags: list) -> bool:
if False == util.IsExistsPath(strXMLPath):
util.DbgOut(f"comicinfo.xml 파일이 존재하지 않습니다: {strXMLPath}", True)
return False
with open(strXMLPath, "rb") as f:
xml_bytes = f.read()
modified = ModComicInfoXML(xml_bytes, info_title, info_writer, info_series, tags)
if modified is not None:
with open(strXMLPath, "wb") as f:
f.write(modified)
return True
return False
#
def ModComicInfoXML(pBytes, info_title, info_writer, info_series, tags: list,
info_number=None, info_volume=None, info_count=None) -> bytes | None:
try:
root = ET.fromstring(pBytes)
cur_title = root.findtext('Title') if root.find('Title') is not None else ""
cur_writer = root.findtext('Writer') if root.find('Writer') is not None else ""
cur_series = root.findtext('Series') if root.find('Series') is not None else ""
info_title = ResolveRegexTransform(info_title, cur_title)
info_writer = ResolveRegexTransform(info_writer, cur_writer)
info_series = ResolveRegexTransform(info_series, cur_series)
if info_title is not None and info_title != "":
title_elem = root.find('Title')
if title_elem is None:
title_elem = ET.SubElement(root, 'Title')
title_elem.text = info_title
if info_writer is not None and info_writer != "":
writer_elem = root.find('Writer')
if writer_elem is None:
writer_elem = ET.SubElement(root, 'Writer')
writer_elem.text = info_writer
if info_series is not None and info_series != "":
series_elem = root.find('Series')
if series_elem is None:
series_elem = ET.SubElement(root, 'Series')
series_elem.text = info_series
if tags is not None and len(tags) > 0:
tags_elem = root.find('Tags')
if tags_elem is not None:
root.remove(tags_elem)
tags_elem = ET.SubElement(root, 'Tags')
tags_elem.text = ','.join(tags)
if info_number is not None and info_number != "" and info_number != "-1":
number_elem = root.find('Number')
if number_elem is None:
number_elem = ET.SubElement(root, 'Number')
number_elem.text = str(info_number)
if info_volume is not None and info_volume != "" and info_volume != "-1":
volume_elem = root.find('Volume')
if volume_elem is None:
volume_elem = ET.SubElement(root, 'Volume')
volume_elem.text = str(info_volume)
if info_count is not None and info_count != -1:
count_elem = root.find('Count')
if count_elem is None:
count_elem = ET.SubElement(root, 'Count')
count_elem.text = str(info_count)
output = BytesIO()
tree = ET.ElementTree(root)
tree.write(output, encoding='utf-8', xml_declaration=True)
modified = output.getvalue()
if modified != pBytes:
return modified
return None
except ET.ParseError as e:
util.DbgOut(f"XML 파싱 오류: {e}", True)
return None
except Exception as e:
util.DbgOut(f"ComicInfo.xml 수정 중 오류 발생: {e}", True)
return None
#
def ConvertLanguageToISO(lang_name: str) -> str:
"""
언어 이름을 ISO 639-1 코드로 변환합니다.
Args:
lang_name: 언어 이름 (예: "korean", "japanese", "english")
Returns:
ISO 639-1 코드 (예: "ko", "ja", "en")
"""
lang_map = {
'korean': 'ko',
'japanese': 'ja',
'english': 'en',
'chinese': 'zh',
'french': 'fr',
'german': 'de',
'spanish': 'es',
'italian': 'it',
'russian': 'ru',
'arabic': 'ar',
'portuguese': 'pt',
'korea': 'ko', # 별칭
'japan': 'ja', # 별칭
}
if lang_name:
return lang_map.get(lang_name.lower(), lang_name)
return ""
#
def ProcessMetadataToCBZ(strArcPath: str, bPrint: bool = False,
volume: str = "-1", number: str = "", count: int = -1,
info_title: str = None, info_writer: str = None,
info_series: str = None, tags: list[str] = None) -> bool:
"""
CBZ 파일의 .metadata를 읽어 ComicInfo.xml을 생성하고 CBZ에 추가합니다.
info_title/info_writer/info_series에 s/pattern/replacement/ 문법을 사용하면
.metadata의 원본 값에 정규식 치환을 적용합니다.
Args:
strArcPath: CBZ 파일 경로
bPrint: ComicInfo.xml 내용을 출력할지 여부
volume: 권/화 묶음 번호 (예: 3)
number: 개별 권/화 번호 (예: 1)
count: 시리즈 총 권수/화수
info_title: 제목 오버라이드 (또는 s/// 정규식)
info_writer: 작가 오버라이드 (또는 s/// 정규식)
info_series: 시리즈 오버라이드 (또는 s/// 정규식)
tags: 태그 리스트 오버라이드
Returns:
성공 여부
"""
try:
util.DbgOut(f".metadata 처리 중: {strArcPath}", True)
xml_content = MetadataToComicInfoXML(strArcPath, volume=volume, number=number, count=count)
if not xml_content:
util.DbgOut(f"ComicInfo.xml 생성 실패: {strArcPath}", True)
return False
# XML 파싱해서 추가 필드 삽입
try:
root = ET.fromstring(xml_content)
# 현재 값 읽기 (regex 치환용)
cur_title = root.findtext('Title') if root.find('Title') is not None else ""
cur_writer = root.findtext('Writer') if root.find('Writer') is not None else ""
cur_series = root.findtext('Series') if root.find('Series') is not None else ""
# Regex 치환 적용
info_title = ResolveRegexTransform(info_title, cur_title)
info_writer = ResolveRegexTransform(info_writer, cur_writer)
info_series = ResolveRegexTransform(info_series, cur_series)
# Title 오버라이드
if info_title is not None and info_title != "":
title_elem = root.find('Title')
if title_elem is None:
title_elem = ET.SubElement(root, 'Title')
title_elem.text = info_title
# Writer 오버라이드
if info_writer is not None and info_writer != "":
writer_elem = root.find('Writer')
if writer_elem is None:
writer_elem = ET.SubElement(root, 'Writer')
writer_elem.text = info_writer
# Series 오버라이드
if info_series is not None and info_series != "":
series_elem = root.find('Series')
if series_elem is None:
series_elem = ET.SubElement(root, 'Series')
series_elem.text = info_series
# Tags 오버라이드
if tags is not None and len(tags) > 0:
tags_elem = root.find('Tags')
if tags_elem is not None:
root.remove(tags_elem)
tags_elem = ET.SubElement(root, 'Tags')
tags_elem.text = ','.join(tags)
# Volume 추가
if volume is not None and volume != "-1" and volume != "":
volume_elem = root.find('Volume')
if volume_elem is None:
volume_elem = ET.SubElement(root, 'Volume')
volume_elem.text = str(volume)
# Number 추가
if number is not None and number != "" and number != "-1":
number_elem = root.find('Number')
if number_elem is None:
number_elem = ET.SubElement(root, 'Number')
number_elem.text = str(number)
# Count 추가
if count is not None and count != -1:
count_elem = root.find('Count')
if count_elem is None:
count_elem = ET.SubElement(root, 'Count')
count_elem.text = str(count)
# XML을 문자열로 변환
tree = ET.ElementTree(root)
output = BytesIO()
tree.write(output, encoding='utf-8', xml_declaration=True)
xml_content = output.getvalue().decode('utf-8')
except Exception as e:
util.DbgOut(f"XML 수정 중 오류: {e}", True)
# 출력 옵션이 켜져 있으면 내용 출력
if True == bPrint:
print(xml_content)
# XML을 바이트로 변환하여 CBZ에 추가
xml_bytes = xml_content.encode('utf-8')
if True == util.AddFileToZip(strArcPath, "ComicInfo.xml", xml_bytes):
util.DbgOut(f"ComicInfo.xml이 CBZ에 추가됨: {strArcPath}", True)
return True
else:
util.DbgOut(f"CBZ에 ComicInfo.xml 추가 실패: {strArcPath}", True)
return False
except Exception as e:
util.DbgOut(f".metadata 처리 중 오류 발생: {e}", True)
return False
#
def MetadataToComicInfoXML(strArcPath: str, volume: str = "", number: str = "", count: int = -1) -> str:
try:
listContents = util.GetZipContentList(strArcPath)
metadata_file = None
for item in listContents:
if item.lower() == ".metadata":
metadata_file = item
break
if not metadata_file:
util.DbgOut(f".metadata 파일을 찾을 수 없습니다: {strArcPath}", True)
return ""
metadata_bytes = util.GetZippedFileByte(strArcPath, metadata_file)
if not metadata_bytes:
util.DbgOut(f".metadata 파일을 읽을 수 없습니다: {strArcPath}", True)
return ""
metadata_json = json.loads(metadata_bytes.decode('utf-8'))
info_title = ""
info_writer = ""
info_series = ""
info_language = ""
tags = []
characters = ""
teams = ""
pages = []
gallery_block = metadata_json.get('galleryBlock', {})
info_title = gallery_block.get('title', '')
artists = gallery_block.get('artists', [])
if artists:
info_writer = ', '.join(artists)
series = gallery_block.get('series', [])
if series:
info_series = series[0]
lang_name = gallery_block.get('language', '')
info_language = ConvertLanguageToISO(lang_name)
related_tags = gallery_block.get('relatedTags', [])
if related_tags:
tags = related_tags
groups_block = gallery_block.get('groups', [])
if groups_block and isinstance(groups_block, list):
teams = ','.join([str(g) for g in groups_block if g])
gallery_info = metadata_json.get('galleryInfo', {})
chars_info = gallery_info.get('characters', [])
if chars_info and isinstance(chars_info, list):
char_names = [c.get('character', '') for c in chars_info if isinstance(c, dict)]
characters = ','.join([c for c in char_names if c])
groups_info = gallery_info.get('groups', [])
if groups_info and isinstance(groups_info, list):
group_names = [g.get('group', '') for g in groups_info if isinstance(g, dict)]
teams = ','.join([g for g in group_names if g])
files = gallery_info.get('files', [])
for image_index, file_info in enumerate(files):
page_data = {
'Image': image_index
}
pages.append(page_data)
return CreateComicInfoXML(info_title, info_writer, info_series, tags, pages, info_language, characters, teams, volume, number, count)
except json.JSONDecodeError as e:
util.DbgOut(f"JSON 파싱 오류: {e}", True)
return ""
except Exception as e:
util.DbgOut(f".metadata 변환 중 오류 발생: {e}", True)
return ""
#
def CreateComicInfoXML(info_title, info_writer, info_series, tags: list, pages: list, language: str = "", characters: str = "", teams: str = "", volume: str = "", number: str = "", count: int = -1) -> str:
"""
새로운 ComicInfo.xml을 생성합니다.
Args:
info_title: 제목
info_writer: 작가
info_series: 시리즈
tags: 태그 리스트
pages: 페이지 정보 리스트 (dict 또는 객체)
language: 언어 정보 (LanguageISO)
Returns:
ComicInfo.xml의 XML 문자열
"""
try:
# 루트 엘리먼트 생성
root = ET.Element('ComicInfo')
# Title 추가
if info_title is not None and info_title != "":
title_elem = ET.SubElement(root, 'Title')
title_elem.text = info_title
# Series 추가
if info_series is not None and info_series != "":
series_elem = ET.SubElement(root, 'Series')
series_elem.text = info_series
# Writer 추가
if info_writer is not None and info_writer != "":
writer_elem = ET.SubElement(root, 'Writer')
writer_elem.text = info_writer
# Tags 추가 (쉼표로 구분)
if tags is not None and len(tags) > 0:
# 빈 문자열 제거
filtered_tags = [tag.strip() for tag in tags if tag and tag.strip()]
if filtered_tags:
tags_elem = ET.SubElement(root, 'Tags')
tags_elem.text = ','.join(filtered_tags)
# Characters 추가
if characters is not None and characters != "":
char_elem = ET.SubElement(root, 'Characters')
char_elem.text = characters
# Teams 추가 (groups -> Teams)
if teams is not None and teams != "":
teams_elem = ET.SubElement(root, 'Teams')
teams_elem.text = teams
# LanguageISO 추가
if language is not None and language != "":
language_elem = ET.SubElement(root, 'LanguageISO')
language_elem.text = language
# Manga 항목 추가 (만화 유형 - Yes/No/YesAndRightToLeft)
manga_elem = ET.SubElement(root, 'Manga')
manga_elem.text = "Yes"
# Volume 추가 (기본값 -1)
if volume is not None and volume != "":
volume_elem = ET.SubElement(root, 'Volume')
volume_elem.text = str(volume)
# Number 추가 (개별 권/화 번호)
if number is not None and number != "" and number != "-1":
number_elem = ET.SubElement(root, 'Number')
number_elem.text = str(number)
# Count 추가 (기본값 -1)
if count is not None:
count_elem = ET.SubElement(root, 'Count')
count_elem.text = str(count)
# Pages 추가
if pages is not None and len(pages) > 0:
pages_elem = ET.SubElement(root, 'Pages')
for page in pages:
page_elem = ET.SubElement(pages_elem, 'Page')
# Type 속성 추가 (고정값: Story)
page_elem.set('Type', 'Story')
# Image 속성 추가
if isinstance(page, dict):
if 'Image' in page and page['Image'] is not None:
page_elem.set('Image', str(page['Image']))
else:
# page가 객체인 경우, Image 속성만 추출
if hasattr(page, 'Image'):
page_elem.set('Image', str(page.Image))
# XML을 문자열로 변환
tree = ET.ElementTree(root)
output = BytesIO()
tree.write(output, encoding='utf-8', xml_declaration=True)
return output.getvalue().decode('utf-8')
except Exception as e:
util.DbgOut(f"ComicInfo.xml 생성 중 오류 발생: {e}", True)
return ""
# For Main Loop
if __name__ == '__main__':
argc = len(sys.argv)
argv = sys.argv
main(argc, argv)
#
def printHowto():
print("Usage: python main.py [options]")
print("Options:")
print(" -h, --help Show help message.")
print(" -v, --version Show version.")