Compare commits

..

67 Commits

Author SHA1 Message Date
Jan
08c78bb568 Merge pull request 'Adding disclaimer as seperate site' (#17) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #17
2024-11-19 16:00:02 +01:00
Jan
807d104d06 Adding disclaimer as seperate site 2024-11-19 15:58:23 +01:00
Jan
09d26bd73f Merge pull request 'Added new disclaimer Video' (#16) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #16
2024-11-19 14:24:22 +01:00
Jan
053aa4bd5a Added new disclaimer Video 2024-11-19 14:22:55 +01:00
Jan
1d1f5be753 Merge pull request 'Removed the hopefuly last placeholdervideo' (#15) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #15
2024-11-13 11:22:40 +01:00
Jan
40e24501f6 Removed the hopefuly last placeholdervideo 2024-11-13 11:16:41 +01:00
Jan
aedc1cf9ea Merge pull request 'letzte kleine fixes' (#14) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #14
2024-11-13 11:11:57 +01:00
Jan
522d10f422 Small file naming typo fixed 2024-11-13 11:05:52 +01:00
Jan
789d49a2c9 Fixed error in p3info text 2024-11-13 11:02:41 +01:00
Jan
f548c60157 Merge pull request 'final study push (hopefully)' (#13) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #13
2024-11-12 16:19:17 +01:00
Jan
86c5f3fdea Final changes (deploy without exp videos) 2024-11-12 16:17:07 +01:00
Jan
26c28cd0da 0044 as learning phase video now 2024-10-23 15:51:42 +02:00
Jan
da2c959ba7 Merge pull request 'new Videos and task 3 Text corrected' (#12) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #12
2024-10-23 14:22:04 +02:00
Jan
e1b814da4f new Videos and task 3 Text corrected 2024-10-23 14:20:07 +02:00
Jan
df80e62707 Merge pull request 'video management' (#11) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #11
2024-10-13 16:14:08 +02:00
Jan
edcd32db47 small improvement to video management page 2024-10-13 16:13:03 +02:00
Jan
e253586467 video management page now exists 2024-10-13 16:09:42 +02:00
Jan
45d2874999 Merge pull request 'More fixes, those are the final texts !!!' (#10) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #10
2024-10-13 15:43:46 +02:00
Jan
728c282a3d try to remove database via git 2024-10-13 15:42:25 +02:00
Jan
23e6ee55c6 multiple choice bug fixed 2024-10-13 15:29:30 +02:00
Jan
52db4cc0fb fixed small bug 2024-10-13 15:22:18 +02:00
Jan
2702ce3c3c password is checked via hash now 2024-10-13 15:18:14 +02:00
Jan
ab27d70946 More fixes, those are the final texts !!! 2024-10-07 14:05:43 +02:00
Jan
39bf5861b8 Merge pull request 'small fixes' (#9) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #9
2024-10-07 12:18:33 +02:00
Jan
73fc70917c Small fixes 2024-10-07 12:16:42 +02:00
Jan
8898f7dfb8 root page now redirects to start, /all_links exists 2024-10-07 10:29:54 +02:00
Jan
9bbbd39c9b Merge pull request 'added impressum page' (#8) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #8
2024-10-07 10:18:59 +02:00
Jan
aa2fa44207 added impressum page 2024-10-07 10:17:53 +02:00
Jan
a329baeb78 Merge pull request 'Feedback von Ella' (#7) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #7
2024-10-03 17:41:58 +02:00
Jan
15ae63d48b Feedback von Ella 2024-10-02 15:54:24 +02:00
Jan
97cb17733e Merge pull request 'working' (#6) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #6
2024-10-02 13:23:44 +02:00
Jan
0d92d281e1 New Email: avatarstudy@proton.me as contact 2024-10-02 13:19:12 +02:00
Jan
38e9c9f7ed Can now export the db 2024-10-02 13:03:32 +02:00
Jan
3ca3307528 gitignore the exports 2024-10-02 12:54:15 +02:00
Jan
666a92ecd0 Now with calibration phase 2024-10-02 12:46:24 +02:00
Jan
64d89a8821 Merge pull request 'working' (#5) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
2024-10-02 12:25:24 +02:00
Jan
ecf5531026 Some more finetuning of the study 2024-10-02 12:24:14 +02:00
Jan
8edfd3ef2f slight text adjustment 2024-09-23 15:56:35 +02:00
Jan
f29ba7c8e0 Merge pull request 'update für Feedback' (#4) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #4
2024-09-23 15:17:21 +02:00
Jan
5eed494c16 Some finetuning before feedback 2024-09-23 15:15:32 +02:00
Jan
c4e312662c Video timer now implemented (videos stop after 75 seconds) 2024-09-21 11:50:34 +02:00
Jan
785b4c4ff5 Some adjustments to video sizes, max record length is now 80sek 2024-09-19 16:50:19 +02:00
Jan
915b69a775 Merge pull request 'working into main, userstudy mostly finished' (#3) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #3
2024-09-19 15:26:30 +02:00
Jan
c630f4e54b userstudy1 finalized, also some bug fixes and small improvements 2024-09-19 15:22:33 +02:00
Jan
664c5bc1af New feature: infovide text can be html block now 2024-09-18 17:53:59 +02:00
Jan
428a9b0b44 Fixed a but with singlepages in the startpage backend 2024-09-18 16:51:57 +02:00
Jan
f4dd20e76d Added into page, some more progress and a small bugfixtypo 2024-09-18 16:40:10 +02:00
Jan
8a1c7aff42 More UI improvements 2024-09-17 17:03:44 +02:00
Jan
91802c2ecd small corrections 2024-09-15 18:42:40 +02:00
Jan
3c37288476 Small typo/bugfix and more progress 2024-09-15 18:17:42 +02:00
Jan
5ad3676b7f progress on studytest css 2024-09-15 18:12:21 +02:00
Jan
4c6ec411b1 startpage text is set up now 2024-09-13 16:39:24 +02:00
Jan
f0526555f1 Start 2024-09-13 16:38:43 +02:00
Jan
ceb5e0bec9 Added Datenschutzerklärung Page 2024-09-12 18:45:33 +02:00
Jan
d758e97001 Videos are now locally hosted, it works 2024-09-12 18:13:40 +02:00
Jan
cdbb57d50d multiple choice implemented + Dem Quest implemented 2024-09-05 16:36:44 +02:00
Jan
cf630bc040 Merge pull request 'Cleanup, now redirects work again' (#2) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #2
2024-08-20 17:12:40 +02:00
Jan
73127d9950 Redirects uncommented again 2024-08-20 17:10:25 +02:00
Jan
7e331ba91b Leafblade git instructions in readme 2024-08-20 17:04:39 +02:00
Jan
fb82d4c19a Merge pull request 'Merging Latest changes into main' (#1) from working into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #1
2024-08-20 17:03:27 +02:00
56a20f4465 Merge branch 'working' into 'main'
Working again

See merge request JanDickmann/masterproject!4
2024-08-20 14:37:14 +00:00
Jan
3eac9d64cb Fixed Video upload not working 2024-08-20 16:29:51 +02:00
Jan
54e805ec50 removing comments again, but still not working 2024-08-20 15:48:05 +02:00
Jan
b1e939adeb Trying to fix video upload (not working at all yet) 2024-08-20 15:30:38 +02:00
Jan
dfb37e4a86 typo fixed 2024-08-07 13:56:15 +02:00
Jan
63ffaf02a6 Added documentation to videoscript.js 2024-07-18 14:20:44 +02:00
be84a7e8b3 Add CI
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-02 12:00:33 +02:00
55 changed files with 3970 additions and 214 deletions

30
.drone.yml Normal file
View File

@@ -0,0 +1,30 @@
kind: pipeline
name: default
steps:
- name: deploy
image: registry.eterna.leafbla.de/pelipper
settings:
ssh_key:
from_secret: rsync_key
source: slaeforms/
destination: pelipper@oreburgh.leafbla.de:/srv/docker/janapp/slaeforms/
- name: restart
image: registry.eterna.leafbla.de/drowzee
settings:
host: oreburgh.leafbla.de
username: psyduck
key:
from_secret: deploy_key
command: |
cd /srv/docker/janapp;
docker compose down;
docker compose up --build -d;
trigger:
branch:
- main
image_pull_secrets:
- docker_config

2
.gitignore vendored
View File

@@ -6,6 +6,8 @@ __pycache__/
instance/ instance/
uploads/ uploads/
zip_exports/
exports/
.pytest_cache/ .pytest_cache/
.coverage .coverage

View File

@@ -6,3 +6,14 @@ build the dockerfile with: docker build -t slaeforms .
run the container on port 8000 with: docker run -d -p 8000:8000 slaeforms run the container on port 8000 with: docker run -d -p 8000:8000 slaeforms
export the container: docker save -o slaeforms.tar slaforms:latest export the container: docker save -o slaeforms.tar slaforms:latest
Pushing code from desktop to leafblade:
See remotes: git remote -v
git push leafblade working //push working branch
then create a pull request on leafblade and merge everything

View File

@@ -11,13 +11,13 @@ import uuid
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import select, join from sqlalchemy import select, join
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import DeclarativeBase
from flask_wtf.csrf import CSRFProtect #from flask_wtf.csrf import CSRFProtect
import os import os
import csv import csv
from zipfile import ZipFile from zipfile import ZipFile
import hashlib
random_order = True # activate environment: cd C:\...\...\....\...\Code\SLAEForms Testing\.venv\Scripts\
# activate environment: cd C:\Users\Jan\Google Drive\Master Stuff\Code\SLAEForms Testing\.venv\Scripts\
# then this: activate # then this: activate
#SETUP-------------------------------------------------- #SETUP--------------------------------------------------
@@ -33,6 +33,7 @@ app = Flask(__name__)
# configure the database, give it a path (it will be in the instances folder) # configure the database, give it a path (it will be in the instances folder)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db" app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
app.config["PERMANENT_SESSION_LIFETIME"] = 10800 #3 Stunden, 10800 sekunden app.config["PERMANENT_SESSION_LIFETIME"] = 10800 #3 Stunden, 10800 sekunden
app.config['MAX_CONTENT_LENGTH'] = 22 * 1000 * 1000 # try and fix video upload not working
db.init_app(app) db.init_app(app)
#set the secret key (TODO change this for final deployment) #set the secret key (TODO change this for final deployment)
@@ -40,7 +41,10 @@ app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a5
UPLOAD_FOLDER = 'uploads' UPLOAD_FOLDER = 'uploads'
EXPORT_FOLDER = 'exports' EXPORT_FOLDER = 'exports'
PASSWORD = '#1ACGmsjd' PASSWORD = 'd5aff9fc14d1f20f4ccddaa8b4f2c1765228b74ed0b1dfb868bf1064e0d655e2'
CONFIGFILE = 'userstudy1.json'
# CONFIGFILE = 'test.json'
# CONFIGFILE = 'default.json'
#csrf = CSRFProtect(app) #enable CSRF protection globally #csrf = CSRFProtect(app) #enable CSRF protection globally
@@ -70,7 +74,7 @@ except SQLAlchemyError as e:
#open, parse and execute json file #open, parse and execute json file
#open the json file with the config #open the json file with the config
configfile = open("default.json", encoding='utf-8') #todo replace with other name configfile = open(CONFIGFILE, encoding='utf-8') #todo replace with other name
#convert it to dict #convert it to dict
config = json.load(configfile) config = json.load(configfile)
configfile.close() configfile.close()
@@ -182,11 +186,12 @@ def startpage():
for name in block_names: for name in block_names:
print("block: ",name) print("block: ",name)
if config[name]["type"] == "TaskTemplate" and ("stimuli" in current_block): if config[name]["type"] == "TaskTemplate" and ("stimuli" in config[name]):
match config[name]["stimuli"]["type"]: match config[name]["stimuli"]["type"]:
case "single_video": case "single_video":
order = list(config[name]["stimuli"]["list"]) # order = list of simuli keys order = list(config[name]["stimuli"]["list"]) # order = list of simuli keys
print("order: ",order) print("order: ",order)
if "order" in config[name]["stimuli"]:
if config[name]["stimuli"]["order"] == "random": if config[name]["stimuli"]["order"] == "random":
random.shuffle(order) #in random order random.shuffle(order) #in random order
session["block_order"][name] = order session["block_order"][name] = order
@@ -233,7 +238,7 @@ def startpage():
return redirect("/form") return redirect("/form")
return render_template( return render_template(
"teststartpage.html" "startpage.html"
) )
@app.route("/endpage") @app.route("/endpage")
@@ -241,6 +246,18 @@ def endpage():
print("Form is done, sent to endpage") print("Form is done, sent to endpage")
return render_template("endpage.html") return render_template("endpage.html")
@app.route("/datenschutz")
def datenschutz():
return render_template("datenschutz.html")
@app.route("/impressum")
def impressum():
return render_template("impressum.html")
@app.route("/studytest")
def studytest():
return render_template("studytest.html")
@app.route("/form") @app.route("/form")
def form(): def form():
@@ -349,7 +366,7 @@ def sendpage():
video = request.files['recordedVideo'] video = request.files['recordedVideo']
formatted_date = date.strftime("%Y.%m.%d %H-%M-%S") formatted_date = date.strftime("%Y.%m.%d %H-%M-%S")
print("date: ", date) print("date: ", date)
video_name = str(session_user_id) + "_" + session["current_block_name"] + "_" + session["current_stimulus_name"] + "_" + str(formatted_date) + ".webm" video_name = str(session_user_id) + "_" + session["current_block_name"] + "_" + str(session["current_stimulus_name"]) + "_" + str(formatted_date) + ".webm"
path = os.path.join(UPLOAD_FOLDER, video_name) path = os.path.join(UPLOAD_FOLDER, video_name)
print("path: ",path) print("path: ",path)
os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(UPLOAD_FOLDER, exist_ok=True)
@@ -360,7 +377,19 @@ def sendpage():
setattr(new_entry, "video_upload", video_name) setattr(new_entry, "video_upload", video_name)
for key, value in request.form.items(): # TODO maybe find a prettier solution, this handeles multiple choice now, so the fact that there can be
# multiple keys that are the same in the form data, but I want to bring them together to 1 key value pair
form_data = {}
for key in request.form:
values = request.form.getlist(key)
# If there's more than one value for the key, join them with commas
if len(values) > 1:
form_data[key] = ','.join(map(str, values)) # Join multiple values into a single comma-separated string
else:
form_data[key] = values[0] # If only one value, store it directly
for key, value in form_data.items():
print("hasattr key: ", key) print("hasattr key: ", key)
if hasattr(new_entry, key): if hasattr(new_entry, key):
print("key exists: ", key) print("key exists: ", key)
@@ -374,16 +403,6 @@ def sendpage():
print("Error occurred: {e}".format(e=str(e))) print("Error occurred: {e}".format(e=str(e)))
return "There was a problem while adding the response to the Database" return "There was a problem while adding the response to the Database"
# handle possible Video that was send
if 'recordedVideo' in request.files:
video = request.files['recordedVideo']
formatted_date = date.strftime("%Y.%m.%d %H-%M-%S")
print("date: ", date)
video_name = str(session_user_id) + "_" + session["current_block_name"] + "_" + session["current_stimulus_name"] + "_" + str(formatted_date) + ".webm"
path = os.path.join(UPLOAD_FOLDER, video_name)
print("path: ",path)
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
video.save(path)
if (session["current_block_index"] == session["number_of_blocks"]-1) and (session["current_stimulus_index"] >= session["number_of_stimuli"]-1): if (session["current_block_index"] == session["number_of_blocks"]-1) and (session["current_stimulus_index"] >= session["number_of_stimuli"]-1):
#update the database entry, the form is completed #update the database entry, the form is completed
@@ -435,10 +454,19 @@ def update_session():
session["current_block_index"] += 1 session["current_block_index"] += 1
session["current_block_name"] = session["block_names"][session["current_block_index"]] session["current_block_name"] = session["block_names"][session["current_block_index"]]
if "stimuli" in config[session["current_block_name"]]: #set values for the new block
current_block = config[session["current_block_name"]]
if "stimuli" in current_block:
# set the name of the current stimulus # set the name of the current stimulus
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]] session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
# if the block has stimuli, get how many
if current_block["stimuli"]["type"] == "single_video" or current_block["stimuli"]["type"] == "empty":
session["number_of_stimuli"] = len(list(current_block["stimuli"]["list"]))
elif current_block["stimuli"]["type"] == "double_video":
session["number_of_stimuli"] = len(list(current_block["stimuli"]["list_1"]))
print("---Session updated-----------------------------------------------") print("---Session updated-----------------------------------------------")
print("current_block_index / number_of_blocks: {current_block_index} / {number_of_blocks}".format(current_block_index=session["current_block_index"],number_of_blocks=session["number_of_blocks"])) print("current_block_index / number_of_blocks: {current_block_index} / {number_of_blocks}".format(current_block_index=session["current_block_index"],number_of_blocks=session["number_of_blocks"]))
print("current_block_name: ", session["current_block_name"]) print("current_block_name: ", session["current_block_name"])
@@ -493,6 +521,17 @@ def export_all_tables():
return send_file("zip_exports/all_tables.zip", as_attachment=False, download_name="all_tables.zip") return send_file("zip_exports/all_tables.zip", as_attachment=False, download_name="all_tables.zip")
# export Database
@app.route("/export_db")
def export_db():
if not session.get("logged_in"):
return redirect("/login")
with ZipFile('zip_exports/db.zip', 'w') as zipf: #no compression, need to add zipfile.ZIP_DEFLATED for compression
zipdir('instance/', zipf)
return send_file("zip_exports/db.zip", as_attachment=False, download_name="db.zip")
# export CSV # export CSV
@app.route("/export_all_videos") @app.route("/export_all_videos")
def export_all_videos(): def export_all_videos():
@@ -502,7 +541,7 @@ def export_all_videos():
with ZipFile('zip_exports/all_videos.zip', 'w') as zipf: #no compression, need to add zipfile.ZIP_DEFLATED for compression with ZipFile('zip_exports/all_videos.zip', 'w') as zipf: #no compression, need to add zipfile.ZIP_DEFLATED for compression
zipdir('uploads/', zipf) zipdir('uploads/', zipf)
return send_file("zip_exports/all_videos.zip", as_attachment=False, download_name="all_tables.zip") return send_file("zip_exports/all_videos.zip", as_attachment=False, download_name="all_videos.zip")
def create_csv(table, filename): def create_csv(table, filename):
@@ -577,6 +616,39 @@ def show_tables():
tables = meta.tables tables = meta.tables
return render_template('show_tables.html', tables=tables) return render_template('show_tables.html', tables=tables)
@app.route("/manage_uploads")
def manage_uploads():
if not session.get("logged_in"):
return redirect("/login")
videodir = "uploads/"
videolist = os.listdir(videodir)
num_videos = len(videolist)
return render_template("manage_uploads.html", videolist=videolist, num_videos=num_videos)
@app.route("/deleteuploads", methods=["POST"])
def deleteuploads():
if not session.get("logged_in"):
return redirect("/login")
print("deleting all videos")
videodir = "uploads/"
for video in os.listdir(videodir):
os.remove(os.path.join(videodir, video))
print("videos deleted")
return redirect("/all_links")
@app.route("/task3")
def task3():
return render_template("task3.html")
# Root page ----------------------------- # Root page -----------------------------
@@ -584,9 +656,10 @@ def show_tables():
def login(): def login():
if request.method == "POST": if request.method == "POST":
if request.form["password"] == PASSWORD: pwhash = hashlib.sha256(request.form["password"].encode('utf-8')).hexdigest()
if pwhash == PASSWORD:
session["logged_in"] = True session["logged_in"] = True
return redirect("/") return redirect(url_for("all_links"))
return render_template("login.html") return render_template("login.html")
@@ -602,6 +675,10 @@ def has_no_empty_params(rule):
return len(defaults) >= len(arguments) return len(defaults) >= len(arguments)
@app.route("/") @app.route("/")
def root():
return redirect("/start")
@app.route("/all_links")
def all_links(): def all_links():
links = [] links = []
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():

View File

@@ -295,7 +295,7 @@
"question1": { "question1": {
"type": "textinput", "type": "textinput",
"name": "formfeedback", "name": "formfeedback",
"text": "Das war der Prototyp für dieses Studientool. Über Feedback würde ich mich freuen. Entweder hier oder per Email unter: jan.dickmann@web.de", "text": "Das war der Prototyp für dieses Studientool. Über Feedback würde ich mich freuen. Entweder hier oder per Email unter: avatarstudy@proton.me",
"required": "false", "required": "false",
"size": "1000" "size": "1000"
} }

View File

@@ -1,3 +1,7 @@
id,user_id,date_created,stimulus_name,alter,geschlecht,hoerstatus,bevorzugte_kommunikation,gebärdenzeitraum,gebärdensprachkompetenz id,user_id,date_created,stimulus_name,alter,geschlecht,bildung,hoerstatus,wann_gehörlos,wann_gebärdensprache,eltern_gehörlos,zuhause_sl,arbeit_sl,avatar_erfahrung
33b2f49f-ccc8-48c5-b7f6-fa4bb9aa1bf3,60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,2024-06-27 17:43:45.381824,empty_stimulus,1,Männlich,Hörend,Gesprochene Sprache,1.0,1 e0b08915-2975-42ad-8a7c-6c057d914120,1cbab591-9fe8-4e2b-adce-e40ef14ed49d,2024-09-19 15:16:25.413235,empty_stimulus,23,Männlich,Bachelor,Hörend,-1,23,keiner,zuhause-Deutsch,"arbeit-Deutsch,arbeit-Sonstige",Ja
70afa75b-d522-4f26-b8f5-1a22ef425ad5,253b881d-cc08-4835-bf03-4d0ffdd8eddd,2024-06-27 17:44:43.877367,empty_stimulus,1,Weiblich,Schwerhörig,Text,1.0,1 6b1d95ed-646d-42dc-b386-d3aacc41d6ed,e8797746-73a1-4b0a-b644-c084d155b3d6,2024-09-19 16:21:20.372629,empty_stimulus,2,Männlich,Hauptschule,GehörlosCI,2,3,beide,"zuhause-Deutsch,zuhause-Gebärdensprache","arbeit-Deutsch,arbeit-Gebärdensprache,arbeit-Sonstige",Ja
0103472b-943b-4728-9b50-dc43a886facf,9eb6c08d-1eab-44c3-87a6-8a0a6848adc6,2024-09-21 11:40:35.811739,empty_stimulus,2,Männlich,Abitur,Schwerhörig,2,2,einer,zuhause-Deutsch,arbeit-Sonstige,Nein
88e4cb27-0d97-466d-a164-55b657d648ef,dd296f2b-a497-4957-899a-9393b25ab86f,2024-09-23 14:52:29.344441,empty_stimulus,2,Divers,Realschule,GehörlosCI,2,2,einer,"zuhause-Deutsch,zuhause-Gebärdensprache",arbeit-Deutsch,Ja
85e2cc7c-25ba-473b-a3d1-26fac4ca9629,6ece9745-96d7-40e1-a8b1-598608723f3a,2024-10-02 11:51:05.384542,empty_stimulus,67,Männlich,Master,Schwerhörig,66,66,keiner,"zuhause-Deutsch,zuhause-Gebärdensprache",arbeit-Deutsch,Nein
54f00de7-4351-47f1-a798-1c5326b3d8da,1e63ca71-bb2e-4d19-a69e-b7e8dccaba35,2024-10-02 11:56:41.492195,empty_stimulus,27,Männlich,Bachelor,Hörend,-1,23,keiner,zuhause-Deutsch,"arbeit-Deutsch,arbeit-Sonstige",Ja
1 id user_id date_created stimulus_name alter geschlecht bildung bevorzugte_kommunikation hoerstatus gebärdenzeitraum wann_gehörlos gebärdensprachkompetenz wann_gebärdensprache eltern_gehörlos zuhause_sl arbeit_sl avatar_erfahrung
2 33b2f49f-ccc8-48c5-b7f6-fa4bb9aa1bf3 e0b08915-2975-42ad-8a7c-6c057d914120 60c9c65b-b6b7-4510-8fb1-e9f220a7c46e 1cbab591-9fe8-4e2b-adce-e40ef14ed49d 2024-06-27 17:43:45.381824 2024-09-19 15:16:25.413235 empty_stimulus 1 23 Männlich Bachelor Gesprochene Sprache Hörend 1.0 -1 1 23 keiner zuhause-Deutsch arbeit-Deutsch,arbeit-Sonstige Ja
3 70afa75b-d522-4f26-b8f5-1a22ef425ad5 6b1d95ed-646d-42dc-b386-d3aacc41d6ed 253b881d-cc08-4835-bf03-4d0ffdd8eddd e8797746-73a1-4b0a-b644-c084d155b3d6 2024-06-27 17:44:43.877367 2024-09-19 16:21:20.372629 empty_stimulus 1 2 Weiblich Männlich Hauptschule Text Schwerhörig GehörlosCI 1.0 2 1 3 beide zuhause-Deutsch,zuhause-Gebärdensprache arbeit-Deutsch,arbeit-Gebärdensprache,arbeit-Sonstige Ja
4 0103472b-943b-4728-9b50-dc43a886facf 9eb6c08d-1eab-44c3-87a6-8a0a6848adc6 2024-09-21 11:40:35.811739 empty_stimulus 2 Männlich Abitur Schwerhörig 2 2 einer zuhause-Deutsch arbeit-Sonstige Nein
5 88e4cb27-0d97-466d-a164-55b657d648ef dd296f2b-a497-4957-899a-9393b25ab86f 2024-09-23 14:52:29.344441 empty_stimulus 2 Divers Realschule GehörlosCI 2 2 einer zuhause-Deutsch,zuhause-Gebärdensprache arbeit-Deutsch Ja
6 85e2cc7c-25ba-473b-a3d1-26fac4ca9629 6ece9745-96d7-40e1-a8b1-598608723f3a 2024-10-02 11:51:05.384542 empty_stimulus 67 Männlich Master Schwerhörig 66 66 keiner zuhause-Deutsch,zuhause-Gebärdensprache arbeit-Deutsch Nein
7 54f00de7-4351-47f1-a798-1c5326b3d8da 1e63ca71-bb2e-4d19-a69e-b7e8dccaba35 2024-10-02 11:56:41.492195 empty_stimulus 27 Männlich Bachelor Hörend -1 23 keiner zuhause-Deutsch arbeit-Deutsch,arbeit-Sonstige Ja

View File

@@ -1,4 +1,7 @@
user_id,device_id,question_order,date_created,form_completed user_id,device_id,question_order,date_created,form_completed
60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,f9a2bd4b-e1ef-43a2-a358-a74feea0076d,"{'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_1']}",2024-06-27 17:43:23.511495,False 1cbab591-9fe8-4e2b-adce-e40ef14ed49d,71f05b18-36d1-45bc-8b7a-418d05a78c3b,"{'Block 1': ['empty_stimulus'], 'Block 2': ['video_7', 'video_15', 'video_5', 'video_9', 'video_3', 'video_8', 'video_11', 'video_6', 'video_4', 'video_12', 'video_1', 'video_14', 'video_10', 'video_13', 'video_2'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']}",2024-09-19 15:15:17.910737,True
253b881d-cc08-4835-bf03-4d0ffdd8eddd,f9a2bd4b-e1ef-43a2-a358-a74feea0076d,"{'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_1', 'video_2']}",2024-06-27 17:44:22.713605,True e8797746-73a1-4b0a-b644-c084d155b3d6,71f05b18-36d1-45bc-8b7a-418d05a78c3b,"{'Block 1': ['empty_stimulus'], 'Block 2': ['video_10', 'video_11', 'video_14', 'video_5', 'video_1', 'video_2', 'video_12', 'video_7', 'video_3', 'video_13', 'video_8', 'video_6', 'video_4', 'video_15', 'video_9'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']}",2024-09-19 16:21:05.738180,False
89ad42ff-3310-4548-b179-7df9c138e794,f9a2bd4b-e1ef-43a2-a358-a74feea0076d,"{'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_1']}",2024-06-27 17:56:57.288776,False 9eb6c08d-1eab-44c3-87a6-8a0a6848adc6,ccc2054d-295e-47f5-b277-2282109c4ed1,"{'Block 1': ['empty_stimulus'], 'Block 2': ['video_9', 'video_2', 'video_4', 'video_6', 'video_13', 'video_14', 'video_15', 'video_11', 'video_10', 'video_7', 'video_5', 'video_8', 'video_12', 'video_1', 'video_3'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']}",2024-09-21 11:40:21.272823,False
dd296f2b-a497-4957-899a-9393b25ab86f,d94cc6b1-958c-43e4-9789-9e0c369ba29a,"{'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_11', 'video_9', 'video_6', 'video_4', 'video_5', 'video_3', 'video_8', 'video_10', 'video_7', 'video_15', 'video_14', 'video_1', 'video_13', 'video_12'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']}",2024-09-23 14:52:02.034386,False
6ece9745-96d7-40e1-a8b1-598608723f3a,588e94df-870d-4c61-b3c8-098edf01e988,"{'Block 1': ['empty_stimulus'], 'Block 2': ['video_14', 'video_8', 'video_5', 'video_6', 'video_1', 'video_7', 'video_4', 'video_2', 'video_15', 'video_9', 'video_11', 'video_13', 'video_10', 'video_3', 'video_12'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1'], 'Block 5': ['video_1']}",2024-10-02 11:49:11.652504,False
1e63ca71-bb2e-4d19-a69e-b7e8dccaba35,8a1063c3-7130-4ab9-9d88-bd6bf470e685,"{'Block 1': ['empty_stimulus'], 'Block 2': ['video_13', 'video_2', 'video_12', 'video_8', 'video_3', 'video_7', 'video_14', 'video_1', 'video_11', 'video_9', 'video_15', 'video_6', 'video_4', 'video_5', 'video_10'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1'], 'Block 5': ['video_1']}",2024-10-02 11:55:43.185190,True
1 user_id device_id question_order date_created form_completed
2 60c9c65b-b6b7-4510-8fb1-e9f220a7c46e 1cbab591-9fe8-4e2b-adce-e40ef14ed49d f9a2bd4b-e1ef-43a2-a358-a74feea0076d 71f05b18-36d1-45bc-8b7a-418d05a78c3b {'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_1']} {'Block 1': ['empty_stimulus'], 'Block 2': ['video_7', 'video_15', 'video_5', 'video_9', 'video_3', 'video_8', 'video_11', 'video_6', 'video_4', 'video_12', 'video_1', 'video_14', 'video_10', 'video_13', 'video_2'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']} 2024-06-27 17:43:23.511495 2024-09-19 15:15:17.910737 False True
3 253b881d-cc08-4835-bf03-4d0ffdd8eddd e8797746-73a1-4b0a-b644-c084d155b3d6 f9a2bd4b-e1ef-43a2-a358-a74feea0076d 71f05b18-36d1-45bc-8b7a-418d05a78c3b {'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_1', 'video_2']} {'Block 1': ['empty_stimulus'], 'Block 2': ['video_10', 'video_11', 'video_14', 'video_5', 'video_1', 'video_2', 'video_12', 'video_7', 'video_3', 'video_13', 'video_8', 'video_6', 'video_4', 'video_15', 'video_9'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']} 2024-06-27 17:44:22.713605 2024-09-19 16:21:05.738180 True False
4 89ad42ff-3310-4548-b179-7df9c138e794 9eb6c08d-1eab-44c3-87a6-8a0a6848adc6 f9a2bd4b-e1ef-43a2-a358-a74feea0076d ccc2054d-295e-47f5-b277-2282109c4ed1 {'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_1']} {'Block 1': ['empty_stimulus'], 'Block 2': ['video_9', 'video_2', 'video_4', 'video_6', 'video_13', 'video_14', 'video_15', 'video_11', 'video_10', 'video_7', 'video_5', 'video_8', 'video_12', 'video_1', 'video_3'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']} 2024-06-27 17:56:57.288776 2024-09-21 11:40:21.272823 False
5 dd296f2b-a497-4957-899a-9393b25ab86f d94cc6b1-958c-43e4-9789-9e0c369ba29a {'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_11', 'video_9', 'video_6', 'video_4', 'video_5', 'video_3', 'video_8', 'video_10', 'video_7', 'video_15', 'video_14', 'video_1', 'video_13', 'video_12'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1']} 2024-09-23 14:52:02.034386 False
6 6ece9745-96d7-40e1-a8b1-598608723f3a 588e94df-870d-4c61-b3c8-098edf01e988 {'Block 1': ['empty_stimulus'], 'Block 2': ['video_14', 'video_8', 'video_5', 'video_6', 'video_1', 'video_7', 'video_4', 'video_2', 'video_15', 'video_9', 'video_11', 'video_13', 'video_10', 'video_3', 'video_12'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1'], 'Block 5': ['video_1']} 2024-10-02 11:49:11.652504 False
7 1e63ca71-bb2e-4d19-a69e-b7e8dccaba35 8a1063c3-7130-4ab9-9d88-bd6bf470e685 {'Block 1': ['empty_stimulus'], 'Block 2': ['video_13', 'video_2', 'video_12', 'video_8', 'video_3', 'video_7', 'video_14', 'video_1', 'video_11', 'video_9', 'video_15', 'video_6', 'video_4', 'video_5', 'video_10'], 'Block 3': [('video_1', 'video_1')], 'Block 4': ['video_1'], 'Block 5': ['video_1']} 2024-10-02 11:55:43.185190 True

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -88,7 +88,7 @@ dialog .iframe-container {
max-width: 1690px; max-width: 1690px;
/* Maximum width to keep it from getting too wide on large screens */ /* Maximum width to keep it from getting too wide on large screens */
padding: 20px; padding: 20px;
background-color: #7b8cdb; background-color: #b6c3ff; /* used to be 7b8cdb */
/* Just for visual differentiation */ /* Just for visual differentiation */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
} }
@@ -106,6 +106,10 @@ form {
padding-bottom: 2em; padding-bottom: 2em;
} }
.questioncontainer {
max-width: 70%;
margin: auto;
}
.dsgvoform { .dsgvoform {
text-align: right; text-align: right;
@@ -118,9 +122,7 @@ label {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
input, .inputs {
textarea,
select {
margin: 10px 0 0 0; margin: 10px 0 0 0;
width: 60%; width: 60%;
min-height: 2em; min-height: 2em;
@@ -141,6 +143,10 @@ select {
width: 60%; width: 60%;
} }
button {
border: 1px solid #000;
}
/* buttons */ /* buttons */
.buttondisable { .buttondisable {
filter: invert(65%); filter: invert(65%);
@@ -157,6 +163,8 @@ select {
border-radius: 8px; border-radius: 8px;
/* Optional: rounds the corners of the button */ /* Optional: rounds the corners of the button */
width: auto; width: auto;
float: right;
border: 1px solid #000;
} }
#submitbutton:hover { #submitbutton:hover {
@@ -176,8 +184,10 @@ select {
} }
.textarea-label { .textarea-label {
align-self: flex-start; font-size: 18px;
/* Aligns the label to the start of the container */ font-weight: bold;
text-align: center;
} }
/* Helper */ /* Helper */
@@ -190,8 +200,7 @@ select {
text-align: center; text-align: center;
} }
h2, h2 {
h3 {
text-align: center; text-align: center;
} }
@@ -215,7 +224,7 @@ h3 {
height: 70px; height: 70px;
/* Set a specific height for the buttons */ /* Set a specific height for the buttons */
background-color: #cae4ff; background-color: #cae4ff;
border: none; border: 1px solid #000;
color: white; color: white;
padding: 10px 20px; padding: 10px 20px;
margin: 0 10px; margin: 0 10px;
@@ -265,6 +274,7 @@ video {
width: auto; width: auto;
height: auto; height: auto;
margin: auto auto; margin: auto auto;
display: block;
} }
iframe { iframe {
@@ -281,12 +291,29 @@ iframe {
} }
.iframe-container { .iframe-container {
position: relative;
/*padding-bottom: 56.25%;*/
/* 16:9 */
padding-bottom: 100%;
/* 1:1 */
height: 0;
}
.iframe-container2 {
position: relative; position: relative;
padding-bottom: 56.25%; padding-bottom: 56.25%;
/* 16:9 */ /* 16:9 */
height: 0; height: 0;
} }
.iframe-container2 iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.video-container { .video-container {
max-width: 100%; max-width: 100%;
position: relative; position: relative;
@@ -315,7 +342,7 @@ iframe {
.dv_half { .dv_half {
display: inline-block; display: inline-block;
width: 45%; width: 48%;
margin: auto; margin: auto;
} }
@@ -324,6 +351,34 @@ iframe {
display: flex; display: flex;
} }
/* Likert stuff 2 */
.outer-likert-container {
display: flex;
}
.normal-likert-container { /* inner container */
display: flex;
gap: 10px; /* Adjust spacing */
text-align: center;
flex-grow: 1;
max-width: 70%;
margin: auto;
}
.likertlabel {
flex-grow: 1;
width: 100%;
}
.likertinput {
margin: auto;
width: 60%;
min-height: 2em;
display: flex;
}
/* Likert stuff */ /* Likert stuff */
.likert { .likert {
/* --likert-rows: 5;*/ /* --likert-rows: 5;*/
@@ -347,7 +402,7 @@ iframe {
aspect-ratio: 1.5 / 1; aspect-ratio: 1.5 / 1;
} }
.likercontainer { .likertcontainer {
margin: 30px auto; margin: 30px auto;
text-align: center; text-align: center;
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,64 +1,77 @@
const buttonCamera = document.getElementById('buttonCamera'); const buttonCamera = document.getElementById('buttonCamera');
const buttonRecord = document.getElementById('buttonRecord'); const buttonRecord = document.getElementById('buttonRecord');
const buttonDelete = document.getElementById('buttonDelete'); const buttonDelete = document.getElementById('buttonDelete');
const videoDisplay = document.getElementById('videoDisplay');
const buttonCameraIcon = document.getElementById('buttonCameraIcon'); const buttonCameraIcon = document.getElementById('buttonCameraIcon');
const buttonRecordIcon = document.getElementById('buttonRecordIcon'); const buttonRecordIcon = document.getElementById('buttonRecordIcon');
const buttonDeleteIcon = document.getElementById('buttonDeleteIcon'); const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
const videoContainer = document.getElementById('videoContainer'); const countdownText = document.getElementById('countdown')
const videoContainerCss = document.querySelector(".video-container ") //might be unnecessary
var mediaRecorder = null; const videoDisplay = document.getElementById('videoDisplay');
var stream = null; const videoContainer = document.getElementById('videoContainer'); // div that contains the videodisplay
let recordedVideoBlob = null;
let isRecording = false; var mediaRecorder = null; // interface of the Media Stream Recording API to record the video stream
let videoAccess = false; var stream = null; // webcam video input stream
let videoHeigt = 720; let recordedVideoBlob = null; // recorded videodata
let isRecording = false; // false to display record button, true to display stop button
let videoAccess = false; // true if user has given permission to use the webcam
let videotime = 70; // how many seconds video is allowed to be recorded
let timeleft = videotime; // counter for how much time is still left
var countdownTimer; //the timer object that will end the recording if it takes too long
// default video dimensions
let videoHeight = 720;
let videoWidth = 1280; let videoWidth = 1280;
// Handle form submission
// handle form submission
//the Video is beeing send together with the form data
document.getElementById("question_form").addEventListener("submit", function (event) { document.getElementById("question_form").addEventListener("submit", function (event) {
event.preventDefault(); // Prevent the default form submission event.preventDefault(); // Prevent the default form submission
console.log("submit button pressed"); console.log("submit button pressed");
// Create a FormData object // create a FormData object
const formData = new FormData(event.target); const formData = new FormData(event.target);
console.log("form data: ",formData); console.log("form data: ",formData);
// Append the recorded video Blob to the FormData object // append the recorded video blob to the FormData object
if (recordedVideoBlob) { if (recordedVideoBlob) {
console.log("video is available: ", recordedVideoBlob); console.log("video is available: ", recordedVideoBlob);
formData.append("recordedVideo", recordedVideoBlob, "recordedVideo.webm"); formData.append("recordedVideo", recordedVideoBlob, "recordedVideo.webm");
} }
console.log("Data to be submitted: ",formData); console.log("Data to be submitted: ",formData);
// Use fetch to send the form data and video to the backend
console.log("try to send the form and video"); console.log("try to send the form and video");
// send the form data and video to the backend
fetch("/send", { fetch("/send", {
method: "POST", method: "POST",
body: formData body: formData
}).then(response => { }).then(response => {
// code to handle proper redirection after submit
console.log('Response:', response); console.log('Response:', response);
// Check if response is a redirect (HTTP 3xx status) // check if response is a redirect
if (response.redirected) { if (response.redirected) {
console.log("Redirecting to:", response.url); console.log("Redirecting to:", response.url);
window.location.href = response.url; // Redirect to the new page window.location.href = response.url; // Redirect to the new page
} else { } else {
console.log("Non-redirect response received."); console.log("No redirect response received.");
// Handle other responses if needed
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
// Handle errors if fetch fails
}); });
}); });
// enable/disable video recording feature
async function cameraButton() { async function cameraButton() {
if (!videoAccess) { if (!videoAccess) {
console.log("cameraButton case videoAccess = false"); console.log("cameraButton case videoAccess = false");
try { try {
// ask the user for permission to use the webcam
// if given, assign it to "stream"
stream = await navigator.mediaDevices.getUserMedia({ stream = await navigator.mediaDevices.getUserMedia({
video: true, video: true,
}); });
@@ -67,39 +80,62 @@ async function cameraButton() {
return; return;
} }
console.log("stream is active"); console.log("stream is active");
videoAccess = true; videoAccess = true;
videoHeigt = stream.getVideoTracks()[0].getSettings().height // set the dimensions of the webcam stream to the video display
videoHeight = stream.getVideoTracks()[0].getSettings().height
videoWidth = stream.getVideoTracks()[0].getSettings().width videoWidth = stream.getVideoTracks()[0].getSettings().width
let aspectratio = (videoHeigt / videoWidth) *100 // calculate the aspec ratio
console.log("videoHeigt: ",videoHeigt); let aspectratio = (videoHeight / videoWidth) *100;
console.log("videoWidth: ",videoWidth); //console.log("videoHeight: ",videoHeight);
console.log("aspect ratio: ",aspectratio); //console.log("videoWidth: ",videoWidth);
console.log("device: ",stream.getVideoTracks()[0].getSettings().deviceId); //console.log("aspect ratio: ",aspectratio);
videoContainer.style.setProperty("padding-bottom", "min("+videoHeigt+"px,"+aspectratio+"%)"); //console.log("device: ",stream.getVideoTracks()[0].getSettings().deviceId);
if (videoHeigt > videoWidth){ //hochkant video // adjust CSS to make the video fit properly
videoContainer.style.setProperty("max-width", "min(80%,576"); // this makes sure that there is no buggy "jumping" when the video display source changes
}else{ //Normal, horizontal videoContainer.style.setProperty("padding-bottom", "min("+videoHeight+"px,"+aspectratio+"%), 100vh");
//TODO: Solution for this part:
// If the video is vertical, I need to set the width to 0, the max-height to 100% and do the padding on the width, so turn the code around
//This should always make the video fitt
// adjust the max width for horizontal and vertical video input
if (videoHeight > videoWidth){ //hochkant video
// I did this originally to display vertical video on a horizontal screen properly (video should not be higher than the screem)
// but now I do this by adjusting the paddint to be at most 100vh, so this whole part should be useless now
// But a new problem could be if I do too little padding and the ratio is lost because of it, so actually I also need to adjust the width
videoContainer.style.setProperty("max-width", "min(80%,576)"); // maybe actually use the screen height here: instead of 80% maybe 80vh
}else{ //normal, horizontal
videoContainer.style.setProperty("max-width", "100%"); videoContainer.style.setProperty("max-width", "100%");
} }
// show the webcam input stream on the video display
videoDisplay.srcObject = stream; videoDisplay.srcObject = stream;
// change the icon to indicate the disable option
buttonCameraIcon.src = ICON_PATHS.cameraofficon; buttonCameraIcon.src = ICON_PATHS.cameraofficon;
buttonCameraIcon.alt = "Camera-off Icon"; buttonCameraIcon.alt = "Camera-off Icon";
// display the elements for the video recording
buttonRecord.style.display = 'inline-block'; buttonRecord.style.display = 'inline-block';
buttonDelete.style.display = 'inline-block'; buttonDelete.style.display = 'inline-block';
videoDisplay.style.display = 'block'; videoDisplay.style.display = 'block';
videoContainer.style.display = 'block'; videoContainer.style.display = 'block';
videoDisplay.classList.add("videomirror"); videoDisplay.classList.add("videomirror");
// initialize MediaRecorder, give it the webcam stream as input to record
mediaRecorder = new MediaRecorder(stream, { mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm", // could use different video format mimeType: "video/webm",
// videoBitsPerSecond: 5000000, // Standard bitrate for video is 2,5 mbps audioBitsPerSecond: 0,
videoBitsPerSecond: 1900000, // Standard bitrate for video is 2,5 mbps
}); });
// when data is available at the mediaRecorder (this means the recording has ended, since I have not specified time intervalls)
// save the data in "recordedVideoBlob"
mediaRecorder.addEventListener("dataavailable", (event) => { mediaRecorder.addEventListener("dataavailable", (event) => {
console.log("Data available Event triggered"); console.log("Data available Event triggered");
if (event.data.size > 0) { if (event.data.size > 0) {
@@ -110,28 +146,37 @@ async function cameraButton() {
} }
}); });
// when recording is stopped, display the recorded video
// TODO possibly replace this and just add it to the dataavailable event
// TODO this would ensure proper order in a concurrent scenario (the stop code here can only be executed after the dataavailable code happened)
// TODO part of it could also go to the record button function
mediaRecorder.addEventListener("stop", () => { mediaRecorder.addEventListener("stop", () => {
if (recordedVideoBlob) { if (recordedVideoBlob) {
videoDisplay.srcObject = null; videoDisplay.srcObject = null; //remove the stream as input
videoDisplay.src = URL.createObjectURL(recordedVideoBlob); videoDisplay.src = URL.createObjectURL(recordedVideoBlob); // create a url for the recorded video and set it as input
videoDisplay.controls = true; videoDisplay.controls = true; // display video controls
videoDisplay.pause(); videoDisplay.pause(); // pause, so that it doesn't immediately play, TODO maybe disable autoplay?
buttonRecordIcon.src = ICON_PATHS.recordicon; // TODO maybe move this to the record button function
buttonRecordIcon.src = ICON_PATHS.recordicon; //change the icon of the record button back
buttonRecordIcon.alt = 'Record Icon'; buttonRecordIcon.alt = 'Record Icon';
isRecording = false; isRecording = false;
console.log('Recording stopped'); console.log('Recording stopped');
console.log("Src path:", videoDisplay.src); console.log("Src path:", videoDisplay.src);
} }
}); });
} else { } else {
// -> the user disabled the videorecording feature
console.log("cameraButton case videoAccess = true"); console.log("cameraButton case videoAccess = true");
stream.getTracks().forEach(track => track.stop()); // Stop all tracks to release the camera stream.getTracks().forEach(track => track.stop()); // stop all tracks to release the camera
stream = null; stream = null;
videoAccess = false; videoAccess = false;
// change the icon and hide all the elements again
buttonCameraIcon.src = ICON_PATHS.cameraicon; buttonCameraIcon.src = ICON_PATHS.cameraicon;
buttonCameraIcon.alt = "Camera Icon"; buttonCameraIcon.alt = "Camera Icon";
buttonRecord.style.display = 'none'; buttonRecord.style.display = 'none';
@@ -142,41 +187,99 @@ async function cameraButton() {
console.log("camera button function ends"); console.log("camera button function ends");
} }
function recordButton() { function recordButton() {
console.log("recordButton pressed"); console.log("recordButton pressed");
if (!isRecording) { if (!isRecording) {
// a new recording starts
console.log("recordButton pressed case isRecording = false"); console.log("recordButton pressed case isRecording = false");
deleteButton();
videoDisplay.classList.add("videomirror"); deleteButton(); //delete previous video
buttonDelete.setAttribute("disabled", "");
buttonDeleteIcon.classList.add("buttondisable"); videoDisplay.classList.add("videomirror"); // mirror the video while recording, for a more natural experience
videoDisplay.srcObject = stream; buttonDelete.setAttribute("disabled", ""); // can't delete during recording
videoDisplay.src = null; buttonDeleteIcon.classList.add("buttondisable"); // delete button icon indicates that it is disabled
videoDisplay.controls = false;
mediaRecorder.start(); videoDisplay.src = null; // get rid of the previous video on the display
videoDisplay.srcObject = stream; // set webcam stream as input for the video display
videoDisplay.controls = false; // hide the video controls durin recording, because they are confusing
mediaRecorder.start(); // start recording
timeleft = videotime;
countdownTimer = setInterval(countdown, 1000) //stop the recording after "videotime" Seconds
console.log("video bitrate = ",mediaRecorder.videoBitsPerSecond); console.log("video bitrate = ",mediaRecorder.videoBitsPerSecond);
// change icon to indicate "stop"
buttonRecordIcon.src = ICON_PATHS.stopicon; buttonRecordIcon.src = ICON_PATHS.stopicon;
buttonRecordIcon.alt = 'Stop Icon'; buttonRecordIcon.alt = 'Stop Icon';
isRecording = true; isRecording = true;
console.log('Recording started'); console.log('Recording started');
} else { } else {
console.log("recordButton pressed case isRecording = true"); // recording got stopped
mediaRecorder.stop();
videoDisplay.classList.remove("videomirror");
console.log("recordButton pressed case isRecording = true");
clearInterval(countdownTimer)
stopRecording()
}
}
function stopRecording() {
mediaRecorder.stop(); // stop recording
videoDisplay.classList.remove("videomirror"); // don't mirror the videodisplay anymore
clearInterval(countdownTimer);
console.log("recording stops"); console.log("recording stops");
// enable the deletebutton again
buttonDelete.removeAttribute("disabled"); buttonDelete.removeAttribute("disabled");
buttonDeleteIcon.classList.remove("buttondisable"); buttonDeleteIcon.classList.remove("buttondisable");
} }
function timerStop() {
// recording got stopped by timer
console.log("timer stopped the recording");
clearInterval(countdownTimer);
stopRecording();
}
function countdown() {
if(timeleft <= 0){
countdownText.textContent = "";
timerStop();
clearInterval(countdownTimer);
return;
}
if(timeleft >= 60){
r = timeleft - 60;
if(r< 10){
countdownText.textContent = "01:0"+r;
}else{
countdownText.textContent = "01:"+r;
}
}else{
if(timeleft< 10){
countdownText.textContent = "00:0"+timeleft;
}else{
countdownText.textContent = "00:"+timeleft;
}
}
timeleft -= 1;
} }
function deleteButton() { function deleteButton() {
// TODO delete data // delete the recorded video
videoDisplay.controls = false; videoDisplay.controls = false;
videoDisplay.srcObject = stream; videoDisplay.srcObject = stream; // set webcam stream as source for the video display again
recordedVideoBlob = null recordedVideoBlob = null
videoDisplay.classList.add("videomirror"); videoDisplay.classList.add("videomirror"); // mirror the video display during live feed again
// disable the deletebutton again
buttonDelete.setAttribute("disabled", ""); buttonDelete.setAttribute("disabled", "");
buttonDeleteIcon.classList.add("buttondisable"); buttonDeleteIcon.classList.add("buttondisable");
} }

View File

@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study - Datenschutzerklärung</title>
</head>
<body>
<div class="container">
<h2>Datenschutzerklärung</h2>
<div class="textblock">
<h3>
Information betroffener Personen zur Verarbeitung personenbezogener Daten
</h3>
<p>
Das DFKI (Deutsches Forschungszentrum für Künstliche Intelligenz) und seine Mitarbeiter/-innen setzen
sich ziel- und risikoorientiert für die informationelle Selbstbestimmung und das Grundrecht auf Schutz
personenbezogener Daten ein. In dieser Datenschutzerklärung informieren wir über die Verarbeitung
personenbezogener Daten in der gegebenen Studie.
</p>
<h3>
Verantwortlicher
</h3>
<p>
Organisation: Deutsches Forschungszentrum für Künstliche Intelligenz, 66123 Saarbrücken
</p>
<p>
Ansprechpartner: Patrick Gebhard, patrick.gebhard@dfki.de
</p>
<h3>
Datenschutzbeauftragter
</h3>
<p>
DFKI-Datenschutzbeauftragter: Roland Vogt, roland.vogt@dfki.de
</p>
<h3>
Art der erhobenen Daten
</h3>
<p>
Im Rahmen der Studie werden personenbezogene Daten erhoben, namentlich Alter, Geschlecht, Bildungsgrad,
Hörstatus, seit wann der/die Teilnehmer*in Gehörlos ist, wie lange er/sie Gebärdensprache spricht, ob
die Eltern Gehörlos waren und welche Sprachen er/sie zuhause und auf der Arbeit/Schule verwendet.
Optional können Teilnehmer auch eigenständig Videoaufnahmen aufnehmen und hochladen.
</p>
<h3>
Zweck der Verarbeitung
</h3>
<p>
In der Studie bewerten Teilnehmer Videos von Gebärdensprachavataren und können freiwillig Feedback in
Form von Text oder Videoaufnahmen geben. Diese Daten werden soweit möglich vollständig anonymisiert
gespeichert. Aufgrund der Charakteristik der Videoaufnahmen ist eine vollständige Anonymisierung im
Auswertungsprozess nicht möglich. Die persönliche Zuordnung der betroffenen Personen ist jedoch zu jedem
Zeitpunkt für die Verarbeitung der Daten unerheblich. Die Videoaufnahmen werden nach der Studie von uns
ausgewertet und soweit wie möglich als Text ausgeschrieben.
</p>
<h3>
Rechtsgrundlage der Verarbeitung
</h3>
<p>
Die Verarbeitung der personenbezogenen Daten im Rahmen dieser Studie ist rechtmäßig, weil die
betroffenen Personen in die Verarbeitung einwilligen (Art. 6 Abs. 1, lit. a EU-DSGVO) oder weil die
Verarbeitung für die wissenschaftlichen Forschungszwecke im Bereich des Affective Computing erforderlich
ist und die Interessen der betroffenen Personen nicht überwiegen (Art. 6 Abs. 1, lit. e und f EU-DSGVO).
</p>
<h3>
Empfänger
</h3>
<p>
Unter der unmittelbaren Verantwortung des DFKI können die personenbezogenen Daten ausschließlich nach
Anonymisierung veröffentlicht oder an Forschungspartner für deren wissenschaftliche Forschungszwecke im
Bereich des Affective Computing übermittelt werden. Personenbezogene Daten werden nicht an
Auftragsverarbeiter oder andere Empfänger übermittelt, auch nicht zur Löschung oder Vernichtung.
</p>
<h3>
Keine automatisierte Entscheidungsfindung
</h3>
<p>
Die personenbezogenen Daten werden nicht für eine automatisierte Entscheidungsfindung einschließlich
Profiling verwendet.
</p>
<h3>
Dauer der Speicherung
</h3>
<p>
Die personenbezogenen Daten werden grundsätzlich für die Dauer der Forschungsprojekte im Bereich des
Affective Computing gespeichert. Sie sollen anonymisiert veröffentlicht werden. Nach der Anonymisierung
ist kein Rückschluss aus den Daten auf die Identität der Person möglich.
</p>
<h3>
Transparenz und Intervention
</h3>
<p>
Zur Gewährleistung einer transparenten und fairen Verarbeitung im Rahmen dieser Studie bestehen für
betroffene Personen die folgenden Rechte:
</p>
<ul>
<li>Das Recht auf Auskunft über die personenbezogenen Daten</li>
<li>Das Recht auf Berichtigung und Vervollständigung der personenbezogenen Daten</li>
<li>Das Recht auf Löschung der personenbezogenen Daten</li>
<li>Das Recht auf Einschränkung der Verarbeitung der personenbezogenen Daten</li>
<li>Das Recht auf Widerruf einer erteilten Einwilligung</li>
<li>Das Recht auf Widerspruch gegen die Verarbeitung der personenbezogenen Daten</li>
</ul>
<h3>
Recht auf Beschwerde
</h3>
<p>
Betroffene Personen haben das Recht auf Beschwerde bei einer Aufsichtsbehörde, wenn sie der Ansicht
sind, dass die Verarbeitung ihrer personenbezogenen Daten durch diese Studie gegen gesetzliche
Datenschutzvorschriften verstößt.
</p>
<h3>
Einwilligung betroffener Personen zur Veröffentlichung personenbezogener Daten und zu deren Übermittlung
an Forschungspartner
</h3>
<p>
In dieser Studie werden verschiedene personenbezogene Informationen erhoben. Die Daten werden
ausschließlich durch das DFKI und Forschungspartner im Rahmen wissenschaftlicher Forschungsprojekte
verarbeitet. Alle weiteren personenbezogenen Daten werden nur anonymisiert gespeichert und im Rahmen
einer wissenschaftlichen Arbeit veröffentlicht.
</p>
<h3>
Einwilligung zur Veröffentlichung und Übermittlung
</h3>
<p>
Ich bestätige, dass ich die „Information betroffener Personen zur Verarbeitung personenbezogener Daten“
erhalten habe.
</p>
<p>
Ich willige hiermit ein, dass das DFKI Daten aus wissenschaftlichen Experimenten im Zusammenhang mit
dieser Studie für die Darstellung von Forschungsergebnissen im Bereich des Affective Computing verwendet.
</p>
<p>
Mir ist bekannt, dass ich das Recht habe, meine Einwilligung jederzeit zu widerrufen. Durch den Widerruf
der Einwilligung wird die Rechtmäßigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten
Verarbeitung nicht berührt. Ein Widerruf kann per Email an avatarstudy@proton.me geschickt werden.
</p>
</div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,65 @@
<div style="margin: auto; max-width: 80%;">
<p>Demografische Fragen:</p>
<ol start="1">
<li>Wie alt sind sie?</li>
<li>Wie ist ihr Geschlecht?
<ol>
<li>Männlich</li>
<li>Weiblich</li>
<li>Divers</li>
<li>Keine Angabe</li>
</ol></li>
<li>Was ist ihr höchster Bildungsabschluss?
<ol>
<li>Keiner</li>
<li>Hauptschule</li>
<li>Realschule</li>
<li>Ausbildung</li>
<li>Abitur</li>
<li>Fachhochschulreife</li>
<li>Bachelor</li>
<li>Master</li>
<li>Diplom</li>
<li>Magister</li>
<li>Promotion</li>
</ol>
</li>
<li>Wie ist ihr Hörstatus?
<ol>
<li>Gehörlos</li>
<li>Schwerhörig</li>
<li>Gehörlos mit Cochlea-Implantat</li>
<li>Hörend</li>
</ol>
</li>
<li>In welchem Alter wurden sie gehörlos/schwerhörig? ("0" für ab Geburt, "-1" falls sie hörend sind)</li>
<li>Seit welchem Alter lernen sie Gebärdensprache?</li>
<li>Sind ihre Eltern Gehörlos?
<ol>
<li>Ein Elternteil</li>
<li>Beide</li>
<li>Nein</li>
</ol>
</li>
<li>Welche Sprache(n) verwenden sie zuhause?
<ol>
<li>Deutsch</li>
<li>Gebärdensprache</li>
<li>Sonstige</li>
</ol>
</li>
<li>Welche Sprache(n) verwenden sie auf der Arbeit/in der Schule?
<ol>
<li>Deutsch</li>
<li>Gebärdensprache</li>
<li>Sonstige</li>
</ol>
</li>
<li>Haben sie schonmal Computeranimationen von Gebärdensprache gesehen? (Gebärdensprachavatare)
<ol>
<li>Ja</li>
<li>Nein</li>
</ol>
</li>
</ol>
</div>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}--> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
@@ -9,13 +10,27 @@
<body> <body>
<div class="container"> <div class="container">
<h2>Danke für ihre Teilnahme!</h2> <h2>Vielen Dank für Ihre Teilnahme!</h2>
<div class="textblock"> <div class="textblock">
<p> <p>
Falls sie noch Fragen oder Rückmeldungen haben, schreiben sie mir unter: jan.dickmann@web.de Die Studie ist nun beendet und Sie können die Seite verlassen.
</p>
<p>
Wir würden uns freuen, wenn Sie die Studie mit anderen Gehörlosen oder Personen, die Gebärdensprache
sprechenden, teilen würden: <a href="https://slaeforms.leafbla.de/start">https://slaeforms.leafbla.de/start</a>
</p>
<p>
Falls Sie noch Fragen oder Anmerkungen haben, schreiben Sie uns unter: avatarstudy@proton.me
</p> </p>
</div> </div>
</div> </div>
</body> </body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html> </html>

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container" style="height: 100%; font-size: 22px;">
<h2>Impressum</h2>
<div class="textblock">
<p>
Datenschutzerklärung: <a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</p>
<p>
Verantwortlicher für slaeforms.leafbla.de: Jan Dickmann, zusammen mit dem DFKI Saarbrücken, Affective Computing Group
</p>
<p>
Adresse:
</p>
<p>
Deutsches Forschungszentrum für Künstliche Intelligenz GmbH (DFKI)
</p>
<p>
Stuhlsatzenhausweg 3
</p>
<p>
Saarland Informatics Campus D 3_2
</p>
<p>
66123 Saarbrücken
</p>
<p>
avatarstudy@proton.me
</p>
</div>
<div class="spacer" aria-hidden="true" style="height:50px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container">
<h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<div class="textblock">
<p style="font-size: 22px;">
Bei allen Aufgaben in dieser Studie gibt es Texte, in denen die Aufgaben und die Antwortmöglichkeiten erklärt werden.
Falls Sie Fragen haben oder eine Aufgabe nicht verstehen, können Sie den "Info"-Button auf der rechten Seite des Bildschirms klicken, um den Erklärtext zu sehen.
Sie können den Button nochmal anklicken, um wieder zur Aufgabe zurückzukommen.
</p>
<div style="margin: auto; display: block; max-width: 90px;">
<button style="margin: auto; max-width: 90px;"><img class="infoButtonIcon" id="buttonInfoIcon"
src="{{ url_for('static', filename='icons/info-icon.png')}}" alt="info-icon"></button>
</div>
<p style="font-size: 22px; text-align: center;">
Das ist der Info-Button.
</p>
</div>
<form class="dsgvoform" action="{{ url_for('sendpage') }}" method="post">
<div class="button-container">
<button id="submitbutton" type="submit">Starten</button>
</div>
</form>
<div class="spacer" aria-hidden="true" style="height:80px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container" style="height: 100%; font-size: 22px;">
<h2>Upload management</h2>
<div class="textblock">
<p>
Anzahl Videos: {{ num_videos }}
</p>
<p>
Die aktuellen Videos:
</p>
</div>
{% for video in videolist %}
<p>{{video}}</p>
{% endfor %}
<div class="textblock">
<p>
Download <a href="{{ url_for('export_all_videos') }}" target="_blank">hier</a>.
</p>
</div>
<form class="dsgvoform" action="{{ url_for('deleteuploads') }}" method="post">
<label for="terms-and-conditions">
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" />
Alle Videos löschen.
</label>
<div class="button-container">
<button id="submitbutton" type="submit">Löschen</button>
</div>
</form>
<div class="spacer" aria-hidden="true" style="height:50px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container">
<h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<div class="textblock">
<div style="margin: auto; font-size: 20px;">
<p>
Hallo und willkommen zu dieser Studie, danke für Ihre Teilnahme.
</p>
<p>
In dieser Studie geht es um die Entwicklung von Gebärdensprachavataren.
</p>
<p>
Bitte beachten Sie, dass diese Avatare noch in einer frühen Entwicklungsphase und noch nicht für den
Gebrauch in einer App/einem Computerprogramm oder auf einer Webseite geeignet sind. In dieser Studie
geht es darum, einzelne Aspekte der Avatare und neue technologische Ansätze zu testen. Dementsprechend
haben die Avatare noch viele offensichtliche Schwächen, wie zum Beispiel, dass bisher nur die Arme und
der Oberkörper, aber nicht das Gesicht animiert ist. Bitte bewerten Sie nur die Qualität der Hände und
Arme.
</p>
<p>
Diese Studie richtet sich an Menschen, die Gebärdensprache beherrschen, insbesondere gehörlose Menschen.
Nehmen Sie bitte nur Teil, wenn Sie (Deutsche) Gebärdensprache beherrschen.
Die Studie dauert ca. 20 Minuten.
</p>
<p>
Während der Studie werden sie sich Videos anschauen, diese bewerten und Feedback dazu geben können.
Verwenden Sie deshalb bitte, wenn möglich ein Gerät mit einem großen Bildschirm (Laptop, PC, Tablet) für
Ihre Teilnahme, damit Sie die Videos in ausreichender Größe sehen können.
</p>
<p>
Sie haben während der Studie die Möglichkeit (optional) Videofeedback zu geben, dazu brauchen sie eine
Webcam. Die Videos werden nicht veröffentlicht und nur Übersetzern zur Auswertung der Studienergebnisse
gezeigt.
</p>
<p>
Das Laden der Seiten kann manchmal einige Sekunden dauern. Falls es zu einem Fehler kommen sollte und ein Video nicht geladen wird, versuchen Sie bitte die Seite neu zu laden.
</p>
<p>
Für jeden Teil der Studie gibt es einen Text, der die Fragestellung und die Antwortmöglichkeiten erklärt. Falls Sie den Text während der Studie nochmal sehen möchten, können Sie es
über den „Info“-Knopf auf der rechten Seite aufrufen.
</p>
<p>
Bitte versuchen sie während der Studie nicht auf die vorherige Seite "zurück" zu gehen, da sie nicht zu vorherigen Fragen zurück können.
</p>
</div>
</div>
<form class="dsgvoform" action="{{ url_for('sendpage') }}" method="post">
<div class="button-container">
<button id="submitbutton" type="submit">Weiter</button>
</div>
</form>
<div class="spacer" aria-hidden="true" style="height:80px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,9 @@
<div style="margin: auto; max-width: 80%; font-size: 20px;">
<p>Studie Teil 1:</p>
<p>Im Folgenden werden Ihnen Videos gezeigt, die Sie in den Kategorien „Natürlichkeit“, „Verständlichkeit“ und „grammatikalischer Korrektheit“ bewerten sollen.</p>
<p>Dafür können Sie auf einer Skala mit 7 Punkten bewerten, wobei der Punkt ganz links für „Sehr schlecht“, der Punkt in der Mitte für "Weder gut noch schlecht", und der Punkt ganz rechts für „Sehr gut“ steht.</p>
<p>Dann können Sie weiteres Feedback als Text oder als Video geben. Das Video können Sie direkt im Browser aufnehmen. Beachten Sie, dass sie dazu eine Webcam benötigen und der Webseite erlauben müssen diese zu benutzen. Wenn Sie auf den Kamera-Knopf drücken, wird ihr Browser Sie nach der Berechtigung fragen.</p>
<p>Videoaufnahmen können maximal 70 Sekunden lang sein, dann wird die Aufnahme automatisch beendet.</p>
<p>Wenn Sie Feedback als Video geben, kann es einige Sekunden dauern die nächste Seite aufzurufen, weil das Video erst hochgeladen werden muss.</p>
<p>Genaue Informationen darüber, wie wir die Videos verarbeiten, finden Sie in unserer <a target="_blank" href="{{ url_for('datenschutz') }}">Datenschutzerklärung</a>.</p>
</div>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container">
<h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<div class="textblock">
{% include "p1infos.html" %}
</div>
<form class="dsgvoform" action="{{ url_for('sendpage') }}" method="post">
<div class="button-container">
<button id="submitbutton" type="submit">Weiter</button>
</div>
</form>
<div class="spacer" aria-hidden="true" style="height:80px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,20 @@
<div style="margin: auto; max-width: 80%; font-size: 20px;">
<p>Studie Teil 2:</p>
<p>Im Folgenden sehen Sie zwei Videos, in denen der gleiche Satz mit unterschiedlichen Formulierungen gebärdet wird.</p>
<p>Der folgende Satz wird gebärdet: Einfahrt RE 77 Richtung Köln Hauptbahnhof über Hannover, Abfahrt 3:44 Uhr.</p>
<p>Bitte verwenden Sie die Punkteskala, um zu bewerten, welches der beiden Videos Sie besser finden.</p>
<ol start="1">
<li>Welche Formulierung war natürlicher?</li>
<li>Welche Formulierung haben Sie besser verstanden?</li>
<li>Welche Formulierung war korrekter?</li>
<li>Welche Formulierung hat Ihnen besser gefallen?</li>
</ol>
<p>Die Antwortmöglichkeiten sind jedes Mal:</p>
<ol start="1">
<li>Links</li>
<li>Eher links</li>
<li>Beide gleich</li>
<li>Eher rechts</li>
<li>Rechts</li>
</ol>
</div>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container">
<h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<div class="textblock">
{% include "p2infos.html" %}
</div>
<form class="dsgvoform" action="{{ url_for('sendpage') }}" method="post">
<div class="button-container">
<button id="submitbutton" type="submit">Weiter</button>
</div>
</form>
<div class="spacer" aria-hidden="true" style="height:80px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -0,0 +1,20 @@
<div style="margin: auto; max-width: 80%; font-size: 20px;">
<p>Studie Teil 3:</p>
<p>Im Folgenden sehen Sie ein Video, in dem der Avatar drei Mal „Gleis“ gebärdet, jedes Mal in eine etwas andere Richtung. So soll der Avatar in Zukunft die Position und Richtung des Gleises relativ zum Avatar und Zuschauer zum Ausdruck bringen.</p>
<p>
Bewerten Sie bitte die folgenden Aussagen auf einer Skala mit fünf Punkten, von „Trifft überhaupt nicht zu“, bis „Trifft vollkommen zu“.</p>
<ol start="1">
<li>Man kann gut erkennen, in welche Richtung das Gleis zeigt.</li>
<li>Die Gebärde ist schwer zu erkennen.</li>
<li>Das Anpassen der Richtung und der Position der Gebärde kann dabei helfen, besser zu verstehen, wo das Gleis liegt.</li>
<li>Ich finde diesen Ansatz nicht gut.</li>
</ol>
<p>Die Antwortmöglichkeiten sind jedes Mal:</p>
<ol start="1">
<li>Trifft überhaupt nicht zu</li>
<li>Trifft eher nicht zu</li>
<li>Ich weiß nicht</li>
<li>Trifft eher zu</li>
<li>Trifft vollkommen zu</li>
</ol>
</div>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title>
</head>
<body>
<div class="container">
<h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<div class="textblock">
{% include "p3infos.html" %}
</div>
<form class="dsgvoform" action="{{ url_for('sendpage') }}" method="post">
<div class="button-container">
<button id="submitbutton" type="submit">Weiter</button>
</div>
</form>
<div class="spacer" aria-hidden="true" style="height:80px"></div>
</div>
</body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html>

View File

@@ -13,6 +13,19 @@
allowfullscreen></iframe> allowfullscreen></iframe>
</div> </div>
{% elif (embed == "vimeo") %}
<div class="iframe-container">
<iframe title="vimeo-player" class="center" src="{{ video_url }}" frameborder="0" allowfullscreen></iframe>
</div>
{% elif (embed == "no") %}
<video controls>
<source src="{{ url_for('static', filename='videos/' + video_url) }}" type="video/mp4">
</video>
{% else %} {% else %}
{{code}} {{code}}
{% endif %} {% endif %}
@@ -36,8 +49,6 @@
</div> </div>
</div> </div>
<div class="dv_half"> <div class="dv_half">
<div class="iframe-container"> <div class="iframe-container">
<iframe class="center" src="{{ video_url2 }}" title="YouTube video player" frameborder="0" <iframe class="center" src="{{ video_url2 }}" title="YouTube video player" frameborder="0"
@@ -48,6 +59,42 @@
</div> </div>
{% elif (embed == "vimeo") %}
<div class="double_video_container">
<div class="dv_half">
<div class="iframe-container">
<iframe title="vimeo-player" class="center" src="{{ video_url1 }}" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<div class="dv_half">
<div class="iframe-container">
<iframe title="vimeo-player" class="center" src="{{ video_url2 }}" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
{% elif (embed == "no") %}
<div class="double_video_container">
<div class="dv_half">
<div class="iframe-container">
<video controls>
<source src="{{ url_for('static', filename='videos/' + video_url1) }}" type="video/mp4">
</video>
</div>
</div>
<div class="dv_half">
<div class="iframe-container">
<video controls>
<source src="{{ url_for('static', filename='videos/' + video_url2) }}" type="video/mp4">
</video>
</div>
</div>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% else %} {% else %}
{{code}} {{code}}
{% endif %} {% endif %}
@@ -73,7 +120,7 @@ step={{question["step"]}}
{% macro input(name, value='', type='text', size=20) -%} {% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{ <input type="{{ type }}" name="{{ name }}" value="{{
value|e }}" size="{{ size }}"> value|e }}" size="{{ size }}" class="inputs">
{%- endmacro %} {%- endmacro %}
<head> <head>
@@ -96,19 +143,24 @@ step={{question["step"]}}
{% if (infovideo) %} {% if (infovideo) %}
<dialog> <dialog>
<button class="dialogBtn" autofocus><img class="infoButtonIcon" id="buttonClose" src="{{ url_for('static', filename='icons/x-icon.png')}}" <button class="dialogBtn" autofocus><img class="infoButtonIcon" id="buttonClose"
alt="Delete Icon"></button> src="{{ url_for('static', filename='icons/x-icon.png')}}" alt="Delete Icon"></button>
<div class="iframe-container"> {% if ("videourl" in infovideo) %}
<iframe class="center" src="{{ infovideo['videourl'] }}" title="YouTube video player" frameborder="0" <video controls>
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" <source src="{{ url_for('static', filename=infovideo['videourl']) }}" type="video/mp4">
allowfullscreen></iframe> </video>
</div> {% endif %}
<div class="dialogTextContainer"> <div class="dialogTextContainer">
{% if ("infotext" in infovideo) %}
<p>{{ infovideo["infotext"] }}</p> <p>{{ infovideo["infotext"] }}</p>
{% endif %}
{% if ("htmlblock" in infovideo) %}
{% include infovideo["htmlblock"] %}
{% endif %}
</div> </div>
</dialog> </dialog>
<button class="dialogBtn"><img class="infoButtonIcon" id="buttonInfoIcon" src="{{ url_for('static', filename='icons/info-icon.png')}}" <button class="dialogBtn"><img class="infoButtonIcon" id="buttonInfoIcon"
alt="Info Icon"></button> src="{{ url_for('static', filename='icons/info-icon.png')}}" alt="Info Icon"></button>
<script src="{{ url_for('static', filename='infoDialogScript.js')}}"></script> <script src="{{ url_for('static', filename='infoDialogScript.js')}}"></script>
{% endif %} {% endif %}
@@ -126,13 +178,25 @@ step={{question["step"]}}
<form class="formlayout" id="question_form" action="{{ url_for('sendpage') }}" method="post"> <form class="formlayout" id="question_form" action="{{ url_for('sendpage') }}" method="post">
{% for question in questions %} {% for question in questions %}
{% if (questions[question]["type"] == "likert") %} {% if (questions[question]["type"] == "textblock") %}
<div class="likercontainer">
<div class="spacer" aria-hidden="true" style="height:30px"></div>
<div class="questioncontainer">
<h3 style="margin: auto;">{{ questions[question]['text']}}</h3>
</div>
<div class="spacer" aria-hidden="true" style="height:60px"></div>
{% elif (questions[question]["type"] == "likert") %}
<div class="likertcontainer">
<div class="questioncontainer">
<h3>{{ questions[question]['text']}}</h3> <h3>{{ questions[question]['text']}}</h3>
</div>
<div class="likert" style="--likert-rows: {{ questions[question]['points']|length() }}"> <div class="likert" style="--likert-rows: {{ questions[question]['points']|length() }}">
{% for point in questions[question]["points"] %} {% for point in questions[question]["points"] %}
<label> <label>
<input name="{{ questions[question]['name']}}" type="radio" <input class="inputs" name="{{ questions[question]['name']}}" type="radio"
id="{{ questions[question]['name'] }}" id="{{ questions[question]['name'] }}"
value="{{ questions[question]['points'][point]['value'] }}" value="{{ questions[question]['points'][point]['value'] }}"
{{required(questions[question])}} /><span>{{ questions[question]['points'][point]['text'] {{required(questions[question])}} /><span>{{ questions[question]['points'][point]['text']
@@ -141,44 +205,73 @@ step={{question["step"]}}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="spacer" aria-hidden="true" style="height:60px"></div>
{% elif (questions[question]["type"] == "likert-basic") %}
<div class="questioncontainer">
<h3>{{ questions[question]['text']}}</h3>
</div>
<div class="normal-likert-container">
{% for point in questions[question]["points"] %}
<label class="likertlabel">
<input class="likertinput" name="{{ questions[question]['name']}}" type="radio"
id="{{ questions[question]['name'] }}"
value="{{ questions[question]['points'][point]['value'] }}"
{{required(questions[question])}} /><span>{{ questions[question]['points'][point]['text']
}}
</span>
</label>
{% endfor %}
</div>
<div class="spacer" aria-hidden="true" style="height:60px"></div>
{% elif (questions[question]["type"] == "textinput") %} {% elif (questions[question]["type"] == "textinput") %}
<div class="textarea-container"> <div class="textarea-container">
<label class="textarea-label"> <label class="textarea-label">
{{ questions[question]['text']}} {{ questions[question]['text']}}
<textarea id="{{ questions[question]['name'] }}" name="{{ questions[question]['name'] }}" rows="6" <textarea class="inputs" id="{{ questions[question]['name'] }}"
cols="60" maxlength="{{ questions[question]['size'] }}" name="{{ questions[question]['name'] }}" rows="6" cols="60"
{{required(questions[question])}}></textarea> maxlength="{{ questions[question]['size'] }}" {{required(questions[question])}}></textarea>
</label> </label>
</div> </div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% elif (questions[question]["type"] == "dateinput") %} {% elif (questions[question]["type"] == "dateinput") %}
<div class="compressWidth"> <div class="compressWidth">
<label> <label>
{{ questions[question]['text']}}<input type="date" name="{{ questions[question]['name']}}" {{ questions[question]['text']}}<input class="inputs" type="date"
id="{{ questions[question]['name'] }}" {{required(questions[question])}}> name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}"
</label>
</div>
{% elif (questions[question]["type"] == "numberinput") %}
<div class="compressWidth">
<label>
{{ questions[question]['text']}}<input type="number" name="{{ questions[question]['name']}}"
id="{{ questions[question]['name'] }}" {{inputconfig(questions[question])}}
{{required(questions[question])}}> {{required(questions[question])}}>
</label> </label>
</div> </div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% elif (questions[question]["type"] == "numberinput") %}
<div class="compressWidth">
<label>
{{ questions[question]['text']}}<input class="inputs" type="number"
name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}"
{{inputconfig(questions[question])}} {{required(questions[question])}}>
</label>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% elif (questions[question]["type"] == "emailinput") %} {% elif (questions[question]["type"] == "emailinput") %}
<div class="compressWidth"> <div class="compressWidth">
<label> <label>
{{ questions[question]['text']}}<input type="email" name="{{ questions[question]['name']}}" {{ questions[question]['text']}}<input class="inputs" type="email"
id="{{ questions[question]['name'] }}" {{required(questions[question])}}> name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}"
{{required(questions[question])}}>
</label> </label>
</div> </div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% elif (questions[question]["type"] == "dropdowninput") %} {% elif (questions[question]["type"] == "dropdowninput") %}
<div class="compressWidth"> <div class="compressWidth">
<label> <label>
{{ questions[question]['text']}}<select name="{{ questions[question]['name']}}" {{ questions[question]['text']}}<select class="inputs" name="{{ questions[question]['name']}}"
{{required(questions[question])}}> {{required(questions[question])}}>
<option value="" disabled selected>{{ questions[question]['defaulttext']}}</option> <option value="" disabled selected>{{ questions[question]['defaulttext']}}</option>
{% for point in questions[question]["points"] %} {% for point in questions[question]["points"] %}
@@ -191,10 +284,29 @@ step={{question["step"]}}
</select> </select>
</label> </label>
</div> </div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% elif (questions[question]["type"] == "multiplechoice") %}
<div class="compressWidth">
<p>
<div>
{{ questions[question]['text']}}
{% for point in questions[question]["points"] %}
<label for="{{ point }}">
<input type="checkbox" id="{{ point }}" name="{{ questions[question]['name'] }}"
value="{{ questions[question]['points'][point]['value'] }}">
{{ questions[question]['points'][point]['text']}}</label>
{% endfor %}
</div>
</p>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
{% elif (questions[question]["type"] == "videoinput") %} {% elif (questions[question]["type"] == "videoinput") %}
<div class="spacer" aria-hidden="true" style="height:30px"></div> <div class="spacer" aria-hidden="true" style="height:30px"></div>
<h3>{{ questions[question]['text']}}</h3> <h3 style="text-align: center;">{{ questions[question]['text']}}</h3>
<div class="centertext"> <div class="centertext">
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()"> <button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
@@ -224,19 +336,36 @@ step={{question["step"]}}
<video autoplay muted playsinline id="videoDisplay"></video> <video autoplay muted playsinline id="videoDisplay"></video>
</div> </div>
<script src="{{ url_for('static', filename='videoscript.js')}}"> <p id="countdown" style="font-size: 40px; text-align: center;"></p>
<script src="{{ url_for('static', filename='videoscript.js')}}">
</script> </script>
{% else %} {% else %}
<p>Error: Block {{config["question 1"]["blocks"][block]["type"]}} could not be loaded!</p> <p>Error: Block {{config["question 1"]["blocks"][block]["type"]}} could not be loaded!</p>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="spacer" aria-hidden="true" style="height:30px"></div>
<div class="button-container"> <div class="button-container">
<input id="submitbutton" type="submit" value="submit" />
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> --> {% if ("lastquestion" in stimlui) %}
<button id="submitbutton" type="submit">Studie Beenden</button>
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> /// <input class="inputs" id="submitbutton" type="submit" value="submit" />-->
{% else %}
<button id="submitbutton" type="submit">Weiter</button>
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> /// <input class="inputs" id="submitbutton" type="submit" value="submit" />-->
{% endif %}
</div> </div>
</form> </form>
<div class="spacer" aria-hidden="true" style="height:80px"></div>
</div> </div>
</body> </body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html> </html>

View File

@@ -4,20 +4,69 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}--> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<title>DGS Avatar Study</title> <title>DGS Avatar Study</title>
</head> </head>
<body> <body>
<div class="container"> <div class="container" style="height: 100%; font-size: 22px;">
<h2>Hello! Thank you for participating in our study!</h2> <h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<form action="{{ url_for('startpage') }}" method="post">
<label for="terms-and-conditions">
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" /> I accept the +terms and conditions</a>
</label>
<p><input id="submitbutton" type = "submit" value = "submit" /></p> <div class="iframe-container2">
<iframe title="vimeo-player" src="https://player.vimeo.com/video/1031133490?h=bfa5b559f9" frameborder="0"
allowfullscreen></iframe>
</div>
<div class="textblock">
<p>
Hallo und willkommen zu dieser Studie, danke für Ihre Teilnahme.
</p>
<p>
In dieser Studie geht es um die Entwicklung von Gebärdensprachavataren.
</p>
<p>
Bitte berücksichtige, dass die Avatare, die wir zeigen, in keiner Weise der Qualität entsprechen, wie
sie genutzt werden sollen. Sie sind nur Beispiele, die niemals in einer Anwendung zu sehen sein werden.
Es geht darum, dass wir die Darstellung der Hände und Arme testen wollen. Bitte bewertet deshalb nicht
Darstellungsqualität und lasst euch nicht davon beeinflussen. Außerdem möchten wir testen, wie die
Nutzung einer Feedback-Funktion per Videoaufnahme verwendet werden kann.
</p>
<p>
Die Rückmeldung von euch ist wichtig und ihr könnt das in Gebärdensprache machen.
Die Teilnahme wird nur für die Auswertung genutzt. In keiner Weise wird etwas von euch veröffentlicht, auch nicht eure Videos.
Alle weiteren Infos findet ihr im Text.
</p>
<p>
Danke für eure Teilnahme, die im Rahmen eine Abschlussarbeit erfolgt. Weitere Infos dazu findet ihr im Impressum.
</p>
<div class="spacer" aria-hidden="true" style="height:20px"></div>
<p>
Die Teilnahme an dieser Studie ist komplett freiwillig, Sie können die Studie zu jedem Zeitpunkt
abbrechen, indem Sie die Seite einfach schließen. Sie können außerdem die Löschung aller Daten bei uns
beantragen: avatarstudy@proton.me
</p>
</div>
<form class="dsgvoform" action="{{ url_for('startpage') }}" method="post">
<label for="terms-and-conditions">
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" />
Ich akzeptiere die <a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutzbestimmungen</a>
</label>
<div class="button-container">
<button id="submitbutton" type="submit">Weiter</button>
</div>
</form> </form>
<div class="spacer" aria-hidden="true" style="height:50px"></div>
</div> </div>
</body> </body>
<footer>
<div class="container" style="font-size: 19px;">
<a href="{{ url_for('startpage') }}" target="_blank">Startseite</a>
<a href="{{ url_for('impressum') }}" target="_blank">Impressum</a>
<a href="{{ url_for('datenschutz') }}" target="_blank">Datenschutz</a>
</div>
</footer>
</html> </html>

View File

@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<script>const ICON_PATHS = {
cameraofficon: "{{ url_for('static', filename='icons/camera-off-icon.png') }}",
cameraicon: "{{ url_for('static', filename='icons/camera-icon.png') }}",
stopicon: "{{ url_for('static', filename='icons/stop-icon.png') }}",
recordicon: "{{ url_for('static', filename='icons/record-icon.png') }}"
};</script>
</head>
<body>
<div class="container">
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/1017028141?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="0020-simple"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
<form class="formlayout" id="question_form" action="{{ url_for('sendpage') }}" method="post">
<div class="likertcontainer">
<div class="likert" style="--likert-rows: 7">
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 1
</span>
</label>
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 2
</span>
</label>
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 3
</span>
</label>
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 4
</span>
</label>
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 5
</span>
</label>
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 6
</span>
</label>
<label>
<input class="inputs" name="test1" type="radio" id="test1" value="1" required /><span> Option 7
</span>
</label>
</div>
</div>
<div class="normal-likert-container" style="--likert-rows: 7">
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 1
</span>
</label>
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 2
</span>
</label>
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 3
</span>
</label>
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 4
</span>
</label>
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 5
</span>
</label>
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 6
</span>
</label>
<label class="likertlabel">
<input class="likertinput" name="test1" type="radio" id="test1" value="1" required /><span> Option 7
</span>
</label>
</div>
<div class="textarea-container">
<label class="textarea-label">
<textarea class="inputs" id="text" name="text" rows="6" cols="60" maxlength="200"></textarea>
</label>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
<div class="centertext">
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}"
alt="Camera Icon">
</button>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
<button type="button" class="videocontrols" id="buttonRecord" style="display:none"
onclick="recordButton()">
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}"
alt="Camera Icon">
</button>
<button type="button" class="videocontrols" id="buttonDelete" style="display:none" disabled
onclick="deleteButton()">
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}"
alt="Delete Icon" class="buttondisable">
</button>
</div>
<div class="spacer" aria-hidden="true" style="height:15px"></div>
<div id="videoContainer" style="display:none" class="video-container">
<video autoplay muted playsinline id="videoDisplay"></video>
</div>
<script src="{{ url_for('static', filename='videoscript.js')}}">
</script>
<div class="button-container">
<input class="inputs" id="submitbutton" type="submit" value="submit" />
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> -->
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Task 3 Videos</title>
</head>
<body>
<div class="container">
<div class="double_video_container">
<div class="dv_half">
<div class="iframe-container">
<video controls>
<source src="{{ url_for('static', filename='videos/0009-intro-inflected.mp4') }}" type="video/mp4">
</video>
</div>
</div>
<div class="dv_half">
<div class="iframe-container">
<video controls>
<source src="{{ url_for('static', filename='videos/0009-variation-inflected.mp4') }}" type="video/mp4">
</video>
</div>
</div>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
</div>
</body>
</html>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}--> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
@@ -9,37 +10,65 @@
<body> <body>
<div class="container"> <div class="container">
<h2>Hallo! Danke, dass sie bei dieser Studie teilnehmen!</h2> <h2>SLAEFORMS Gebärdensprachavatar Studie</h2>
<div class="textblock"> <div class="textblock">
<p>
Wilkommen bei dieser Prototyp Seite für eine Gebärdensprach-Avatar Studie.
</p>
<p>
Dieses Projekt ist noch in Arbeit und nicht fertiggestellt. Es soll die bisher implementierten Funktionen zeigen und wurde nur zu Präsentationszwecken und um Feedback zu sammeln online gestellt.
</p>
<p>
Das Ziel dieses Projekt ist, es Wissenschaftlern (und anderen) zu ermöglichen relativ einfach online Studien zu erstellen, die auf die besonderen Anforderungen von Gehörlosen Studienteilnehmern abgestimmt sind.
Dazu zählt insbesondere die Möglichkeit Videos aufnehmen zu können um Feedback zu geben, und das Einbinden von Videos die Fragen und Aufgabenstellungen in Gebärdensprache erklären sollen.
In diesem Prototyp finden sich aktuell aber nur Platzhaltervideos. Keine der Inhalte sind bereits Teil einer echten Studie.
</p>
<p>
Wenn sie den Prototyp verwenden und dabei Eingaben machen oder Videos hochladen, sind diese auf dem Server gespeichert. Die Daten werden nach der Testphase gelöscht und können bis dahin nur von dem Admin eingesehen werden.
Wenn sie eingegebene oder hochgeladene Daten löschen wollen, wenden sie sich bitte an den Admin unter: jan.dickmann@web.de
</p>
<p>
Auch bei sonstigen Fragen, wenden sie sich bitte an: jan.dickmann@web.de
</p>
<p>
In dieser Studie geht es um die Entwicklung von Gebärdensprachavataren, also animierte 3D-Avatare, die
Gebärdensprache sprechen sollen.
</p>
<p>
Bitte beachten Sie, dass diese Avatare noch in einer frühen Entwicklungsphase und noch nicht für den
Gebrauch in der echten Welt geeignet sind. In dieser Studie geht es darum, einzelne Aspekte der Avatare
und neue technologische Ansätze zu testen. Dementsprechend haben die Avatare noch viele offensichtliche
Schwächen, wie zum Beispiel, dass bisher nur die Arme und der Oberkörper, aber nicht das Gesicht
animiert ist.
</p>
<p>
Diese Studie richtet sich an Menschen, die Gebärdensprache beherrschen, insbesondere gehörlose Menschen.
Nehmen Sie bitte nur Teil, wenn Sie (Deutsche) Gebärdensprache beherrschen.
</p>
<p>
Während der Studie werden sie sich Videos anschauen, diese bewerten und Feedback dazu geben können.
Verwenden Sie deshalb bitte, wenn möglich ein Gerät mit einem großen Bildschirm (Laptop, PC, Tablet) für
Ihre Teilnahme, damit Sie die Videos in ausreichender Größe sehen können.
</p>
<p>
Sie haben während der Studie die Möglichkeit (optional) Videofeedback zu geben, dazu brauchen sie eine
Webcam. Die Videos werden nicht veröffentlicht und nur Übersetzern zur Auswertung der Studienergebnisse
gezeigt.
</p>
<p>
Für jeden Teil der Studie gibt es ein Erklärvideo, das die Fragestellung und die Antwortmöglichkeiten in
Gebärdensprache erklärt. Falls Sie das Video während der Studie nochmal sehen möchten, können Sie es
über den „Info“-Knopf auf der rechten Seite aufrufen.
</p>
<p>
Die Teilnahme an dieser Studie ist komplett freiwillig, Sie können die Studie zu jedem Zeitpunkt
abbrechen, indem Sie die Seite einfach schließen. Sie können außerdem die Löschung aller Daten bei uns
beantragen: avatarstudy@proton.me
</p>
<p>
Ich, bestätige, dass ich die „Information betroffener Personen zur Verarbeitung personenbezogener Daten“
erhalten habe.
Ich willige hiermit ein, dass das DFKI Daten aus wissenschaftlichen Experimenten im Zusammenhang mit
dieser Studie für die Darstellung von Forschungsergebnissen im Bereich des Affective Computing verwendet
werden.
</p>
<p>
Mir ist bekannt, dass ich das Recht habe, meine Einwilligung jederzeit zu widerrufen. Durch den Widerruf
der Einwilligung wird die Rechtmäßigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten
Verarbeitung nicht berührt. Ein Widerruf kann per Email an avatarstudy@proton.me geschickt werden.
</p>
</div> </div>
<form class="dsgvoform" action="{{ url_for('startpage') }}" method="post"> <form class="dsgvoform" action="{{ url_for('startpage') }}" method="post">
<label for="terms-and-conditions">
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" /> I accept the +terms and conditions
</label>
<div class="button-container"> <div class="button-container">
<input id="submitbutton" type="submit" value="submit" /> <input id="submitbutton" type="submit" value="submit" />
</div> </div>
</form> </form>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,37 +1,325 @@
{ {
"Block 1":{
"type": "SinglePage",
"template": "startpage.html"
},
"Block 2": { "Block 2": {
"type": "TaskTemplate", "type": "TaskTemplate",
"tempalte": "tempaltetest1.html", "tempalte": "standard_template.html",
"name" : "Block2Responses", "stimuli": {
"databasetable": { "type": "single_video",
"question_title" : { "order": "random",
"type":"likert" "list": {
"video_1": "https://www.youtube-nocookie.com/embed/iI2bjpgLgu0?si=pWWwitq8Tl9zAs04",
"video_2": "https://www.youtube-nocookie.com/embed/R_LNEJiShRM?si=vm5mj-xs04nfijkf",
"video_3": "https://www.youtube-nocookie.com/embed/DJnHVnT2YEk?si=e6qe7nrHaRYJ3NQe",
"video_4": "https://www.youtube-nocookie.com/embed/6BTZn7tqSzk?si=jQH8RHGMIP9ARsT5",
"video_5": "https://www.youtube-nocookie.com/embed/bCRqmhWjuCk?si=iM2dwOSTosLq08Pr",
"video_6": "https://www.youtube-nocookie.com/embed/hgE2k8y9-qc?si=kR2rshmVcb9P70r0"
},
"configuration": {
"embed": "yt"
}
},
"questions": {
"question1": {
"type": "likert",
"name": "likertscale",
"text": "Wie würden sie dieses Video bewerten?",
"required": "true",
"points": {
"p1": {
"value": "1",
"text": "Ich finde es gar nicht gut 🙁👎"
},
"p2": {
"value": "2",
"text": "Ich finde es nicht gut 👎"
},
"p3": {
"value": "3",
"text": "Ich finde es weder gut noch schlecht"
},
"p4": {
"value": "4",
"text": "Ich finde es gut 👍"
},
"p5": {
"value": "5",
"text": "Ich finde es sehr gut 😊👍"
} }
} }
}, },
"question2": {
"type": "textinput",
"name": "text_feedback",
"text": "Hier können sie uns Feedback geben",
"required": "false",
"size": "250"
},
"question3": { "question3": {
"type": "single", "type": "videoinput",
"video1": "https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6", "text": "Hier können sie per Video Feedback geben",
"scales": { "name": "video_feedback",
"block1":{ "required": "false"
"type": "likert", }
"numberofpoints": "3", },
"infovideo": {
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden.\\n Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt.\\n Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
"configuration": {
"embed": "yt"
}
},
"database_table": {
"table_name": "single_video_responses",
"fields": {
"likertscale": {
"type": "integer",
"nullable": "false"
},
"text_feedback": {
"type": "string",
"size": "250",
"nullable": "true"
},
"video_upload": {
"type": "string",
"size": "100",
"nullable": "true"
}
}
}
},
"Block 1": {
"type": "TaskTemplate",
"tempalte": "standard_template.html",
"stimuli": {
"type": "empty",
"list": {
"empty_stimulus": ""
}
},
"questions": {
"question1_alter": {
"type": "numberinput",
"name": "alter",
"text": "Alter:",
"required": "true",
"min": "1",
"max": "120"
},
"question2_geschlecht": {
"type": "dropdowninput",
"name": "geschlecht",
"text": "Geschlecht:",
"required": "true",
"defaulttext": "",
"points": { "points": {
"point1": "left", "männlich": {
"point2": "none", "value": "Männlich",
"point3": "right" "text": "Männlich"
},
"weiblich": {
"value": "Weiblich",
"text": "Weiblich"
},
"divers": {
"value": "Divers",
"text": "Divers"
},
"keine_angabe": {
"value": "keine_angabe",
"text": "Keine Angabe"
}
} }
}, },
"block2":{ "question3_hoerstatus": {
"type": "dropdowninput",
"name": "hoerstatus",
"text": "Hörstatus:",
"required": "true",
"defaulttext": "",
"points": {
"hörend": {
"value": "Hörend",
"text": "Hörend"
},
"schwerhörig": {
"value": "Schwerhörig",
"text": "Schwerhörig"
},
"gehörlos": {
"value": "Gehörlos",
"text": "Gehörlos"
}
}
},
"question4_bevorzugte_kommunikation": {
"type": "dropdowninput",
"name": "bevorzugte_kommunikation",
"text": "Bevorzugte Kommunikationsform:",
"required": "true",
"defaulttext": "",
"points": {
"gesprochen": {
"value": "Gesprochene Sprache",
"text": "Gesprochene Sprache"
},
"text": {
"value": "Text",
"text": "Text"
},
"gebärdensprache": {
"value": "Gebärdensprache",
"text": "Gebärdensprache"
}
}
},
"question5_gebeardenzeitraum": {
"type": "numberinput",
"name": "gebärdenzeitraum",
"text": "Wie viele Jahre verwenden sie schon Gebärdensprache:",
"required": "true",
"min": "0",
"max": "100",
"step": "0.5"
},
"question6_sprachkompetenz": {
"type": "numberinput",
"name": "gebärdensprachkompetenz",
"text": "Wie schätzen sie ihre Gebärdensprachkompetenz ein (1-10):",
"required": "true",
"min": "1",
"max": "10"
}
},
"infovideo": {
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden. \n Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt. Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
"configuration": {
"embed": "yt"
}
},
"database_table": {
"table_name": "demographic_data",
"fields": {
"alter": {
"type": "integer",
"nullable": "false"
},
"geschlecht": {
"type": "string",
"size": "14",
"nullable": "false"
},
"hoerstatus": {
"type": "string",
"size": "14",
"nullable": "false"
},
"bevorzugte_kommunikation": {
"type": "string",
"size": "22",
"nullable": "false"
},
"gebärdenzeitraum": {
"type": "float",
"nullable": "false"
},
"gebärdensprachkompetenz": {
"type": "integer",
"nullable": "false"
}
}
}
},
"Block 3": {
"type": "TaskTemplate",
"tempalte": "standard_template.html",
"stimuli": {
"type": "double_video",
"list_1": {
"video_1": "https://www.youtube-nocookie.com/embed/IqGVT1q1PtM?si=kel7ZWEQl3h-h522",
"video_2": "https://www.youtube-nocookie.com/embed/g9KA72jN5SM?si=O7dfqTXdFCCAScJ-"
},
"list_2": {
"video_2": "https://www.youtube-nocookie.com/embed/g9KA72jN5SM?si=O7dfqTXdFCCAScJ-",
"video_1": "https://www.youtube-nocookie.com/embed/IqGVT1q1PtM?si=kel7ZWEQl3h-h522"
},
"configuration": {
"embed": "yt"
}
},
"questions": {
"question1": {
"type": "likert",
"name": "likertscale",
"text": "Welches Video gefällt ihnen besser?",
"required": "true",
"points": {
"p1": {
"value": "1",
"text": "Ich finde das linke Video besser"
},
"p2": {
"value": "2",
"text": "Ich finde beide Videos gleich gut"
},
"p3": {
"value": "3",
"text": "Ich finde das rechte Video besser"
}
}
}
},
"infovideo": {
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden. Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt. Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
"configuration": {
"embed": "yt"
}
},
"database_table": {
"table_name": "double_video_responses",
"fields": {
"likertscale": {
"type": "integer",
"nullable": "false"
}
}
}
},
"Block 4": {
"type": "TaskTemplate",
"tempalte": "standard_template.html",
"stimuli": {
"type": "empty",
"list": {
"empty_stimulus": ""
}
},
"questions": {
"question1": {
"type": "textinput", "type": "textinput",
"length": "200" "name": "formfeedback",
"text": "Das war der Prototyp für dieses Studientool. Über Feedback würde ich mich freuen. Entweder hier oder per Email unter: avatarstudy@proton.me",
"required": "false",
"size": "1000"
}
}, },
"block3":{ "infovideo": {
"type": "video" "videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden. Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt. Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
"configuration": {
"embed": "yt"
}
},
"database_table": {
"table_name": "feedback_responses",
"fields": {
"formfeedback": {
"type": "string",
"size": "1000",
"nullable": "true"
}
} }
} }
} }

File diff suppressed because it is too large Load Diff

1089
slaeforms/userstudy1.json Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.