Name | sub2nan JSON |
Version |
0.1.0
JSON |
| download |
home_page | None |
Summary | A lightweight Python library for handling subtitle files |
upload_time | 2025-08-30 07:07:55 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8 |
license | MIT |
keywords |
subtitle
srt
captions
video
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Python Package 개발 가이드: subtitle-utils
## 1. 프로젝트 초기화
### 1.1 디렉토리 구조 설계
현대적인 Python 패키지는 다음과 같은 구조를 권장합니다:
```
subtitle_lib_v0.1/
├── src/ # 소스 코드 분리 (src layout)
│ └── subtitle_utils/ # 실제 패키지
│ ├── __init__.py # 패키지 초기화
│ ├── srt_handler.py # 핵심 기능 모듈
│ └── exceptions.py # 커스텀 예외
├── tests/ # 테스트 코드
│ ├── test_srt_handler.py # 단위 테스트
│ └── test_data/ # 테스트 데이터
├── pyproject.toml # 현대적 패키징 설정
├── requirements.txt # 의존성 관리
├── README.md # 프로젝트 문서
└── LICENSE # 라이선스
```
**설계 원칙:**
- **src layout**: 패키지와 테스트 코드 분리로 import 오류 방지
- **단일 책임**: 각 모듈은 하나의 명확한 역할
- **확장성**: 향후 webvtt, smi 지원을 위한 구조
### 1.2 pyproject.toml 설정
```toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
```
**핵심 포인트:**
- PEP 518 표준 준수
- setuptools 기반 빌드 시스템
- wheel 형식 지원
## 2. 의존성 관리
### 2.1 런타임 의존성
```toml
dependencies = [
"srt>=3.5.0",
]
```
**선택 이유:**
- `srt` vs `pysrt`: 더 현대적이고 가벼운 라이브러리
- 시간 처리에 표준 `datetime.timedelta` 사용
- 파싱/생성 성능 우수
### 2.2 개발 의존성
```toml
[project.optional-dependencies]
dev = [
"pytest>=7.0.0", # 테스트 프레임워크
"pytest-cov>=4.0.0", # 커버리지 측정
"black>=22.0.0", # 코드 포매터
"flake8>=5.0.0", # 린터
"mypy>=1.0.0", # 타입 체커
]
```
## 3. 핵심 모듈 설계
### 3.1 예외 처리 계층구조
```python
SubtitleError (기본)
├── SubtitleParseError (파싱 오류)
└── SubtitleFileError (파일 I/O 오류)
```
**설계 원칙:**
- 명확한 오류 분류
- 계층적 예외 처리
- 사용자 친화적 메시지
### 3.2 SRTHandler 클래스 설계
**주요 기능:**
- 파일 로드/저장
- 자막 항목 조작
- 시간 형식 변환
- Python 매직 메서드 지원
**API 설계 원칙:**
- 직관적인 메서드명
- 일관된 에러 처리
- 타입 힌트 완전 지원
## 4. 테스트 전략
### 4.1 테스트 구조
```python
class TestSRTHandler:
@pytest.fixture
def sample_srt_content(self): ...
def test_init_empty(self): ...
def test_load_valid_file(self): ...
def test_error_handling(self): ...
```
**테스트 원칙:**
- AAA 패턴 (Arrange, Act, Assert)
- 각 기능별 독립적 테스트
- Edge case 포함
### 4.2 픽스처 활용
```python
@pytest.fixture
def temp_srt_file(self, sample_srt_content):
with tempfile.NamedTemporaryFile(...) as f:
yield temp_path
```
**장점:**
- 테스트 데이터 격리
- 자동 정리
- 재사용 가능
## 5. 빌드 및 배포
### 5.1 빌드 프로세스
```bash
python -m build
```
**생성 파일:**
- `dist/subtitle-utils-0.1.0.tar.gz` (소스 배포)
- `dist/subtitle_utils-0.1.0-py3-none-any.whl` (휠 배포)
### 5.2 품질 보증
```bash
# 코드 포매팅
black src/ tests/
# 린팅
flake8 src/ tests/
# 타입 체킹
mypy src/
# 테스트 + 커버리지
pytest --cov=subtitle_utils --cov-report=html
```
## 6. 개발 워크플로우
### 6.1 일반적인 개발 사이클
1. **기능 개발**: 새 기능 구현
2. **테스트 작성**: 단위 테스트 추가
3. **품질 검사**: 린팅, 포매팅, 타입 체킹
4. **통합 테스트**: 전체 테스트 스위트 실행
5. **빌드**: 배포 가능한 패키지 생성
### 6.2 버전 관리
```toml
version = "0.1.0" # MAJOR.MINOR.PATCH
```
**Semantic Versioning:**
- MAJOR: 호환성을 깨는 변경
- MINOR: 하위 호환 기능 추가
- PATCH: 하위 호환 버그 수정
## 7. 성능 고려사항
### 7.1 메모리 효율성
- 대용량 파일 처리시 스트리밍 고려
- 불필요한 객체 생성 최소화
### 7.2 처리 속도
- `srt` 라이브러리의 C 확장 활용
- 적절한 데이터 구조 선택
## 8. 확장성 설계
### 8.1 미래 기능 지원
현재 구조로 쉽게 추가 가능:
- `WebVTTHandler` 클래스
- `SMIHandler` 클래스
- 형식 간 변환 유틸리티
### 8.2 플러그인 아키텍처
```python
from abc import ABC, abstractmethod
class SubtitleHandler(ABC):
@abstractmethod
def load(self, file_path): ...
@abstractmethod
def save(self, file_path): ...
```
## 9. 배포 전략
### 9.1 TestPyPI 활용
```bash
twine upload --repository testpypi dist/*
```
**장점:**
- 실제 환경 테스트
- 배포 프로세스 검증
- 위험 최소화
### 9.2 CI/CD 파이프라인
GitHub Actions 예시:
- PR시 자동 테스트
- 태그시 자동 배포
- 다중 Python 버전 테스트
## 10. 문서화 전략
### 10.1 README.md 구성
- 간단한 설치/사용법
- API 레퍼런스
- 예제 코드
- 기여 가이드
### 10.2 코드 문서화
- docstring 완전 작성
- 타입 힌트 활용
- 예제 포함
Raw data
{
"_id": null,
"home_page": null,
"name": "sub2nan",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "subtitle, srt, captions, video",
"author": null,
"author_email": "2nan <dame2623@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/c1/32/b39162fcbff9e7779aea6e2cf216bce38ddf66bf75e1b2eb8860c5940f74/sub2nan-0.1.0.tar.gz",
"platform": null,
"description": "# Python Package \uac1c\ubc1c \uac00\uc774\ub4dc: subtitle-utils\n\n## 1. \ud504\ub85c\uc81d\ud2b8 \ucd08\uae30\ud654\n\n### 1.1 \ub514\ub809\ud1a0\ub9ac \uad6c\uc870 \uc124\uacc4\n\n\ud604\ub300\uc801\uc778 Python \ud328\ud0a4\uc9c0\ub294 \ub2e4\uc74c\uacfc \uac19\uc740 \uad6c\uc870\ub97c \uad8c\uc7a5\ud569\ub2c8\ub2e4:\n\n```\nsubtitle_lib_v0.1/\n\u251c\u2500\u2500 src/ # \uc18c\uc2a4 \ucf54\ub4dc \ubd84\ub9ac (src layout)\n\u2502 \u2514\u2500\u2500 subtitle_utils/ # \uc2e4\uc81c \ud328\ud0a4\uc9c0\n\u2502 \u251c\u2500\u2500 __init__.py # \ud328\ud0a4\uc9c0 \ucd08\uae30\ud654\n\u2502 \u251c\u2500\u2500 srt_handler.py # \ud575\uc2ec \uae30\ub2a5 \ubaa8\ub4c8\n\u2502 \u2514\u2500\u2500 exceptions.py # \ucee4\uc2a4\ud140 \uc608\uc678\n\u251c\u2500\u2500 tests/ # \ud14c\uc2a4\ud2b8 \ucf54\ub4dc\n\u2502 \u251c\u2500\u2500 test_srt_handler.py # \ub2e8\uc704 \ud14c\uc2a4\ud2b8\n\u2502 \u2514\u2500\u2500 test_data/ # \ud14c\uc2a4\ud2b8 \ub370\uc774\ud130\n\u251c\u2500\u2500 pyproject.toml # \ud604\ub300\uc801 \ud328\ud0a4\uc9d5 \uc124\uc815\n\u251c\u2500\u2500 requirements.txt # \uc758\uc874\uc131 \uad00\ub9ac\n\u251c\u2500\u2500 README.md # \ud504\ub85c\uc81d\ud2b8 \ubb38\uc11c\n\u2514\u2500\u2500 LICENSE # \ub77c\uc774\uc120\uc2a4\n```\n\n**\uc124\uacc4 \uc6d0\uce59:**\n- **src layout**: \ud328\ud0a4\uc9c0\uc640 \ud14c\uc2a4\ud2b8 \ucf54\ub4dc \ubd84\ub9ac\ub85c import \uc624\ub958 \ubc29\uc9c0\n- **\ub2e8\uc77c \ucc45\uc784**: \uac01 \ubaa8\ub4c8\uc740 \ud558\ub098\uc758 \uba85\ud655\ud55c \uc5ed\ud560\n- **\ud655\uc7a5\uc131**: \ud5a5\ud6c4 webvtt, smi \uc9c0\uc6d0\uc744 \uc704\ud55c \uad6c\uc870\n\n### 1.2 pyproject.toml \uc124\uc815\n\n```toml\n[build-system]\nrequires = [\"setuptools>=61.0\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n```\n\n**\ud575\uc2ec \ud3ec\uc778\ud2b8:**\n- PEP 518 \ud45c\uc900 \uc900\uc218\n- setuptools \uae30\ubc18 \ube4c\ub4dc \uc2dc\uc2a4\ud15c\n- wheel \ud615\uc2dd \uc9c0\uc6d0\n\n## 2. \uc758\uc874\uc131 \uad00\ub9ac\n\n### 2.1 \ub7f0\ud0c0\uc784 \uc758\uc874\uc131\n\n```toml\ndependencies = [\n \"srt>=3.5.0\",\n]\n```\n\n**\uc120\ud0dd \uc774\uc720:**\n- `srt` vs `pysrt`: \ub354 \ud604\ub300\uc801\uc774\uace0 \uac00\ubcbc\uc6b4 \ub77c\uc774\ube0c\ub7ec\ub9ac\n- \uc2dc\uac04 \ucc98\ub9ac\uc5d0 \ud45c\uc900 `datetime.timedelta` \uc0ac\uc6a9\n- \ud30c\uc2f1/\uc0dd\uc131 \uc131\ub2a5 \uc6b0\uc218\n\n### 2.2 \uac1c\ubc1c \uc758\uc874\uc131\n\n```toml\n[project.optional-dependencies]\ndev = [\n \"pytest>=7.0.0\", # \ud14c\uc2a4\ud2b8 \ud504\ub808\uc784\uc6cc\ud06c\n \"pytest-cov>=4.0.0\", # \ucee4\ubc84\ub9ac\uc9c0 \uce21\uc815\n \"black>=22.0.0\", # \ucf54\ub4dc \ud3ec\ub9e4\ud130\n \"flake8>=5.0.0\", # \ub9b0\ud130\n \"mypy>=1.0.0\", # \ud0c0\uc785 \uccb4\ucee4\n]\n```\n\n## 3. \ud575\uc2ec \ubaa8\ub4c8 \uc124\uacc4\n\n### 3.1 \uc608\uc678 \ucc98\ub9ac \uacc4\uce35\uad6c\uc870\n\n```python\nSubtitleError (\uae30\ubcf8)\n\u251c\u2500\u2500 SubtitleParseError (\ud30c\uc2f1 \uc624\ub958)\n\u2514\u2500\u2500 SubtitleFileError (\ud30c\uc77c I/O \uc624\ub958)\n```\n\n**\uc124\uacc4 \uc6d0\uce59:**\n- \uba85\ud655\ud55c \uc624\ub958 \ubd84\ub958\n- \uacc4\uce35\uc801 \uc608\uc678 \ucc98\ub9ac\n- \uc0ac\uc6a9\uc790 \uce5c\ud654\uc801 \uba54\uc2dc\uc9c0\n\n### 3.2 SRTHandler \ud074\ub798\uc2a4 \uc124\uacc4\n\n**\uc8fc\uc694 \uae30\ub2a5:**\n- \ud30c\uc77c \ub85c\ub4dc/\uc800\uc7a5\n- \uc790\ub9c9 \ud56d\ubaa9 \uc870\uc791\n- \uc2dc\uac04 \ud615\uc2dd \ubcc0\ud658\n- Python \ub9e4\uc9c1 \uba54\uc11c\ub4dc \uc9c0\uc6d0\n\n**API \uc124\uacc4 \uc6d0\uce59:**\n- \uc9c1\uad00\uc801\uc778 \uba54\uc11c\ub4dc\uba85\n- \uc77c\uad00\ub41c \uc5d0\ub7ec \ucc98\ub9ac\n- \ud0c0\uc785 \ud78c\ud2b8 \uc644\uc804 \uc9c0\uc6d0\n\n## 4. \ud14c\uc2a4\ud2b8 \uc804\ub7b5\n\n### 4.1 \ud14c\uc2a4\ud2b8 \uad6c\uc870\n\n```python\nclass TestSRTHandler:\n @pytest.fixture\n def sample_srt_content(self): ...\n \n def test_init_empty(self): ...\n def test_load_valid_file(self): ...\n def test_error_handling(self): ...\n```\n\n**\ud14c\uc2a4\ud2b8 \uc6d0\uce59:**\n- AAA \ud328\ud134 (Arrange, Act, Assert)\n- \uac01 \uae30\ub2a5\ubcc4 \ub3c5\ub9bd\uc801 \ud14c\uc2a4\ud2b8\n- Edge case \ud3ec\ud568\n\n### 4.2 \ud53d\uc2a4\ucc98 \ud65c\uc6a9\n\n```python\n@pytest.fixture\ndef temp_srt_file(self, sample_srt_content):\n with tempfile.NamedTemporaryFile(...) as f:\n yield temp_path\n```\n\n**\uc7a5\uc810:**\n- \ud14c\uc2a4\ud2b8 \ub370\uc774\ud130 \uaca9\ub9ac\n- \uc790\ub3d9 \uc815\ub9ac\n- \uc7ac\uc0ac\uc6a9 \uac00\ub2a5\n\n## 5. \ube4c\ub4dc \ubc0f \ubc30\ud3ec\n\n### 5.1 \ube4c\ub4dc \ud504\ub85c\uc138\uc2a4\n\n```bash\npython -m build\n```\n\n**\uc0dd\uc131 \ud30c\uc77c:**\n- `dist/subtitle-utils-0.1.0.tar.gz` (\uc18c\uc2a4 \ubc30\ud3ec)\n- `dist/subtitle_utils-0.1.0-py3-none-any.whl` (\ud720 \ubc30\ud3ec)\n\n### 5.2 \ud488\uc9c8 \ubcf4\uc99d\n\n```bash\n# \ucf54\ub4dc \ud3ec\ub9e4\ud305\nblack src/ tests/\n\n# \ub9b0\ud305\nflake8 src/ tests/\n\n# \ud0c0\uc785 \uccb4\ud0b9\nmypy src/\n\n# \ud14c\uc2a4\ud2b8 + \ucee4\ubc84\ub9ac\uc9c0\npytest --cov=subtitle_utils --cov-report=html\n```\n\n## 6. \uac1c\ubc1c \uc6cc\ud06c\ud50c\ub85c\uc6b0\n\n### 6.1 \uc77c\ubc18\uc801\uc778 \uac1c\ubc1c \uc0ac\uc774\ud074\n\n1. **\uae30\ub2a5 \uac1c\ubc1c**: \uc0c8 \uae30\ub2a5 \uad6c\ud604\n2. **\ud14c\uc2a4\ud2b8 \uc791\uc131**: \ub2e8\uc704 \ud14c\uc2a4\ud2b8 \ucd94\uac00\n3. **\ud488\uc9c8 \uac80\uc0ac**: \ub9b0\ud305, \ud3ec\ub9e4\ud305, \ud0c0\uc785 \uccb4\ud0b9\n4. **\ud1b5\ud569 \ud14c\uc2a4\ud2b8**: \uc804\uccb4 \ud14c\uc2a4\ud2b8 \uc2a4\uc704\ud2b8 \uc2e4\ud589\n5. **\ube4c\ub4dc**: \ubc30\ud3ec \uac00\ub2a5\ud55c \ud328\ud0a4\uc9c0 \uc0dd\uc131\n\n### 6.2 \ubc84\uc804 \uad00\ub9ac\n\n```toml\nversion = \"0.1.0\" # MAJOR.MINOR.PATCH\n```\n\n**Semantic Versioning:**\n- MAJOR: \ud638\ud658\uc131\uc744 \uae68\ub294 \ubcc0\uacbd\n- MINOR: \ud558\uc704 \ud638\ud658 \uae30\ub2a5 \ucd94\uac00\n- PATCH: \ud558\uc704 \ud638\ud658 \ubc84\uadf8 \uc218\uc815\n\n## 7. \uc131\ub2a5 \uace0\ub824\uc0ac\ud56d\n\n### 7.1 \uba54\ubaa8\ub9ac \ud6a8\uc728\uc131\n\n- \ub300\uc6a9\ub7c9 \ud30c\uc77c \ucc98\ub9ac\uc2dc \uc2a4\ud2b8\ub9ac\ubc0d \uace0\ub824\n- \ubd88\ud544\uc694\ud55c \uac1d\uccb4 \uc0dd\uc131 \ucd5c\uc18c\ud654\n\n### 7.2 \ucc98\ub9ac \uc18d\ub3c4\n\n- `srt` \ub77c\uc774\ube0c\ub7ec\ub9ac\uc758 C \ud655\uc7a5 \ud65c\uc6a9\n- \uc801\uc808\ud55c \ub370\uc774\ud130 \uad6c\uc870 \uc120\ud0dd\n\n## 8. \ud655\uc7a5\uc131 \uc124\uacc4\n\n### 8.1 \ubbf8\ub798 \uae30\ub2a5 \uc9c0\uc6d0\n\n\ud604\uc7ac \uad6c\uc870\ub85c \uc27d\uac8c \ucd94\uac00 \uac00\ub2a5:\n- `WebVTTHandler` \ud074\ub798\uc2a4\n- `SMIHandler` \ud074\ub798\uc2a4\n- \ud615\uc2dd \uac04 \ubcc0\ud658 \uc720\ud2f8\ub9ac\ud2f0\n\n### 8.2 \ud50c\ub7ec\uadf8\uc778 \uc544\ud0a4\ud14d\ucc98\n\n```python\nfrom abc import ABC, abstractmethod\n\nclass SubtitleHandler(ABC):\n @abstractmethod\n def load(self, file_path): ...\n @abstractmethod\n def save(self, file_path): ...\n```\n\n## 9. \ubc30\ud3ec \uc804\ub7b5\n\n### 9.1 TestPyPI \ud65c\uc6a9\n\n```bash\ntwine upload --repository testpypi dist/*\n```\n\n**\uc7a5\uc810:**\n- \uc2e4\uc81c \ud658\uacbd \ud14c\uc2a4\ud2b8\n- \ubc30\ud3ec \ud504\ub85c\uc138\uc2a4 \uac80\uc99d\n- \uc704\ud5d8 \ucd5c\uc18c\ud654\n\n### 9.2 CI/CD \ud30c\uc774\ud504\ub77c\uc778\n\nGitHub Actions \uc608\uc2dc:\n- PR\uc2dc \uc790\ub3d9 \ud14c\uc2a4\ud2b8\n- \ud0dc\uadf8\uc2dc \uc790\ub3d9 \ubc30\ud3ec\n- \ub2e4\uc911 Python \ubc84\uc804 \ud14c\uc2a4\ud2b8\n\n## 10. \ubb38\uc11c\ud654 \uc804\ub7b5\n\n### 10.1 README.md \uad6c\uc131\n\n- \uac04\ub2e8\ud55c \uc124\uce58/\uc0ac\uc6a9\ubc95\n- API \ub808\ud37c\ub7f0\uc2a4\n- \uc608\uc81c \ucf54\ub4dc\n- \uae30\uc5ec \uac00\uc774\ub4dc\n\n### 10.2 \ucf54\ub4dc \ubb38\uc11c\ud654\n\n- docstring \uc644\uc804 \uc791\uc131\n- \ud0c0\uc785 \ud78c\ud2b8 \ud65c\uc6a9\n- \uc608\uc81c \ud3ec\ud568\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A lightweight Python library for handling subtitle files",
"version": "0.1.0",
"project_urls": {
"Homepage": "https://github.com/2nan/sub2nan",
"Issues": "https://github.com/2nan/sub2nan/issues",
"Repository": "https://github.com/2nan/sub2nan.git"
},
"split_keywords": [
"subtitle",
" srt",
" captions",
" video"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "eed9f321ab9b60c54405ae92f28bc515d85b83fb3032499178440490ca587c77",
"md5": "ce8c155f2364a56287934ce9733a3514",
"sha256": "de067f4be558a051e901ca7985aa49c34ec6eb73fd8d4427fff0a8ba1b602e21"
},
"downloads": -1,
"filename": "sub2nan-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ce8c155f2364a56287934ce9733a3514",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 8210,
"upload_time": "2025-08-30T07:07:54",
"upload_time_iso_8601": "2025-08-30T07:07:54.291582Z",
"url": "https://files.pythonhosted.org/packages/ee/d9/f321ab9b60c54405ae92f28bc515d85b83fb3032499178440490ca587c77/sub2nan-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c132b39162fcbff9e7779aea6e2cf216bce38ddf66bf75e1b2eb8860c5940f74",
"md5": "aae290cf92735cebc478a34bb71428ed",
"sha256": "1bdc4721ed17de93b1b47dbc9e0e349b0631c426f37048019188a963d341e44a"
},
"downloads": -1,
"filename": "sub2nan-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "aae290cf92735cebc478a34bb71428ed",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 8759,
"upload_time": "2025-08-30T07:07:55",
"upload_time_iso_8601": "2025-08-30T07:07:55.597856Z",
"url": "https://files.pythonhosted.org/packages/c1/32/b39162fcbff9e7779aea6e2cf216bce38ddf66bf75e1b2eb8860c5940f74/sub2nan-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-30 07:07:55",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "2nan",
"github_project": "sub2nan",
"github_not_found": true,
"lcname": "sub2nan"
}