본문으로 이동

위키문헌:현대 한글 문헌 프로젝트/WikisourceClovaOCR.py

위키문헌, 우리 모두의 도서관.
  • 파일을 위키미디어 공용에서 다운로드받아 클로바 OCR로 텍스트를 인식시키는 프로그램입니다.
  • 다운로드나 변환된 파일은 프로그램 종료 후 삭제됩니다.
  • 클로바 General OCR은 100건/월까지 무료입니다. 그 이상의 사용이 필요한 경우 특수:이메일보내기/Motoko (WMKR)로 문의해 주세요.
"""
pdf2image 패키지 설치 필요
    (공통)
    pip install pdf2image requests pillow

    (Ubuntu/Debian) 
    sudo apt-get update
    sudo apt-get install poppler-utils    

    (macOS)
    brew install poppler

DjVu 도구 설치

    (Ubuntu/Debian)
    sudo apt-get install djvulibre-bin

    (macOS)
    brew install djvulibre

네이버 API 콘솔에서 결제 정보를 입력하고 도메인을 활성화한 뒤 API_URL, SECRET-KEY에 각각 APIGW invoke url과 secret-key 값을 할당해 주세요. (GW자동연동 추천)
    https://guide.ncloud-docs.com/docs/clovaocr-domain

"""

from pdf2image import convert_from_path
from urllib.parse import urlparse
from pathlib import Path
import uuid
import time
import base64
import json
import subprocess
import requests
import re
import io
import os

HEADERS = {
    'User-Agent': 'WikisourceClovaOCR/1.0 (Phython/User:Motoko C. K.)'
}

API_URL = ''
SECRET_KEY = ''


def get_file_url(file_name):

    api_url = 'https://commons.wikimedia.org/w/api.php'

    params = {
        'action': 'query',
        'titles': file_name,
        'prop': 'imageinfo',
        'iiprop': 'url',
        'format': 'json',
    }

    response = requests.get(api_url, params=params, headers=HEADERS)
    response.raise_for_status()
    data = response.json()
    
    pages = data.get('query', {}).get('pages', {})
    page = next(iter(pages.values()))

    image_url = page['imageinfo'][0]['url']

    return image_url

def decect_file_from_cloava(file_path):
    
    file_extention = file_path.rsplit('.', 1)[-1]
    
    with open(file_path, "rb") as f:
        image_data = base64.b64encode(f.read()).decode("utf-8")

    api_url = API_URL
    
    headers =  {
        "X-OCR-SECRET": SECRET_KEY,
        "Content-Type": "application/json"
    }   

    params = {
        "version": "V2",
        "requestId": str(uuid.uuid4()), 
        "timestamp": int(time.time() * 1000),
        "lang": "ko",
        "images": [
            {
                "format": file_extention,
                "name": file_path,
                "data": image_data
            }
        ]
    }

    response = requests.post(api_url, headers=headers, data=json.dumps(params).encode('UTF-8'))
    response.raise_for_status()
    data = response.json()

    line_info = data['images'][0]['fields']

    text = ''

    for line in line_info:
        text = text + line['inferText'] + '\n'

    return text


def file_creator(title, texts):
        
    # 파일명 생성 (특수문자 제거)
    safe_filename = re.sub(r'[<>:"/\\|?*]', '_', title)
    filename = f"{safe_filename}.txt"
    
    clean_text = texts[0].description

    # 파일 저장
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(clean_text)
        
    print(f"✓ 성공: '{filename}' 파일로 저장되었습니다.")
    print(f"  문자 수: {len(clean_text):,}")
        
    return True

