에이아이파크
경력근거: V-GEN 및 AiVATAR의 화면 설계부터 API 개발, 데이터 모델링까지
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 V-GEN 과 AiVATAR 의 화면 설계부터 API 와 데이터 모델링까지 직접 맡는다고 적고, 다국어 영상 자동 생성을 제품의 축으로 둡니다. 이 포트폴리오는 그 자동 생성 앞단의 확인 자리를 만듭니다. 구간과 번역문을 데이터로 두고 계산을 규칙으로 고정해, 렌더를 돌리기 전에 고칠 문장이 목록으로 나옵니다. 다만 이 포트폴리오는 길이 계산 규칙과 화면 구현을 보여줍니다. 음성 합성이나 영상 렌더링 최적화에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
V-GEN 과 AiVATAR 의 화면 설계부터 API 개발과 데이터 모델링까지 직접 참여해 기능을 완성한다고 주요업무 첫 항목에 적습니다. 기능 하나를 처음부터 끝까지 붙이는 자리입니다.
AI 아바타 제작부터 다국어 영상 자동 생성까지 누구나 전문가 수준의 영상을 만들 수 있게 한다고 회사 소개에 적습니다. 언어를 바꿔도 결과가 자연스러운지가 제품의 품질입니다.
AI 도구를 개발 프로세스에 도입하고 생성한 코드의 품질과 안정성을 직접 검증하고 책임진다고 적습니다. 결과를 자동으로 받는 데서 끝나지 않는 방식입니다.
영상 담당자가 작업을 열면, 원본 자막 구간의 길이와 번역문의 예상 낭독 시간을 비교해 구간을 넘긴 문장을 언어별로 보여줍니다. 넘쳤다는 판정은 글자 수와 낭독 속도로 계산하는 규칙이 정하고, 모델은 판정에 손대지 않고 어느 문장이 얼마나 넘쳤는지를 담당자가 읽을 한 줄로 요약합니다.
저는 홍보 영상의 다국어 버전을 만듭니다. 한국어로 만든 영상을 영어와 일본어로 바꾸면 문장이 길어져 자막이 다음 장면까지 넘어갑니다. 완성본을 재생해 보고 나서야 발견해 다시 만드는 일이 반복됩니다.
다시 만드는 일이 반복되는 이유는 번역이 나빠서가 아니라 구간 길이에 맞는지 만들기 전에 확인할 자리가 없기 때문입니다. 구간별로 넘친 시간을 미리 계산해 보여주면, 렌더 전에 문장을 줄여 한 번에 끝냅니다.
영상 담당자가 쓰는 화면 하나가 완결됩니다. 구간 길이와 번역문 예상 낭독 시간을 비교해 넘친 문장을 언어별로 보여주고, 계산에 쓴 값을 같이 냅니다. 실제 음성 합성 길이를 재는 일은 언어별 낭독 속도 기준으로 대신하고 3주차로 미룹니다.
이미 만든 영상의 실제 자막 길이 220건과 예상 시간을 맞춰 보고, 언어별로 얼마나 어긋나는지 냅니다. 낭독 속도 기준을 그 결과로 조정합니다.
화면에서 문장을 줄이면 예상 시간이 바로 다시 계산되고, 모든 구간이 들어가면 렌더를 걸 수 있는 상태로 표시합니다.
영상 담당자가 작업을 열면 구간별 예상 낭독 시간이 계산되고, 넘친 자막이 계산에 쓴 값과 같이 보입니다.
번역문이 비거나 구간 길이가 0 이거나 등록되지 않은 언어 코드가 섞인 입력 30종을 올려도 화면이 죽지 않고, 계산할 수 없는 구간은 미판정으로 남습니다.
실제 자막 길이 220건과 예상 시간을 맞춰 언어별 오차를 냅니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 예측 오차는 에이아이파크의 실 합성 결과가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 번역 결측·구간 길이 0·등록되지 않은 언어 코드를 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
글자 수로 낸 예상 시간이 실제 낭독과 정확히 같지는 않습니다. 목소리와 속도 설정에 따라 달라지므로 판정을 결론으로 읽지 않게 계산 값을 항상 같이 보여줍니다.
실제 도입에서는 언어마다 문자 폭과 띄어쓰기 관례가 달라 기준이 하나로 서지 않습니다. 언어가 늘어나면 규칙을 코드에서 빼 설정으로 옮겨야 하며, 그것은 도입 단계 과제로 분리합니다.
음성 합성과 영상 렌더링 자체는 이 포트폴리오의 범위 밖입니다. 여기서는 렌더 전에 고칠 문장을 짚는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
작업별 자막 구간과 언어별 번역문입니다. 계산의 입력이며 번역 결측과 구간 길이 0, 등록되지 않은 언어 코드를 의도적으로 포함합니다.
| segment_no | job_no | seq | lang_code_raw | source_text | translated_text | segment_sec | char_count | voice_preset | note |
|---|---|---|---|---|---|---|---|---|---|
| SG-4001 | JB-77 | 1 | en | 안녕하세요, 오늘은 신제품을 소개합니다. | Hello, today we introduce our new prod… | 3.5 | 42 | en_female_1 | — |
| SG-4002 | JB-77 | 2 | en | 이 제품은 세 가지 기능을 담았습니다. | This product packs three distinct capa… | 3 | 113 | en_female_1 | 번역문이 크게 길어짐 |
| SG-4003 | JB-77 | 3 | ja | 지금 바로 확인해 보세요. | 今すぐご確認ください。 | 2 | 11 | ja_male_2 | — |
| SG-4004 | JB-78 | 1 | ja | 설치는 다섯 단계로 끝납니다. | インストールは五つのステップで完了します。とても簡単です。 | 2.5 | 29 | ja_male_2 | 문장이 하나 더 붙음 |
| SG-4005 | JB-78 | 2 | en | 질문이 있으면 도움말을 보세요. | — | 2.8 | — | en_female_1 | 번역이 아직 없음 |
아래 JSON Schema 에 맞는 자막 구간 행을 더 만들어 주세요. {"type":"object","properties":{"segment_no":{"type":"string"},"job_no":{"type":"string"},"seq":{"type":"integer"},"lang_code_raw":{"type":"string"},"source_text":{"type":"string"},"translated_text":{"type":["string","null"]},"segment_sec":{"type":["number","null"]},"char_count":{"type":["integer","null"]},"voice_preset":{"type":["string","null"]},"note":{"type":["string","null"]}},"required":["segment_no","job_no","seq","lang_code_raw","source_text"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) translated_text 나 char_count 가 null 인 행, (2) segment_sec 가 0 인 무음 구간과 번역문이 원문의 세 배가 넘는 행, (3) lang_code_raw 의 대소문자가 흔들린 행, (4) 낭독 속도 기준이 없는 언어 코드. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"segment_no":{"type":"string"},"job_no":{"type":"string"},"seq":{"type":"integer"},"lang_code_raw":{"type":"string"},"source_text":{"type":"string"},"translated_text":{"type":["string","null"]},"segment_sec":{"type":["number","null"]},"char_count":{"type":["integer","null"]},"voice_preset":{"type":["string","null"]},"note":{"type":["string","null"]}},"required":["segment_no","job_no","seq","lang_code_raw","source_text"]}언어별 낭독 속도 계수표입니다. 예상 낭독 시간은 이 표의 chars_per_sec 로 char_count 를 나눠 냅니다. 속도 기준을 아직 정하지 않은 언어는 chars_per_sec 가 null 이라 그 구간은 예상 시간을 내지 않고 사유로 남깁니다. 2주차 실측 대조 뒤에 이 표의 값을 조정합니다.
| lang_code | lang_name | chars_per_sec | note |
|---|---|---|---|
| ko | 한국어 | 7 | 원문 기준입니다 |
| en | 영어 | 15 | 공백을 포함해 셉니다 |
| ja | 일본어 | 7.5 | — |
| zh | 중국어(간체) | 5.5 | — |
| es | 스페인어 | 15.5 | — |
아래 JSON Schema 에 맞는 언어별 낭독 속도 행을 더 만들어 주세요. {"type":"object","properties":{"lang_code":{"type":"string"},"lang_name":{"type":"string"},"chars_per_sec":{"type":["number","null"]},"note":{"type":["string","null"]}},"required":["lang_code","lang_name"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) chars_per_sec 가 null 인 언어(속도 기준을 아직 정하지 않은 언어), (2) 글자 수 기준을 그대로 쓰기 어려운 표기 체계의 언어, (3) 같은 언어의 지역 변종 코드(pt·pt-BR 처럼)로 값이 다른 행. 생성한 행은 반드시 위 스키마를 통과해야 하며, chars_per_sec 는 0 보다 커야 합니다. 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"lang_code":{"type":"string"},"lang_name":{"type":"string"},"chars_per_sec":{"type":["number","null"]},"note":{"type":["string","null"]}},"required":["lang_code","lang_name"]}| TypeScript | 계산 규칙과 화면을 함께 쓰는 언어입니다. 글자 수와 낭독 속도로 시간을 내는 규칙이 여기 있습니다. |
| React | 영상 담당자가 보는 화면입니다. 구간 막대와 번역문을 한 화면에 둡니다. |
| Java | 작업과 자막을 담는 백엔드 API 입니다. |
| Spring Boot | 구간 조회와 판정 실행을 맡습니다. |
| MySQL | 작업과 언어별 자막을 담습니다. 원문과 번역문을 분리한 스키마가 계산의 기준입니다. |
| Azure | 3주차부터 배포와 실행을 맡습니다. |
| LLM | 판정이 끝난 뒤 어느 문장을 얼마나 줄여야 하는지 한 줄로 요약합니다. 시간은 규칙이 냅니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 실제 합성 결과와 예상 시간을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 문장을 고치는 즉시 다시 계산하는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
fit_code사유 라벨 · segment_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
fits구간 이내overflow구간 초과silent_gap무음 구간translation_missing번역 결측lang_unmapped언어 기준 없음record_missing기록 결측자막 구간 한 건의 길이 판정입니다. 판정 코드와 구간 길이와 예상 낭독 시간을 함께 남기고, 계산에 쓴 글자 수와 속도 계수를 근거로 답니다.
{"type":"object","properties":{"fit_id":{"type":"string"},"segment_no":{"type":"string"},"job_no":{"type":"string"},"fit_code":{"type":"string","enum":["fits","overflow","silent_gap","translation_missing","lang_unmapped","record_missing"]},"estimated_read_sec":{"type":["number","null"],"description":"예상 낭독 시간(초)입니다. char_count ÷ chars_per_sec 을 소수 첫째 자리로 반올림해 냅니다. chars_per_sec 는 lang_profile 에서 그 언어 행을 찾아 씁니다. 번역문이 없거나 속도 계수가 없으면 null 입니다."},"overflow_sec":{"type":["number","null"],"description":"구간을 넘긴 시간(초)입니다. estimated_read_sec − segment_sec 이며 0 이하이거나 예상 시간을 내지 못하면 null 입니다."},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"],"enum":["count","sum","avg","min","max",null]},"value":{"type":["string","number","null"]}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["fit_id","segment_no","job_no","fit_code","evidence","summary"]}[
{
"fit_id": "ft_0001",
"segment_no": "SG-4001",
"job_no": "JB-77",
"fit_code": "fits",
"estimated_read_sec": 2.8,
"overflow_sec": null,
"evidence": [
{
"field": "char_count",
"agg": null,
"value": 42
},
{
"field": "chars_per_sec",
"agg": null,
"value": 15
},
{
"field": "segment_sec",
"agg": null,
"value": 3.5
}
],
"summary": "영어 번역문이 구간 안에 들어갑니다.",
"hedge": null
},
{
"fit_id": "ft_0002",
"segment_no": "SG-4009",
"job_no": "JB-79",
"fit_code": "fits",
"estimated_read_sec": 1.9,
"overflow_sec": null,
"evidence": [
{
"field": "char_count",
"agg": null,
"value": 14
},
{
"field": "chars_per_sec",
"agg": null,
"value": 7.5
},
{
"field": "segment_sec",
"agg": null,
"value": 2.6
}
],
"summary": "일본어 문장이 짧아 구간에 여유가 있습니다.",
"hedge": null
},
{
"fit_id": "ft_0003",
"segment_no": "SG-4002",
"job_no": "JB-77",
"fit_code": "overflow",
"estimated_read_sec": 7.5,
"overflow_sec": 4.5,
"evidence": [
{
"field": "char_count",
"agg": null,
"value": 113
},
{
"field": "chars_per_sec",
"agg": null,
"value": 15
},
{
"field": "segment_sec",
"agg": null,
"value": 3
}
],
"summary": "예상 낭독이 7.5초라 3초 구간을 4.5초 넘깁니다.",
"hedge": "합성 기준 계산이라 실제 목소리 속도에 따라 달라질 수 있습니다."
},
{
"fit_id": "ft_0004",
"segment_no": "SG-4012",
"job_no": "JB-80",
"fit_code": "overflow",
"estimated_read_sec": 6.5,
"overflow_sec": 2.9,
"evidence": [
{
"field": "char_count",
"agg": null,
"value": 97
},
{
"field": "chars_per_sec",
"agg": null,
"value": 15
},
{
"field": "segment_sec",
"agg": null,
"value": 3.6
}
],
"summary": "가장 긴 번역문이라 구간을 2.9초 넘겨 문장을 줄여야 합니다.",
"hedge": null
},
{
"fit_id": "ft_0005",
"segment_no": "SG-4004",
"job_no": "JB-78",
"fit_code": "overflow",
"estimated_read_sec": 3.9,
"overflow_sec": 1.4,
"evidence": [
{
"field": "char_count",
"agg": null,
"value": 29
},
{
"field": "chars_per_sec",
"agg": null,
"value": 7.5
},
{
"field": "segment_sec",
"agg": null,
"value": 2.5
}
],
"summary": "설명 문장이 하나 더 붙어 구간을 1.4초 넘깁니다.",
"hedge": null
},
{
"fit_id": "ft_0006",
"segment_no": "SG-4008",
"job_no": "JB-79",
"fit_code": "silent_gap",
"estimated_read_sec": 0,
"overflow_sec": null,
"evidence": [
{
"field": "char_count",
"agg": null,
"value": 0
},
{
"field": "chars_per_sec",
"agg": null,
"value": 15
},
{
"field": "segment_sec",
"agg": null,
"value": 0
}
],
"summary": "말이 없는 구간이라 판정 대상에서 뺍니다.",
"hedge": null
},
{
"fit_id": "ft_0007",
"segment_no": "SG-4005",
"job_no": "JB-78",
"fit_code": "translation_missing",
"estimated_read_sec": null,
"overflow_sec": null,
"evidence": [
{
"field": "translated_text",
"agg": null,
"value": null
},
{
"field": "char_count",
"agg": null,
"value": null
},
{
"field": "segment_sec",
"agg": null,
"value": 2.8
}
],
"summary": "번역문이 아직 없어 길이를 계산하지 못합니다.",
"hedge": null
},
{
"fit_id": "ft_0008",
"segment_no": "SG-4006",
"job_no": "JB-78",
"fit_code": "lang_unmapped",
"estimated_read_sec": null,
"overflow_sec": null,
"evidence": [
{
"field": "lang_code_raw",
"agg": null,
"value": "vi"
},
{
"field": "chars_per_sec",
"agg": null,
"value": null
},
{
"field": "segment_sec",
"agg": null,
"value": 1.5
}
],
"summary": "속도 계수가 없는 언어라 예상 시간을 낼 수 없습니다.",
"hedge": null
},
{
"fit_id": "ft_0009",
"segment_no": "SG-4010",
"job_no": "JB-80",
"fit_code": "fits",
"estimated_read_sec": 1.9,
"overflow_sec": null,
"evidence": [
{
"field": "lang_code_raw",
"agg": null,
"value": "EN"
},
{
"field": "char_count",
"agg": null,
"value": 28
},
{
"field": "chars_per_sec",
"agg": null,
"value": 15
},
{
"field": "segment_sec",
"agg": null,
"value": 2.2
}
],
"summary": "언어 코드 표기가 달랐지만 정규화 후 영어 기준으로 계산해 구간에 들어갑니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. fit_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. fit_id·segment_no·job_no·fit_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. segment_sec 와 char_count 가 0 이상인지 확인합니다. 4. evidence 의 field 이름이 자막 데이터나 속도 계수표에 실재하는 컬럼인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 행의 값과 같은지 확인합니다. char_count·segment_sec·lang_code_raw 는 그 segment_no 행과, chars_per_sec 는 lang_profile 의 그 언어 행과 맞춥니다. 6. agg 를 적은 evidence 는 그 값이 count·sum·avg·min·max 다섯 개 중 하나인지 확인합니다. 계산 이름을 agg 자리에 적은 항목은 반려합니다. 7. estimated_read_sec 이 evidence 의 char_count 를 chars_per_sec 으로 나눠 소수 첫째 자리로 반올림한 값과 같은지 다시 계산합니다. 8. overflow_sec 이 estimated_read_sec 에서 segment_sec 을 뺀 값과 같은지 다시 계산합니다. 번역문이 비거나 속도 계수가 없으면 estimated_read_sec 과 overflow_sec 이 모두 null 인지 확인합니다. 9. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 이미 만든 영상의 실제 자막 길이 220건과 예상 낭독 시간을 비교해 언어별 오차를 내고, 그 결과로 lang_profile 의 chars_per_sec 를 조정합니다. [수동 검증 · 사람이 봅니다] 1. 구간을 조금 넘긴 문장을 줄일지 구간을 늘릴지 표본 20건을 눈으로 확인합니다. 2. 무음 구간에 자막을 남길지는 편집 기준을 따라 담당자가 정합니다.