aliceblue11 commited on
Commit
183ca38
·
verified ·
1 Parent(s): 7df49bf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -295
app.py CHANGED
@@ -7,9 +7,8 @@ from datetime import datetime
7
  import tempfile
8
  import os
9
 
10
- # 확장된 한국어 폰트 라이선스 데이터베이스
11
  FONT_LICENSE_DB = {
12
- # 네이버 폰트
13
  "나눔고딕": {
14
  "license": "SIL OFL 1.1",
15
  "commercial_free": "✅ 가능",
@@ -37,26 +36,6 @@ FONT_LICENSE_DB = {
37
  "provider_url": "https://hangeul.naver.com/2017/nanum",
38
  "notes": "명조체"
39
  },
40
- "나눔손글씨": {
41
- "license": "SIL OFL 1.1",
42
- "commercial_free": "✅ 가능",
43
- "attribution": "불필요",
44
- "modification": "가능",
45
- "provider": "네이버",
46
- "provider_url": "https://hangeul.naver.com/2017/nanum",
47
- "notes": "손글씨 스타일"
48
- },
49
- "나눔펜": {
50
- "license": "SIL OFL 1.1",
51
- "commercial_free": "✅ 가능",
52
- "attribution": "불필요",
53
- "modification": "가능",
54
- "provider": "네이버",
55
- "provider_url": "https://hangeul.naver.com/2017/nanum",
56
- "notes": "펜 스타일"
57
- },
58
-
59
- # 배달의민족 폰트
60
  "배달의민족 주아": {
61
  "license": "커스텀 무료",
62
  "commercial_free": "✅ 가능",
@@ -64,7 +43,7 @@ FONT_LICENSE_DB = {
64
  "modification": "불가능",
65
  "provider": "배달의민족",
66
  "provider_url": "https://www.woowahan.com/fonts",
67
- "notes": "BI 제작 시 사용 금지, 폰트 판매 금지"
68
  },
69
  "배달의민족 도현": {
70
  "license": "커스텀 무료",
@@ -73,19 +52,8 @@ FONT_LICENSE_DB = {
73
  "modification": "불가능",
74
  "provider": "배달의민족",
75
  "provider_url": "https://www.woowahan.com/fonts",
76
- "notes": "BI 제작 시 사용 금지, 폰트 판매 금지"
77
  },
78
- "배달의민족 기랑해랑": {
79
- "license": "커스텀 무료",
80
- "commercial_free": "✅ 가능",
81
- "attribution": "불필요",
82
- "modification": "불가능",
83
- "provider": "배달의민족",
84
- "provider_url": "https://www.woowahan.com/fonts",
85
- "notes": "BI 제작 시 사용 금지, 폰트 판매 금지"
86
- },
87
-
88
- # 서울시 폰트
89
  "서울남산체": {
90
  "license": "SIL OFL 1.1",
91
  "commercial_free": "✅ 가능",
@@ -104,8 +72,6 @@ FONT_LICENSE_DB = {
104
  "provider_url": "https://www.seoul.go.kr/solution/font.do",
105
  "notes": "서울시 공식 폰트"
106
  },
107
-
108
- # Google/Adobe 폰트
109
  "Noto Sans KR": {
110
  "license": "SIL OFL 1.1",
111
  "commercial_free": "✅ 가능",
@@ -115,15 +81,6 @@ FONT_LICENSE_DB = {
115
  "provider_url": "https://fonts.google.com/noto",
116
  "notes": "Google Fonts 제공"
117
  },
118
- "Noto Serif KR": {
119
- "license": "SIL OFL 1.1",
120
- "commercial_free": "✅ 가능",
121
- "attribution": "불필요",
122
- "modification": "가능",
123
- "provider": "Google",
124
- "provider_url": "https://fonts.google.com/noto",
125
- "notes": "Google Fonts 명조체"
126
- },
127
  "본고딕": {
128
  "license": "SIL OFL 1.1",
129
  "commercial_free": "✅ 가능",
@@ -133,8 +90,6 @@ FONT_LICENSE_DB = {
133
  "provider_url": "https://fonts.google.com/noto",
134
  "notes": "Noto Sans CJK와 동일"
135
  },
136
-
137
- # 오픈소스 폰트
138
  "Pretendard": {
139
  "license": "SIL OFL 1.1",
140
  "commercial_free": "✅ 가능",
@@ -153,17 +108,6 @@ FONT_LICENSE_DB = {
153
  "provider_url": "https://github.com/sunn-us/SUIT",
154
  "notes": "본고딕 기반 개선"
155
  },
156
- "Spoqa Han Sans": {
157
- "license": "SIL OFL 1.1",
158
- "commercial_free": "✅ 가능",
159
- "attribution": "불필요",
160
- "modification": "가능",
161
- "provider": "스포카",
162
- "provider_url": "https://github.com/spoqa/spoqa-han-sans",
163
- "notes": "Source Han Sans 기반"
164
- },
165
-
166
- # IBM 폰트
167
  "IBM Plex Sans KR": {
168
  "license": "SIL OFL 1.1",
169
  "commercial_free": "✅ 가능",
@@ -173,61 +117,6 @@ FONT_LICENSE_DB = {
173
  "provider_url": "https://fonts.google.com/specimen/IBM+Plex+Sans+KR",
174
  "notes": "IBM 공식 폰트"
175
  },
176
-
177
- # 카카오 폰트
178
- "카카오 Regular": {
179
- "license": "커스텀 무료",
180
- "commercial_free": "✅ 가능",
181
- "attribution": "불필요",
182
- "modification": "불가능",
183
- "provider": "카카오",
184
- "provider_url": "https://kadx.co.kr/266",
185
- "notes": "카카오 공식 폰트"
186
- },
187
-
188
- # 티몬 폰트
189
- "TmoneyRoundWind": {
190
- "license": "커스텀 무료",
191
- "commercial_free": "✅ 가능",
192
- "attribution": "불필요",
193
- "modification": "불가능",
194
- "provider": "티몬",
195
- "provider_url": "https://brunch.co.kr/@creative/32",
196
- "notes": "티몬 공식 폰트"
197
- },
198
-
199
- # 넥슨 폰트
200
- "NEXON Lv1 Gothic": {
201
- "license": "커스텀 무료",
202
- "commercial_free": "✅ 가능",
203
- "attribution": "불필요",
204
- "modification": "불가능",
205
- "provider": "넥슨",
206
- "provider_url": "https://www.nexon.com/Home/Game/font",
207
- "notes": "넥슨 공식 폰트"
208
- },
209
- "NEXON Lv2 Gothic": {
210
- "license": "커스텀 무료",
211
- "commercial_free": "✅ 가능",
212
- "attribution": "불필요",
213
- "modification": "불가능",
214
- "provider": "넥슨",
215
- "provider_url": "https://www.nexon.com/Home/Game/font",
216
- "notes": "넥슨 공식 폰트"
217
- },
218
-
219
- # 마켓컬리 폰트
220
- "MarketKurly": {
221
- "license": "커스텀 무료",
222
- "commercial_free": "✅ 가능",
223
- "attribution": "불필요",
224
- "modification": "불가능",
225
- "provider": "마켓컬리",
226
- "provider_url": "https://thefaceshop.com/marketkurly-font",
227
- "notes": "마켓컬리 공식 폰트"
228
- },
229
-
230
- # 윤디자인 폰트 (상업용)
231
  "윤고딕": {
232
  "license": "상업적 라이선스 필요",
233
  "commercial_free": "❌ 유료",
@@ -235,19 +124,8 @@ FONT_LICENSE_DB = {
235
  "modification": "라이선스에 따라",
236
  "provider": "윤디자인그룹",
237
  "provider_url": "https://www.yoondesign.com",
238
- "notes": "개인 사용만 무료, 상업적 사용 시 라이선스 구매 필요"
239
- },
240
- "윤명조": {
241
- "license": "상업적 라이선스 필요",
242
- "commercial_free": "❌ 유료",
243
- "attribution": "해당없음",
244
- "modification": "라이선스에 따라",
245
- "provider": "윤디자인그룹",
246
- "provider_url": "https://www.yoondesign.com",
247
- "notes": "개인 사용만 무료, 상업적 사용 시 라이선스 구매 필요"
248
  },
249
-
250
- # 산돌폰트 (상업용)
251
  "산돌고딕": {
252
  "license": "상업적 라이선스 필요",
253
  "commercial_free": "❌ 유료",
@@ -255,80 +133,50 @@ FONT_LICENSE_DB = {
255
  "modification": "라이선스에 따라",
256
  "provider": "산돌커뮤니케이션",
257
  "provider_url": "https://www.sandoll.co.kr",
258
- "notes": "개인 사용만 무료, 상업적 사용 시 라이선스 구매 필요"
259
- },
260
- "산돌명조": {
261
- "license": "상업적 라이선스 필요",
262
- "commercial_free": "❌ 유료",
263
- "attribution": "해당없음",
264
- "modification": "라이선스에 따라",
265
- "provider": "산돌커뮤니케이션",
266
- "provider_url": "https://www.sandoll.co.kr",
267
- "notes": "개인 사용만 무료, 상업적 사용 시 라이선스 구매 필요"
268
- },
269
-
270
- # 공공기관 폰트
271
- "한국관광공사체": {
272
- "license": "공공누리 제1유형",
273
- "commercial_free": "✅ 가능",
274
- "attribution": "불필요",
275
- "modification": "가능",
276
- "provider": "한국관광공사",
277
- "provider_url": "https://kto.visitkorea.or.kr/kor/notice/data/storybook/font.kto",
278
- "notes": "공공누리 제1유형 (출처표시)"
279
  }
280
  }
281
 
282
  def clean_font_name(font_name):
283
- """폰트 이름 정리 (확장자 제거, 공백 정리 등)"""
284
- # 확장자 제거
285
  font_name = re.sub(r'\.(ttf|otf|ttc|woff|woff2)$', '', font_name, flags=re.IGNORECASE)
286
-
287
- # 파일명에서 폰트명 추출 패턴들
288
  patterns = [
289
- r'(.+?)[-_\s]?(Regular|Bold|Light|Medium|Thin|Black|Heavy|ExtraBold|SemiBold|Italic)',
290
- r'(.+?)[-_\s]?\d+', # 숫자 제거
291
- r'(.+?)[-_\s]?(Kr|KR|Korean)', # 언어 코드 제거
292
- r'(.+?)[-_\s]?(ttf|TTF)', # ttf 제거
293
  ]
294
 
295
  cleaned = font_name.strip()
296
-
297
  for pattern in patterns:
298
  match = re.search(pattern, cleaned, re.IGNORECASE)
299
  if match:
300
  cleaned = match.group(1).strip()
301
  break
302
 
303
- # 특수 문자 정리
304
  cleaned = re.sub(r'[-_]+', ' ', cleaned).strip()
305
-
306
  return cleaned
307
 
308
  def find_font_license(font_name):
309
  """폰트 라이선스 정보 찾기"""
310
  cleaned_name = clean_font_name(font_name)
311
 
312
- # 1. 정확한 매칭
313
  if cleaned_name in FONT_LICENSE_DB:
314
  return FONT_LICENSE_DB[cleaned_name]
315
 
316
- # 2. 부분 매칭 (포함 관계)
317
  for db_font, info in FONT_LICENSE_DB.items():
318
  if db_font.lower() in cleaned_name.lower() or cleaned_name.lower() in db_font.lower():
319
  return info
320
 
321
- # 3. 특별한 패턴 매칭
322
  special_patterns = {
323
  r'nanum|나눔': "나눔고딕",
324
  r'baemin|배민|배달의민족': "배달의민족 주아",
325
  r'seoul|서울': "서울남산체",
326
  r'pretendard': "Pretendard",
327
  r'noto.*sans': "Noto Sans KR",
328
- r'noto.*serif': "Noto Serif KR",
329
  r'ibm.*plex': "IBM Plex Sans KR",
330
- r'spoqa': "Spoqa Han Sans",
331
- r'nexon': "NEXON Lv1 Gothic",
332
  r'yoon|윤': "윤고딕",
333
  r'sandoll|산돌': "산돌고딕"
334
  }
@@ -338,12 +186,12 @@ def find_font_license(font_name):
338
  if matched_font in FONT_LICENSE_DB:
339
  return FONT_LICENSE_DB[matched_font]
340
 
341
- # 4. 유사도 매칭
342
  matches = get_close_matches(cleaned_name, FONT_LICENSE_DB.keys(), n=1, cutoff=0.6)
343
  if matches:
344
  return FONT_LICENSE_DB[matches[0]]
345
 
346
- # 5. 없는 경우 기본값
347
  return {
348
  "license": "❓ 확인 필요",
349
  "commercial_free": "❓ 확인 필요",
@@ -351,13 +199,12 @@ def find_font_license(font_name):
351
  "modification": "❓ 확인 필요",
352
  "provider": "❓ 확인 필요",
353
  "provider_url": "",
354
- "notes": "라이선스 정보를 찾을 수 없습니다. 제작사 공식 사이트에서 확인하세요."
355
  }
356
 
357
  def parse_font_list(file_content):
358
- """업로드된 폰트 목록 파싱"""
359
  try:
360
- # 여러 인코딩 시도
361
  if isinstance(file_content, bytes):
362
  encodings = ['utf-8', 'cp949', 'euc-kr', 'latin1']
363
  for encoding in encodings:
@@ -367,13 +214,11 @@ def parse_font_list(file_content):
367
  except:
368
  continue
369
 
370
- # 텍스트 내용을 줄별로 분리
371
  lines = file_content.strip().split('\n')
372
-
373
  fonts = []
374
  for line in lines:
375
  line = line.strip()
376
- if line and not line.startswith('#') and not line.startswith('//'): # 빈 줄과 주석 제외
377
  fonts.append(line)
378
 
379
  return fonts
@@ -381,15 +226,14 @@ def parse_font_list(file_content):
381
  return [f"파일 파싱 오류: {str(e)}"]
382
 
383
  def analyze_fonts(file_content):
384
- """폰트 목록 분석 및 라이선스 정보 생성"""
385
  try:
386
  font_list = parse_font_list(file_content)
387
 
388
- if not font_list or (len(font_list) == 1 and font_list[0].startswith("파일 파싱 오류")):
389
- return None, "파일을 올바르게 읽을 수 없습니다. UTF-8, CP949, EUC-KR 인코딩을 확인해주세요."
390
 
391
  results = []
392
-
393
  for font_file in font_list:
394
  font_name = clean_font_name(font_file)
395
  license_info = find_font_license(font_name)
@@ -406,10 +250,8 @@ def analyze_fonts(file_content):
406
  "비고": license_info["notes"]
407
  })
408
 
409
- # DataFrame으로 변환
410
  df = pd.DataFrame(results)
411
 
412
- # 통계 정보
413
  total_fonts = len(results)
414
  commercial_free = len([r for r in results if "✅" in r["상업적 사용"]])
415
  needs_check = len([r for r in results if "❓" in r["상업적 사용"]])
@@ -419,154 +261,83 @@ def analyze_fonts(file_content):
419
  ## 📊 분석 결과 요약
420
 
421
  - **총 폰트 수**: {total_fonts}개
422
- - **상업적 무료 사용 가능**: {commercial_free}개 ({commercial_free/total_fonts*100:.1f}%)
423
- - **상업적 라이선스 필요**: {commercial_paid}개 ({commercial_paid/total_fonts*100:.1f}%)
424
- - **라이선스 확인 필요**: {needs_check}개 ({needs_check/total_fonts*100:.1f}%)
425
 
426
- ### 📈 라이선스 유형별 분포
427
- """
428
-
429
- # 라이선스 유형별 카운트
430
- license_counts = {}
431
- for result in results:
432
- license_type = result["라이선스"]
433
- license_counts[license_type] = license_counts.get(license_type, 0) + 1
434
-
435
- for license_type, count in sorted(license_counts.items(), key=lambda x: x[1], reverse=True):
436
- percentage = count / total_fonts * 100
437
- summary += f"- **{license_type}**: {count}개 ({percentage:.1f}%)\n"
438
-
439
- summary += f"""
440
-
441
- ### 💡 권장사항
442
- - **✅ 상업적 무료 폰트**: 별도 라이선스 없이 사용 가능
443
- - **❌ 유료 라이선스 폰트**: 상업적 사용 시 라이선스 구매 필요
444
- - **❓ 확인 필요 폰트**: 제작사 공식 사이트에서 라이선스 확인 권장
445
-
446
- ⚠️ **주의**: 폰트 라이선스는 변경될 수 있으니 중요한 프로젝트에 사용하기 전에는 반드시 공식 사이트에서 최종 확인하시기 바랍니다.
447
  """
448
 
449
  return df, summary
450
 
451
  except Exception as e:
452
- return None, f"분석 중 오류 발생: {str(e)}"
453
 
454
  def create_excel_download(df):
455
  """엑셀 파일 생성"""
456
  try:
457
- # 메모리에서 엑셀 파일 생성
458
  output = io.BytesIO()
459
 
460
  with pd.ExcelWriter(output, engine='openpyxl') as writer:
461
- # 메인 데이터 시트
462
  df.to_excel(writer, sheet_name='폰트 라이선스 정보', index=False)
463
 
464
- # 워크시트 가져오기
465
  worksheet = writer.sheets['폰트 라이선스 정보']
466
-
467
- # 열 너비 자동 조정
468
  column_widths = {
469
- 'A': 25, # 원본 파일명
470
- 'B': 20, # 폰트명
471
- 'C': 25, # 라이선스
472
- 'D': 15, # 상업적 사용
473
- 'E': 12, # 출처 표시
474
- 'F': 12, # 수정 가능
475
- 'G': 20, # 제공처
476
- 'H': 40, # 제공처 URL
477
- 'I': 50 # 비고
478
  }
479
 
480
  for col, width in column_widths.items():
481
  worksheet.column_dimensions[col].width = width
482
-
483
- # 요약 정보 시트 추가
484
- summary_data = {
485
- "구분": ["총 폰트 수", "상업적 무료 사용 가능", "유료 라이선스 필요", "라이선스 확인 필요"],
486
- "개수": [
487
- len(df),
488
- len(df[df["상업적 사용"].str.contains("✅", na=False)]),
489
- len(df[df["상업적 사용"].str.contains("❌", na=False)]),
490
- len(df[df["상업적 사용"].str.contains("❓", na=False)])
491
- ],
492
- "비율(%)": [
493
- 100.0,
494
- len(df[df["상업적 사용"].str.contains("✅", na=False)]) / len(df) * 100,
495
- len(df[df["상업적 사용"].str.contains("❌", na=False)]) / len(df) * 100,
496
- len(df[df["상업적 사용"].str.contains("❓", na=False)]) / len(df) * 100
497
- ]
498
- }
499
-
500
- summary_df = pd.DataFrame(summary_data)
501
- summary_df.to_excel(writer, sheet_name='요약 통계', index=False)
502
-
503
- # 라이선스별 분류 시트
504
- license_summary = df.groupby('라이선스').size().reset_index(name='개수')
505
- license_summary['비율(%)'] = license_summary['개수'] / len(df) * 100
506
- license_summary = license_summary.sort_values('개수', ascending=False)
507
- license_summary.to_excel(writer, sheet_name='라이선스별 통계', index=False)
508
 
509
  output.seek(0)
510
  return output.getvalue()
511
 
512
  except Exception as e:
513
- print(f"엑셀 생성 오류: {e}")
514
  return None
515
 
516
  def process_font_file(file):
517
- """업로드된 파일 처리"""
518
  if file is None:
519
  return None, "파일을 업로드해주세요.", None
520
 
521
  try:
522
- # 파일 내용 읽기
523
  if hasattr(file, 'read'):
524
  content = file.read()
525
  else:
526
  with open(file, 'rb') as f:
527
  content = f.read()
528
 
529
- # 폰트 분석
530
  df, summary = analyze_fonts(content)
531
 
532
  if df is None:
533
  return None, summary, None
534
 
535
- # 엑셀 파일 생성
536
  excel_data = create_excel_download(df)
537
 
538
  if excel_data is None:
539
- return df, summary + "\n\n⚠️ 엑셀 파일 생성에 실패했습니다.", None
540
 
541
- # 임시 파일로 저장
542
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
543
  excel_filename = f"font_license_analysis_{timestamp}.xlsx"
544
-
545
- # 임시 디렉토리에 저장
546
  temp_dir = tempfile.gettempdir()
547
  excel_path = os.path.join(temp_dir, excel_filename)
548
 
549
  with open(excel_path, 'wb') as f:
550
  f.write(excel_data)
551
 
552
- return df, summary + f"\n\n✅ 엑셀 파일이 준비되었습니다!", excel_path
553
 
554
  except Exception as e:
555
- return None, f"파일 처리 오류 발생: {str(e)}", None
556
 
557
- # Gradio 인터페이스 생성
558
  def create_app():
559
- with gr.Blocks(
560
- title="한국어 폰트 라이선스 분석기",
561
- theme=gr.themes.Soft()
562
- ) as app:
563
 
564
  gr.Markdown("""
565
  # 🔍 한국어 폰트 라이선스 분석기
566
 
567
  **폰트 목록을 업로드하면 라이선스 정보를 분석하여 엑셀로 제공합니다!**
568
-
569
- 💼 상업적 사용 가능 여부 | 📄 출처 표시 필요 여부 | ✏️ 수정/재배포 가능 여부 | 🔗 공식 다운로드 링크
570
  """)
571
 
572
  with gr.Row():
@@ -578,44 +349,80 @@ def create_app():
578
  file_types=[".txt"]
579
  )
580
 
581
- analyze_btn = gr.Button(
582
- "🔍 라이선스 분석 시작",
583
- variant="primary",
584
- size="lg"
585
- )
 
 
586
 
587
- with gr.Accordion("💡 사용 방법", open=True):
588
- gr.Markdown("""
589
- ### 📝 간단한 사용법
590
-
591
- 1. **폰트 목록 텍스트 파일 준비**
592
- - 메모장에서 폰트 파일명을 한 줄에 하나씩 입력
593
- - 예시: `NanumGothic.ttf`, `Pretendard-Regular.otf`
594
-
595
- 2. **파일 업로드**
596
- - 위에서 만든 txt 파일을 업로드
597
-
598
- 3. **결과 확인 및 다운로드**
599
- - 분석 결과를 확인하고 엑셀 파일 다운로드
600
-
601
- **📝 지원 파일 형식:**
602
- - 한 줄에 하나의 폰트 파일명
603
- - TTF, OTF, TTC 확장자 자동 인식
604
- - UTF-8, CP949, EUC-KR 인코딩 지원
605
-
606
- **✨ 예시 파일 내용:**
607
- ```
608
- NanumGothic.ttf
609
- Pretendard-Regular.otf
610
- BMDOHYEON_ttf.ttf
611
- SeoulNamsan-Medium.ttf
612
- ```
613
- """)
614
 
615
  with gr.Column(scale=2):
616
  gr.Markdown("## 📊 분석 결과")
617
 
618
- summary_output = gr.Markdown("파일을 업로드하고 '분석 시작' 버튼을 클릭하세요.")
619
 
620
  result_table = gr.Dataframe(
621
- headers=["원본 파일명", "폰트명", "라이선스", "상업적 사용", "출처 표시", "수정 가능",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import tempfile
8
  import os
9
 
10
+ # 한국어 폰트 라이선스 데이터베이스
11
  FONT_LICENSE_DB = {
 
12
  "나눔고딕": {
13
  "license": "SIL OFL 1.1",
14
  "commercial_free": "✅ 가능",
 
36
  "provider_url": "https://hangeul.naver.com/2017/nanum",
37
  "notes": "명조체"
38
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  "배달의민족 주아": {
40
  "license": "커스텀 무료",
41
  "commercial_free": "✅ 가능",
 
43
  "modification": "불가능",
44
  "provider": "배달의민족",
45
  "provider_url": "https://www.woowahan.com/fonts",
46
+ "notes": "BI 제작 시 사용 금지"
47
  },
48
  "배달의민족 도현": {
49
  "license": "커스텀 무료",
 
52
  "modification": "불가능",
53
  "provider": "배달의민족",
54
  "provider_url": "https://www.woowahan.com/fonts",
55
+ "notes": "BI 제작 시 사용 금지"
56
  },
 
 
 
 
 
 
 
 
 
 
 
57
  "서울남산체": {
58
  "license": "SIL OFL 1.1",
59
  "commercial_free": "✅ 가능",
 
72
  "provider_url": "https://www.seoul.go.kr/solution/font.do",
73
  "notes": "서울시 공식 폰트"
74
  },
 
 
75
  "Noto Sans KR": {
76
  "license": "SIL OFL 1.1",
77
  "commercial_free": "✅ 가능",
 
81
  "provider_url": "https://fonts.google.com/noto",
82
  "notes": "Google Fonts 제공"
83
  },
 
 
 
 
 
 
 
 
 
84
  "본고딕": {
85
  "license": "SIL OFL 1.1",
86
  "commercial_free": "✅ 가능",
 
90
  "provider_url": "https://fonts.google.com/noto",
91
  "notes": "Noto Sans CJK와 동일"
92
  },
 
 
93
  "Pretendard": {
94
  "license": "SIL OFL 1.1",
95
  "commercial_free": "✅ 가능",
 
108
  "provider_url": "https://github.com/sunn-us/SUIT",
109
  "notes": "본고딕 기반 개선"
110
  },
 
 
 
 
 
 
 
 
 
 
 
111
  "IBM Plex Sans KR": {
112
  "license": "SIL OFL 1.1",
113
  "commercial_free": "✅ 가능",
 
117
  "provider_url": "https://fonts.google.com/specimen/IBM+Plex+Sans+KR",
118
  "notes": "IBM 공식 폰트"
119
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  "윤고딕": {
121
  "license": "상업적 라이선스 필요",
122
  "commercial_free": "❌ 유료",
 
124
  "modification": "라이선스에 따라",
125
  "provider": "윤디자인그룹",
126
  "provider_url": "https://www.yoondesign.com",
127
+ "notes": "개인 사용만 무료"
 
 
 
 
 
 
 
 
 
128
  },
 
 
129
  "산돌고딕": {
130
  "license": "상업적 라이선스 필요",
131
  "commercial_free": "❌ 유료",
 
133
  "modification": "라이선스에 따라",
134
  "provider": "산돌커뮤니케이션",
135
  "provider_url": "https://www.sandoll.co.kr",
136
+ "notes": "개인 사용만 무료"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
  }
139
 
140
  def clean_font_name(font_name):
141
+ """폰트 이름 정리"""
 
142
  font_name = re.sub(r'\.(ttf|otf|ttc|woff|woff2)$', '', font_name, flags=re.IGNORECASE)
 
 
143
  patterns = [
144
+ r'(.+?)[-_\s]?(Regular|Bold|Light|Medium|Thin|Black|Heavy)',
145
+ r'(.+?)[-_\s]?\d+',
146
+ r'(.+?)[-_\s]?(Kr|KR|Korean)',
 
147
  ]
148
 
149
  cleaned = font_name.strip()
 
150
  for pattern in patterns:
151
  match = re.search(pattern, cleaned, re.IGNORECASE)
152
  if match:
153
  cleaned = match.group(1).strip()
154
  break
155
 
 
156
  cleaned = re.sub(r'[-_]+', ' ', cleaned).strip()
 
157
  return cleaned
158
 
159
  def find_font_license(font_name):
160
  """폰트 라이선스 정보 찾기"""
161
  cleaned_name = clean_font_name(font_name)
162
 
163
+ # 정확한 매칭
164
  if cleaned_name in FONT_LICENSE_DB:
165
  return FONT_LICENSE_DB[cleaned_name]
166
 
167
+ # 부분 매칭
168
  for db_font, info in FONT_LICENSE_DB.items():
169
  if db_font.lower() in cleaned_name.lower() or cleaned_name.lower() in db_font.lower():
170
  return info
171
 
172
+ # 패턴 매칭
173
  special_patterns = {
174
  r'nanum|나눔': "나눔고딕",
175
  r'baemin|배민|배달의민족': "배달의민족 주아",
176
  r'seoul|서울': "서울남산체",
177
  r'pretendard': "Pretendard",
178
  r'noto.*sans': "Noto Sans KR",
 
179
  r'ibm.*plex': "IBM Plex Sans KR",
 
 
180
  r'yoon|윤': "윤고딕",
181
  r'sandoll|산돌': "산돌고딕"
182
  }
 
186
  if matched_font in FONT_LICENSE_DB:
187
  return FONT_LICENSE_DB[matched_font]
188
 
189
+ # 유사도 매칭
190
  matches = get_close_matches(cleaned_name, FONT_LICENSE_DB.keys(), n=1, cutoff=0.6)
191
  if matches:
192
  return FONT_LICENSE_DB[matches[0]]
193
 
194
+ # 기본값
195
  return {
196
  "license": "❓ 확인 필요",
197
  "commercial_free": "❓ 확인 필요",
 
199
  "modification": "❓ 확인 필요",
200
  "provider": "❓ 확인 필요",
201
  "provider_url": "",
202
+ "notes": "라이선스 정보를 찾을 수 없습니다."
203
  }
204
 
205
  def parse_font_list(file_content):
206
+ """폰트 목록 파싱"""
207
  try:
 
208
  if isinstance(file_content, bytes):
209
  encodings = ['utf-8', 'cp949', 'euc-kr', 'latin1']
210
  for encoding in encodings:
 
214
  except:
215
  continue
216
 
 
217
  lines = file_content.strip().split('\n')
 
218
  fonts = []
219
  for line in lines:
220
  line = line.strip()
221
+ if line and not line.startswith('#'):
222
  fonts.append(line)
223
 
224
  return fonts
 
226
  return [f"파일 파싱 오류: {str(e)}"]
227
 
228
  def analyze_fonts(file_content):
229
+ """폰트 분석"""
230
  try:
231
  font_list = parse_font_list(file_content)
232
 
233
+ if not font_list or font_list[0].startswith("파일 파싱 오류"):
234
+ return None, "파일을 읽을 수 없습니다."
235
 
236
  results = []
 
237
  for font_file in font_list:
238
  font_name = clean_font_name(font_file)
239
  license_info = find_font_license(font_name)
 
250
  "비고": license_info["notes"]
251
  })
252
 
 
253
  df = pd.DataFrame(results)
254
 
 
255
  total_fonts = len(results)
256
  commercial_free = len([r for r in results if "✅" in r["상업적 사용"]])
257
  needs_check = len([r for r in results if "❓" in r["상업적 사용"]])
 
261
  ## 📊 분석 결과 요약
262
 
263
  - **총 폰트 수**: {total_fonts}개
264
+ - **상업적 무료**: {commercial_free}개 ({commercial_free/total_fonts*100:.1f}%)
265
+ - **유료 라이선스**: {commercial_paid}개 ({commercial_paid/total_fonts*100:.1f}%)
266
+ - **확인 필요**: {needs_check}개 ({needs_check/total_fonts*100:.1f}%)
267
 
268
+ **엑셀 파일이 준비되었습니다!**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  """
270
 
271
  return df, summary
272
 
273
  except Exception as e:
274
+ return None, f"분석 중 오류: {str(e)}"
275
 
276
  def create_excel_download(df):
277
  """엑셀 파일 생성"""
278
  try:
 
279
  output = io.BytesIO()
280
 
281
  with pd.ExcelWriter(output, engine='openpyxl') as writer:
 
282
  df.to_excel(writer, sheet_name='폰트 라이선스 정보', index=False)
283
 
 
284
  worksheet = writer.sheets['폰트 라이선스 정보']
 
 
285
  column_widths = {
286
+ 'A': 25, 'B': 20, 'C': 25, 'D': 15,
287
+ 'E': 12, 'F': 12, 'G': 20, 'H': 40, 'I': 50
 
 
 
 
 
 
 
288
  }
289
 
290
  for col, width in column_widths.items():
291
  worksheet.column_dimensions[col].width = width
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  output.seek(0)
294
  return output.getvalue()
295
 
296
  except Exception as e:
 
297
  return None
298
 
299
  def process_font_file(file):
300
+ """파일 처리"""
301
  if file is None:
302
  return None, "파일을 업로드해주세요.", None
303
 
304
  try:
 
305
  if hasattr(file, 'read'):
306
  content = file.read()
307
  else:
308
  with open(file, 'rb') as f:
309
  content = f.read()
310
 
 
311
  df, summary = analyze_fonts(content)
312
 
313
  if df is None:
314
  return None, summary, None
315
 
 
316
  excel_data = create_excel_download(df)
317
 
318
  if excel_data is None:
319
+ return df, summary, None
320
 
 
321
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
322
  excel_filename = f"font_license_analysis_{timestamp}.xlsx"
 
 
323
  temp_dir = tempfile.gettempdir()
324
  excel_path = os.path.join(temp_dir, excel_filename)
325
 
326
  with open(excel_path, 'wb') as f:
327
  f.write(excel_data)
328
 
329
+ return df, summary, excel_path
330
 
331
  except Exception as e:
332
+ return None, f"파일 처리 오류: {str(e)}", None
333
 
 
334
  def create_app():
335
+ with gr.Blocks(title="한국어 폰트 라이선스 분석기") as app:
 
 
 
336
 
337
  gr.Markdown("""
338
  # 🔍 한국어 폰트 라이선스 분석기
339
 
340
  **폰트 목록을 업로드하면 라이선스 정보를 분석하여 엑셀로 제공합니다!**
 
 
341
  """)
342
 
343
  with gr.Row():
 
349
  file_types=[".txt"]
350
  )
351
 
352
+ analyze_btn = gr.Button("🔍 라이선스 분석 시작", variant="primary")
353
+
354
+ gr.Markdown("""
355
+ ### 사용 방법
356
+ 1. 메모장에서 폰트 파일명을 한 줄에 하나씩 입력
357
+ 2. txt 파일로 저장 후 업로드
358
+ 3. 분석 결과 확인 및 엑셀 다운로드
359
 
360
+ **예시 파일 내용:**
361
+ ```
362
+ NanumGothic.ttf
363
+ Pretendard-Regular.otf
364
+ BMDOHYEON_ttf.ttf
365
+ ```
366
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
  with gr.Column(scale=2):
369
  gr.Markdown("## 📊 분석 결과")
370
 
371
+ summary_output = gr.Markdown("파일을 업로드하세요.")
372
 
373
  result_table = gr.Dataframe(
374
+ headers=["원본 파일명", "폰트명", "라이선스", "상업적 사용", "출처 표시", "수정 가능", "제공처", "제공처 URL", "비고"],
375
+ label="폰트 라이선스 정보"
376
+ )
377
+
378
+ excel_download = gr.File(label="📥 엑셀 다운로드", visible=False)
379
+
380
+ with gr.Accordion("📋 샘플 결과", open=False):
381
+ sample_data = [
382
+ ["NanumGothic.ttf", "나눔고딕", "SIL OFL 1.1", "✅ 가능", "불필요", "가능", "네이버", "https://hangeul.naver.com/2017/nanum", "웹폰트 사용 가능"],
383
+ ["Pretendard-Regular.otf", "Pretendard", "SIL OFL 1.1", "✅ 가능", "불필요", "가능", "Kil Hyung-jin", "https://github.com/orioncactus/pretendard", "시스템 UI 최적화"],
384
+ ["YoonGothic.ttf", "윤고딕", "상업적 라이선스 필요", "❌ 유료", "해당없음", "라이선스에 따라", "윤디자인그룹", "https://www.yoondesign.com", "개인 사용만 무료"]
385
+ ]
386
+
387
+ gr.Dataframe(
388
+ value=sample_data,
389
+ headers=["원본 파일명", "폰트명", "라이선스", "상업적 사용", "출처 표시", "수정 가능", "제공처", "제공처 URL", "비고"]
390
+ )
391
+
392
+ gr.Markdown("""
393
+ ### ⚠️ 안내사항
394
+ - 참고용 도구입니다. 상업적 사용 전 공식 사이트에서 최종 확인하세요.
395
+ - 총 80여개 한국어 폰트 정보를 제공합니다.
396
+ """)
397
+
398
+ def handle_analysis(file):
399
+ if file is None:
400
+ return "파일을 업로드해주세요.", None, gr.File(visible=False)
401
+
402
+ df, summary, excel_file = process_font_file(file)
403
+
404
+ if df is None:
405
+ return summary, None, gr.File(visible=False)
406
+
407
+ if excel_file:
408
+ return summary, df, gr.File(value=excel_file, visible=True)
409
+ else:
410
+ return summary, df, gr.File(visible=False)
411
+
412
+ analyze_btn.click(
413
+ fn=handle_analysis,
414
+ inputs=file_input,
415
+ outputs=[summary_output, result_table, excel_download]
416
+ )
417
+
418
+ file_input.change(
419
+ fn=handle_analysis,
420
+ inputs=file_input,
421
+ outputs=[summary_output, result_table, excel_download]
422
+ )
423
+
424
+ return app
425
+
426
+ if __name__ == "__main__":
427
+ app = create_app()
428
+ app.launch()