""" 측량/GIS/드론 관련 자료 PDF 변환 및 정리 시스템 - 모든 파일 형식을 PDF로 변환 - DWG 파일: DWG TrueView를 사용한 자동 PDF 변환 - 동영상 파일: Whisper를 사용한 음성→텍스트 변환 후 PDF 생성 - 원본 경로와 변환 파일 경로를 엑셀로 관리 """ import os import shutil from pathlib import Path from datetime import datetime import openpyxl from openpyxl.styles import Font, PatternFill, Alignment import win32com.client import pythoncom from PIL import Image import subprocess import json class SurveyingFileConverter: def _dbg(self, msg): if getattr(self, "debug", False): print(msg) def _ensure_ffmpeg_on_path(self): import os import shutil from pathlib import Path found = shutil.which("ffmpeg") self._dbg(f"DEBUG ffmpeg which before: {found}") if found: self.ffmpeg_exe = found return True try: import imageio_ffmpeg src = Path(imageio_ffmpeg.get_ffmpeg_exe()) self._dbg(f"DEBUG imageio ffmpeg exe: {src}") self._dbg(f"DEBUG imageio ffmpeg exists: {src.exists()}") if not src.exists(): return False tools_dir = Path(self.output_dir) / "tools_ffmpeg" tools_dir.mkdir(parents=True, exist_ok=True) dst = tools_dir / "ffmpeg.exe" if not dst.exists(): shutil.copyfile(str(src), str(dst)) os.environ["PATH"] = str(tools_dir) + os.pathsep + os.environ.get("PATH", "") found2 = shutil.which("ffmpeg") self._dbg(f"DEBUG ffmpeg which after: {found2}") if found2: self.ffmpeg_exe = found2 return True return False except Exception as e: self._dbg(f"DEBUG ensure ffmpeg error: {e}") return False def __init__(self, source_dir, output_dir): self.source_dir = Path(source_dir) self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.debug = True self.ffmpeg_exe = None ok = self._ensure_ffmpeg_on_path() self._dbg(f"DEBUG ensure_ffmpeg_on_path result: {ok}") # 변환 로그를 저장할 리스트 self.conversion_log = [] # ★ 추가: 도메인 용어 사전 self.domain_terms = "" # HWP 보안 모듈 후보 목록 추가 self.hwp_security_modules = [ "FilePathCheckerModuleExample", "SecurityModule", "" ] # 지원 파일 확장자 정의 self.image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.tif', '.webp'} self.office_extensions = {'.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.hwp', '.hwpx'} self.video_extensions = {'.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.m4v'} self.text_extensions = {'.txt', '.csv', '.log', '.md'} self.pdf_extension = {'.pdf'} self.dwg_extensions = {'.dwg', '.dxf'} # DWG TrueView 경로 설정 (설치 버전에 맞게 조정) self.trueview_path = self._find_trueview() def _find_trueview(self): """DWG TrueView 설치 경로 자동 탐색""" possible_paths = [ r"C:\Program Files\Autodesk\DWG TrueView 2025\dwgviewr.exe", r"C:\Program Files\Autodesk\DWG TrueView 2024\dwgviewr.exe", r"C:\Program Files\Autodesk\DWG TrueView 2023\dwgviewr.exe", r"C:\Program Files (x86)\Autodesk\DWG TrueView 2025\dwgviewr.exe", r"C:\Program Files (x86)\Autodesk\DWG TrueView 2024\dwgviewr.exe", ] for path in possible_paths: if Path(path).exists(): return path return None def get_all_files(self): """하위 모든 폴더의 파일 목록 가져오기""" all_files = [] for file_path in self.source_dir.rglob('*'): if file_path.is_file(): all_files.append(file_path) return all_files def extract_audio_from_video(self, video_path, audio_output_path): try: import imageio_ffmpeg from pathlib import Path ffmpeg_exe = imageio_ffmpeg.get_ffmpeg_exe() self._dbg(f"DEBUG extract ffmpeg_exe: {ffmpeg_exe}") self._dbg(f"DEBUG extract ffmpeg_exe exists: {Path(ffmpeg_exe).exists()}") self._dbg(f"DEBUG extract input exists: {Path(video_path).exists()}") self._dbg(f"DEBUG extract out path: {audio_output_path}") cmd = [ ffmpeg_exe, "-i", str(video_path), "-vn", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-y", str(audio_output_path), ] self._dbg("DEBUG extract cmd: " + " ".join(cmd)) result = subprocess.run(cmd, capture_output=True, timeout=300, check=True, text=True) self._dbg(f"DEBUG extract returncode: {result.returncode}") self._dbg(f"DEBUG extract stderr tail: {(result.stderr or '')[-300:]}") return True except subprocess.CalledProcessError as e: self._dbg(f"DEBUG extract CalledProcessError returncode: {e.returncode}") self._dbg(f"DEBUG extract stderr tail: {(e.stderr or '')[-300:]}") return False except Exception as e: self._dbg(f"DEBUG extract exception: {e}") return False def transcribe_audio_with_whisper(self, audio_path): try: self._ensure_ffmpeg_on_path() import shutil from pathlib import Path ffmpeg_path = shutil.which("ffmpeg") self._dbg(f"DEBUG whisper ffmpeg which: {ffmpeg_path}") if not ffmpeg_path: if self.ffmpeg_exe: import os os.environ["PATH"] = str(Path(self.ffmpeg_exe).parent) + os.pathsep + os.environ.get("PATH", "") audio_file = Path(audio_path) self._dbg(f"DEBUG whisper audio exists: {audio_file.exists()}") self._dbg(f"DEBUG whisper audio size: {audio_file.stat().st_size if audio_file.exists() else 'NA'}") if not audio_file.exists() or audio_file.stat().st_size == 0: return "[오디오 파일이 비어있거나 존재하지 않음]" import whisper model = whisper.load_model("medium") # ★ base → medium 변경 # ★ domain_terms를 initial_prompt로 사용 result = model.transcribe( str(audio_path), language="ko", task="transcribe", initial_prompt=self.domain_terms if self.domain_terms else None, condition_on_previous_text=True, # ★ 다시 True로 ) # ★ 후처리: 반복 및 이상한 텍스트 제거 text = result["text"] text = self.clean_transcript(text) return text except Exception as e: import traceback self._dbg(f"DEBUG whisper traceback: {traceback.format_exc()}") return f"[음성 인식 실패: {str(e)}]" def clean_transcript(self, text): """Whisper 결과 후처리 - 반복/환각 제거""" import re # 1. 영어/일본어/중국어 환각 제거 text = re.sub(r'[A-Za-z]{3,}', '', text) # 3글자 이상 영어 제거 text = re.sub(r'[\u3040-\u309F\u30A0-\u30FF]+', '', text) # 일본어 제거 text = re.sub(r'[\u4E00-\u9FFF]+', '', text) # 한자 제거 (필요시) # 2. 반복 문장 제거 sentences = text.split('.') seen = set() unique_sentences = [] for s in sentences: s_clean = s.strip() if s_clean and s_clean not in seen: seen.add(s_clean) unique_sentences.append(s_clean) text = '. '.join(unique_sentences) # 3. 이상한 문자 정리 text = re.sub(r'\s+', ' ', text) # 다중 공백 제거 text = text.strip() return text def get_video_transcript(self, video_path): """동영상 파일의 음성을 텍스트로 변환""" try: # 임시 오디오 파일 경로 temp_audio = video_path.parent / f"{video_path.stem}_temp_audio.wav" # 1. 동영상에서 오디오 추출 if not self.extract_audio_from_video(video_path, temp_audio): return self.get_basic_file_info(video_path) + "\n\n[오디오 추출 실패]" if (not temp_audio.exists()) or temp_audio.stat().st_size == 0: return self.get_basic_file_info(video_path) + "\n\n[오디오 파일 생성 실패]" # 2. Whisper로 음성 인식 transcript = self.transcribe_audio_with_whisper(temp_audio) # 3. 임시 오디오 파일 삭제 if temp_audio.exists(): temp_audio.unlink() # 4. 결과 포맷팅 stat = video_path.stat() lines = [] lines.append(f"동영상 파일 음성 전사 (Speech-to-Text)") lines.append(f"=" * 60) lines.append(f"파일명: {video_path.name}") lines.append(f"경로: {video_path}") lines.append(f"파일 크기: {self.format_file_size(stat.st_size)}") lines.append(f"생성일: {datetime.fromtimestamp(stat.st_ctime).strftime('%Y-%m-%d %H:%M:%S')}") lines.append("") lines.append("=" * 60) lines.append("음성 내용:") lines.append("=" * 60) lines.append("") lines.append(transcript) return "\n".join(lines) except Exception as e: return self.get_basic_file_info(video_path) + f"\n\n[음성 인식 오류: {str(e)}]" def convert_dwg_to_pdf_trueview(self, dwg_path, pdf_path): """DWG TrueView를 사용한 DWG → PDF 변환""" if not self.trueview_path: return False, "DWG TrueView가 설치되지 않음" try: # AutoCAD 스크립트 생성 script_content = f"""_-EXPORT_PDF{pdf_path}_Y""" script_path = dwg_path.parent / f"{dwg_path.stem}_plot.scr" with open(script_path, 'w') as f: f.write(script_content) # TrueView 실행 cmd = [ self.trueview_path, str(dwg_path.absolute()), "/b", str(script_path.absolute()), "/nologo" ] result = subprocess.run(cmd, timeout=120, capture_output=True) # 스크립트 파일 삭제 if script_path.exists(): try: script_path.unlink() except: pass # PDF 생성 확인 if pdf_path.exists(): return True, "성공" else: return False, "PDF 생성 실패" except subprocess.TimeoutExpired: return False, "변환 시간 초과" except Exception as e: return False, f"DWG 변환 실패: {str(e)}" def get_basic_file_info(self, file_path): """기본 파일 정보 반환""" stat = file_path.stat() lines = [] lines.append(f"파일 정보") lines.append(f"=" * 60) lines.append(f"파일명: {file_path.name}") lines.append(f"경로: {file_path}") lines.append(f"파일 크기: {self.format_file_size(stat.st_size)}") lines.append(f"생성일: {datetime.fromtimestamp(stat.st_ctime).strftime('%Y-%m-%d %H:%M:%S')}") lines.append(f"수정일: {datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S')}") return "\n".join(lines) def format_file_size(self, size_bytes): """파일 크기를 읽기 쉬운 형식으로 변환""" for unit in ['B', 'KB', 'MB', 'GB']: if size_bytes < 1024.0: return f"{size_bytes:.2f} {unit}" size_bytes /= 1024.0 return f"{size_bytes:.2f} TB" def convert_image_to_pdf(self, image_path, output_path): """이미지 파일을 PDF로 변환""" try: img = Image.open(image_path) # RGB 모드로 변환 (RGBA나 다른 모드 처리) if img.mode in ('RGBA', 'LA', 'P'): # 흰색 배경 생성 background = Image.new('RGB', img.size, (255, 255, 255)) if img.mode == 'P': img = img.convert('RGBA') background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None) img = background elif img.mode != 'RGB': img = img.convert('RGB') img.save(output_path, 'PDF', resolution=100.0) return True, "성공" except Exception as e: return False, f"이미지 변환 실패: {str(e)}" def convert_office_to_pdf(self, file_path, output_path): """Office 문서를 PDF로 변환""" pythoncom.CoInitialize() try: ext = file_path.suffix.lower() if ext in {'.hwp', '.hwpx'}: return self.convert_hwp_to_pdf(file_path, output_path) elif ext in {'.doc', '.docx'}: return self.convert_word_to_pdf(file_path, output_path) elif ext in {'.xls', '.xlsx'}: return self.convert_excel_to_pdf(file_path, output_path) elif ext in {'.ppt', '.pptx'}: return self.convert_ppt_to_pdf(file_path, output_path) else: return False, "지원하지 않는 Office 형식" except Exception as e: return False, f"Office 변환 실패: {str(e)}" finally: pythoncom.CoUninitialize() def convert_word_to_pdf(self, file_path, output_path): """Word 문서를 PDF로 변환""" try: word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(str(file_path.absolute())) doc.SaveAs(str(output_path.absolute()), FileFormat=17) # 17 = PDF doc.Close() word.Quit() return True, "성공" except Exception as e: return False, f"Word 변환 실패: {str(e)}" def convert_excel_to_pdf(self, file_path, output_path): """Excel 파일을 PDF로 변환 - 열 너비에 맞춰 출력""" try: excel = win32com.client.Dispatch("Excel.Application") excel.Visible = False wb = excel.Workbooks.Open(str(file_path.absolute())) # 모든 시트에 대해 페이지 설정 for ws in wb.Worksheets: # 페이지 설정 ws.PageSetup.Zoom = False # 자동 크기 조정 비활성화 ws.PageSetup.FitToPagesWide = 1 # 너비를 1페이지에 맞춤 ws.PageSetup.FitToPagesTall = False # 높이는 자동 (내용에 따라) # 여백 최소화 (단위: 포인트, 1cm ≈ 28.35 포인트) ws.PageSetup.LeftMargin = excel.CentimetersToPoints(1) ws.PageSetup.RightMargin = excel.CentimetersToPoints(1) ws.PageSetup.TopMargin = excel.CentimetersToPoints(1) ws.PageSetup.BottomMargin = excel.CentimetersToPoints(1) # 용지 방향 자동 결정 (가로가 긴 경우 가로 방향) used_range = ws.UsedRange if used_range.Columns.Count > used_range.Rows.Count: ws.PageSetup.Orientation = 2 # xlLandscape (가로) else: ws.PageSetup.Orientation = 1 # xlPortrait (세로) # PDF로 저장 wb.ExportAsFixedFormat(0, str(output_path.absolute())) # 0 = PDF wb.Close() excel.Quit() return True, "성공" except Exception as e: return False, f"Excel 변환 실패: {str(e)}" def convert_ppt_to_pdf(self, file_path, output_path): """PowerPoint 파일을 PDF로 변환""" try: ppt = win32com.client.Dispatch("PowerPoint.Application") ppt.Visible = True presentation = ppt.Presentations.Open(str(file_path.absolute())) presentation.SaveAs(str(output_path.absolute()), 32) # 32 = PDF presentation.Close() ppt.Quit() return True, "성공" except Exception as e: return False, f"PowerPoint 변환 실패: {str(e)}" def convert_hwp_to_pdf(self, file_path, output_path): hwp = None try: output_path.parent.mkdir(parents=True, exist_ok=True) try: hwp = win32com.client.gencache.EnsureDispatch("HWPFrame.HwpObject") except Exception: hwp = win32com.client.Dispatch("HWPFrame.HwpObject") registered = False last_reg_error = None for module_name in getattr(self, "hwp_security_modules", [""]): try: hwp.RegisterModule("FilePathCheckDLL", module_name) registered = True break except Exception as e: last_reg_error = e if not registered: return False, f"HWP 보안 모듈 등록 실패: {last_reg_error}" hwp.Open(str(file_path.absolute()), "", "") hwp.HAction.GetDefault("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet) hwp.HParameterSet.HFileOpenSave.filename = str(output_path.absolute()) hwp.HParameterSet.HFileOpenSave.Format = "PDF" hwp.HAction.Execute("FileSaveAsPdf", hwp.HParameterSet.HFileOpenSave.HSet) if output_path.exists() and output_path.stat().st_size > 0: return True, "성공" return False, "PDF 생성 확인 실패" except Exception as e: return False, f"HWP 변환 실패: {str(e)}" finally: try: if hwp: try: hwp.Clear(1) except Exception: pass try: hwp.Quit() except Exception: pass except Exception: pass def convert_text_to_pdf(self, text_path, output_path): """텍스트 파일을 PDF로 변환 (reportlab 사용)""" try: from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont # 한글 폰트 등록 (시스템에 설치된 폰트 사용) try: pdfmetrics.registerFont(TTFont('Malgun', 'malgun.ttf')) font_name = 'Malgun' except: font_name = 'Helvetica' # 텍스트 읽기 with open(text_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # PDF 생성 c = canvas.Canvas(str(output_path), pagesize=A4) width, height = A4 c.setFont(font_name, 10) # 여백 설정 margin = 50 y = height - margin line_height = 14 # 줄 단위로 처리 for line in content.split('\n'): if y < margin: # 페이지 넘김 c.showPage() c.setFont(font_name, 10) y = height - margin # 긴 줄은 자동으로 줄바꿈 if len(line) > 100: chunks = [line[i:i+100] for i in range(0, len(line), 100)] for chunk in chunks: c.drawString(margin, y, chunk) y -= line_height else: c.drawString(margin, y, line) y -= line_height c.save() return True, "성공" except Exception as e: return False, f"텍스트 변환 실패: {str(e)}" def process_file(self, file_path): """개별 파일 처리""" ext = file_path.suffix.lower() # 출력 파일명 생성 (원본 경로 구조 유지) relative_path = file_path.relative_to(self.source_dir) output_subdir = self.output_dir / relative_path.parent output_subdir.mkdir(parents=True, exist_ok=True) # PDF 파일명 output_pdf = output_subdir / f"{file_path.stem}.pdf" success = False message = "" try: # 이미 PDF인 경우 if ext in self.pdf_extension: shutil.copy2(file_path, output_pdf) success = True message = "PDF 복사 완료" # DWG/DXF 파일 elif ext in self.dwg_extensions: success, message = self.convert_dwg_to_pdf_trueview(file_path, output_pdf) # 이미지 파일 elif ext in self.image_extensions: success, message = self.convert_image_to_pdf(file_path, output_pdf) # Office 문서 elif ext in self.office_extensions: success, message = self.convert_office_to_pdf(file_path, output_pdf) # 동영상 파일 - 음성을 텍스트로 변환 후 PDF 생성 elif ext in self.video_extensions: # 음성→텍스트 변환 transcript_text = self.get_video_transcript(file_path) # 임시 txt 파일 생성 temp_txt = output_subdir / f"{file_path.stem}_transcript.txt" with open(temp_txt, 'w', encoding='utf-8') as f: f.write(transcript_text) # txt를 PDF로 변환 success, message = self.convert_text_to_pdf(temp_txt, output_pdf) if success: message = "성공 (음성 인식 완료)" # 임시 txt 파일은 남겨둠 (참고용) # 텍스트 파일 elif ext in self.text_extensions: success, message = self.convert_text_to_pdf(file_path, output_pdf) else: message = f"지원하지 않는 파일 형식: {ext}" except Exception as e: message = f"처리 중 오류: {str(e)}" # 로그 기록 self.conversion_log.append({ '원본 경로': str(file_path), '파일명': file_path.name, '파일 형식': ext, '변환 PDF 경로': str(output_pdf) if success else "", '상태': "성공" if success else "실패", '메시지': message, '처리 시간': datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) return success, message def create_excel_report(self, excel_path): """변환 결과를 엑셀로 저장""" wb = openpyxl.Workbook() ws = wb.active ws.title = "변환 결과" # 헤더 스타일 header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid") header_font = Font(bold=True, color="FFFFFF") # 헤더 작성 headers = ['번호', '원본 경로', '파일명', '파일 형식', '변환 PDF 경로', '상태', '메시지', '처리 시간'] for col, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col, value=header) cell.fill = header_fill cell.font = header_font cell.alignment = Alignment(horizontal='center', vertical='center') # 데이터 작성 for idx, log in enumerate(self.conversion_log, 2): ws.cell(row=idx, column=1, value=idx-1) ws.cell(row=idx, column=2, value=log['원본 경로']) ws.cell(row=idx, column=3, value=log['파일명']) ws.cell(row=idx, column=4, value=log['파일 형식']) ws.cell(row=idx, column=5, value=log['변환 PDF 경로']) # 상태에 따라 색상 표시 status_cell = ws.cell(row=idx, column=6, value=log['상태']) if log['상태'] == "성공": status_cell.fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid") status_cell.font = Font(color="006100") else: status_cell.fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid") status_cell.font = Font(color="9C0006") ws.cell(row=idx, column=7, value=log['메시지']) ws.cell(row=idx, column=8, value=log['처리 시간']) # 열 너비 자동 조정 for column in ws.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length + 2, 50) ws.column_dimensions[column_letter].width = adjusted_width # 요약 시트 추가 summary_ws = wb.create_sheet(title="요약") total_files = len(self.conversion_log) success_count = sum(1 for log in self.conversion_log if log['상태'] == "성공") fail_count = total_files - success_count summary_data = [ ['항목', '값'], ['총 파일 수', total_files], ['변환 성공', success_count], ['변환 실패', fail_count], ['성공률', f"{(success_count/total_files*100):.1f}%" if total_files > 0 else "0%"], ['', ''], ['원본 폴더', str(self.source_dir)], ['출력 폴더', str(self.output_dir)], ['작업 완료 시간', datetime.now().strftime('%Y-%m-%d %H:%M:%S')] ] for row_idx, row_data in enumerate(summary_data, 1): for col_idx, value in enumerate(row_data, 1): cell = summary_ws.cell(row=row_idx, column=col_idx, value=value) if row_idx == 1: cell.fill = header_fill cell.font = header_font cell.alignment = Alignment(horizontal='center' if col_idx == 1 else 'left') summary_ws.column_dimensions['A'].width = 20 summary_ws.column_dimensions['B'].width = 60 # 저장 wb.save(excel_path) print(f"\n엑셀 보고서 생성 완료: {excel_path}") def run(self): """전체 변환 작업 실행""" print(f"작업 시작: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"원본 폴더: {self.source_dir}") print(f"출력 폴더: {self.output_dir}") # DWG TrueView 확인 if self.trueview_path: print(f"DWG TrueView 발견: {self.trueview_path}") else: print("경고: DWG TrueView를 찾을 수 없습니다. DWG 파일 변환이 불가능합니다.") print("-" * 80) # 모든 파일 가져오기 all_files = self.get_all_files() total_files = len(all_files) # ★ 파일 분류: 동영상 vs 나머지 video_files = [] other_files = [] for file_path in all_files: if file_path.suffix.lower() in self.video_extensions: video_files.append(file_path) else: other_files.append(file_path) print(f"\n총 {total_files}개 파일 발견") print(f" - 문서/이미지 등: {len(other_files)}개") print(f" - 동영상: {len(video_files)}개") print("\n[1단계] 문서 파일 변환 시작...\n") # ★ 1단계: 문서 파일 먼저 처리 for idx, file_path in enumerate(other_files, 1): print(f"[{idx}/{len(other_files)}] {file_path.name} 처리 중...", end=' ') success, message = self.process_file(file_path) print(f"{'✓' if success else '✗'} {message}") # ★ 2단계: domain.txt 로드 domain_path = self.source_dir.parent / "domain.txt" # D:\for python\테스트 중(측량)\domain.txt if domain_path.exists(): self.domain_terms = domain_path.read_text(encoding='utf-8') print(f"\n[2단계] 도메인 용어 사전 로드 완료: {domain_path}") print(f" - 용어 수: 약 {len(self.domain_terms.split())}개 단어") else: print(f"\n[2단계] 도메인 용어 사전 없음: {domain_path}") print(" - 기본 음성 인식으로 진행합니다.") # ★ 3단계: 동영상 파일 처리 if video_files: print(f"\n[3단계] 동영상 음성 인식 시작...\n") for idx, file_path in enumerate(video_files, 1): print(f"[{idx}/{len(video_files)}] {file_path.name} 처리 중...", end=' ') success, message = self.process_file(file_path) print(f"{'✓' if success else '✗'} {message}") # 엑셀 보고서 생성 excel_path = self.output_dir / f"변환_결과_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" self.create_excel_report(excel_path) # 최종 요약 success_count = sum(1 for log in self.conversion_log if log['상태'] == "성공") print("\n" + "=" * 80) print(f"작업 완료!") print(f"총 파일: {total_files}개") print(f"성공: {success_count}개") print(f"실패: {total_files - success_count}개") print(f"성공률: {(success_count/total_files*100):.1f}%" if total_files > 0 else "0%") print("=" * 80) if __name__ == "__main__": # 경로 설정 SOURCE_DIR = r"D:\for python\테스트 중(측량)\측량_GIS_드론 관련 자료들" OUTPUT_DIR = r"D:\for python\테스트 중(측량)\추출" # 변환기 실행 converter = SurveyingFileConverter(SOURCE_DIR, OUTPUT_DIR) converter.run()