아후 개 생노가다.
근데, 단어 단위로 인식하기 때문에, 재처리를 위해서는 캐쉬 시스템을 구현해서 처리하는게 좋겠다.
#!/usr/bin/env python3
"""
Faster-Whisper를 사용한 TOEIC 오디오 전사 스크립트
키워드 기반 세그먼테이션을 위한 단어별 타임스탬프 포함
"""
import argparse
import json
import sys
import logging
from pathlib import Path
import re
try:
from faster_whisper import WhisperModel
except ImportError:
print("faster-whisper가 설치되지 않았습니다. 설치해주세요:")
print("pip install faster-whisper")
sys.exit(1)
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='%(levelname)s:%(name)s:%(message)s',
handlers=[
logging.StreamHandler(sys.stdout) # stdout으로 출력
]
)
logger = logging.getLogger(__name__)
# faster-whisper 로그 레벨 조정
logging.getLogger('faster_whisper').setLevel(logging.WARNING)
class ToeicWhisperTranscriber:
def __init__(self, model_size="base", device="cpu"):
"""
TOEIC 전문 Whisper 전사기
Args:
model_size: whisper 모델 크기 (tiny, base, small, medium, large)
device: 실행 디바이스 (cpu, cuda)
"""
self.model_size = model_size
self.device = device
self.model = None
# TOEIC 특화 키워드
self.toeic_keywords = [
"listening", "test", "part", "one", "two", "three", "four",
"directions", "photograph", "picture", "conversation", "talk",
"question", "response", "number", "now", "listen"
]
def load_model(self):
"""Whisper 모델 로드"""
try:
logger.info(f"Whisper 모델 로드 중... (크기: {self.model_size}, 디바이스: {self.device})")
self.model = WhisperModel(
self.model_size,
device=self.device,
compute_type="float16" if self.device == "cuda" else "int8"
)
logger.info("모델 로드 완료")
except Exception as e:
logger.error(f"모델 로드 실패: {e}")
raise
def transcribe_audio(self, audio_path, language="en"):
"""
오디오 파일 전사
Args:
audio_path: 오디오 파일 경로
language: 언어 코드 (기본: en)
Returns:
전사 결과 딕셔너리
"""
if not self.model:
self.load_model()
try:
logger.info(f"오디오 전사 시작: {audio_path}")
# Whisper 전사 실행
logger.info(f"오디오 파일 처리 시작: {audio_path}")
segments, info = self.model.transcribe(
audio_path,
language=language,
word_timestamps=True,
vad_filter=True, # Voice Activity Detection
vad_parameters=dict(
min_silence_duration_ms=800, # 더 긴 무음으로 완전한 문장 단위 분할
max_speech_duration_s=30, # 더 긴 음성 구간으로 완전한 문장 단위 분할
speech_pad_ms=200 # 음성 패딩 추가
),
initial_prompt="This is a TOEIC listening test. Transcribe complete sentences, not fragments. Keep sentences together as complete thoughts. Separate at question numbers like 'Number 7' and answer choices like 'A', 'B', 'C', 'D'.",
without_timestamps=False,
temperature=0.0, # 더 일관된 결과를 위해
beam_size=5, # 더 나은 품질
best_of=5 # 여러 후보 중 최고 선택
)
logger.info(f"전사 완료 - 언어: {info.language}, 확률: {info.language_probability:.2f}")
# 결과 정리
result = {
"language": info.language,
"language_probability": info.language_probability,
"duration": info.duration,
"segments": [],
"word_segments": [],
"text": ""
}
full_text = []
for segment in segments:
# 세그먼트 정보
segment_data = {
"id": segment.id,
"start": segment.start,
"end": segment.end,
"text": segment.text.strip(),
"avg_logprob": segment.avg_logprob,
"no_speech_prob": segment.no_speech_prob,
"words": []
}
# 단어별 타임스탬프
if segment.words:
for word in segment.words:
word_data = {
"word": word.word,
"start": word.start,
"end": word.end,
"probability": word.probability
}
segment_data["words"].append(word_data)
result["word_segments"].append(word_data)
result["segments"].append(segment_data)
full_text.append(segment.text.strip())
result["text"] = " ".join(full_text)
# TOEIC 키워드 매칭 점수 계산
result["toeic_score"] = self.calculate_toeic_score(result["text"])
logger.info(f"세그먼트 수: {len(result['segments'])}, TOEIC 점수: {result['toeic_score']:.2f}")
return result
except Exception as e:
logger.error(f"전사 실패: {e}")
raise
def calculate_toeic_score(self, text):
"""TOEIC 관련 키워드 매칭 점수 계산"""
text_lower = text.lower()
matches = sum(1 for keyword in self.toeic_keywords if keyword in text_lower)
return matches / len(self.toeic_keywords)
def enhance_segments_for_toeic(self, result):
"""TOEIC 특화 세그먼트 후처리"""
enhanced = result.copy()
# 파트별 구분자 감지
part_markers = []
for segment in result["segments"]:
text = segment["text"].lower()
if "part one" in text or "part 1" in text:
part_markers.append({"part": 1, "start": segment["start"], "text": segment["text"]})
elif "part two" in text or "part 2" in text:
part_markers.append({"part": 2, "start": segment["start"], "text": segment["text"]})
elif "part three" in text or "part 3" in text:
part_markers.append({"part": 3, "start": segment["start"], "text": segment["text"]})
elif "part four" in text or "part 4" in text:
part_markers.append({"part": 4, "start": segment["start"], "text": segment["text"]})
enhanced["part_markers"] = part_markers
# 질문 번호 감지
question_markers = []
for segment in result["segments"]:
text = segment["text"].lower()
# "number X" 패턴 찾기
import re
numbers = re.findall(r'number\s+(\d+)', text)
for num in numbers:
question_markers.append({
"question": int(num),
"start": segment["start"],
"text": segment["text"]
})
enhanced["question_markers"] = question_markers
return enhanced
def main():
parser = argparse.ArgumentParser(description="TOEIC 오디오 전사")
parser.add_argument("--audio", required=True, help="입력 오디오 파일")
parser.add_argument("--output", help="출력 JSON 파일 (선택사항)")
parser.add_argument("--model", default="base", help="Whisper 모델 크기")
parser.add_argument("--language", default="en", help="언어 코드")
parser.add_argument("--device", default="cpu", help="실행 디바이스")
parser.add_argument("--output-format", default="file", choices=["file", "json", "srt"], help="출력 형식")
parser.add_argument("--output-dir", default=".", help="출력 디렉토리")
args = parser.parse_args()
# 입력 파일 확인
input_path = Path(args.audio)
if not input_path.exists():
logger.error(f"입력 파일이 없습니다: {input_path}")
sys.exit(1)
try:
# 전사기 생성
transcriber = ToeicWhisperTranscriber(
model_size=args.model,
device=args.device
)
# 전사 실행
result = transcriber.transcribe_audio(
str(input_path),
language=args.language
)
# TOEIC 후처리
enhanced_result = transcriber.enhance_segments_for_toeic(result)
if args.output_format == "json":
# JSON 형식으로 stdout에 출력 (API용)
print(json.dumps(enhanced_result, ensure_ascii=False))
elif args.output_format == "srt":
# SRT 파일만 생성
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
srt_path = output_dir / f"{input_path.stem}.srt"
generate_srt_file(enhanced_result, srt_path)
logger.info(f"SRT 파일 생성 완료: {srt_path}")
print(f"SRT 파일 생성: {srt_path}") # stdout 출력으로 Node.js에서 확인 가능
else:
# 파일 저장 모드 (JSON + SRT)
if not args.output:
output_path = input_path.with_suffix('.json')
else:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(enhanced_result, f, ensure_ascii=False, indent=2)
# SRT 파일도 자동 생성
srt_path = output_path.with_suffix('.srt')
generate_srt_file(enhanced_result, srt_path)
logger.info(f"전사 완료: {output_path}")
logger.info(f"SRT 파일 생성: {srt_path}")
except Exception as e:
error_msg = {
"status": "error",
"error_type": type(e).__name__,
"error_message": str(e)
}
if args.output_format == "json":
print(json.dumps(error_msg, ensure_ascii=False))
else:
logger.error(f"전사 실패: {e}")
sys.exit(1)
def split_into_sentences(text):
"""텍스트를 문장 단위로 분할"""
import re
# 문장 끝 패턴: 마침표, 물음표, 느낌표 + 공백 또는 끝
sentence_endings = re.compile(r'[.!?]+\s+|[.!?]+$')
# 약어나 숫자를 고려한 더 정교한 패턴
sentences = []
current_pos = 0
for match in sentence_endings.finditer(text):
sentence = text[current_pos:match.end()].strip()
if sentence:
sentences.append(sentence)
current_pos = match.end()
# 마지막 남은 텍스트
if current_pos < len(text):
remaining = text[current_pos:].strip()
if remaining:
sentences.append(remaining)
return sentences
def generate_srt_file(transcript_data, srt_path, max_words_per_subtitle=15):
"""전사 데이터를 문장 단위로 SRT 파일로 변환"""
try:
srt_entries = []
entry_id = 1
for segment in transcript_data.get('segments', []):
text = segment.get('text', '').strip()
if not text:
continue
start_time = segment.get('start', 0)
end_time = segment.get('end', start_time + 3)
# 먼저 문장 단위로 분할 시도
sentences = split_into_sentences(text)
if len(sentences) <= 1:
# 문장이 하나이거나 분할되지 않은 경우, 단어 수로 분할
words = text.split()
chunks = []
for i in range(0, len(words), max_words_per_subtitle):
chunk = ' '.join(words[i:i + max_words_per_subtitle])
chunks.append(chunk)
sentences = chunks
# 각 문장/청크별로 SRT 엔트리 생성
sentence_duration = (end_time - start_time) / len(sentences) if sentences else 1
for i, sentence in enumerate(sentences):
sentence_start = start_time + (i * sentence_duration)
sentence_end = start_time + ((i + 1) * sentence_duration)
# 마지막 문장은 원래 세그먼트 끝시간으로
if i == len(sentences) - 1:
sentence_end = end_time
srt_entries.append({
'id': entry_id,
'start': sentence_start,
'end': sentence_end,
'text': sentence.strip()
})
entry_id += 1
# SRT 파일 작성
with open(srt_path, 'w', encoding='utf-8') as f:
for entry in srt_entries:
f.write(f"{entry['id']}\n")
f.write(f"{format_srt_time(entry['start'])} --> {format_srt_time(entry['end'])}\n")
f.write(f"{entry['text']}\n\n")
logger.info(f"SRT 파일 생성 완료: {len(srt_entries)}개 엔트리")
except Exception as e:
logger.error(f"SRT 파일 생성 오류: {e}")
def format_srt_time(seconds):
"""초 단위를 SRT 시간 형식으로 변환"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
milliseconds = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{milliseconds:03d}"
if __name__ == "__main__":
main()
{
"metadata": {
"title": "TOEIC Listening Test Structure",
"totalQuestions": 100,
"totalSegments": 54,
"estimatedDuration": 2700,
"version": "1.0"
},
"parts": {
"part1": {
"name": "Part 1",
"type": "photographs",
"description": "사진 설명",
"questionRange": [1, 6],
"questionCount": 6,
"avgQuestionDuration": 8,
"directionDuration": 35,
"color": "rgba(255, 99, 132, 0.3)",
"icon": "📷",
"segments": {
"direction": {
"id": "part1-direction",
"label": "Part 1 (Direction)",
"description": "Part 1 지시문 - 사진 설명",
"type": "direction"
},
"questions": [
{
"id": "q1",
"label": "Q1",
"description": "문항 1번 - 사진 설명",
"questionNumber": 1,
"type": "question"
},
{
"id": "q2",
"label": "Q2",
"description": "문항 2번 - 사진 설명",
"questionNumber": 2,
"type": "question"
},
{
"id": "q3",
"label": "Q3",
"description": "문항 3번 - 사진 설명",
"questionNumber": 3,
"type": "question"
},
{
"id": "q4",
"label": "Q4",
"description": "문항 4번 - 사진 설명",
"questionNumber": 4,
"type": "question"
},
{
"id": "q5",
"label": "Q5",
"description": "문항 5번 - 사진 설명",
"questionNumber": 5,
"type": "question"
},
{
"id": "q6",
"label": "Q6",
"description": "문항 6번 - 사진 설명",
"questionNumber": 6,
"type": "question"
}
]
}
},
"part2": {
"name": "Part 2",
"type": "question_response",
"description": "질문-응답",
"questionRange": [7, 31],
"questionCount": 25,
"avgQuestionDuration": 6,
"directionDuration": 12,
"color": "rgba(54, 162, 235, 0.3)",
"icon": "🗣️",
"segments": {
"direction": {
"id": "part2-direction",
"label": "Part 2 (Direction)",
"description": "Part 2 지시문 - 질문-응답",
"type": "direction"
},
"questions": [
{"id": "q7", "label": "Q7", "description": "문항 7번 - 질문-응답", "questionNumber": 7, "type": "question"},
{"id": "q8", "label": "Q8", "description": "문항 8번 - 질문-응답", "questionNumber": 8, "type": "question"},
{"id": "q9", "label": "Q9", "description": "문항 9번 - 질문-응답", "questionNumber": 9, "type": "question"},
{"id": "q10", "label": "Q10", "description": "문항 10번 - 질문-응답", "questionNumber": 10, "type": "question"},
{"id": "q11", "label": "Q11", "description": "문항 11번 - 질문-응답", "questionNumber": 11, "type": "question"},
{"id": "q12", "label": "Q12", "description": "문항 12번 - 질문-응답", "questionNumber": 12, "type": "question"},
{"id": "q13", "label": "Q13", "description": "문항 13번 - 질문-응답", "questionNumber": 13, "type": "question"},
{"id": "q14", "label": "Q14", "description": "문항 14번 - 질문-응답", "questionNumber": 14, "type": "question"},
{"id": "q15", "label": "Q15", "description": "문항 15번 - 질문-응답", "questionNumber": 15, "type": "question"},
{"id": "q16", "label": "Q16", "description": "문항 16번 - 질문-응답", "questionNumber": 16, "type": "question"},
{"id": "q17", "label": "Q17", "description": "문항 17번 - 질문-응답", "questionNumber": 17, "type": "question"},
{"id": "q18", "label": "Q18", "description": "문항 18번 - 질문-응답", "questionNumber": 18, "type": "question"},
{"id": "q19", "label": "Q19", "description": "문항 19번 - 질문-응답", "questionNumber": 19, "type": "question"},
{"id": "q20", "label": "Q20", "description": "문항 20번 - 질문-응답", "questionNumber": 20, "type": "question"},
{"id": "q21", "label": "Q21", "description": "문항 21번 - 질문-응답", "questionNumber": 21, "type": "question"},
{"id": "q22", "label": "Q22", "description": "문항 22번 - 질문-응답", "questionNumber": 22, "type": "question"},
{"id": "q23", "label": "Q23", "description": "문항 23번 - 질문-응답", "questionNumber": 23, "type": "question"},
{"id": "q24", "label": "Q24", "description": "문항 24번 - 질문-응답", "questionNumber": 24, "type": "question"},
{"id": "q25", "label": "Q25", "description": "문항 25번 - 질문-응답", "questionNumber": 25, "type": "question"},
{"id": "q26", "label": "Q26", "description": "문항 26번 - 질문-응답", "questionNumber": 26, "type": "question"},
{"id": "q27", "label": "Q27", "description": "문항 27번 - 질문-응답", "questionNumber": 27, "type": "question"},
{"id": "q28", "label": "Q28", "description": "문항 28번 - 질문-응답", "questionNumber": 28, "type": "question"},
{"id": "q29", "label": "Q29", "description": "문항 29번 - 질문-응답", "questionNumber": 29, "type": "question"},
{"id": "q30", "label": "Q30", "description": "문항 30번 - 질문-응답", "questionNumber": 30, "type": "question"},
{"id": "q31", "label": "Q31", "description": "문항 31번 - 질문-응답", "questionNumber": 31, "type": "question"}
]
}
},
"part3": {
"name": "Part 3",
"type": "conversations",
"description": "대화",
"questionRange": [32, 70],
"questionCount": 39,
"groupCount": 13,
"questionsPerGroup": 3,
"avgGroupDuration": 35,
"directionDuration": 15,
"color": "rgba(255, 206, 86, 0.3)",
"icon": "💬",
"categories": ["회의", "은퇴", "교육", "여행", "음식", "음악", "운동", "의료", "정부", "교통"],
"segments": {
"direction": {
"id": "part3-direction",
"label": "Part 3 (Direction)",
"description": "Part 3 지시문 - 대화",
"type": "direction"
},
"groups": [
{"id": "q32-34", "label": "Q32-34", "description": "문항 32-34번 - 대화", "questionNumbers": [32, 33, 34], "type": "passage"},
{"id": "q32", "label": "Q32", "description": "문항 32번 - 대화", "questionNumbers": [32], "type": "question"},
{"id": "q33", "label": "Q33", "description": "문항 33번 - 대화", "questionNumbers": [33], "type": "question"},
{"id": "q34", "label": "Q34", "description": "문항 34번 - 대화", "questionNumbers": [34], "type": "question"},
{"id": "q35-37", "label": "Q35-37", "description": "문항 35-37번 - 대화", "questionNumbers": [35, 36, 37], "type": "passage"},
{"id": "q35", "label": "Q35", "description": "문항 35번 - 대화", "questionNumbers": [35], "type": "question"},
{"id": "q36", "label": "Q36", "description": "문항 36번 - 대화", "questionNumbers": [36], "type": "question"},
{"id": "q37", "label": "Q37", "description": "문항 37번 - 대화", "questionNumbers": [37], "type": "question"},
{"id": "q38-40", "label": "Q38-40", "description": "문항 38-40번 - 대화", "questionNumbers": [38, 39, 40], "type": "passage"},
{"id": "q38", "label": "Q38", "description": "문항 38번 - 대화", "questionNumbers": [38], "type": "question"},
{"id": "q39", "label": "Q39", "description": "문항 39번 - 대화", "questionNumbers": [39], "type": "question"},
{"id": "q40", "label": "Q40", "description": "문항 40번 - 대화", "questionNumbers": [40], "type": "question"},
{"id": "q41-43", "label": "Q41-43", "description": "문항 41-43번 - 대화", "questionNumbers": [41, 42, 43], "type": "passage"},
{"id": "q41", "label": "Q41", "description": "문항 41번 - 대화", "questionNumbers": [41], "type": "question"},
{"id": "q42", "label": "Q42", "description": "문항 42번 - 대화", "questionNumbers": [42], "type": "question"},
{"id": "q43", "label": "Q43", "description": "문항 43번 - 대화", "questionNumbers": [43], "type": "question"},
{"id": "q44-46", "label": "Q44-46", "description": "문항 44-46번 - 대화", "questionNumbers": [44, 45, 46], "type": "passage"},
{"id": "q44", "label": "Q44", "description": "문항 44번 - 대화", "questionNumbers": [44], "type": "question"},
{"id": "q45", "label": "Q45", "description": "문항 45번 - 대화", "questionNumbers": [45], "type": "question"},
{"id": "q46", "label": "Q46", "description": "문항 46번 - 대화", "questionNumbers": [46], "type": "question"},
{"id": "q47-49", "label": "Q47-49", "description": "문항 47-49번 - 대화", "questionNumbers": [47, 48, 49], "type": "passage"},
{"id": "q47", "label": "Q47", "description": "문항 47번 - 대화", "questionNumbers": [47], "type": "question"},
{"id": "q48", "label": "Q48", "description": "문항 48번 - 대화", "questionNumbers": [48], "type": "question"},
{"id": "q49", "label": "Q49", "description": "문항 49번 - 대화", "questionNumbers": [49], "type": "question"},
{"id": "q50-52", "label": "Q50-52", "description": "문항 50-52번 - 대화", "questionNumbers": [50, 51, 52], "type": "passage"},
{"id": "q50", "label": "Q50", "description": "문항 50번 - 대화", "questionNumbers": [50], "type": "question"},
{"id": "q51", "label": "Q51", "description": "문항 51번 - 대화", "questionNumbers": [51], "type": "question"},
{"id": "q52", "label": "Q52", "description": "문항 52번 - 대화", "questionNumbers": [52], "type": "question"},
{"id": "q53-55", "label": "Q53-55", "description": "문항 53-55번 - 대화", "questionNumbers": [53, 54, 55], "type": "passage"},
{"id": "q53", "label": "Q53", "description": "문항 53번 - 대화", "questionNumbers": [53], "type": "question"},
{"id": "q54", "label": "Q54", "description": "문항 54번 - 대화", "questionNumbers": [54], "type": "question"},
{"id": "q55", "label": "Q55", "description": "문항 55번 - 대화", "questionNumbers": [55], "type": "question"},
{"id": "q56-58", "label": "Q56-58", "description": "문항 56-58번 - 대화", "questionNumbers": [56, 57, 58], "type": "passage"},
{"id": "q56", "label": "Q56", "description": "문항 56번 - 대화", "questionNumbers": [56], "type": "question"},
{"id": "q57", "label": "Q57", "description": "문항 57번 - 대화", "questionNumbers": [57], "type": "question"},
{"id": "q58", "label": "Q58", "description": "문항 58번 - 대화", "questionNumbers": [58], "type": "question"},
{"id": "q59-61", "label": "Q59-61", "description": "문항 59-61번 - 대화", "questionNumbers": [59, 60, 61], "type": "passage"},
{"id": "q59", "label": "Q59", "description": "문항 59번 - 대화", "questionNumbers": [59], "type": "question"},
{"id": "q60", "label": "Q60", "description": "문항 60번 - 대화", "questionNumbers": [60], "type": "question"},
{"id": "q61", "label": "Q61", "description": "문항 61번 - 대화", "questionNumbers": [61], "type": "question"},
{"id": "q62-64", "label": "Q62-64", "description": "문항 62-64번 - 대화", "questionNumbers": [62, 63, 64], "type": "passage"},
{"id": "q62", "label": "Q62", "description": "문항 62번 - 대화", "questionNumbers": [62], "type": "question"},
{"id": "q63", "label": "Q63", "description": "문항 63번 - 대화", "questionNumbers": [63], "type": "question"},
{"id": "q64", "label": "Q64", "description": "문항 64번 - 대화", "questionNumbers": [64], "type": "question"},
{"id": "q65-67", "label": "Q65-67", "description": "문항 65-67번 - 대화", "questionNumbers": [65, 66, 67], "type": "passage"},
{"id": "q65", "label": "Q65", "description": "문항 65번 - 대화", "questionNumbers": [65], "type": "question"},
{"id": "q66", "label": "Q66", "description": "문항 66번 - 대화", "questionNumbers": [66], "type": "question"},
{"id": "q67", "label": "Q67", "description": "문항 67번 - 대화", "questionNumbers": [67], "type": "question"},
{"id": "q68-70", "label": "Q68-70", "description": "문항 68-70번 - 대화", "questionNumbers": [68, 69, 70], "type": "passage"},
{"id": "q68", "label": "Q68", "description": "문항 68번 - 대화", "questionNumbers": [68], "type": "question"},
{"id": "q69", "label": "Q69", "description": "문항 69번 - 대화", "questionNumbers": [69], "type": "question"},
{"id": "q70", "label": "Q70", "description": "문항 70번 - 대화", "questionNumbers": [70], "type": "question"}
]
}
},
"part4": {
"name": "Part 4",
"type": "talks",
"description": "토크",
"questionRange": [71, 100],
"questionCount": 30,
"groupCount": 10,
"questionsPerGroup": 3,
"avgGroupDuration": 45,
"directionDuration": 15,
"color": "rgba(75, 192, 192, 0.3)",
"icon": "🎤",
"categories": ["광고", "안내", "박물관", "뉴스", "공지사항", "프레젠테이션"],
"segments": {
"direction": {
"id": "part4-direction",
"label": "Part 4 (Direction)",
"description": "Part 4 지시문 - 토크",
"type": "direction"
},
"groups": [
{"id": "q71-73", "label": "Q71-73", "description": "문항 71-73번 - 토크", "questionNumbers": [71, 72, 73], "type": "passage"},
{"id": "q71", "label": "Q71", "description": "문항 71번 - 토크", "questionNumbers": [71], "type": "question"},
{"id": "q72", "label": "Q72", "description": "문항 72번 - 토크", "questionNumbers": [72], "type": "question"},
{"id": "q73", "label": "Q73", "description": "문항 73번 - 토크", "questionNumbers": [73], "type": "question"},
{"id": "q74-76", "label": "Q74-76", "description": "문항 74-76번 - 토크", "questionNumbers": [74, 75, 76], "type": "passage"},
{"id": "q74", "label": "Q74", "description": "문항 74번 - 토크", "questionNumbers": [74], "type": "question"},
{"id": "q75", "label": "Q75", "description": "문항 75번 - 토크", "questionNumbers": [75], "type": "question"},
{"id": "q76", "label": "Q76", "description": "문항 76번 - 토크", "questionNumbers": [76], "type": "question"},
{"id": "q77-79", "label": "Q77-79", "description": "문항 77-79번 - 토크", "questionNumbers": [77, 78, 79], "type": "passage"},
{"id": "q77", "label": "Q77", "description": "문항 77번 - 토크", "questionNumbers": [77], "type": "question"},
{"id": "q78", "label": "Q78", "description": "문항 78번 - 토크", "questionNumbers": [78], "type": "question"},
{"id": "q79", "label": "Q79", "description": "문항 79번 - 토크", "questionNumbers": [79], "type": "question"},
{"id": "q80-82", "label": "Q80-82", "description": "문항 80-82번 - 토크", "questionNumbers": [80, 81, 82], "type": "passage"},
{"id": "q80", "label": "Q80", "description": "문항 80번 - 토크", "questionNumbers": [80], "type": "question"},
{"id": "q81", "label": "Q81", "description": "문항 81번 - 토크", "questionNumbers": [81], "type": "question"},
{"id": "q82", "label": "Q82", "description": "문항 82번 - 토크", "questionNumbers": [82], "type": "question"},
{"id": "q83-85", "label": "Q83-85", "description": "문항 83-85번 - 토크", "questionNumbers": [83, 84, 85], "type": "passage"},
{"id": "q83", "label": "Q83", "description": "문항 83번 - 토크", "questionNumbers": [83], "type": "question"},
{"id": "q84", "label": "Q84", "description": "문항 84번 - 토크", "questionNumbers": [84], "type": "question"},
{"id": "q85", "label": "Q85", "description": "문항 85번 - 토크", "questionNumbers": [85], "type": "question"},
{"id": "q86-88", "label": "Q86-88", "description": "문항 86-88번 - 토크", "questionNumbers": [86, 87, 88], "type": "passage"},
{"id": "q86", "label": "Q86", "description": "문항 86번 - 토크", "questionNumbers": [86], "type": "question"},
{"id": "q87", "label": "Q87", "description": "문항 87번 - 토크", "questionNumbers": [87], "type": "question"},
{"id": "q88", "label": "Q88", "description": "문항 88번 - 토크", "questionNumbers": [88], "type": "question"},
{"id": "q89-91", "label": "Q89-91", "description": "문항 89-91번 - 토크", "questionNumbers": [89, 90, 91], "type": "passage"},
{"id": "q89", "label": "Q89", "description": "문항 89번 - 토크", "questionNumbers": [89], "type": "question"},
{"id": "q90", "label": "Q90", "description": "문항 90번 - 토크", "questionNumbers": [90], "type": "question"},
{"id": "q91", "label": "Q91", "description": "문항 91번 - 토크", "questionNumbers": [91], "type": "question"},
{"id": "q92-94", "label": "Q92-94", "description": "문항 92-94번 - 토크", "questionNumbers": [92, 93, 94], "type": "passage"},
{"id": "q92", "label": "Q92", "description": "문항 92번 - 토크", "questionNumbers": [92], "type": "question"},
{"id": "q93", "label": "Q93", "description": "문항 93번 - 토크", "questionNumbers": [93], "type": "question"},
{"id": "q94", "label": "Q94", "description": "문항 94번 - 토크", "questionNumbers": [94], "type": "question"},
{"id": "q95-97", "label": "Q95-97", "description": "문항 95-97번 - 토크", "questionNumbers": [95, 96, 97], "type": "passage"},
{"id": "q95", "label": "Q95", "description": "문항 95번 - 토크", "questionNumbers": [95], "type": "question"},
{"id": "q96", "label": "Q96", "description": "문항 96번 - 토크", "questionNumbers": [96], "type": "question"},
{"id": "q97", "label": "Q97", "description": "문항 97번 - 토크", "questionNumbers": [97], "type": "question"},
{"id": "q98-100", "label": "Q98-100", "description": "문항 98-100번 - 토크", "questionNumbers": [98, 99, 100], "type": "passage"},
{"id": "q98", "label": "Q98", "description": "문항 98번 - 토크", "questionNumbers": [98], "type": "question"},
{"id": "q99", "label": "Q99", "description": "문항 99번 - 토크", "questionNumbers": [99], "type": "question"},
{"id": "q100", "label": "Q100", "description": "문항 100번 - 토크", "questionNumbers": [100], "type": "question"}
]
}
}
},
"segmentOrder": [
"part1-direction", "q1", "q2", "q3", "q4", "q5", "q6",
"part2-direction", "q7", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "q16", "q17", "q18", "q19", "q20", "q21", "q22", "q23", "q24", "q25", "q26", "q27", "q28", "q29", "q30", "q31",
"part3-direction",
"q32-34", "q32", "q33", "q34",
"q35-37", "q35", "q36", "q37",
"q38-40", "q38", "q39", "q40",
"q41-43", "q41", "q42", "q43",
"q44-46", "q44", "q45", "q46",
"q47-49", "q47", "q48", "q49",
"q50-52", "q50", "q51", "q52",
"q53-55", "q53", "q54", "q55",
"q56-58", "q56", "q57", "q58",
"q59-61", "q59", "q60", "q61",
"q62-64", "q62", "q63", "q64",
"q65-67", "q65", "q66", "q67",
"q68-70", "q68", "q69", "q70",
"part4-direction",
"q71-73", "q71", "q72", "q73",
"q74-76", "q74", "q75", "q76",
"q77-79", "q77", "q78", "q79",
"q80-82", "q80", "q81", "q82",
"q83-85", "q83", "q84", "q85",
"q86-88", "q86", "q87", "q88",
"q89-91", "q89", "q90", "q91",
"q92-94", "q92", "q93", "q94",
"q95-97", "q95", "q96", "q97",
"q98-100", "q98", "q99", "q100"
],
"timingDefaults": {
"part1Direction": 35,
"part1QuestionAvg": 8,
"part2Direction": 12,
"part2QuestionAvg": 6,
"part3Direction": 15,
"part3GroupAvg": 35,
"part4Direction": 15,
"part4GroupAvg": 45
},
"segmentKeywords": {
"part1": {
"direction": {
"primary": ["Listening test", "part 1", "part 1."],
"content": ["look at the picture", "in your test book", "select the one statement", "best describes", "what you see", "photograph"]
},
"questions": [
{"number": 1, "keywords": ["number one", "number 1", "number 1."], "variations": ["question 1", "question one"]},
{"number": 2, "keywords": ["number two", "number 2", "number 2."], "variations": ["question 2", "question two"]},
{"number": 3, "keywords": ["number three", "number 3", "number 3."], "variations": ["question 3", "question three"]},
{"number": 4, "keywords": ["number four", "number 4", "number 4."], "variations": ["question 4", "question four"]},
{"number": 5, "keywords": ["number five", "number 5", "number 5."], "variations": ["question 5", "question five"]},
{"number": 6, "keywords": ["number six", "number 6", "number 6."], "variations": ["question 6", "question six"]}
]
},
"part2": {
"direction": {
"primary": ["part two", "part 2", "part 2."],
"content": ["you will hear a question", "followed by three responses", "select the best response"]
},
"questions": [
{"number": 7, "keywords": ["number seven", "number 7", "number 7."], "variations": ["question 7", "question seven"]},
{"number": 8, "keywords": ["number 8", "number eight"], "variations": ["question 8", "question eight"]},
{"number": 9, "keywords": ["number 9", "number nine"], "variations": ["question 9", "question nine"]},
{"number": 10, "keywords": ["number 10", "number ten"], "variations": ["question 10", "question ten"]},
{"number": 11, "keywords": ["number 11", "number eleven"], "variations": ["question 11", "question eleven"]},
{"number": 12, "keywords": ["number 12", "number twelve"], "variations": ["question 12", "question twelve"]},
{"number": 13, "keywords": ["number 13", "number thirteen"], "variations": ["question 13", "question thirteen"]},
{"number": 14, "keywords": ["number 14", "number fourteen"], "variations": ["question 14", "question fourteen"]},
{"number": 15, "keywords": ["number 15", "number fifteen"], "variations": ["question 15", "question fifteen"]},
{"number": 16, "keywords": ["number 16", "number sixteen"], "variations": ["question 16", "question sixteen"]},
{"number": 17, "keywords": ["number 17", "number seventeen"], "variations": ["question 17", "question seventeen"]},
{"number": 18, "keywords": ["number 18", "number eighteen"], "variations": ["question 18", "question eighteen"]},
{"number": 19, "keywords": ["number 19", "number nineteen"], "variations": ["question 19", "question nineteen"]},
{"number": 20, "keywords": ["number 20", "number twenty"], "variations": ["question 20", "question twenty"]},
{"number": 21, "keywords": ["number 21", "number twenty-one"], "variations": ["question 21", "question twenty-one"]},
{"number": 22, "keywords": ["number 22", "number twenty-two"], "variations": ["question 22", "question twenty-two"]},
{"number": 23, "keywords": ["number 23", "number twenty-three"], "variations": ["question 23", "question twenty-three"]},
{"number": 24, "keywords": ["number 24", "number twenty-four"], "variations": ["question 24", "question twenty-four"]},
{"number": 25, "keywords": ["number 25", "number twenty-five"], "variations": ["question 25", "question twenty-five"]},
{"number": 26, "keywords": ["number 26", "number twenty-six"], "variations": ["question 26", "question twenty-six"]},
{"number": 27, "keywords": ["number 27", "number twenty-seven"], "variations": ["question 27", "question twenty-seven"]},
{"number": 28, "keywords": ["number 28", "number twenty-eight"], "variations": ["question 28", "question twenty-eight"]},
{"number": 29, "keywords": ["number 29", "number twenty-nine"], "variations": ["question 29", "question twenty-nine"]},
{"number": 30, "keywords": ["number 30", "number thirty"], "variations": ["question 30", "question thirty"]},
{"number": 31, "keywords": ["number 31", "number thirty-one"], "variations": ["question 31", "question thirty-one"]}
]
},
"part3": {
"direction": {
"primary": ["part three", "part 3"],
"content": ["you will hear some conversations", "between two people", "three questions", "conversation"]
},
"groups": [
{"questions": [32,33,34], "keywords": ["question 32 through 34", "questions 32 through 34"], "variations": ["question thirty-two through thirty-four", "questions thirty-two through thirty-four"]},
{"number": 32, "keywords": ["number thirty-two", "number 32", "number 32."], "variations": ["question 32", "question thirty-two"]},
{"number": 33, "keywords": ["number thirty-three", "number 33", "number 33."], "variations": ["question 33", "question thirty-three"]},
{"number": 34, "keywords": ["number thirty-four", "number 34", "number 34."], "variations": ["question 34", "question thirty-four"]},
{"questions": [35,36,37], "keywords": ["question 35 through 37", "questions 35 through 37"], "variations": ["question thirty-five through thirty-seven", "questions thirty-five through thirty-seven"]},
{"number": 35, "keywords": ["number thirty-five", "number 35", "number 35."], "variations": ["question 35", "question thirty-five"]},
{"number": 36, "keywords": ["number thirty-six", "number 36", "number 36."], "variations": ["question 36", "question thirty-six"]},
{"number": 37, "keywords": ["number thirty-seven", "number 37", "number 37."], "variations": ["question 37", "question thirty-seven"]},
{"questions": [38,39,40], "keywords": ["question 38 through 40", "questions 38 through 40"], "variations": ["question thirty-eight through forty", "questions thirty-eight through forty"]},
{"number": 38, "keywords": ["number thirty-eight", "number 38", "number 38."], "variations": ["question 38", "question thirty-eight"]},
{"number": 39, "keywords": ["number thirty-nine", "number 39", "number 39."], "variations": ["question 39", "question thirty-nine"]},
{"number": 40, "keywords": ["number forty", "number 40", "number 40."], "variations": ["question 40", "question forty"]},
{"questions": [41,42,43], "keywords": ["question 41 through 43", "questions 41 through 43"], "variations": ["question forty-one through forty-three", "questions forty-one through forty-three"]},
{"number": 41, "keywords": ["number forty-one", "number 41", "number 41."], "variations": ["question 41", "question forty-one"]},
{"number": 42, "keywords": ["number forty-two", "number 42", "number 42."], "variations": ["question 42", "question forty-two"]},
{"number": 43, "keywords": ["number forty-three", "number 43", "number 43."], "variations": ["question 43", "question forty-three"]},
{"questions": [44,45,46], "keywords": ["question 44 through 46", "questions 44 through 46"], "variations": ["question forty-four through forty-six", "questions forty-four through forty-six"]},
{"number": 44, "keywords": ["number forty-four", "number 44", "number 44."], "variations": ["question 44", "question forty-four"]},
{"number": 45, "keywords": ["number forty-five", "number 45", "number 45."], "variations": ["question 45", "question forty-five"]},
{"number": 46, "keywords": ["number forty-six", "number 46", "number 46."], "variations": ["question 46", "question forty-six"]},
{"questions": [47,48,49], "keywords": ["question 47 through 49", "questions 47 through 49"], "variations": ["question forty-seven through forty-nine", "questions forty-seven through forty-nine"]},
{"number": 47, "keywords": ["number forty-seven", "number 47", "number 47."], "variations": ["question 47", "question forty-seven"]},
{"number": 48, "keywords": ["number forty-eight", "number 48", "number 48."], "variations": ["question 48", "question forty-eight"]},
{"number": 49, "keywords": ["number forty-nine", "number 49", "number 49."], "variations": ["question 49", "question forty-nine"]},
{"questions": [50,51,52], "keywords": ["question 50 through 52", "questions 50 through 52"], "variations": ["question fifty through fifty-two", "questions fifty through fifty-two"]},
{"number": 50, "keywords": ["number fifty", "number 50", "number 50."], "variations": ["question 50", "question fifty"]},
{"number": 51, "keywords": ["number fifty-one", "number 51", "number 51."], "variations": ["question 51", "question fifty-one"]},
{"number": 52, "keywords": ["number fifty-two", "number 52", "number 52."], "variations": ["question 52", "question fifty-two"]},
{"questions": [53,54,55], "keywords": ["question 53 through 55", "questions 53 through 55"], "variations": ["question fifty-three through fifty-five", "questions fifty-three through fifty-five"]},
{"number": 53, "keywords": ["number fifty-three", "number 53", "number 53."], "variations": ["question 53", "question fifty-three"]},
{"number": 54, "keywords": ["number fifty-four", "number 54", "number 54."], "variations": ["question 54", "question fifty-four"]},
{"number": 55, "keywords": ["number fifty-five", "number 55", "number 55."], "variations": ["question 55", "question fifty-five"]},
{"questions": [56,57,58], "keywords": ["question 56 through 58", "questions 56 through 58"], "variations": ["question fifty-six through fifty-eight", "questions fifty-six through fifty-eight"]},
{"number": 56, "keywords": ["number fifty-six", "number 56", "number 56."], "variations": ["question 56", "question fifty-six"]},
{"number": 57, "keywords": ["number fifty-seven", "number 57", "number 57."], "variations": ["question 57", "question fifty-seven"]},
{"number": 58, "keywords": ["number fifty-eight", "number 58", "number 58."], "variations": ["question 58", "question fifty-eight"]},
{"questions": [59,60,61], "keywords": ["question 59 through 61", "questions 59 through 61"], "variations": ["question fifty-nine through sixty-one", "questions fifty-nine through sixty-one"]},
{"number": 59, "keywords": ["number fifty-nine", "number 59", "number 59."], "variations": ["question 59", "question fifty-nine"]},
{"number": 60, "keywords": ["number sixty", "number 60", "number 60."], "variations": ["question 60", "question sixty"]},
{"number": 61, "keywords": ["number sixty-one", "number 61", "number 61."], "variations": ["question 61", "question sixty-one"]},
{"questions": [62,63,64], "keywords": ["question 62 through 64", "questions 62 through 64"], "variations": ["question sixty-two through sixty-four", "questions sixty-two through sixty-four"]},
{"number": 62, "keywords": ["number sixty-two", "number 62", "number 62."], "variations": ["question 62", "question sixty-two"]},
{"number": 63, "keywords": ["number sixty-three", "number 63", "number 63."], "variations": ["question 63", "question sixty-three"]},
{"number": 64, "keywords": ["number sixty-four", "number 64", "number 64."], "variations": ["question 64", "question sixty-four"]},
{"questions": [65,66,67], "keywords": ["question 65 through 67", "questions 65 through 67"], "variations": ["question sixty-five through sixty-seven", "questions sixty-five through sixty-seven"]},
{"number": 65, "keywords": ["number sixty-five", "number 65", "number 65."], "variations": ["question 65", "question sixty-five"]},
{"number": 66, "keywords": ["number sixty-six", "number 66", "number 66."], "variations": ["question 66", "question sixty-six"]},
{"number": 67, "keywords": ["number sixty-seven", "number 67", "number 67."], "variations": ["question 67", "question sixty-seven"]},
{"questions": [68,69,70], "keywords": ["question 68 through 70", "questions 68 through 70"], "variations": ["question sixty-eight through seventy", "questions sixty-eight through seventy"]},
{"number": 68, "keywords": ["number sixty-eight", "number 68", "number 68."], "variations": ["question 68", "question sixty-eight"]},
{"number": 69, "keywords": ["number sixty-nine", "number 69", "number 69."], "variations": ["question 69", "question sixty-nine"]},
{"number": 70, "keywords": ["number seventy", "number 70", "number 70."], "variations": ["question 70", "question seventy"]}
]
},
"part4": {
"direction": {
"primary": ["part four", "part 4", "part 4."],
"content": ["you will hear some talks", "given by a single speaker", "about each talk"]
},
"groups": [
{"questions": [71,72,73], "keywords": ["question 71 through 73", "questions 71 through 73"], "variations": ["question seventy-one through seventy-three", "questions seventy-one through seventy-three"]},
{"number": 71, "keywords": ["number seventy-one", "number 71", "number 71."], "variations": ["question 71", "question seventy-one"]},
{"number": 72, "keywords": ["number seventy-two", "number 72", "number 72."], "variations": ["question 72", "question seventy-two"]},
{"number": 73, "keywords": ["number seventy-three", "number 73", "number 73."], "variations": ["question 73", "question seventy-three"]},
{"questions": [74,75,76], "keywords": ["question 74 through 76", "questions 74 through 76"], "variations": ["question seventy-four through seventy-six", "questions seventy-four through seventy-six"]},
{"number": 74, "keywords": ["number seventy-four", "number 74", "number 74."], "variations": ["question 74", "question seventy-four"]},
{"number": 75, "keywords": ["number seventy-five", "number 75", "number 75."], "variations": ["question 75", "question seventy-five"]},
{"number": 76, "keywords": ["number seventy-six", "number 76", "number 76."], "variations": ["question 76", "question seventy-six"]},
{"questions": [77,78,79], "keywords": ["question 77 through 79", "questions 77 through 79"], "variations": ["question seventy-seven through seventy-nine", "questions seventy-seven through seventy-nine"]},
{"number": 77, "keywords": ["number seventy-seven", "number 77", "number 77."], "variations": ["question 77", "question seventy-seven"]},
{"number": 78, "keywords": ["number seventy-eight", "number 78", "number 78."], "variations": ["question 78", "question seventy-eight"]},
{"number": 79, "keywords": ["number seventy-nine", "number 79", "number 79."], "variations": ["question 79", "question seventy-nine"]},
{"questions": [80,81,82], "keywords": ["question 80 through 82", "questions 80 through 82"], "variations": ["question eighty through eighty-two", "questions eighty through eighty-two"]},
{"number": 80, "keywords": ["number eighty", "number 80", "number 80."], "variations": ["question 80", "question eighty"]},
{"number": 81, "keywords": ["number eighty-one", "number 81", "number 81."], "variations": ["question 81", "question eighty-one"]},
{"number": 82, "keywords": ["number eighty-two", "number 82", "number 82."], "variations": ["question 82", "question eighty-two"]},
{"questions": [83,84,85], "keywords": ["question 83 through 85", "questions 83 through 85"], "variations": ["question eighty-three through eighty-five", "questions eighty-three through eighty-five"]},
{"number": 83, "keywords": ["number eighty-three", "number 83", "number 83."], "variations": ["question 83", "question eighty-three"]},
{"number": 84, "keywords": ["number eighty-four", "number 84", "number 84."], "variations": ["question 84", "question eighty-four"]},
{"number": 85, "keywords": ["number eighty-five", "number 85", "number 85."], "variations": ["question 85", "question eighty-five"]},
{"questions": [86,87,88], "keywords": ["question 86 through 88", "questions 86 through 88"], "variations": ["question eighty-six through eighty-eight", "questions eighty-six through eighty-eight"]},
{"number": 86, "keywords": ["number eighty-six", "number 86", "number 86."], "variations": ["question 86", "question eighty-six"]},
{"number": 87, "keywords": ["number eighty-seven", "number 87", "number 87."], "variations": ["question 87", "question eighty-seven"]},
{"number": 88, "keywords": ["number eighty-eight", "number 88", "number 88."], "variations": ["question 88", "question eighty-eight"]},
{"questions": [89,90,91], "keywords": ["question 89 through 91", "questions 89 through 91"], "variations": ["question eighty-nine through ninety-one", "questions eighty-nine through ninety-one"]},
{"number": 89, "keywords": ["number eighty-nine", "number 89", "number 89."], "variations": ["question 89", "question eighty-nine"]},
{"number": 90, "keywords": ["number ninety", "number 90", "number 90."], "variations": ["question 90", "question ninety"]},
{"number": 91, "keywords": ["number ninety-one", "number 91", "number 91."], "variations": ["question 91", "question ninety-one"]},
{"questions": [92,93,94], "keywords": ["question 92 through 94", "questions 92 through 94"], "variations": ["question ninety-two through ninety-four", "questions ninety-two through ninety-four"]},
{"number": 92, "keywords": ["number ninety-two", "number 92", "number 92."], "variations": ["question 92", "question ninety-two"]},
{"number": 93, "keywords": ["number ninety-three", "number 93", "number 93."], "variations": ["question 93", "question ninety-three"]},
{"number": 94, "keywords": ["number ninety-four", "number 94", "number 94."], "variations": ["question 94", "question ninety-four"]},
{"questions": [95,96,97], "keywords": ["question 95 through 97", "questions 95 through 97"], "variations": ["question ninety-five through ninety-seven", "questions ninety-five through ninety-seven"]},
{"number": 95, "keywords": ["number ninety-five", "number 95", "number 95."], "variations": ["question 95", "question ninety-five"]},
{"number": 96, "keywords": ["number ninety-six", "number 96", "number 96."], "variations": ["question 96", "question ninety-six"]},
{"number": 97, "keywords": ["number ninety-seven", "number 97", "number 97."], "variations": ["question 97", "question ninety-seven"]},
{"questions": [98,99,100], "keywords": ["question 98 through 100", "questions 98 through 100"], "variations": ["question ninety-eight through one hundred", "questions ninety-eight through one hundred"]},
{"number": 98, "keywords": ["number ninety-eight", "number 98", "number 98."], "variations": ["question 98", "question ninety-eight"]},
{"number": 99, "keywords": ["number ninety-nine", "number 99", "number 99."], "variations": ["question 99", "question ninety-nine"]},
{"number": 100, "keywords": ["number one hundred", "number 100", "number 100."], "variations": ["question 100", "question one hundred"]}
]
}
}
}
댓글