def download_file(url, save_path='downloaded.pdf'):
    """
    URL에서 PDF 파일을 다운로드합니다.
    
    Args:
        url: PDF 파일 URL
        save_path: 저장할 파일 경로
        
    Returns:
        다운로드된 파일 경로 또는 None
    """
    try:
        print(f"📥 파일 다운로드 중: {url}")
        
        response = requests.get(url, headers=HEADERS, stream=True)
        response.raise_for_status()
        
        # Content-Type 확인
        content_type = response.headers.get('Content-Type', '')
        
        print(f"Content-Type: {content_type}")
        
        # 파일 저장12
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)
        
        file_size = os.path.getsize(save_path)
        print(f"✅ 다운로드 완료: {save_path} ({file_size:,} bytes)")
        
        return save_path
        
    except requests.exceptions.RequestException as e:
        print(f"❌ 다운로드 오류: {e}")
        return None
    except Exception as e:
        print(f"❌ 오류 발생: {e}")
        return None


def pdf_to_png(pdf_path, output_dir='output_images', dpi=300, max_dimension=2000):
    """
    PDF 파일을 낱장의 PNG 이미지로 변환합니다.
    
    Args:
        pdf_path: PDF 파일 경로
        output_dir: PNG 파일을 저장할 디렉토리
        dpi: 이미지 해상도 (기본 200, 높을수록 고화질)
        max_dimension: 최대 이미지 크기 (픽셀, 큰 파일 방지용)
        
    Returns:
        생성된 이미지 파일 경로 리스트
    """
    try:
        # 출력 디렉토리 생성
        os.makedirs(output_dir, exist_ok=True)
        
        print(f"\nPDF를 PNG로 변환 중...")
        print(f"   - 입력: {pdf_path}")
        print(f"   - 출력 디렉토리: {output_dir}")
        print(f"   - 해상도: {dpi} DPI")
        
        # PDF를 이미지로 변환
        images = convert_from_path(pdf_path, dpi=dpi)
        
        print(f"   - 총 페이지 수: {len(images)}")
        
        saved_files = []
        
        for i, image in enumerate(images, start=1):
            width, height = image.size
            
            # 이미지 크기 조정 (너무 큰 경우)
            if width > max_dimension or height > max_dimension:
                scale_factor = min(max_dimension / width, max_dimension / height)
                new_width = int(width * scale_factor)
                new_height = int(height * scale_factor)
                image = image.resize((new_width, new_height))
                print(f"   📄 페이지 {i}: {width}x{height} → {new_width}x{new_height} (크기 조정됨)")
            else:
                print(f"   📄 페이지 {i}: {width}x{height}")
            
            # PNG 파일로 저장
            output_path = os.path.join(output_dir, f"page_{i:03d}.png")
            image.save(output_path, 'PNG')
            saved_files.append(output_path)
            
            # 파일 크기 표시
            file_size = os.path.getsize(output_path)
            print(f"      ✅ 저장: {output_path} ({file_size:,} bytes)")
        
        print(f"\n✨ 변환 완료! {len(saved_files)}개의 PNG 파일 생성")
        
        return saved_files
        
    except Exception as e:
        print(f"❌ 변환 오류: {e}")
        return []


def pdf_download_and_convert(url, output_dir='output_images', dpi=200):
    """
    PDF를 다운로드하고 PNG로 변환하는 전체 프로세스
    
    Args:
        url: PDF 파일 URL
        output_dir: PNG 파일을 저장할 디렉토리
        dpi: 이미지 해상도
        
    Returns:
        생성된 이미지 파일 경로 리스트
    """
    # 1. PDF 다운로드
    pdf_path = download_file(url)
    
    if not pdf_path:
        print("❌ PDF 다운로드에 실패했습니다.")
        return []
    
    # 2. PNG로 변환
    image_files = pdf_to_png(pdf_path, output_dir=output_dir, dpi=dpi)
    
    # 3. 다운로드한 PDF 파일 삭제 (선택사항)
    os.remove(pdf_path)
    print(f"🗑️  임시 PDF 파일 삭제: {pdf_path}\n")
    
    return image_files

