2505회 오프닝, 동영상 자막 판단기준 변경 [2-10,11,28,29]

This commit is contained in:
2025-06-13 17:03:43 +09:00
parent b17a33e1d7
commit 5c62e57cfb
7 changed files with 296 additions and 110 deletions

BIN
00_DIC_2505B_TEST.xlsx Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -181,6 +181,7 @@
} }
}, },
"2": { "2": {
"desc": "videoStartTime 항목은 동영상파일>자막>시작시간 문항의 정답을 작성",
"videoStartTime": 170, "videoStartTime": 170,
"openingStartTime": 0, "openingStartTime": 0,
"1": { "1": {
@@ -193,7 +194,7 @@
"이미지2.jpg" "이미지2.jpg"
], ],
"point": 4, "point": 4,
"desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5이하인 경우는 제외한다." "desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다."
}, },
"2": { "2": {
"ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed", "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed",
@@ -205,7 +206,7 @@
"desc": "100당 1배속 / 130 = 1.3배속" "desc": "100당 1배속 / 130 = 1.3배속"
}, },
"3": { "3": {
"ele": "//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']",
"type": "startEnd", "type": "startEnd",
"media": "동영상.mp4", "media": "동영상.mp4",
"value": { "value": {
@@ -216,19 +217,20 @@
"desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다." "desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다."
}, },
"4": { "4": {
"ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@ID='168'][@VID100='0.80000001'][@VID102='10']", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter",
"type": "", "type": "effect",
"media": "동영상.mp4",
"value": { "value": {
"ID": "168", "ID": "168",
"VID100": "0.80000001", "VID102": "10",
"VID102": "10" "VID100": "0.80000001"
}, },
"point": 3, "point": 3,
"desc": "/CROASTERP/CRTrackArr/CRVideoTrackArr/CRTrackList[1]/CRTrackClip[1]/CRFilterArr/CRFilter 요소의 속성값 확인" "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요"
}, },
"5": { "5": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", "ele": "//CRCUnitArr[@Name='{search}']/@Name",
"ele2": "//CRCUnitArr[@Name='{search}']/@Name", "ele2": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name",
"type": "video", "type": "video",
"value": "모래 촉감 놀이", "value": "모래 촉감 놀이",
"search": "모래 촉감 놀이", "search": "모래 촉감 놀이",
@@ -270,19 +272,17 @@
"point": 2 "point": 2
}, },
"10": { "10": {
"ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", "ele": "{search}",
"ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)",
"search": "모래 촉감 놀이", "search": "모래 촉감 놀이",
"type": "video", "type": "videoStartTime",
"value": 170, "value": 170,
"point": 2 "point": 2
}, },
"11": { "11": {
"ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", "ele": "{search}",
"ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length",
"search": "모래 촉감 놀이", "search": "모래 촉감 놀이",
"type": "video", "type": "videoLength",
"value": "150", "value": 150,
"point": 2 "point": 2
}, },
"12": { "12": {
@@ -438,7 +438,7 @@
}, },
"28": { "28": {
"ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", "ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)",
"ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)", "ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={textClipIndex}]/preceding-sibling::CRTrackClip/@Length)",
"search": "자연 놀이터 (Nature Playground)", "search": "자연 놀이터 (Nature Playground)",
"type": "opening", "type": "opening",
"value": 0, "value": 0,
@@ -446,7 +446,7 @@
}, },
"29": { "29": {
"ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", "ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length",
"ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length", "ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={textClipIndex}]/@Length",
"search": "자연 놀이터 (Nature Playground)", "search": "자연 놀이터 (Nature Playground)",
"type": "opening", "type": "opening",
"value": "120", "value": "120",

View File

@@ -181,40 +181,52 @@
} }
}, },
"2": { "2": {
"desc": "videoStartTime 항목은 동영상파일>자막>시작시간 문항의 정답을 작성",
"videoStartTime": 170, "videoStartTime": 170,
"openingStartTime": 0, "openingStartTime": 0,
"1": { "1": {
"ele": "//CRClipArr/CRClip[position() = //CRTrackList[1]/CRTrackClip/@ClipIndex]/@Path", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex",
"type": "array", "type": "mediaOrder",
"value": [ "value": [
"동영상.mp4", "동영상.mp4",
"이미지2.jpg", "이미지2.jpg",
"이미지3.jpg", "이미지3.jpg",
"이미지1.jpg" "이미지1.jpg"
], ],
"point": 4 "point": 4,
"desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다."
}, },
"2": { "2": {
"ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1][@Speed='120']", "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed",
"point": 2 "type": "oneAnswer",
"value": {
"speed": "120"
},
"point": 2,
"desc": "100당 1배속 / 130 = 1.3배속"
}, },
"3": { "3": {
"ele": "count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']",
"type": "startend", "type": "startEnd",
"media": "동영상.mp4",
"value": {
"start": "0", "start": "0",
"end": "380", "end": "360"
"point": 2 },
"point": 2,
"desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다."
}, },
"4": { "4": {
"ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@ID='70'][@VID100='30'][@VID101='20']", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter",
"type": "", "type": "effect",
"media": "동영상.mp4",
"value": { "value": {
"ID": "70", "ID": "70",
"VID100": "30", "VID100": "30",
"VID101": "20" "VID101": "20"
}, },
"point": 3, "point": 3,
"desc": "/CROASTERP/CRTrackArr/CRVideoTrackArr/CRTrackList[1]/CRTrackClip[1]/CRFilterArr/CRFilter 요소의 속성값 확인" "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요"
}, },
"5": { "5": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name",
@@ -260,19 +272,17 @@
"point": 2 "point": 2
}, },
"10": { "10": {
"ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", "ele": "{search}",
"ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)",
"search": "청량하고 시원한 폭포", "search": "청량하고 시원한 폭포",
"type": "video", "type": "videoStartTime",
"value": 170, "value": 170,
"point": 2 "point": 2
}, },
"11": { "11": {
"ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", "ele": "{search}",
"ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length",
"search": "청량하고 시원한 폭포", "search": "청량하고 시원한 폭포",
"type": "video", "type": "videoLength",
"value": "150", "value": 150,
"point": 2 "point": 2
}, },
"12": { "12": {
@@ -427,19 +437,18 @@
"point": 3 "point": 3
}, },
"28": { "28": {
"ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", "ele":"{search}",
"ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "openingStartTime",
"value": 0, "value": 0,
"point": 2 "point": 2,
"desc": "오프닝자막의 시작시간 value 속성만 수정"
}, },
"29": { "29": {
"ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", "ele":"{search}",
"ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length",
"search": "전통 공원 (Traditional Park)", "search": "전통 공원 (Traditional Park)",
"type": "opening", "type": "openingLength",
"value": "120", "value": 120,
"point": 2 "point": 2
}, },
"30": { "30": {

View File

@@ -186,37 +186,48 @@
"videoStartTime": 160, "videoStartTime": 160,
"openingStartTime": 0, "openingStartTime": 0,
"1": { "1": {
"ele": "//CRClipArr/CRClip[position() = //CRTrackList[1]/CRTrackClip/@ClipIndex]/@Path", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[not(@Length<='5' and @ClipLength='-1')]/@ClipIndex",
"type": "array", "type": "mediaOrder",
"value": [ "value": [
"동영상.mp4", "동영상.mp4",
"이미지2.jpg", "이미지2.jpg",
"이미지1.jpg", "이미지1.jpg",
"이미지3.jpg" "이미지3.jpg"
], ],
"point": 4 "point": 4,
"desc": "비디오1 트랙에 있는 클립의 ClipIndex값을 기준으로 CRClipArr에서 Path값을 가져와서 정답 채점, 클립의 ClipIndex값이 -1인 경우와 길이가 5프레임 이하인 경우는 제외한다."
}, },
"2": { "2": {
"ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1][@Speed='130']", "ele": "/CROASTERP/CRTrackArr[1]/CRVideoTrackArr[1]/CRTrackList[1]/CRTrackClip[1]/@Speed",
"point": 2 "type": "oneAnswer",
"value": {
"speed": "130"
},
"point": 2,
"desc": "100당 1배속 / 130 = 1.3배속"
}, },
"3": { "3": {
"ele": "count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']",
"type": "startend", "type": "startEnd",
"media": "동영상.mp4",
"value": {
"start": "0", "start": "0",
"end": "350", "end": "350"
"point": 2 },
"point": 2,
"desc": "시작시간과 재생시간 정답값 입력, 3번문항은 '동영상.mp4' 클립의 길이를 확인하는 문항이므로 media는 수정할 필요가 없다."
}, },
"4": { "4": {
"ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='동영상.mp4']/preceding-sibling::*)]//CRFilter[@ID='56'][@VID100='45'][@VID101='60']", "ele": "//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter",
"type": "", "type": "effect",
"media": "동영상.mp4",
"value": { "value": {
"ID": "56", "ID": "56",
"VID100": "45", "VID100": "45",
"VID101": "60" "VID101": "60"
}, },
"point": 3, "point": 3,
"desc": "/CROASTERP/CRTrackArr/CRVideoTrackArr/CRTrackList[1]/CRTrackClip[1]/CRFilterArr/CRFilter 요소의 속성값 확인" "desc": "value값의 키값(VID___)은 이펙트의 속성종류에 따라 변경되므로 채점기준표작성시 확인 필요"
}, },
"5": { "5": {
"ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name", "ele": "//CROwneUnit[{subtitleIndex}]/CRCUnitArr/@Name",
@@ -262,19 +273,17 @@
"point": 2 "point": 2
}, },
"10": { "10": {
"ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", "ele": "{search}",
"ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)",
"search": "연못의 연잎들", "search": "연못의 연잎들",
"type": "video", "type": "videoStartTime",
"value": 160, "value": 170,
"point": 2 "point": 2
}, },
"11": { "11": {
"ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", "ele": "{search}",
"ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length",
"search": "연못의 연잎들", "search": "연못의 연잎들",
"type": "video", "type": "videoLength",
"value": "120", "value": 120,
"point": 2 "point": 2
}, },
"12": { "12": {
@@ -427,19 +436,17 @@
"point": 3 "point": 3
}, },
"28": { "28": {
"ele": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/preceding-sibling::CRTrackClip/@Length)", "ele":"{search}",
"ele2": "sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/preceding-sibling::CRTrackClip/@Length)",
"search": "초록빛 이파리들 Green leaves", "search": "초록빛 이파리들 Green leaves",
"type": "opening", "type": "openingStartTime",
"value": 0, "value": 0,
"point": 2 "point": 2
}, },
"29": { "29": {
"ele": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][{subtitleOrder}]/@Length", "ele":"{search}",
"ele2": "//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@ClipIndex='-1')][@ClipIndex={clipIndex}]/@Length",
"search": "초록빛 이파리들 Green leaves", "search": "초록빛 이파리들 Green leaves",
"type": "opening", "type": "openingLength",
"value": "120", "value": 120,
"point": 2 "point": 2
}, },
"30": { "30": {

View File

@@ -15,8 +15,8 @@ const examRound = '2505';
const dic_or_dpi = 'DIC' const dic_or_dpi = 'DIC'
// const dic_or_dpi = 'DPI' // const dic_or_dpi = 'DPI'
const examTypes = [ const examTypes = [
'A', // 'A',
// 'B', 'B',
// 'C', // 'C',
// 'D' // 'D'
]; ];
@@ -172,7 +172,6 @@ outputExcelFiles.forEach((outputFile, index) => {
// scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐 // scoring.json 파일 내에 있는 type에 따라 비교하는 방식이 달라짐
// 채점 결과를 scoringResultList 배열에 저장 // 채점 결과를 scoringResultList 배열에 저장
function getGmepScore(gmepData, scoringJson, index) { function getGmepScore(gmepData, scoringJson, index) {
function compareAndScore(userAnswer, rightAnswer, point, key, scoringResult) { function compareAndScore(userAnswer, rightAnswer, point, key, scoringResult) {
let score = 0; let score = 0;
@@ -192,9 +191,6 @@ function getGmepScore(gmepData, scoringJson, index) {
scoringResult[key] = score; scoringResult[key] = score;
return score; return score;
} }
function getMediaOrderbyClipIndex(gmepXmlDoc, clipIndex) {
}
const gmepXmlDoc = gmepData; const gmepXmlDoc = gmepData;
const scoringResult = {}; const scoringResult = {};
@@ -207,6 +203,116 @@ function getGmepScore(gmepData, scoringJson, index) {
// 채점기준표 문항별 분류 // 채점기준표 문항별 분류
for (const key in scoringData) { for (const key in scoringData) {
function getClipIndexByMediaPath(mediaName) {
// CRClipArr/CRClip 요소의 Path속성 리스트를 구함
// 모션 클립 이미지도 고려해 처리
const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc);
// "동영상.mp4"의 clipIndex를 구함
const videoClipIndex = mediaPathList.findIndex(mediaPath => mediaPath.value === mediaName);
let xpathList = [ele, ele2];
xpathList = xpathList.map(e => e ? e
.replace(/{videoClipIndex}/g, videoClipIndex)
: e
);
[ele, ele2] = xpathList;
// clipIndex가 -1이면 해당 미디어가 존재하지 않는 것
return videoClipIndex;
}
// 자막 텍스트로 자막클립인덱스 반환
function getClipIndexByText(text) {
const crOwneUnits = xpath.select(`//CROwneUnitArr/CROwneUnit`, gmepXmlDoc);
let subtitleClipIndex = null;
// 자막 텍스트와 일치하는 요소의 인덱스를 반환
for (let i = 0; i < crOwneUnits.length; i++) {
const crcUnitArr = xpath.select1('.//CRCUnitArr', crOwneUnits[i]);
if (crcUnitArr && crcUnitArr.getAttribute('Name') === text) {
subtitleClipIndex = i;
break;
}
}
console.log('🟢 자막 텍스트로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex);
return subtitleClipIndex;
}
function getClipIndexByOrder(order) {
// 자막의 갯수가 2개 이상 (오프닝과 동영상 자막이 있을 경우)
// 앞은 오프닝 뒤는 동영상 자막으로 판단
// crTrackClips[0] : 오프닝 자막
// crTrackClips[1] : 동영상 자막
const crOwneUnits = xpath.select(`//CROwneUnitArr/CROwneUnit`, gmepXmlDoc);
const crTrackClips = xpath.select("//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[not(@Type='0') and not(@ClipIndex='-1')]", gmepXmlDoc);
let subtitleClipIndex = null;
if (subtitleClipIndex === null && crOwneUnits.length >= 2) {
// if (crOwneUnits.length >= 2) {
for (let i = 0; i < crTrackClips.length; i++) {
if ((order - 1) === i) {
subtitleClipIndex = parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10);
break;
}
}
}
console.log('🟡 자막 순서로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex);
return subtitleClipIndex;
}
// 자막들의 시작 시간을 가지는 리스트에서
// 구하고자 하는 영상의 시작시간(startTime)과 일치하는 시간이 있다면 인덱스를 가져와
// 자막의 인덱스를 구함
function getCilpIndexByStartTime(startTime) {
const crTrackClips = xpath.select("//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip", gmepXmlDoc);
const subtitleStartTimeList = getSubtitleStartTime();
const startTimeIndex = subtitleStartTimeList.findIndex(value => value === startTime);
let subtitleClipIndex = null;
for (let i = 0; i < crTrackClips.length; i++) {
if (parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10) == -1) {
continue;
}
const clipIndex = parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10);
if (startTimeIndex === i) {
subtitleClipIndex = clipIndex;
break;
}
}
console.log('🟠 자막 시작시간으로 검색한 CROwneUnit 인덱스 : ', subtitleClipIndex);
return subtitleClipIndex;
}
// 영상내 존재하는 자막과 자막사이 공백의 시작시간 리스트를 구함
function getSubtitleStartTime() {
const trackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc);
let cumulativeLengths = [];
let total = 0;
for (let i = 0; i < trackClips.length; i++) {
const length = parseInt(trackClips[i].getAttribute('Length'), 10);
cumulativeLengths.push(total);
total += length;
}
console.log("🔵 자막 구간 시작시간 : ", cumulativeLengths);
return cumulativeLengths;
}
function getCrtrackClipIndex(clipIndex) {
const crTrackClips = xpath.select(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip`, gmepXmlDoc);
let index = null;
for (let i = 0; crTrackClips.length; i++) {
if (clipIndex == parseInt(crTrackClips[i].getAttribute('ClipIndex'), 10)) {
index = i;
break;
}
}
return index;
}
let ele = scoringData[key].ele; let ele = scoringData[key].ele;
let ele2 = scoringData[key].ele2; let ele2 = scoringData[key].ele2;
let ele3 = scoringData[key].ele3; let ele3 = scoringData[key].ele3;
@@ -226,10 +332,16 @@ function getGmepScore(gmepData, scoringJson, index) {
// xpath 전처리 // xpath 전처리
const trackClipNode = getTrackClipNode(gmepXmlDoc, type, videoStartTime, openingStartTime); const trackClipNode = getTrackClipNode(gmepXmlDoc, type, videoStartTime, openingStartTime);
const subtitleIndex = trackClipNode ? parseInt(trackClipNode.getAttribute('ClipIndex'), 10) + 1 : null; const subtitleIndex = trackClipNode ? parseInt(trackClipNode.getAttribute('ClipIndex'), 10) + 1 : null;
const clipIndex = getClipIndexBySubtitle(gmepXmlDoc, search); const textClipIndex = getTextClipIndex(gmepXmlDoc, search);
// const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null; const typeToOrderMap = {
// 2503회 문제오류 처리를 위한 임시 변경 opening: 1,
const subtitleOrder = type === 'video' ? 2 : type === 'opening' ? 1 : null; openingStartTime: 1,
openingLength: 1,
video: 2,
videoStartTime: 2,
videoLength: 2,
};
const subtitleOrder = typeToOrderMap[type] ?? null;
const startTime = type === 'video' ? videoStartTime : type === 'opening' ? openingStartTime : null; const startTime = type === 'video' ? videoStartTime : type === 'opening' ? openingStartTime : null;
let xpathList = [ele, ele2, ele3, existEle]; let xpathList = [ele, ele2, ele3, existEle];
@@ -237,17 +349,12 @@ function getGmepScore(gmepData, scoringJson, index) {
.replace(/{subtitleIndex}/g, subtitleIndex) .replace(/{subtitleIndex}/g, subtitleIndex)
.replace(/{subtitleOrder}/g, subtitleOrder) .replace(/{subtitleOrder}/g, subtitleOrder)
.replace(/{startTime}/g, startTime) .replace(/{startTime}/g, startTime)
.replace(/{clipIndex}/g, clipIndex) .replace(/{textClipIndex}/g, textClipIndex)
.replace(/{image}/g, image) .replace(/{image}/g, image)
: e : e
); );
[ele, ele2, ele3, existEle] = xpathList; [ele, ele2, ele3, existEle] = xpathList;
// [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{subtitleIndex}/g, subtitleIndex));
// [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{subtitleOrder}/g, subtitleOrder));
// [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{startTime}/g, startTime));
// [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.replace(/{clipIndex}/g, clipIndex));
// search 값이 undefined 아니면 ele의 {search}부분을 search로 치환 // search 값이 undefined 아니면 ele의 {search}부분을 search로 치환
/** /**
* JSON파일 곰믹스 5번문항/22번 문항 * JSON파일 곰믹스 5번문항/22번 문항
@@ -260,11 +367,9 @@ function getGmepScore(gmepData, scoringJson, index) {
let result = findSimilarString(gmepXmlDoc, search, 0.8); let result = findSimilarString(gmepXmlDoc, search, 0.8);
if (result !== null) { if (result !== null) {
result = result.replace(/"/g, "'"); result = result.replace(/"/g, "'");
[ele, ele2, ele3, existEle] [ele, ele2, ele3, existEle] = [ele, ele2, ele3, existEle].map(e => e?.replace(/{search}/g, result));
= [ele, ele2, ele3, existEle].map(e => e?.replace(/{search}/g, result));
} else { } else {
[ele, ele2, ele3] [ele, ele2, ele3] = [ele, ele2, ele3].map(e => e?.includes('{search}') ? null : e);
= [ele, ele2, ele3].map(e => e?.includes('{search}') ? null : e);
} }
} }
@@ -305,20 +410,24 @@ function getGmepScore(gmepData, scoringJson, index) {
// 미디어의 인덱스 순서 // 미디어의 인덱스 순서
const clipIndexOrder = xpath.select(ele, gmepXmlDoc); const clipIndexOrder = xpath.select(ele, gmepXmlDoc);
clipIndexOrder.forEach((clipIndex) => { clipIndexOrder.forEach((clipIndex) => {
CRClipIndex = parseInt(clipIndex.value, 10) + 1; // XPath는 1-based index를 사용 CRClipIndex = parseInt(clipIndex.value, 10) + 1; // XPath는 1-based index를 사용
// 인덱스 순서에 따른 CRClip 요소의 Path를 찾기 // 인덱스 순서에 따른 CRClip 요소의 Path를 찾기
const mediaPath = xpath.select1(`//CRClipArr/CRClip[${CRClipIndex}]/@Path`, gmepXmlDoc); const mediaPath = xpath.select1(`//CRClipArr/CRClip[${CRClipIndex}]/@Path`, gmepXmlDoc);
// 만약 CRClip 요소가 motion clip인 경우 CRCUnitArr의 Path를 찾기 // 만약 CRClip 요소가 motion clip인 경우 CRCUnitArr의 Path를 찾기
if (mediaPath == null) { if (mediaPath == null) {
const motionClipPath = xpath.select1(`//CRClipArr/CRClip[${CRClipIndex}]/CRCUnitArr/@Path`, gmepXmlDoc); const motionClipPath = xpath.select1(`//CRClipArr/CRClip[${CRClipIndex}]/CRCUnitArr/@Path`, gmepXmlDoc);
if (motionClipPath !== null) { if (motionClipPath !== null) {
mediaOrderList.push(motionClipPath.value); const fileName = path.basename(motionClipPath.value);
mediaOrderList.push(fileName);
} }
} }
else if (mediaPath != null) { else if (mediaPath != null) {
mediaOrderList.push(mediaPath.value); const fileName = path.basename(mediaPath.value);
mediaOrderList.push(fileName);
} }
}); });
const userAnswer = mediaOrderList; const userAnswer = mediaOrderList;
@@ -326,36 +435,97 @@ function getGmepScore(gmepData, scoringJson, index) {
} }
else if (type == "startEnd") { else if (type == "startEnd") {
// CRClipArr/CRClip 요소의 Path속성 리스트를 구함 const videoClipIndex = getClipIndexByMediaPath(media);
// 모션 클립 이미지도 고려해 처리
const mediaPathList = xpath.select("//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path", gmepXmlDoc);
// "동영상.mp4"의 clipIndex를 구함 // 해당 미디어가 없을경우 clipIndex값 -1
let clipIndex = mediaPathList.findIndex(mediaPath => mediaPath.value === media); if (videoClipIndex == -1) {
// clipIndex가 -1이면 해당 미디어가 존재하지 않는 것
if (clipIndex === -1) {
scoringResult[key] = 0; scoringResult[key] = 0;
continue; continue;
} }
else { else {
// //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='동영상.mp4'] 요소를 찾음 // //CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='동영상.mp4'] 요소를 찾음
const trackClipNode = xpath.select1(`//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='${clipIndex}']`, gmepXmlDoc); const CRTrackClipNode = xpath.select1(ele, gmepXmlDoc);
if (!trackClipNode) { if (!CRTrackClipNode) {
scoringResult[key] = 0; scoringResult[key] = 0;
continue; continue;
} }
else { else {
// CRTrackClip 요소의 Pos(시작시간)과 Length(재생길이)를 구함 // CRTrackClip 요소의 Pos(시작시간)과 Length(재생길이)를 구함
const pos = xpath.select1('@Pos', trackClipNode); const pos = xpath.select1('@Pos', CRTrackClipNode);
const length = xpath.select1('@Length', trackClipNode); const length = xpath.select1('@Length', CRTrackClipNode);
const userAnswer = { start: pos.value, end: length.value } const userAnswer = { start: pos.value, end: length.value }
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult); totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
} }
} }
}
else if (type == "effect") {
const videoClipIndex = getClipIndexByMediaPath(media);
if (videoClipIndex == -1) {
scoringResult[key] = 0;
continue;
}
else {
//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='{videoClipIndex}']//CRFilter
const CRFilterNode = xpath.select1(ele, gmepXmlDoc);
if (!CRFilterNode) {
scoringResult[key] = 0;
continue;
}
else {
const userAnswer = {}
const attributes = CRFilterNode.attributes;
// rightAnswer의 key값을 순회하면서
// CRFilterNode의 속성명과 일치하는 값을 userAnswer에 저장
// for (const [keyName, expectedValue] of Object.keys(rightAnswer)) {
for (const keyName of Object.keys(rightAnswer)) {
const attr = attributes.getNamedItem(keyName);
userAnswer[keyName] = attr ? attr.value : null;
}
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
}
}
}
else if (type === 'openingStartTime' || type === 'openingLength'
|| type === 'videoStartTime' || type === 'videoLength') {
// 자막의 정보를 이용해 CROwneUnit의 인덱스를 구함
// 1. 텍스트
// 2. 순서
// 3. 시작시간
const indexByText = getClipIndexByText(ele);
const indexByOrder = getClipIndexByOrder(subtitleOrder);
if (type.includes('opening')) time = openingStartTime;
else if (type.includes('video')) time = videoStartTime;
else time = null;
const indexByStartTime = getCilpIndexByStartTime(time);
// 1, 2, 3순으로 자막을 찾음
const index = indexByText ?? indexByOrder ?? indexByStartTime;
if (index != null) {
// 자막 시작시간
if (type.includes('StartTime')) {
const crtrackClipIndex = getCrtrackClipIndex(index)
const startTimeList = getSubtitleStartTime();
const startTime = startTimeList[crtrackClipIndex];
userAnswer = startTime;
}
// 자막 길이
else if (type.includes('Length')) {
const crtrackClipIndex = getCrtrackClipIndex(index) + 1 // XML 1-based index
const clipLength = xpath.select1(`//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[${crtrackClipIndex}]/@Length`, gmepXmlDoc);
userAnswer = parseInt(clipLength.value, 10);
}
}
else {
userAnswer = null;
}
totalScore += compareAndScore(userAnswer, rightAnswer, point, key, scoringResult);
} }
else if (type == "color") { else if (type == "color") {
@@ -468,7 +638,7 @@ function getGmepScore(gmepData, scoringJson, index) {
// 찾으려는 자막이 존재하지 않는 경우 // 찾으려는 자막이 존재하지 않는 경우
// (2-28) 문항의 경우 오프닝 자막이 없어도 xpath구문의 sum함수 결과값이 0이 반환되는것을 방지 // (2-28) 문항의 경우 오프닝 자막이 없어도 xpath구문의 sum함수 결과값이 0이 반환되는것을 방지
if (trackClipNode === undefined && clipIndex === null) { if (trackClipNode === undefined && textClipIndex === null) {
scoringResult[key] = 0; scoringResult[key] = 0;
continue; continue;
} }
@@ -703,7 +873,7 @@ function getTrackClipNode(xmlDoc, type, videoStartTime, openingStartTime) {
* 1. 자막 텍스트의 유사도를 판별 * 1. 자막 텍스트의 유사도를 판별
* 2. 자막텍스트와 일치하는 자막요소(CROWneUnit)의 순서를 구함 * 2. 자막텍스트와 일치하는 자막요소(CROWneUnit)의 순서를 구함
*/ */
function getClipIndexBySubtitle(xmlDoc, search) { function getTextClipIndex(xmlDoc, search) {
// 1. search값이 일치하지 않는 경우 : count가 0이 되어 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [오류] // 1. search값이 일치하지 않는 경우 : count가 0이 되어 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [오류]
// 2. search값이 일치하는 경우 // 2. search값이 일치하는 경우
// 1) search값이 CROwneUnit[1]이면 : preceding-sibling::CROwneUnit이 없어서 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [정상] // 1) search값이 CROwneUnit[1]이면 : preceding-sibling::CROwneUnit이 없어서 @ClipIndex = 0 / CROwneUnit[1]을 가리킴 [정상]

View File

@@ -1 +1 @@
[{"kind":2,"language":"xpath","value":"//Layer[Name[@value='Tracking']]/Effects/Item/Name/@value"},{"kind":2,"language":"xpath","value":"sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[@ClipIndex=0]/preceding-sibling::CRTrackClip/@Length)"},{"kind":2,"language":"xpath","value":"//CROwneUnit/CRCUnitArr[@Name=\"아름다운 꽃 축제 (Happy Flower Festival)\"]/@Name"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item[EffectData/{option}]/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value\r\n"},{"kind":2,"language":"xpath","value":"//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex='1']"},{"kind":2,"language":"xpath","value":"//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path"}] [{"kind":2,"language":"xpath","value":"//Layer[Name[@value='Tracking']]/Effects/Item/Name/@value"},{"kind":2,"language":"xpath","value":"sum(//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[@ClipIndex=0]/preceding-sibling::CRTrackClip/@Length)"},{"kind":2,"language":"xpath","value":"//CROwneUnit/CRCUnitArr[@Name=\"아름다운 꽃 축제 (Happy Flower Festival)\"]/@Name"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item[EffectData/{option}]/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value"},{"kind":2,"language":"xpath","value":"//Layer[Name[@value='{layer}']]/Effects/Item/Name/@value | //Layer[Name[@value='{layer}']]/Effects/Item/EffectData/{option}/@value\r\n"},{"kind":2,"language":"xpath","value":"//CRTransFilter[@ClipIndex=count(//CRTrackList[@Name='비디오1']/CRTrackClip[@ClipIndex=count(//CRClip[@Path='이미지3.jpg']/preceding-sibling::CRClip | //CRClip[@Type='11']/CRCUnitArr[@Path='이미지3.jpg']/../preceding-sibling::CRClip)][1]/preceding-sibling::CRTrackClip)][@Type='2']/@*[name()='ID' or name()='Range' or name()='Type']"},{"kind":2,"language":"xpath","value":"//CRTrackList[@Name='텍스트' or @Name='비디오2']/CRTrackClip[1]/@Length"},{"kind":2,"language":"xpath","value":"//CRClipArr/CRClip[@Type='11']/CRCUnitArr/@Path | //CRClipArr/CRClip[not(@Type='11')]/@Path"}]