/
index.html
234 lines (203 loc) · 12.2 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>worm finder</title>
<meta name="description" content="wormsss">
<meta name="author" content="Exr0n">
<meta property="og:title" content="worms worms worms">
<meta property="og:type" content="website">
<meta property="og:url" content="N/A">
<meta property="og:description" content="worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms worms">
<meta property="og:image" content="n/a">
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-700 overflow-scroll relative">
<div class="w-full fixed z-40">
<div id="saved-alert" class="w-1/6 bg-green-500 m-auto rounded-lg text-7xl text-white p-12 hidden text-center">saved to clipboard</div>
</div>
<div >
<video id="video" class="w-full" width="3584" height="2746" controls>
<source id="video-source" src="emacs">
</video>
<svg id="clickbox" class="absolute top-0 mt-0 w-full h-auto cursor-none" style="border: 1px solid red;" width="3584" height="2646" viewBox="0 0 3584 2646">
<path id="path-display" stroke-width="8" stroke="lime" style="fill: rgba(0, 0, 0, 0);"/>
<path id="path-display-onionskin" stroke-width="3" stroke="lime" style="fill: rgba(0, 0, 0, 0);"/>
<g id="generated-point-group"></g>
<g id="generated-skeletal-group"></g>
</svg>
</div>
<div class="z-40" style="fixed">
<div style="height: 2000px"></div>
<div class="z-40 p-8 w-2/6 ml-0 flex text-6xl text-blue-200 flex-col space-y-8">
<div class="font-mono text-4xl text-blue-200">left click to add a point<br>z or right click to undo<br>c to copy data to clipboard and continue</div>
<div><input class="text-5xl" id="input-file" type="file"></div>
<div>interval:<br><input class="w-3/5 h-24" onclick="this.select();" id="jump-seconds" type="number" value="1"><div id="jump-seconds-display" class="inline h-full text-center align-top p-12 text-6xl text-blue-200">jump seconds</div></div>
<div>skeletal distance:<br><input class="w-3/5 h-24" id="skeletal-distance" type="range" min="1" max="200" value="100"><div id="skeletal-distance-display" class="inline h-full text-center align-top p-12 text-6xl text-blue-200">dist</div></div>
<div><input id="use-constant-subdivision-predicate" type="checkbox" checked></input> use constant number of points per worm</div>
<div><input id="skeletal-divisions" onclick="this.select();" type="number" value="10"></input>points per worm</div>
<div>
timestamp: <input type="number" id="input-time-stamp" onclick="this.select();" class="inline w-1/4 p-4 border-2 border-blue-200 text-6xl text-blue-200"></input>
</div>
</div>
</div>
<script>
const box = document.getElementById('clickbox');
const video = document.getElementById('video');
const input_file = document.getElementById('input-file');
const input_jumpsecs = document.getElementById('jump-seconds');
const input_skeletal_distance = document.getElementById('skeletal-distance');
const input_curtime = document.getElementById('input-time-stamp');
const input_subdivide_predicate = document.getElementById('use-constant-subdivision-predicate')
const input_skeletal_divisions = document.getElementById('skeletal-divisions')
const display_jumpsecs = document.getElementById('jump-seconds-display');
const display_skeldist = document.getElementById('skeletal-distance-display');
const path_display = document.getElementById('path-display');
const path_display_onion = document.getElementById('path-display-onionskin');
const generated_group = document.getElementById('generated-point-group');
const generated_skeletal_group = document.getElementById('generated-skeletal-group');
const video_source_tag = document.getElementById('video-source');
const saved_alert = document.getElementById('saved-alert');
let SEGMENT_APPROX_LENGTH = 100;
input_skeletal_distance.addEventListener('mousemove', e => {
if (e.buttons == 1) {
const value = Math.ceil(Math.pow(e.target.value/10, 2));
SEGMENT_APPROX_LENGTH = value;
display_skeldist.innerHTML = value;
}
});
function createCircle(x, y, size=10, color="red") {
const circ = document.createElementNS('http://www.w3.org/2000/svg',"circle");
circ.setAttributeNS(null, "cx", x);
circ.setAttributeNS(null, "cy", y);
circ.setAttributeNS(null, "r", size);
circ.setAttributeNS(null, "fill", color);
return circ
}
function get_points(dom_path, segment_approx_length) {
const tot_len = dom_path.getTotalLength();
let points = [];
for (let consumed=0; consumed < tot_len; consumed += segment_approx_length) {
points.push(dom_path.getPointAtLength(consumed));
}
points.push(dom_path.getPointAtLength(tot_len));
return points;
}
function generate_skeleton(draw = true) {
while (generated_skeletal_group.firstChild) generated_skeletal_group.removeChild(generated_skeletal_group.lastChild);
let got = get_points(path_display, input_subdivide_predicate.checked ? path_display.getTotalLength()/(parseInt(input_skeletal_divisions.value)-1) : SEGMENT_APPROX_LENGTH);
if (!draw) return got;
for (let { x, y } of got) {
generated_skeletal_group.appendChild(createCircle(x, y, Math.max(Math.min(SEGMENT_APPROX_LENGTH/3, 10), 2), 'orange'));
//generated_skeletal_group.appendChild(createCircle(x, y, 5, 'orange'));
}
}
// STATE
let created_points = { };
let editing_video_timestamp = false;
let current_file = "null";
// output
let frames_list = [];
function update_frame() {
display_jumpsecs.innerHTML = parseFloat(input_jumpsecs.value);
if (! editing_video_timestamp) {
input_curtime.value = video.currentTime;
}
// if a line has been drawn
if (typeof created_points[video.currentTime] === "undefined") return;
if (created_points[video.currentTime]?.length < 1) return;
const lst = created_points[video.currentTime];
const path_string = `M ${lst[0].x} ${lst[0].y}` + lst.slice(1).map(p => `L ${p.x} ${p.y}`).join(" ");
path_display.setAttributeNS(null, "d", path_string);
path_display.setAttributeNS(null, "stroke-width", Math.max(Math.min(SEGMENT_APPROX_LENGTH/3, 8), 2))
generate_skeleton();
}
function undo_prev_point() {
if (typeof created_points[video.currentTime] === "undefined" || created_points[video.currentTime].length <= 0) return;
created_points[video.currentTime] = created_points[video.currentTime].slice(0, -1);
generated_group.removeChild(generated_group.lastChild);
}
function confirm_and_clear() {
if (typeof created_points[video.currentTime] === "undefined" || created_points[video.currentTime].length < 2) { alert("not enough points drawn!"); return; }
//<<<<<<< HEAD
// const gen_got = generate_skeleton(false).map(p => `${current_file} ${video.currentTime} ${p.x} ${p.y} `).join('\n');
// navigator.clipboard.writeText(gen_got).then(() => {
// alert("copied point data to clipboard");
// }).catch(() => {
//=======
const skel = generate_skeleton(false);
const center = skel.slice(0, -1).reduce(([cx, cy], {x, y}) => ([cx+x, cy+y]), [0, 0]);
frames_list.push(`${current_file} ${video.currentTime} ${center[0]/(skel.length-1)} ${center[1]/(skel.length-1)} `
+ skel.map(p => `${p.x} ${p.y}`).join(" "));
const output = "name time cx cy x1 y1 ... \n" + frames_list.join('\n');
navigator.clipboard.writeText(output).then(() => {
saved_alert.style.display = "block";
setTimeout(() => { saved_alert.style.display = "none"; }, 1000);
}).catch(e => {
console.error(e);
//>>>>>>> 5a6eff10aec1c69e180bfe97eed164d0f3354e5c
alert("failed to copy to clipboard. wrote in the console instead. is this site being served over https?");
});
console.log(output);
while (generated_group.firstChild) generated_group.removeChild(generated_group.lastChild);
path_display_onion.setAttributeNS(null, "d", path_display.getAttribute('d'));
path_display.setAttributeNS(null, "d", "");
video.currentTime = parseFloat(video.currentTime) + parseFloat(input_jumpsecs.value);
}
function nuke_data() {
confirm_and_clear();
created_points = {};
frames_list = [];
}
setInterval(update_frame, 100);
alert("zoom way out (eg 25%) (with cmd + minus) and choose a file to begin!");
input_file.addEventListener("input", e => {
console.log('loading file', e.target.files[0]);
current_file = e.target.files[0].name;
created_points = {};
video_source_tag.setAttribute('src', URL.createObjectURL(e.target.files[0]));
video.load();
});
box.addEventListener('mouseup', e => {
if (e.which != 1 || editing_video_timestamp) return;
const pos_x = (e.clientX + window.pageXOffset)/box.clientWidth*3584;
const pos_y = (e.clientY + window.pageYOffset)/box.clientHeight*2646;
//console.log(`${video.currentTime}, ${pos_x}, ${pos_y}`);
if (typeof created_points[video.currentTime] === "undefined") {
created_points[video.currentTime] = [];
}
created_points[video.currentTime].push({ x: pos_x, y: pos_y });
generated_group.appendChild(createCircle(pos_x, pos_y, Math.max(Math.min(SEGMENT_APPROX_LENGTH/2, 15), 3)));
});
input_curtime.addEventListener('click', e => {
editing_video_timestamp = true;
});
document.addEventListener('contextmenu', function(ev) {
ev.preventDefault();
undo_prev_point();
return false;
}, false);
function blur_timestamp(e) {
//let x = parseFloat(e.target.value.replace(/(<([^>]+)>)/gi, ""));
let x = parseFloat(e.target.value);
if (x != x)
alert(`invalid decimal number! ${e.target.value}`);
else
video.currentTime = x;
editing_video_timestamp = false;
e.preventDefault();
return false;
}
input_curtime.addEventListener('blur', blur_timestamp);
input_curtime.addEventListener('keyup', e => {
if (e.key == "Enter") e.target.blur();
});
document.addEventListener('keyup', e => {
if (e.key == "z") undo_prev_point();
if (e.key == "c") confirm_and_clear();
if (e.key == "x") nuke_data();
});
</script>
</body>
</html>