dragonSwing commited on
Commit
e086001
β€’
1 Parent(s): add254f

Add application files

Browse files
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: πŸ“Š
4
  colorFrom: yellow
5
  colorTo: red
6
  sdk: gradio
7
- sdk_version: 3.29.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
 
4
  colorFrom: yellow
5
  colorTo: red
6
  sdk: gradio
7
+ sdk_version: 3.32.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
app.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import glob
4
+ import validators
5
+ from config import *
6
+ from download_video import download_video
7
+ from bg_modeling import capture_slides_bg_modeling
8
+ from frame_differencing import capture_slides_frame_diff
9
+ from post_process import remove_duplicates
10
+ from utils import create_output_directory, convert_slides_to_pdf
11
+
12
+
13
+ def process(
14
+ video_path,
15
+ bg_type,
16
+ frame_buffer_history,
17
+ hash_size,
18
+ hash_func,
19
+ hash_queue_len,
20
+ sim_threshold,
21
+ ):
22
+ output_dir_path = "output_results"
23
+ output_dir_path = create_output_directory(video_path, output_dir_path, bg_type)
24
+
25
+ if bg_type.lower() == "Frame Diff":
26
+ capture_slides_frame_diff(video_path, output_dir_path)
27
+ else:
28
+ if bg_type.lower() == "gmg":
29
+ thresh = DEC_THRESH
30
+ elif bg_type.lower() == "knn":
31
+ thresh = DIST_THRESH
32
+
33
+ capture_slides_bg_modeling(
34
+ video_path,
35
+ output_dir_path,
36
+ type_bgsub=bg_type,
37
+ history=frame_buffer_history,
38
+ threshold=thresh,
39
+ MIN_PERCENT_THRESH=MIN_PERCENT,
40
+ MAX_PERCENT_THRESH=MAX_PERCENT,
41
+ )
42
+
43
+ # Perform post-processing using difference hashing technique to remove duplicate slides.
44
+ hash_func = HASH_FUNC_DICT.get(hash_func.lower())
45
+
46
+ diff_threshold = int(hash_size * hash_size * (100 - sim_threshold) / 100)
47
+ remove_duplicates(
48
+ output_dir_path, hash_size, hash_func, hash_queue_len, diff_threshold
49
+ )
50
+
51
+ pdf_path = convert_slides_to_pdf(video_path, output_dir_path)
52
+
53
+ # Remove unneccessary files
54
+ os.remove(video_path)
55
+ for image_path in glob.glob(f"{output_dir_path}/*.jpg"):
56
+ os.remove(image_path)
57
+ return pdf_path
58
+
59
+
60
+ def process_file(
61
+ file_obj,
62
+ bg_type,
63
+ frame_buffer_history,
64
+ hash_size,
65
+ hash_func,
66
+ hash_queue_len,
67
+ sim_threshold,
68
+ ):
69
+ return process(
70
+ file_obj.name,
71
+ bg_type,
72
+ frame_buffer_history,
73
+ hash_size,
74
+ hash_func,
75
+ hash_queue_len,
76
+ sim_threshold,
77
+ )
78
+
79
+
80
+ def process_via_url(
81
+ url,
82
+ bg_type,
83
+ frame_buffer_history,
84
+ hash_size,
85
+ hash_func,
86
+ hash_queue_len,
87
+ sim_threshold,
88
+ ):
89
+ if validators.url(url):
90
+ video_path = download_video(url)
91
+ if video_path is None:
92
+ raise gr.Error("Please enter a valid video URL")
93
+ return process(
94
+ video_path,
95
+ bg_type,
96
+ frame_buffer_history,
97
+ hash_size,
98
+ hash_func,
99
+ hash_queue_len,
100
+ sim_threshold,
101
+ )
102
+ else:
103
+ raise gr.Error("Please enter a valid video URL")
104
+
105
+
106
+ with gr.Blocks(css="style.css") as demo:
107
+ with gr.Row(elem_classes=["container"]):
108
+ gr.Markdown(
109
+ """
110
+ # Video 2 Slides Converter
111
+
112
+ Convert your video presentation into PDF slides with one click.
113
+
114
+ You can browse your video from the local file system, or enter a video URL/YouTube video link to start processing.
115
+
116
+ **Note**:
117
+ - It will take a bit of time to complete (~40% of the original video length), so stay tuned!
118
+ - Remember to press Enter if you are using an external URL
119
+ """,
120
+ elem_id="container",
121
+ )
122
+
123
+ with gr.Row(elem_classes=["container"]):
124
+ with gr.Column(scale=1):
125
+ with gr.Accordion("Advanced parameters"):
126
+ bg_type = gr.Dropdown(
127
+ ["Frame Diff", "GMG", "KNN"],
128
+ value="GMG",
129
+ label="Background subtraction",
130
+ info="Type of background subtraction to be used",
131
+ )
132
+ frame_buffer_history = gr.Slider(
133
+ minimum=5,
134
+ maximum=20,
135
+ value=FRAME_BUFFER_HISTORY,
136
+ step=5,
137
+ label="Frame buffer history",
138
+ info="Length of the frame buffer history to model background.",
139
+ )
140
+ # Post process
141
+ hash_func = gr.Dropdown(
142
+ ["Difference hashing", "Perceptual hashing", "Average hashing"],
143
+ value="Difference hashing",
144
+ label="Background subtraction",
145
+ info="Hash function to use for image hashing",
146
+ )
147
+ hash_size = gr.Slider(
148
+ minimum=8,
149
+ maximum=16,
150
+ value=HASH_SIZE,
151
+ step=2,
152
+ label="Hash size",
153
+ info="Hash size to use for image hashing",
154
+ )
155
+ hash_queue_len = gr.Slider(
156
+ minimum=5,
157
+ maximum=15,
158
+ value=HASH_BUFFER_HISTORY,
159
+ step=5,
160
+ label="Hash queue len",
161
+ info="Number of history images used to find out duplicate image",
162
+ )
163
+ sim_threshold = gr.Slider(
164
+ minimum=90,
165
+ maximum=100,
166
+ value=SIM_THRESHOLD,
167
+ step=1,
168
+ label="Similarity threshold",
169
+ info="Minimum similarity threshold (in percent) to consider 2 images to be similar",
170
+ )
171
+
172
+ with gr.Column(scale=2):
173
+ with gr.Row(elem_id="row-flex"):
174
+ with gr.Column(scale=3):
175
+ file_url = gr.Textbox(
176
+ value="",
177
+ label="Upload your file",
178
+ placeholder="Enter a video url or YouTube link",
179
+ show_label=False,
180
+ )
181
+ with gr.Column(scale=1, min_width=160):
182
+ upload_button = gr.UploadButton("Browse File", file_types=["video"])
183
+ file_output = gr.File(file_types=[".pdf"], label="Output PDF")
184
+ gr.Examples(
185
+ [
186
+ [
187
+ "https://www.youtube.com/watch?v=bfmFfD2RIcg",
188
+ "output_results/Neural Network In 5 Minutes.pdf",
189
+ ],
190
+ [
191
+ "https://www.youtube.com/watch?v=EEo10bgsh0k",
192
+ "output_results/react-in-5-minutes.pdf",
193
+ ],
194
+ ],
195
+ [file_url, file_output],
196
+ )
197
+
198
+ file_url.submit(
199
+ process_via_url,
200
+ [
201
+ file_url,
202
+ bg_type,
203
+ frame_buffer_history,
204
+ hash_size,
205
+ hash_func,
206
+ hash_queue_len,
207
+ sim_threshold,
208
+ ],
209
+ file_output,
210
+ )
211
+ upload_button.upload(
212
+ process_file,
213
+ [
214
+ upload_button,
215
+ bg_type,
216
+ frame_buffer_history,
217
+ hash_size,
218
+ hash_func,
219
+ hash_queue_len,
220
+ sim_threshold,
221
+ ],
222
+ file_output,
223
+ )
224
+
225
+ demo.queue(concurrency_count=4).launch()
bg_modeling.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import sys
4
+ import cv2
5
+ from utils import resize_image_frame
6
+
7
+
8
+ def capture_slides_bg_modeling(
9
+ video_path,
10
+ output_dir_path,
11
+ type_bgsub,
12
+ history,
13
+ threshold,
14
+ MIN_PERCENT_THRESH,
15
+ MAX_PERCENT_THRESH,
16
+ ):
17
+ print(f"Using {type_bgsub} for Background Modeling...")
18
+ print("---" * 10)
19
+
20
+ if type_bgsub == "GMG":
21
+ bg_sub = cv2.bgsegm.createBackgroundSubtractorGMG(
22
+ initializationFrames=history, decisionThreshold=threshold
23
+ )
24
+ elif type_bgsub == "KNN":
25
+ bg_sub = cv2.createBackgroundSubtractorKNN(
26
+ history=history, dist2Threshold=threshold, detectShadows=False
27
+ )
28
+ else:
29
+ raise ValueError("Please choose GMG or KNN as background subtraction method")
30
+
31
+ capture_frame = False
32
+ screenshots_count = 0
33
+
34
+ # Capture video frames.
35
+ cap = cv2.VideoCapture(video_path)
36
+
37
+ if not cap.isOpened():
38
+ print("Unable to open video file: ", video_path)
39
+ sys.exit()
40
+
41
+ start = time.time()
42
+ # Loop over subsequent frames.
43
+ while cap.isOpened():
44
+ ret, frame = cap.read()
45
+
46
+ if not ret:
47
+ break
48
+
49
+ # Create a copy of the original frame.
50
+ orig_frame = frame.copy()
51
+ # Resize the frame keeping aspect ratio.
52
+ frame = resize_image_frame(frame, resize_width=640)
53
+
54
+ # Apply each frame through the background subtractor.
55
+ fg_mask = bg_sub.apply(frame)
56
+
57
+ # Compute the percentage of the Foreground mask."
58
+ p_non_zero = (cv2.countNonZero(fg_mask) / (1.0 * fg_mask.size)) * 100
59
+
60
+ # %age of non-zero pixels < MAX_PERCENT_THRESH, implies motion has stopped.
61
+ # Therefore, capture the frame.
62
+ if p_non_zero < MAX_PERCENT_THRESH and not capture_frame:
63
+ capture_frame = True
64
+
65
+ screenshots_count += 1
66
+
67
+ png_filename = f"{screenshots_count:03}.jpg"
68
+ out_file_path = os.path.join(output_dir_path, png_filename)
69
+ print(f"Saving file at: {out_file_path}")
70
+ cv2.imwrite(out_file_path, orig_frame, [cv2.IMWRITE_JPEG_QUALITY, 75])
71
+
72
+ # p_non_zero >= MIN_PERCENT_THRESH, indicates motion/animations.
73
+ # Hence wait till the motion across subsequent frames has settled down.
74
+ elif capture_frame and p_non_zero >= MIN_PERCENT_THRESH:
75
+ capture_frame = False
76
+
77
+ end_time = time.time()
78
+ print("***" * 10, "\n")
79
+ print("Statistics:")
80
+ print("---" * 10)
81
+ print(f"Total Time taken: {round(end_time-start, 3)} secs")
82
+ print(f"Total Screenshots captured: {screenshots_count}")
83
+ print("---" * 10, "\n")
84
+
85
+ # Release Video Capture object.
86
+ cap.release()
config.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import imagehash
2
+
3
+ # -------------- Initializations ---------------------
4
+
5
+ DOWNLOAD_DIR = "downloads"
6
+
7
+ FRAME_BUFFER_HISTORY = 15 # Length of the frame buffer history to model background.
8
+ DEC_THRESH = (
9
+ 0.75 # Threshold value, above which it is marked foreground, else background.
10
+ )
11
+ DIST_THRESH = 100 # Threshold on the squared distance between the pixel and the sample to decide whether a pixel is close to that sample.
12
+
13
+ MIN_PERCENT = (
14
+ 0.15 # %age threshold to check if there is motion across subsequent frames
15
+ )
16
+ MAX_PERCENT = (
17
+ 0.01 # %age threshold to determine if the motion across frames has stopped.
18
+ )
19
+
20
+ # Post processing
21
+
22
+ SIM_THRESHOLD = (
23
+ 96 # Minimum similarity threshold (in percent) to consider 2 images to be similar
24
+ )
25
+
26
+ HASH_SIZE = 12 # Hash size to use for image hashing
27
+
28
+ HASH_FUNC = "dhash" # Hash function to use for image hashing
29
+
30
+ HASH_BUFFER_HISTORY = 5 # Number of history images used to find out duplicate image
31
+
32
+ HASH_FUNC_DICT = {
33
+ "dhash": imagehash.dhash,
34
+ "phash": imagehash.phash,
35
+ "ahash": imagehash.average_hash,
36
+ "difference hashing": imagehash.dhash,
37
+ "perceptual hashing": imagehash.phash,
38
+ "average hashing": imagehash.average_hash,
39
+ }
40
+
41
+ # ----------------------------------------------------
download_video.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mimetypes
2
+ import tempfile
3
+ import requests
4
+ import os
5
+ from urllib.parse import urlparse
6
+ from pytube import YouTube
7
+ from config import DOWNLOAD_DIR
8
+
9
+
10
+ def download_video_from_url(url, output_dir=DOWNLOAD_DIR):
11
+ try:
12
+ response = requests.get(url)
13
+ response.raise_for_status() # Check if the request was successful
14
+
15
+ content_type = response.headers.get("content-type")
16
+ if "video" not in content_type:
17
+ print("The given URL is not a valid video")
18
+ return None
19
+ file_extension = mimetypes.guess_extension(content_type)
20
+
21
+ os.makedirs(output_dir, exist_ok=True)
22
+
23
+ temp_file = tempfile.NamedTemporaryFile(
24
+ delete=False, suffix=file_extension, dir=output_dir
25
+ )
26
+ temp_file_path = temp_file.name
27
+
28
+ with open(temp_file_path, "wb") as file:
29
+ file.write(response.content)
30
+ return temp_file_path
31
+
32
+ except requests.exceptions.RequestException as e:
33
+ print("An error occurred while downloading the video:", str(e))
34
+ return None
35
+
36
+
37
+ def download_video_from_youtube(url, output_dir=DOWNLOAD_DIR):
38
+ try:
39
+ yt = YouTube(url)
40
+ video = (
41
+ yt.streams.filter(progressive=True, file_extension="mp4")
42
+ .order_by("resolution")
43
+ .desc()
44
+ .first()
45
+ )
46
+
47
+ os.makedirs(output_dir, exist_ok=True)
48
+
49
+ video_path = video.download(output_dir)
50
+ return video_path
51
+
52
+ except Exception as e:
53
+ print("An error occurred while downloading the video:", str(e))
54
+ return None
55
+
56
+
57
+ def download_video(url, output_dir=DOWNLOAD_DIR):
58
+ parsed_url = urlparse(url)
59
+ domain = parsed_url.netloc.lower()
60
+
61
+ print("---" * 5, "Downloading video file", "---" * 5)
62
+
63
+ if "youtube" in domain:
64
+ video_path = download_video_from_youtube(url, output_dir)
65
+ else:
66
+ video_path = download_video_from_url(url, output_dir)
67
+
68
+ if video_path:
69
+ print(f"Saving file at: {video_path}")
70
+ print("---" * 10)
71
+ return video_path
72
+
73
+
74
+ if __name__ == "__main__":
75
+ youtube_link = "https://www.youtube.com/watch?v=2OTq15A5s0Y"
76
+ temp_video_path = download_video_from_youtube(youtube_link)
77
+
78
+ if temp_video_path is not None:
79
+ print("Video downloaded successfully to:", temp_video_path)
80
+ else:
81
+ print("Failed to download the video.")
frame_differencing.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import os
3
+ import time
4
+ import sys
5
+
6
+
7
+ def capture_slides_frame_diff(
8
+ video_path, output_dir_path, MIN_PERCENT_THRESH=0.06, ELAPSED_FRAME_THRESH=85
9
+ ):
10
+ prev_frame = None
11
+ curr_frame = None
12
+ screenshots_count = 0
13
+ capture_frame = False
14
+ frame_elapsed = 0
15
+
16
+ # Initialize kernel.
17
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
18
+
19
+ # Capture video frames
20
+ cap = cv2.VideoCapture(video_path)
21
+
22
+ if not cap.isOpened():
23
+ print("Unable to open video file: ", video_path)
24
+ sys.exit()
25
+
26
+ success, first_frame = cap.read()
27
+
28
+ print("Using frame differencing for Background Subtraction...")
29
+ print("---" * 10)
30
+
31
+ start = time.time()
32
+
33
+ # The 1st frame should always be present in the output directory.
34
+ # Hence capture and save the 1st frame.
35
+ if success:
36
+ # Convert frame to grayscale.
37
+ first_frame_gray = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)
38
+
39
+ prev_frame = first_frame_gray
40
+
41
+ screenshots_count += 1
42
+
43
+ filename = f"{screenshots_count:03}.lpg"
44
+ out_file_path = os.path.join(output_dir_path, filename)
45
+ print(f"Saving file at: {out_file_path}")
46
+
47
+ # Save frame.
48
+ cv2.imwrite(out_file_path, first_frame, [cv2.IMWRITE_JPEG_QUALITY, 75])
49
+
50
+ # Loop over subsequent frames.
51
+ while cap.isOpened():
52
+ ret, frame = cap.read()
53
+ if not ret:
54
+ break
55
+
56
+ frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
57
+ curr_frame = frame_gray
58
+
59
+ if (prev_frame is not None) and (curr_frame is not None):
60
+ frame_diff = cv2.absdiff(curr_frame, prev_frame)
61
+ _, frame_diff = cv2.threshold(frame_diff, 80, 255, cv2.THRESH_BINARY)
62
+
63
+ # Perform dilation to capture motion.
64
+ frame_diff = cv2.dilate(frame_diff, kernel)
65
+
66
+ # Compute the percentage of non-zero pixels in the frame.
67
+ p_non_zero = (cv2.countNonZero(frame_diff) / (1.0 * frame_gray.size)) * 100
68
+
69
+ if p_non_zero >= MIN_PERCENT_THRESH and not capture_frame:
70
+ capture_frame = True
71
+
72
+ elif capture_frame:
73
+ frame_elapsed += 1
74
+
75
+ if frame_elapsed >= ELAPSED_FRAME_THRESH:
76
+ capture_frame = False
77
+ frame_elapsed = 0
78
+
79
+ screenshots_count += 1
80
+
81
+ filename = f"{screenshots_count:03}.png"
82
+ out_file_path = os.path.join(output_dir_path, filename)
83
+ print(f"Saving file at: {out_file_path}")
84
+
85
+ cv2.imwrite(out_file_path, frame)
86
+
87
+ prev_frame = curr_frame
88
+
89
+ end_time = time.time()
90
+ print("***" * 10, "\n")
91
+ print("Statistics:")
92
+ print("---" * 5)
93
+ print(f"Total Time taken: {round(end_time-start, 3)} secs")
94
+ print(f"Total Screenshots captured: {screenshots_count}")
95
+ print("---" * 10, "\n")
96
+
97
+ cap.release()
output_results/Neural Network In 5 Minutes.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fe8c6f4fc132de07cc528f6fff4021fd084a07b17858f1b294e7726109dac89a
3
+ size 3656629
output_results/react-in-5-minutes.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dee28ec7ce33a50b70553540af6ea4329226785fe1fc9da1f14acf13bf0417d2
3
+ size 371324
post_process.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import imagehash
2
+ import os
3
+ from collections import deque
4
+ from PIL import Image
5
+
6
+
7
+ def find_similar_images(
8
+ base_dir, hash_size=8, hashfunc=imagehash.dhash, queue_len=5, threshold=4
9
+ ):
10
+ snapshots_files = sorted(os.listdir(base_dir))
11
+
12
+ hash_dict = {}
13
+ hash_queue = deque([], maxlen=queue_len)
14
+ duplicates = []
15
+ num_duplicates = 0
16
+
17
+ print("---" * 5, "Finding similar files", "---" * 5)
18
+
19
+ for file in snapshots_files:
20
+ read_file = Image.open(os.path.join(base_dir, file))
21
+ comp_hash = hashfunc(read_file, hash_size=hash_size)
22
+ duplicate = False
23
+
24
+ if comp_hash not in hash_dict:
25
+ hash_dict[comp_hash] = file
26
+ # Compare with hash queue to find out potential duplicates
27
+ for img_hash in hash_queue:
28
+ if img_hash - comp_hash <= threshold:
29
+ duplicate = True
30
+ break
31
+
32
+ if not duplicate:
33
+ hash_queue.append(comp_hash)
34
+ else:
35
+ duplicate = True
36
+
37
+ if duplicate:
38
+ print("Duplicate file: ", file)
39
+ duplicates.append(file)
40
+ num_duplicates += 1
41
+
42
+ print("\nTotal duplicate files:", num_duplicates)
43
+ print("-----" * 10)
44
+ return hash_dict, duplicates
45
+
46
+
47
+ def remove_duplicates(
48
+ base_dir, hash_size=8, hashfunc=imagehash.dhash, queue_len=5, threshold=4
49
+ ):
50
+ _, duplicates = find_similar_images(
51
+ base_dir,
52
+ hash_size=hash_size,
53
+ hashfunc=hashfunc,
54
+ queue_len=queue_len,
55
+ threshold=threshold,
56
+ )
57
+
58
+ if not len(duplicates):
59
+ print("No duplicates found!")
60
+ else:
61
+ print("Removing duplicates...")
62
+
63
+ for dup_file in duplicates:
64
+ file_path = os.path.join(base_dir, dup_file)
65
+
66
+ if os.path.exists(file_path):
67
+ os.remove(file_path)
68
+ else:
69
+ print("Filepath: ", file_path, "does not exists.")
70
+
71
+ print("All duplicates removed!")
72
+
73
+ print("***" * 10, "\n")
74
+
75
+
76
+ if __name__ == "__main__":
77
+ remove_duplicates("sample_1")
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ opencv-contrib-python==4.7.0.72
2
+ numpy
3
+ Pillow
4
+ scipy
5
+ six
6
+ ImageHash
7
+ img2pdf
8
+ pytube
9
+ validators
10
+ requests
style.css ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .container {
2
+ max-width: 1200px;
3
+ margin-left: auto;
4
+ margin-right: auto;
5
+ }
6
+
7
+ #row-flex {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ }
12
+
13
+ a,
14
+ a:hover,
15
+ a:visited {
16
+ text-decoration-line: underline;
17
+ font-weight: 600;
18
+ color: #1f2937 !important;
19
+ }
20
+
21
+ .dark a,
22
+ .dark a:hover,
23
+ .dark a:visited {
24
+ color: #f3f4f6 !important;
25
+ }
utils.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import shutil
4
+ import img2pdf
5
+ import glob
6
+
7
+ # PIL can also be used to convert the image set into PDFs.
8
+ # However, using PIL requires opening each of the images in the set.
9
+ # Hence img2pdf package was used, which is able to convert the entire image set into a PDF
10
+ # without opening at once.
11
+
12
+
13
+ def resize_image_frame(frame, resize_width):
14
+ ht, wd, _ = frame.shape
15
+ new_height = resize_width * ht / wd
16
+ frame = cv2.resize(
17
+ frame, (resize_width, int(new_height)), interpolation=cv2.INTER_AREA
18
+ )
19
+
20
+ return frame
21
+
22
+
23
+ def create_output_directory(video_path, output_path, type_bgsub):
24
+ vid_file_name = video_path.rsplit(os.sep)[-1].split(".")[0]
25
+ output_dir_path = os.path.join(output_path, vid_file_name, type_bgsub)
26
+
27
+ # Remove the output directory if there is already one.
28
+ if os.path.exists(output_dir_path):
29
+ shutil.rmtree(output_dir_path)
30
+
31
+ # Create output directory.
32
+ os.makedirs(output_dir_path, exist_ok=True)
33
+ print("Output directory created...")
34
+ print("Path:", output_dir_path)
35
+ print("***" * 10, "\n")
36
+
37
+ return output_dir_path
38
+
39
+
40
+ def convert_slides_to_pdf(video_path, output_path):
41
+ pdf_file_name = video_path.rsplit(os.sep)[-1].split(".")[0] + ".pdf"
42
+ output_pdf_path = os.path.join(output_path, pdf_file_name)
43
+
44
+ print("Output PDF Path:", output_pdf_path)
45
+ print("Converting captured slide images to PDF...")
46
+
47
+ with open(output_pdf_path, "wb") as f:
48
+ f.write(img2pdf.convert(sorted(glob.glob(f"{output_path}/*.jpg"))))
49
+
50
+ print("PDF Created!")
51
+ print("***" * 10, "\n")
52
+
53
+ return output_pdf_path
video_2_slides.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import validators
4
+ from config import *
5
+ from download_video import download_video
6
+ from bg_modeling import capture_slides_bg_modeling
7
+ from frame_differencing import capture_slides_frame_diff
8
+ from post_process import remove_duplicates
9
+ from utils import create_output_directory, convert_slides_to_pdf
10
+
11
+
12
+ if __name__ == "__main__":
13
+ parser = argparse.ArgumentParser(
14
+ description="This script is used to convert video frames into slide PDFs."
15
+ )
16
+ parser.add_argument(
17
+ "-v", "--video_path", help="Path to the video file, video url, or YouTube video link", type=str
18
+ )
19
+ parser.add_argument(
20
+ "-o",
21
+ "--out_dir",
22
+ default="output_results",
23
+ help="Path to the output directory",
24
+ type=str,
25
+ )
26
+ parser.add_argument(
27
+ "--type",
28
+ help="type of background subtraction to be used",
29
+ default="GMG",
30
+ choices=["Frame_Diff", "GMG", "KNN"],
31
+ type=str,
32
+ )
33
+ parser.add_argument(
34
+ "-hf",
35
+ "--hash-func",
36
+ help="Hash function to use for image hashing. Only effective if post-processing is enabled",
37
+ default=HASH_FUNC,
38
+ choices=["dhash", "phash", "ahash"],
39
+ type=str,
40
+ )
41
+ parser.add_argument(
42
+ "-hs",
43
+ "--hash-size",
44
+ help="Hash size to use for image hashing. Only effective if post-processing is enabled",
45
+ default=HASH_SIZE,
46
+ choices=[8, 12, 16],
47
+ type=int,
48
+ )
49
+ parser.add_argument(
50
+ "--threshold",
51
+ help="Minimum similarity threshold (in percent) to consider 2 images to be similar. Only effective if post-processing is enabled",
52
+ default=SIM_THRESHOLD,
53
+ choices=range(90, 101),
54
+ type=int,
55
+ )
56
+ parser.add_argument(
57
+ "-q",
58
+ "--queue-len",
59
+ help="Number of history images used to find out duplicate image. Only effective if post-processing is enabled",
60
+ default=HASH_BUFFER_HISTORY,
61
+ type=int,
62
+ )
63
+ parser.add_argument(
64
+ "--no_post_process",
65
+ action="store_true",
66
+ default=False,
67
+ help="flag to apply post processing or not",
68
+ )
69
+ parser.add_argument(
70
+ "--convert_to_pdf",
71
+ action="store_true",
72
+ default=False,
73
+ help="flag to convert the entire image set to pdf or not",
74
+ )
75
+ args = parser.parse_args()
76
+
77
+ queue_len = args.queue_len
78
+ if queue_len <= 0:
79
+ print(
80
+ f"Warnings: queue_len argument must be positive. Fallback to {HASH_BUFFER_HISTORY}"
81
+ )
82
+ queue_len = HASH_BUFFER_HISTORY
83
+
84
+ video_path = args.video_file_path
85
+ output_dir_path = args.out_dir
86
+ type_bg_sub = args.type
87
+ temp_file = False
88
+
89
+ if validators.url(video_path):
90
+ video_path = download_video(video_path)
91
+ temp_file = True
92
+ if video_path is None:
93
+ exit(1)
94
+ elif not os.path.exists(video_path):
95
+ raise ValueError(
96
+ "The video doesn't exist or isn't a valid URL. Please check your video path again"
97
+ )
98
+
99
+ output_dir_path = create_output_directory(video_path, output_dir_path, type_bg_sub)
100
+
101
+ if type_bg_sub.lower() == "frame_diff":
102
+ capture_slides_frame_diff(video_path, output_dir_path)
103
+ else:
104
+ if type_bg_sub.lower() == "gmg":
105
+ thresh = DEC_THRESH
106
+ elif type_bg_sub.lower() == "knn":
107
+ thresh = DIST_THRESH
108
+
109
+ capture_slides_bg_modeling(
110
+ video_path,
111
+ output_dir_path,
112
+ type_bgsub=type_bg_sub,
113
+ history=FRAME_BUFFER_HISTORY,
114
+ threshold=thresh,
115
+ MIN_PERCENT_THRESH=MIN_PERCENT,
116
+ MAX_PERCENT_THRESH=MAX_PERCENT,
117
+ )
118
+
119
+ # Perform post-processing using difference hashing technique to remove duplicate slides.
120
+ if not args.no_post_process:
121
+ hash_size = args.hash_size
122
+ hash_func = HASH_FUNC_DICT.get(args.hash_func)
123
+ sim_threshold = args.threshold
124
+
125
+ diff_threshold = int(hash_size * hash_size * (100 - sim_threshold) / 100)
126
+ remove_duplicates(
127
+ output_dir_path, hash_size, hash_func, queue_len, diff_threshold
128
+ )
129
+
130
+ if args.convert_to_pdf:
131
+ convert_slides_to_pdf(video_path, output_dir_path)
132
+
133
+ # if temp_file:
134
+ # os.remove(video_path)