SushantGautam commited on
Commit
04d8858
1 Parent(s): d117b35

Upload folder using huggingface_hub

Browse files
Files changed (7) hide show
  1. .gitignore +8 -0
  2. Dockerfile +16 -0
  3. README.md +39 -12
  4. app.py +182 -0
  5. environment.yml +12 -0
  6. requirements.txt +3 -0
  7. templates/index.html +480 -0
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ __pycache**
2
+ static/frames/**
3
+ static/saved_video/**
4
+ static/poster/**
5
+ static/videos/**
6
+ *DS_Store**
7
+ *.json
8
+ static/**
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM continuumio/miniconda3
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . /app
6
+ COPY environment.yml /app/environment.yml
7
+
8
+ RUN apt-get update && apt-get install -y \
9
+ libgl1-mesa-glx \
10
+ libglib2.0-0
11
+
12
+ RUN conda env create -f environment.yml
13
+
14
+ EXPOSE 7860
15
+
16
+ CMD ["conda", "run", "-n", "playerTV", "python", "app.py"]
README.md CHANGED
@@ -1,12 +1,39 @@
1
- ---
2
- title: PlayerTV Test
3
- emoji: 🦀
4
- colorFrom: indigo
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 4.42.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PlayerTV Flask Application
2
+
3
+ This Flask web application is designed for processing soccer clips and extracting player tracking data. Its primary function is to enable the tracking and clipping of a specific player within a video clip.
4
+
5
+ ## Installation
6
+
7
+ 1. Clone the repository:
8
+
9
+ ```bash
10
+ git clone https://github.com/simula/playertv
11
+
12
+ 2. Navigate to the GUI folder.
13
+
14
+ 3. Install the required dependencies:
15
+ ```bash
16
+ pip install -r requirements.txt
17
+
18
+ ## Usage
19
+
20
+ 1. Start the flask application:
21
+ ```bash
22
+ python app.py
23
+
24
+ 2. Open your web browser and go to http://localhost:5000/ to access the application.
25
+
26
+ 3. Follow the instructions on the web interface to upload a video file and process it.
27
+
28
+
29
+ ## Features
30
+
31
+ 1. Accepts M3U8 HLS playlists as input from the Forzify endpoints for Eliteserien 2023 soccer clips.
32
+ 2. Processes the video into MP4 format.
33
+ 3. Accepts a JSON file containing processed information about all players, including their identification, re-identification and tracking data.
34
+ 4. Provides users with the ability to select teams and players involved in the soccer clip.
35
+ 5. Initiates clipping of the video into frames showcasing the specified player based on user selection.
36
+ 6. Displays the clipped video as output.
37
+
38
+ ## License
39
+ This project is licensed under the MIT License.
app.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, url_for
2
+ import subprocess
3
+ import os
4
+ import uuid
5
+ import json
6
+ import cv2
7
+ import shutil
8
+ from tempfile import TemporaryDirectory
9
+ from PIL import ImageDraw, ImageFont
10
+ from collections import defaultdict
11
+ from PIL import Image as im
12
+
13
+
14
+ app = Flask(__name__)
15
+
16
+ @app.route('/')
17
+ def index():
18
+
19
+ return render_template('index.html')
20
+
21
+
22
+ # Global variable to store the path of the last processed MP4 file
23
+ last_processed_mp4_path = None
24
+
25
+ def convert_to_mp4(input_url):
26
+ global last_processed_mp4_path
27
+ hash_name= hash(input_url)
28
+ output_filename = f"{hash_name}.mp4"
29
+ output_filepath = os.path.join('static/saved_video', output_filename)
30
+ full_output_path = os.path.join('', output_filepath)
31
+
32
+ if input_url.lower().endswith('.mp4'):
33
+ # If the input is already an MP4, just return its absolute path
34
+ last_processed_mp4_path = os.path.abspath(input_url)
35
+ return url_for('static', filename='saved_video/' + os.path.basename(input_url), _external=True)
36
+
37
+ # check ifhashed mp4 file exists already
38
+ if os.path.exists(full_output_path):
39
+ return url_for('static', filename='saved_video/' + output_filename, _external=True)
40
+
41
+ # If the input is M3U8, convert it to MP4
42
+ command = ['ffmpeg', '-i', input_url, '-c:v', 'copy', '-c:a', 'copy', full_output_path]
43
+
44
+ try:
45
+ print("Running command:", " ".join(command))
46
+ subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
47
+ last_processed_mp4_path = full_output_path
48
+ return url_for('static', filename='saved_video/' + output_filename, _external=True)
49
+ except subprocess.CalledProcessError as e:
50
+ print(f"FFmpeg Error: {e.stderr.decode('utf-8')}")
51
+ return None
52
+ except subprocess.TimeoutExpired:
53
+ print("FFmpeg command timed out")
54
+ return None
55
+
56
+ @app.route('/process_video', methods=['POST'])
57
+ def process_video():
58
+ input_url = request.json['m3u8Url']
59
+ mp4_path = convert_to_mp4(input_url)
60
+ if mp4_path:
61
+ return jsonify({'mp4Path': mp4_path})
62
+ else:
63
+ return jsonify({'error': 'Failed to process video'}), 500
64
+
65
+
66
+
67
+ @app.route('/upload_and_filter', methods=['POST'])
68
+ def upload_and_filter():
69
+ team = request.form['team'].strip()
70
+ playerId = request.form['playerId'].strip()
71
+ print(f"Received team: {team}, playerId: {playerId}")
72
+
73
+ file = request.files['file']
74
+
75
+ # Read and parse the JSON file
76
+ json_content = json.load(file.stream)
77
+ frames_data = json_content.get('frame', {})
78
+
79
+ # Process the JSON file
80
+ filtered_data = []
81
+ frame_numbers = []
82
+ bounding_boxes = {}
83
+ player_info = {}
84
+ for frame_number, tracks in frames_data.items():
85
+ for track_id, track_info in tracks.items():
86
+ team_value = str(track_info.get('team_id', '')).strip()
87
+ player_id_value = str(track_info.get('kit_number', '')).strip()
88
+ if team_value == team and player_id_value == playerId:
89
+ player_info[int(frame_number)] = {'kit_number': player_id_value}
90
+ filtered_data.append(track_info)
91
+ frame_numbers.append(int(frame_number))
92
+ bounding_boxes.setdefault(int(frame_number), []).append(track_info.get('xywh'))
93
+
94
+ # Save the filtered data to a new file
95
+ output_file_path = 'filtered_data.json'
96
+ with open(output_file_path, 'w', encoding='utf-8') as f:
97
+ json.dump(filtered_data, f, indent=4)
98
+
99
+ # Draw bounding boxes on frames
100
+ frames_directory = os.path.join(app.static_folder, 'frames')
101
+ shutil.rmtree(frames_directory)
102
+ os.makedirs(frames_directory)
103
+ draw_bounding_boxes(last_processed_mp4_path, frames_directory, filtered_data, player_info)
104
+
105
+ # Create video from frames
106
+ output_video_path = os.path.join(app.static_folder, 'videos', 'output_video.mp4')
107
+ os.makedirs(os.path.dirname(output_video_path), exist_ok=True)
108
+ print("Creating video from frames")
109
+ print("args:", frame_numbers, frames_directory, output_video_path)
110
+ create_video_from_frames(frame_numbers, frames_directory, output_video_path)
111
+
112
+ # URL for the video
113
+ video_url = url_for('static', filename='videos/output_video.mp4', _external=True)
114
+
115
+ return jsonify({'message': 'File processed successfully', 'videoPath': video_url})
116
+
117
+ def create_video_from_frames(frames, frames_directory, output_video_path, fps=25):
118
+ with TemporaryDirectory() as temp_dir:
119
+ # Clear the contents of the temporary directory
120
+ shutil.rmtree(temp_dir)
121
+ os.makedirs(temp_dir)
122
+ print("Temp dir:", temp_dir)
123
+
124
+ # Symlink the specific frames into the temporary directory in sequential order
125
+ for i, frame in enumerate(sorted(frames)):
126
+ frame_file = os.path.join(frames_directory, f"{frame}.png")
127
+ temp_frame_file = os.path.join(temp_dir, f"{i+1:04d}.png")
128
+ if os.path.exists(frame_file):
129
+ os.symlink(frame_file, temp_frame_file)
130
+ else:
131
+ print(f"Frame file {frame_file} not found")
132
+
133
+ # Prepare and run the ffmpeg command
134
+ frame_pattern = os.path.join(temp_dir, "%04d.png")
135
+ ffmpeg_command = [
136
+ "ffmpeg", "-y", "-i", frame_pattern, "-r", str(fps),
137
+ "-pix_fmt", "yuv420p", output_video_path
138
+ ]
139
+
140
+ print("Running ffmpeg command:", " ".join(ffmpeg_command))
141
+ result = subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142
+ print(result.stdout.decode())
143
+ print(result.stderr.decode())
144
+
145
+
146
+
147
+ def draw_bounding_boxes(video_url, frames_directory, bounding_boxes_, player_info):
148
+ _bounding_boxes_ = defaultdict(list)
149
+ for data in bounding_boxes_:
150
+ frame_number, boxes, kit = data.get("frame"), data.get("xywh"), data.get("kit_number")
151
+ _bounding_boxes_[frame_number].append((boxes, kit))
152
+
153
+ capture = cv2.VideoCapture(video_url)
154
+ frame_number = 0
155
+ while True:
156
+ frame_path = os.path.join(frames_directory, f"{frame_number}.png")
157
+ ret, image = capture.read()
158
+ if not ret:
159
+ break
160
+ annots = _bounding_boxes_[frame_number]
161
+ frame_number += 1
162
+ if not annots:
163
+ continue
164
+
165
+ for annon in annots:
166
+ box, kit = annon
167
+ x, y, w, h = box
168
+ top_left = (int(x), int(y))
169
+ bottom_right = (int(x+w), int(y+h))
170
+ cv2.rectangle(image, top_left, bottom_right, (0, 0, 255), 3) # Red rectangle
171
+
172
+ kit_number = str(kit) # Ensure kit number is a string
173
+ print(kit_number, "being drawn on Frame number:", frame_number)
174
+
175
+ text_position = (int(x), max(0, int(y) - 5))
176
+ cv2.putText(image, kit_number, text_position, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # Red text
177
+ cv2.imwrite(frame_path, image)
178
+
179
+
180
+
181
+ if __name__ == '__main__':
182
+ app.run(debug=False, port=7860, host='0.0.0.0')
environment.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: playerTV
2
+ channels:
3
+ - conda-forge
4
+ - defaults
5
+ dependencies:
6
+ - opencv==4.10.0
7
+ - ffmpeg==6.1.2
8
+ - python==3.11.7
9
+ - pip
10
+ - pip:
11
+ - flask==2.2.5
12
+ - pillow==10.4.0
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Flask==2.0.2
2
+ opencv-python==4.5.3.56
3
+ Pillow==8.3.2
templates/index.html ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <title>PlayerTV</title>
6
+ <link rel="icon" type="image/webp" href="/static/poster/playertv.webp">
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
10
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet" />
11
+ </head>
12
+
13
+ <body>
14
+ <!-- Dark mode -->
15
+ <div class="dark-mode-toggle-container">
16
+ <span class="dark-mode-toggle-label">Dark Mode:</span>
17
+ <label class="switch">
18
+ <input type="checkbox" id="darkModeToggle">
19
+ <span class="slider"></span>
20
+ </label>
21
+ </div>
22
+
23
+
24
+
25
+
26
+
27
+ <!-- pop up for the advance mode -->
28
+ <div id="configAdvModal" class="config-modal">
29
+ <div class="config-modal-content">
30
+ <span class="config-modal-close">&times;</span>
31
+ <h2>Advanced Settings</h2>
32
+
33
+ <div class="card-grid">
34
+
35
+ <!-- Object Detection Parameters Card -->
36
+ <div class="config-card">
37
+ <h3>Object Detection Parameters</h3>
38
+ <div class="config-item">
39
+ <label for="predictor_type">Object Detection Model:</label>
40
+ <select id="predictor_type">
41
+ <option value="predictor_type_yolov8" selected>YOLOv8</option>
42
+ <option value="predictor_type_yolov82">YOLOx</option>
43
+ </select>
44
+ </div>
45
+ <div class="config-item">
46
+ <label for="ckpt">Checkpoint:</label>
47
+ <input type="text" id="ckpt" name="ckpt" value="path/to/model/weight.pt">
48
+ </div>
49
+ <div class="config-item">
50
+ <label for="conf_thres">Confidence Threshold:</label>
51
+ <input type="text" id="conf_thres" name="conf_thres" value="0.25" min="0" max="1" step="0.01">
52
+ </div>
53
+ <div class="config-item">
54
+ <label for="iou_thresh">IoU Threshold:</label>
55
+ <input type="text" id="iou_thresh" name="iou_thresh" value="0.7" min="0" max="1" step="0.01">
56
+ </div>
57
+ <div class="config-item">
58
+ <label for="iou_nms_thres">IoU NMS Threshold:</label>
59
+ <input type="number" id="iou_nms_thres" name="iou_nms_thres" value="0.7" min="0" max="1"
60
+ step="0.01">
61
+ </div>
62
+ <div class="config-item">
63
+ <label for="inf_img_size">Information Image Size:</label>
64
+ <input type="text" id="inf_img_size" name="inf_img_size" value="[800, 1440]">
65
+ </div>
66
+ <div class="config-item">
67
+ <label for="class_whitelist">Class Whitelist:</label>
68
+ <input type="text" id="class_whitelist" name="class_whitelist" value="[0]">
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Deep EIOU Parameters Card -->
73
+ <div class="config-card">
74
+
75
+
76
+ <h3>Deep-EIoU Parameters</h3>
77
+ <div class="config-item">
78
+ <label for="saveVideo">Save Video:</label>
79
+ <input type="checkbox" id="saveVideo" name="saveVideo" checked>
80
+ </div>
81
+
82
+ <div class="config-item">
83
+ <label for="device">Device:</label>
84
+ <select id="device">
85
+ <option value="gpu" selected>GPU</option>
86
+ <option value="cpu">CPU</option>
87
+ </select>
88
+ </div>
89
+ <div class="config-item">
90
+ <label for="trackHighThresh">Track High Threshold:</label>
91
+ <input type="number" id="trackHighThresh" name="trackHighThresh" value="0.25" min="0" max="1"
92
+ step="0.01">
93
+ </div>
94
+ <div class="config-item">
95
+ <label for="trackLowThresh">Track Low Threshold:</label>
96
+ <input type="number" id="trackLowThresh" name="trackLowThresh" value="0.25" min="0" max="1"
97
+ step="0.01">
98
+ </div>
99
+ <div class="config-item">
100
+ <label for="trackBuffer">Track Buffer:</label>
101
+ <input type="number" id="trackBuffer" name="trackBuffer" value="0.25" min="0" max="1"
102
+ step="0.01">
103
+ </div>
104
+ <div class="config-item">
105
+ <label for="matchThresh">Match Threshold:</label>
106
+ <input type="number" id="matchThresh" name="matchThresh" value="0.25" min="0" max="1"
107
+ step="0.01">
108
+ </div>
109
+ <div class="config-item">
110
+ <label for="aspectRatioThresh">Aspect Ratio Threshold:</label>
111
+ <input type="number" id="aspectRatioThresh" name="aspectRatioThresh" value="0.25" min="0"
112
+ max="1" step="0.01">
113
+ </div>
114
+ <div class="config-item">
115
+ <label for="minBoxArea">Min Box Area:</label>
116
+ <input type="number" id="minBoxArea" name="minBoxArea" value="0.25" min="0" max="1" step="0.01">
117
+ </div>
118
+ <div class="config-item">
119
+ <label for="nmsThresh">NMS Threshold:</label>
120
+ <input type="number" id="nmsThresh" name="nmsThresh" value="0.25" min="0" max="1" step="0.01">
121
+ </div>
122
+ <div class="config-item">
123
+ <label for="proximity_thresh">Proximity Threshold:</label>
124
+ <input type="number" id="proximity_thresh" name="proximity_thresh" value="0.25" min="0" max="1"
125
+ step="0.01">
126
+ </div>
127
+ <div class="config-item">
128
+ <label for="appearance_thresh">Appearance Threshold:</label>
129
+ <input type="number" id="appearance_thresh" name="appearance_thresh" value="0.25" min="0"
130
+ max="1" step="0.01">
131
+ </div>
132
+ <div class="config-item">
133
+ <label for="frame_skip">Frame Skip:</label>
134
+ <input type="number" id="frame_skip" name="frame_skip" value="1" step="1">
135
+ </div>
136
+ </div>
137
+
138
+ <!-- OCR Parameters Card -->
139
+ <div class="config-card">
140
+ <h3>OCR Parameters</h3>
141
+ <div class="config-item">
142
+ <label for="which_OCR">OCR Method:</label>
143
+ <select id="which_OCR">
144
+ <option value="which_OCR_easyocr" selected>EasyOCR</option>
145
+ <option value="which_OCR_Paddle">Paddle</option>
146
+ </select>
147
+ </div>
148
+ <div class="config-item">
149
+ <label for="lang_lsit">Language List:</label>
150
+ <input type="text" id="lang_lsit" name="lang_lsit" value="en">
151
+ </div>
152
+ <div class="config-item">
153
+ <label for="sample_rate">Sample Rate</label>
154
+ <input type="number" id="sample_rate" name="sample_rate" value="30" min="1" max="100" step="1">
155
+ </div>
156
+ <div class="config-item">
157
+ <label for="stop_at_detection">Stop at Detection:</label>
158
+ <input type="checkbox" id="stop_at_detection" name="stop_at_detection" checked>
159
+ </div>
160
+
161
+ <div class="config-item">
162
+ <label for="num_of_tume_zones">Number of Time Zones:</label>
163
+ <input type="number" id="num_of_tume_zones" name="num_of_tume_zones" value="5" min="1" max="10"
164
+ step="1">
165
+ </div>
166
+ </div>
167
+
168
+ <!-- RGB Parameters Card -->
169
+ <div class="config-card">
170
+ <h3>RGB Parameters</h3>
171
+
172
+ <div class="config-item">
173
+ <label for="RGB_metrics_dropdown">RGB Metric:</label>
174
+ <select id="RGB_metrics_dropdown">
175
+ <option value="RGB_metrics_dropdown_Cielab" selected>Cielab</option>
176
+ <option value="RGB_metrics_dropdown_weighted_rgb_metric">Weighted RGB Metric</option>
177
+ </select>
178
+ </div>
179
+ <div class="config-item">
180
+ <label for="cluster_method">Cluster Method:</label>
181
+ <select id="cluster_method">
182
+ <option value="cluster_method_kmeans" selected>KMeans</option>
183
+ <option value="cluster_method_kmeans2">Kmeans2</option>
184
+ </select>
185
+ </div>
186
+ <div class="config-item">
187
+ <label for="distance_metric">Distance Metric:</label>
188
+ <select id="distance_metric">
189
+ <option value="distance_metric_weighted_rgb" selected>Weighted RGB</option>
190
+ <option value="distance_metric_weighted_rgb2">Weighted RGB2</option>
191
+ </select>
192
+ </div>
193
+ <div class="config-item">
194
+ <label for="scoring_function">Scoring Function:</label>
195
+ <select id="scoring_function">
196
+ <option value="scoring_function_brisque" selected>Brisque</option>
197
+ <option value="scoring_function_brisque2">Brisque2</option>
198
+ </select>
199
+ </div>
200
+ <div class="config-item">
201
+ <label for="offset_y_top">Offset y-top:</label>
202
+ <input type="number" id="offset_y_top" name="offset_y_top" value="0.25" min="0" max="1"
203
+ step="0.1">
204
+ </div>
205
+ <div class="config-item">
206
+ <label for="offset_y_bot">Offset y-bottom:</label>
207
+ <input type="number" id="offset_y_bot" name="offset_y_bot" value="0.5" min="0" max="1"
208
+ step="0.1">
209
+ </div>
210
+ <div class="config-item">
211
+ <label for="offset_x_left">Offset x-left:</label>
212
+ <input type="number" id="offset_x_left" name="offset_x_left" value="0.33" min="0" max="1"
213
+ step="0.1">
214
+ </div>
215
+ <div class="config-item">
216
+ <label for="offset_x_right">Offset x-right:</label>
217
+ <input type="number" id="offset_x_right" name="offset_x_right" value="0.33" min="0" max="1"
218
+ step="0.1">
219
+ </div>
220
+ <div class="config-item">
221
+ <label for="crop_score_function">Crop Score Function:</label>
222
+ <select id="crop_score_function">
223
+ <option value="iou_score" selected>IoU Score</option>
224
+ <option value="iou_score2">IOU score2</option>
225
+ </select>
226
+ </div>
227
+ </div>
228
+
229
+ <!-- Processor Parameters Card -->
230
+
231
+
232
+
233
+ <!-- Other categories (Color Mapping Performance, Team Mapping Performance, Player Mapping Performance) -->
234
+
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+
240
+
241
+ <!-- Version Badge -->
242
+ <div id="versionBadge">v0.4</div>
243
+ <div id="JersyContainer" style="display: none">
244
+ <div id="leftImagesContainer">
245
+ <div class="image-title">Selected Team:</div>
246
+ <div class="image-box" id="boxLeftTeam">
247
+ <div class="image-title" style="text-align: left;">Home Kit</div>
248
+ <img src="/static/teams_jersy/placeholder.png" alt="Left Image" id="leftTeamImage" />
249
+ </div>
250
+
251
+ <div class="image-box" id="boxLeftTeam2">
252
+ <div class="image-title" style="text-align: left;">Away Kit</div>
253
+ <img src="/static/teams_jersy/placeholder.png" alt="Left Image" id="leftTeamImage2" />
254
+ </div>
255
+ </div>
256
+
257
+ <div class="image-box" id="boxRightTeam">
258
+ <div class="image-title">Selected Player:</div>
259
+ <img src="/static/teams_jersy/placeholder.png" alt="Right Image" id="rightTeamImage" />
260
+ </div>
261
+ </div>
262
+
263
+
264
+
265
+ <div class="container title-section">
266
+ <h1 style="text-align: center">PlayerTV</h1>
267
+ </div>
268
+ <div class="container">
269
+ <div class="arrowSection">
270
+ <button id="accordionToggleVideo">
271
+ <i class="fa-solid fa-angle-down"></i>
272
+ <span>Step 1: Input</span>
273
+ </button>
274
+ </div>
275
+
276
+ <div class="row" id="videoSection" style="margin-top: 3%;">
277
+ <div class="column left">
278
+ <form id="videoForm" class="videoPlayerInput1">
279
+ <h2 style="text-align: left">Input Video</h2>
280
+ <h4 for="m3u8Input">M3U8 Playlist URL:</h4>
281
+ <input type="text" id="m3u8Input" name="m3u8Url" />
282
+ <button class="filter-button" type="submit">Fetch Video</button>
283
+ </form>
284
+ <!-- <div class="filtering-container">
285
+ <h2>Input Metadata</h2>
286
+
287
+ <div class="dropdown-box">
288
+ <input type="file" id="fileInput" accept=".json" class="input-file" />
289
+ </div>
290
+
291
+ <div class="dropdown-box">
292
+ <label for="teamDropdown" class="label-dropdown">Select Team:</label>
293
+ <select id="teamDropdown" class="dropdown">
294
+ Options will be populated dynamically -->
295
+ <!-- </select>
296
+ </div>
297
+
298
+ <div class="dropdown-box">
299
+ <label for="playerDropdown" class="label-dropdown">Select Player:</label>
300
+ <select id="playerDropdown" class="dropdown">
301
+ Options will be populated dynamically -->
302
+ <!-- </select>
303
+ </div>
304
+
305
+ <button id="filterButton" class="filter-button" title="Create per-player compilation.">Clip Video</button>
306
+
307
+ </div> -->
308
+ </div>
309
+ <div class="column right">
310
+ <div id="overlay" style="
311
+ border-radius: 10px;
312
+ display: none;
313
+ position: absolute;
314
+ background: rgba(0, 0, 0, 0.75);
315
+ color: white;
316
+ width: 98%;
317
+ height: 100%;
318
+ justify-content: center;
319
+ align-items: center;
320
+ text-align: center;
321
+ ">
322
+ <h2 style="margin-top: 0%">
323
+ Input video is being processed ...
324
+ </h2>
325
+ <img style="width: 20%" src="/static/poster/03-42-18-223_512.webp" alt="" />
326
+ </div>
327
+ <video id="videoPlayer" controls poster="/static/poster/poster.png">
328
+ <source type="video/mp4" />
329
+ Your browser does not support the video tag.
330
+ </video>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ <!-- Configuration Section -->
335
+ <div class="container">
336
+ <div class="arrowSection">
337
+ <button id="accordionToggleVideo3">
338
+ <i class="fa-solid fa-angle-down"></i>
339
+ <span>Step 2: Configuration</span>
340
+ </button>
341
+ </div>
342
+
343
+
344
+ <!-- Container for Configuration Options -->
345
+ <div id="configurationSection" class="configuration-section hidden" style="margin-top: 3%;">
346
+
347
+
348
+ <div class="config-card">
349
+
350
+ <h3>Metadata</h3>
351
+
352
+ <div class="dropdown-box">
353
+ <input type="file" id="fileInput" accept=".json" class="input-file" />
354
+ </div>
355
+
356
+ <div class="dropdown-box">
357
+ <label for="teamDropdown" class="label-dropdown">Select Team:</label>
358
+ <select id="teamDropdown" class="dropdown">
359
+ <!-- Options will be populated dynamically -->
360
+ </select>
361
+ </div>
362
+
363
+ <div class="dropdown-box">
364
+ <label for="playerDropdown" class="label-dropdown">Select Player:</label>
365
+ <select id="playerDropdown" class="dropdown">
366
+ <!-- Options will be populated dynamically -->
367
+ </select>
368
+ </div>
369
+
370
+ </div>
371
+
372
+ <div class="divider">
373
+ <span class="divider-text">OR</span>
374
+ </div>
375
+
376
+ <div class="config-card">
377
+ <!-- The checkbox for the advance mode -->
378
+ <div class="config-adv-checkbox">
379
+ <input type="checkbox" id="configAdvMode" name="configAdvMode">
380
+ <label for="configAdvMode" class="checkmark"></label>
381
+ <label for="configAdvMode" style="cursor: pointer;">
382
+ <h4>Advanced Mode</h4>
383
+ </label>
384
+ </div>
385
+
386
+
387
+ <!-- Row containing three boxes -->
388
+ <div class="config-row">
389
+ <!-- Box 1 -->
390
+ <div class="filtering-container" style="width: 100%;">
391
+ <h3>RGB Metric</h3>
392
+ <div class="dropdown-box">
393
+ <label for="Rgb_metric" class="label-dropdown">RGB Metric:</label>
394
+ <select id="Rgb_metric" class="dropdown">
395
+ <option value="cielab">Cielab</option>
396
+ <option value="weighted_rgb">Weighted RGB Metric</option>
397
+ </select>
398
+ </div>
399
+
400
+ </div>
401
+
402
+ <!-- Box 2 -->
403
+ <div class="filtering-container" style="width: 100%;">
404
+ <h3>OCR Method</h3>
405
+ <div class="dropdown-box">
406
+ <label for="which_ocr" class="label-dropdown">OCR Method:</label>
407
+ <select id="which_ocr" class="dropdown">
408
+ <option value="Paddle">Paddle</option>
409
+ <option value="easyocr">Easy OCR</option>
410
+ </select>
411
+ </div>
412
+ </div>
413
+
414
+ <!-- Box 3 -->
415
+ <div class="filtering-container" style="width: 100%;">
416
+ <h3>Object Detection Model:</h3>
417
+ <!-- Example dropdown -->
418
+ <div class="dropdown-box">
419
+ <label for="detector" class="label-dropdown">Object Detection Model:</label>
420
+ <select id="detector" class="dropdown">
421
+ <option value="yolov8">YOLOv8</option>
422
+ <option value="yolox">YOLOx</option>
423
+ </select>
424
+ </div>
425
+
426
+ </div>
427
+
428
+
429
+ </div>
430
+
431
+ </div>
432
+
433
+ <button id="filterButton" class="filter-button" title="Create per-player compilation.">Clip Video</button>
434
+
435
+ </div>
436
+ </div>
437
+
438
+
439
+ <div class="container">
440
+ <div class="arrowSection">
441
+ <button id="accordionToggleVideo2">
442
+ <i class="fa-solid fa-angle-down"></i>
443
+ <span>Step 3: Output</span>
444
+ </button>
445
+ </div>
446
+ <!-- Container to display filtered results -->
447
+ <div id="filteredResults" class="filtered-results hidden" style="margin-top: 3%; position: relative;">
448
+ <div id="overlay_result" style="
449
+ border-radius: 10px;
450
+ display: none;
451
+ position: absolute;
452
+ background: rgba(0, 0, 0, 0.75);
453
+ color: white;
454
+ width: 70%;
455
+ height: 100%;
456
+ justify-content: center;
457
+ align-items: center;
458
+ text-align: center;
459
+ top: 0;
460
+ left: 15%;
461
+ ">
462
+ <h2 style="margin-top: 0%">
463
+ Output video is being created ...
464
+ </h2>
465
+ <img style="width: 20%" src="/static/poster/03-42-18-223_512.webp" alt="" />
466
+ </div>
467
+
468
+ <video id="videoPlayer_result" controls width="640" height="360" poster="/static/poster/poster.png">
469
+ <source id="videoSource" type="video/mp4" />
470
+ Your browser does not support the video tag.
471
+ </video>
472
+ </div>
473
+
474
+ </div>
475
+
476
+
477
+ <script src="{{ url_for('static', filename='js/script.js') }}"></script>
478
+ </body>
479
+
480
+ </html>