def djvu_to_png(djvu_path, output_dir="output_pages"):
    """DjVu 파일을 PNG 이미지로 변환"""
    # 출력 디렉토리 생성
    Path(output_dir).mkdir(exist_ok=True)

    subprocess.run(['djvm', '-c', 'downloaded.djvu', 'downloaded.djvu'], check=True)

    # 총 페이지 수 확인
    result = subprocess.run(
        ['djvused', djvu_path, '-e', 'n'],
        capture_output=True,
        text=True
    )
    total_pages = int(result.stdout.strip())
    print(f"   - 총 페이지 수: {total_pages}")
    
    output_files = []

    # 각 페이지를 PNG로 변환
    for page_num in range(1, total_pages + 1):
        output_file = os.path.join(output_dir, f"page_{page_num:04d}.tiff")

        subprocess.run([
            'ddjvu',
            '-format=tiff',
            '-page={}'.format(page_num),
            djvu_path,
            output_file
        ], check=True)
        
        print(f"✅ 변환 완료 : {page_num}/{total_pages} -> {output_file}")

        output_files.append(output_file)
    
    print(f"\nAll pages saved to {output_dir}/")

    return output_files

def converted_image_detect(image_files):
        
    file_type = type(image_files)

    if file_type == list:

        page_num = len(image_files)
        print(f"페이지 수:{page_num}\n")

        print("분석할 페이지의 범위를 입력하십시오\n")
        first_page = int(input("첫 번째 페이지: "))
        print("\n")
        last_page = int(input("마지막 페이지: "))

        for file in image_files[(first_page-1):(last_page)]:
            print(f"   - {file}")
            print(f'이미지 파일 분석 중: {file}\n')
            text = decect_file_from_cloava(file)

            try:
                print(text)
            except:
                print("텍스트 감지에 실패했습니다.\n")  
    
    else:
        
        text = decect_file_from_cloava(image_files)
        
        print(text)

        try:
            print(text)
        except:
            print("텍스트 감지에 실패했습니다.\n")


def main():
    """
    메인 함수
    """
    file_name = input("공용 파일 이름을 입력하세요\n(예시: image.jpg): ").strip()
    file_name = 'File:' + file_name
    temp_dir = 'output_from_url'

    if file_name.endswith(('.pdf', '.djvu')):
        if file_name.endswith(('.pdf')):
            print("PDF 파일입니다.\n")
            pdf_url = get_file_url(file_name)

            image_files = pdf_download_and_convert(
                url=pdf_url,
                output_dir=temp_dir,
                dpi=200  # 해상도 (150~300 권장)
            )
    
            if image_files:
                
                while True:

                    converted_image_detect(image_files) 

                    exit_yes_no = input("이미지 파일 분석을 계속하겠습니까?(y/n)\nenter=y: ").strip()
                    
                    if exit_yes_no == 'n':
                        break
                
                for file in image_files:
                    os.remove(file)
                
                print(f"\n🗑️ 임시 PNG 파일 삭제\n")

        else:
            print("DJVU 파일입니다.\n")

            djvu_url = get_file_url(file_name)
            file_path = download_file(djvu_url, 'downloaded.djvu')
            image_files = djvu_to_png(file_path, temp_dir)

            os.remove(file_path)
            print(f"🗑️  임시 Djuv 파일 삭제: {file_path}\n")

            if image_files:

                while True:
                
                    converted_image_detect(image_files)

                    exit_yes_no = input("이미지 파일 분석을 계속하겠습니까?(y/n)\nenter=y: ").strip()
                    
                    if exit_yes_no == 'n':
                        break

            
                for file in image_files:
                    os.remove(file)
                
                print(f"\n🗑️  임시 TIFF 파일 삭제\n")

        os.rmdir(temp_dir)
        print(f"\n🗑️ 임시 {temp_dir} 폴더 삭제\n")
                

    else:
        image_url = get_file_url(file_name)
        file_extention = image_url.rsplit('.', 1)[-1]
        file_name = 'downloaded.' + file_extention 
        file_path = download_file(image_url, file_name)

        print(f'url: {image_url}\n')
        print(f'이미지 파일 분석 중: {image_url}\n')

        converted_image_detect(file_path)
        
        os.remove(file_path)
        print(f"\n🗑️ 임시 {file_extention} 파일 삭제\n")


if __name__ == '__main__':
    main()