Spaces:
No application file
No application file
product2204
commited on
Upload 13 files
Browse files- .gitattributes +1 -0
- clip/.DS_Store +0 -0
- clip/.gitignore +4 -0
- clip/1_website_recording.py +54 -0
- clip/2_slow_elongate.sh +13 -0
- clip/3_overlay.py +78 -0
- clip/README.md +54 -0
- clip/video/.DS_Store +0 -0
- clip/video/1-recording/.DS_Store +0 -0
- clip/video/2-slowed/.DS_Store +0 -0
- clip/video/3-elongated/.DS_Store +0 -0
- clip/video/4-overlayed/.DS_Store +0 -0
- clip/video/5-broken/.DS_Store +0 -0
- clip/video/circle_head_3584x2160.mov +3 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
clip/video/circle_head_3584x2160.mov filter=lfs diff=lfs merge=lfs -text
|
clip/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
clip/.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
__pycache__
|
2 |
+
csv
|
3 |
+
failed_urls.csv
|
4 |
+
batch_emails.csv
|
clip/1_website_recording.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time, subprocess, csv
|
2 |
+
from threading import Timer
|
3 |
+
import pandas
|
4 |
+
from selenium import webdriver
|
5 |
+
from selenium.webdriver.chrome.service import Service
|
6 |
+
|
7 |
+
DRIVER_PATH = '/Users/moonma/Downloads/chromedriver-mac-x64/chromedriver'
|
8 |
+
LEADS_FNAME = 'csv/100_batch.csv'
|
9 |
+
PAGE_LOAD_TIME = 4
|
10 |
+
SCROLL_WAIT_TIME = 2
|
11 |
+
video_time = PAGE_LOAD_TIME + SCROLL_WAIT_TIME*3
|
12 |
+
|
13 |
+
service = Service(executable_path=DRIVER_PATH)
|
14 |
+
options = webdriver.ChromeOptions()
|
15 |
+
options.add_argument("--start-maximized")
|
16 |
+
driver = webdriver.Chrome(service=service, options=options)
|
17 |
+
|
18 |
+
df_raw = pandas.read_csv(LEADS_FNAME)
|
19 |
+
with open("csv/failed_urls.csv", 'a', newline='') as failed_url_csv:
|
20 |
+
csvwriter = csv.writer(failed_url_csv, delimiter=',')
|
21 |
+
|
22 |
+
for index, row in df_raw.iterrows():
|
23 |
+
url = row['organization_primary_domain']
|
24 |
+
if not url.startswith('http://www.'): url = 'http://www.' + url
|
25 |
+
print(f"row#{index}: {url}")
|
26 |
+
|
27 |
+
try:
|
28 |
+
driver.get(url)
|
29 |
+
except Exception:
|
30 |
+
csvwriter.writerow(row)
|
31 |
+
print(f" {url} → NOT WORKING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
32 |
+
continue
|
33 |
+
print(f"url split: {url.split('.')[-2]}")
|
34 |
+
video_fname = str(index) + '_' + url.split('.')[-2] + "_" + row['first_name'] + "_" + row['last_name'] + '.mov'
|
35 |
+
proc = subprocess.Popen(['screencapture', '-v', "video/1-recording/"+video_fname], stdin=subprocess.PIPE)
|
36 |
+
kill = kill = lambda process: process.kill()
|
37 |
+
video_timer = Timer(video_time, kill, [proc])
|
38 |
+
|
39 |
+
last_height = driver.execute_script("return document.body.scrollHeight")
|
40 |
+
time.sleep(PAGE_LOAD_TIME)
|
41 |
+
|
42 |
+
# Scroll to bottom
|
43 |
+
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
|
44 |
+
time.sleep(SCROLL_WAIT_TIME)
|
45 |
+
|
46 |
+
# Scroll to top
|
47 |
+
driver.execute_script("window.scrollTo(document.body.scrollHeight, 0);")
|
48 |
+
time.sleep(SCROLL_WAIT_TIME*2)
|
49 |
+
|
50 |
+
try:
|
51 |
+
video_timer.start()
|
52 |
+
stdout, stderr = proc.communicate()
|
53 |
+
finally:
|
54 |
+
video_timer.cancel()
|
clip/2_slow_elongate.sh
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# video/1-recording/ is 18 characters
|
4 |
+
for FILE in video/1-recording/*; do
|
5 |
+
video_name=${FILE:18}
|
6 |
+
ffmpeg -i $FILE -filter:v "setpts=3*PTS" video/2-slowed/$video_name
|
7 |
+
done
|
8 |
+
|
9 |
+
# video/2-slowed/ is 16 characters
|
10 |
+
for FILE in video/2-slowed/*; do
|
11 |
+
video_name=${FILE:15}
|
12 |
+
ffmpeg -i $FILE -vf tpad=stop_mode=clone:stop_duration=92,scale=3584x2160,setsar=1:1 video/3-elongated/$video_name
|
13 |
+
done
|
clip/3_overlay.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess, os, time
|
2 |
+
import cv2
|
3 |
+
|
4 |
+
directory = 'video/3-elongated'
|
5 |
+
website_vfiles = []
|
6 |
+
for fname in os.listdir(directory):
|
7 |
+
if fname.endswith('.mov'):
|
8 |
+
fname = os.path.join(directory, fname)
|
9 |
+
website_vfiles.append(fname)
|
10 |
+
print(website_vfiles)
|
11 |
+
|
12 |
+
for i, ws in enumerate(website_vfiles):
|
13 |
+
speaker_vfile = 'video/circle_head_3584x2160.mov'
|
14 |
+
speaker = cv2.VideoCapture(speaker_vfile)
|
15 |
+
foreground_width = int(speaker.get(cv2.CAP_PROP_FRAME_WIDTH))
|
16 |
+
foreground_height = int(speaker.get (cv2.CAP_PROP_FRAME_HEIGHT))
|
17 |
+
# print(f"talking head video width and height: {foreground_width, foreground_height}") # 3584 2160
|
18 |
+
|
19 |
+
print(f"####{i}: {ws}")
|
20 |
+
start_time = time.time()
|
21 |
+
id_str = ws[len(directory)+1:-4]
|
22 |
+
overlay_noaudio_vfile = 'overlay_' + id_str + '.mov'
|
23 |
+
output_vfile = 'video/4-overlayed/' + id_str + '.mov'
|
24 |
+
# print(f" overlay_noaudio_vfile: {overlay_noaudio_vfile}")
|
25 |
+
# print(f" final_vfile: {output_vfile}")
|
26 |
+
|
27 |
+
website = cv2.VideoCapture(ws)
|
28 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
29 |
+
output_video = cv2.VideoWriter(overlay_noaudio_vfile, fourcc, 24.0, (int(website.get(3)),int(website.get(4))))
|
30 |
+
|
31 |
+
background_width = int(website.get(cv2.CAP_PROP_FRAME_WIDTH))
|
32 |
+
background_height = int (website.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
33 |
+
# print(f" talking head video width and height: {background_width, background_height}") # 3584 2160
|
34 |
+
|
35 |
+
ct=0
|
36 |
+
while True:
|
37 |
+
background_success, background_frame = website.read()
|
38 |
+
foreground_success, foreground_frame = speaker.read()
|
39 |
+
if not background_success or not foreground_success: break
|
40 |
+
if ct%100 ==0: print(f" keep going ... {ct}")
|
41 |
+
|
42 |
+
foreground_frame_resized = cv2.resize(foreground_frame, (background_width, background_height))
|
43 |
+
|
44 |
+
# Convert the foreground frame to grayscale and threshold it
|
45 |
+
foreground_gray = cv2.cvtColor(foreground_frame_resized, cv2.COLOR_BGR2GRAY)
|
46 |
+
ret, mask = cv2.threshold(foreground_gray, 10, 255, cv2.THRESH_BINARY)
|
47 |
+
# Invert the mask so that the foreground is white and the background is black
|
48 |
+
mask_inv = cv2.bitwise_not(mask)
|
49 |
+
# Use the mask to extract the foreground from the foreground frame
|
50 |
+
foreground_extracted = cv2.bitwise_and(foreground_frame_resized, foreground_frame_resized, mask=mask)
|
51 |
+
# Use the inverted mask to extract the background from the background frame
|
52 |
+
background_extracted = cv2.bitwise_and(background_frame, background_frame, mask=mask_inv)
|
53 |
+
# Combine the foreground and background
|
54 |
+
output_frame = cv2.add(foreground_extracted, background_extracted)
|
55 |
+
# Write the output frame to the output video
|
56 |
+
output_video.write(output_frame)
|
57 |
+
# print(ct)
|
58 |
+
ct += 1
|
59 |
+
# print(output_frame[0][0])
|
60 |
+
|
61 |
+
website.release()
|
62 |
+
output_video.release()
|
63 |
+
speaker.release()
|
64 |
+
cv2.destroyAllWindows()
|
65 |
+
subprocess.call('ls', shell=True)
|
66 |
+
|
67 |
+
cmd = f'ffmpeg -i {overlay_noaudio_vfile} -i video/circle_head_3584x2160.mov -map 0:v:0? -map 1:a:0 {output_vfile}'
|
68 |
+
subprocess.call(cmd, shell=True)
|
69 |
+
# os.remove(overlay_noaudio_vfile)
|
70 |
+
|
71 |
+
end_time = time.time()
|
72 |
+
elapsed_time = end_time - start_time
|
73 |
+
hours, rem = divmod(elapsed_time, 3600)
|
74 |
+
minutes, seconds = divmod(rem, 60)
|
75 |
+
hours, minutes, seconds = int(hours), int(minutes), int(seconds)
|
76 |
+
print(f" this overlay took {minutes} minutes {seconds} seconds")
|
77 |
+
|
78 |
+
# ffmpeg -i overlay_3berkley_Angela_Berkley.mov -i circle_head_3584x2160.mov -map 0:v:0 -map 1:a:0 overlayed/3berkley_Angela_Berkley.mov
|
clip/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Why is this worth doing?
|
2 |
+
|
3 |
+
[Clipio](https://clipio.com/) is a cheaper option than [pitchlane](https://pitchlane.com/?gclid=CjwKCAiAtt2tBhBDEiwALZuhAA6eUbE9iTTvlpcibtNnvmvJpO-58IX8LHac_woj7nIADLqF8qS2hBoCXV8QAvD_BwE), however, both of them have the following problems:
|
4 |
+
- the video doesn't actually scroll over, it's just a stactic website screenshot with a moving cursor
|
5 |
+
- It costs a significant amount of money. If you are ready to scale, it's a good deal, but if you are testing things out, you may not want to pay for that. Clipio still costs $97/mo to generate 5000 videos.
|
6 |
+
|
7 |
+
I found the first 2 problems embarrasing for my brand.
|
8 |
+
|
9 |
+
This solution does take longer time to run, but you can get better screenrecording videos with zero dollar cost:
|
10 |
+
|
11 |
+
# High Level Steps:
|
12 |
+
### Step 1 - Generate 10 second website recording (`website_recording.py`)
|
13 |
+
- a python script
|
14 |
+
- visit each URL with Selenium
|
15 |
+
- interact the website as pause, scroll down, pause, and scroll up for 10 seconds in total
|
16 |
+
- use an unblocking screenrecording solution, which means you can interact and record the scree at the same time
|
17 |
+
- iterate through URLs with each URL visit generates a video file
|
18 |
+
|
19 |
+
### Step 2 - Extends 10 seconds screen recording to 2 minutes video (`slow_elongate.sh`)
|
20 |
+
- a shell script
|
21 |
+
- slow down 10 seconds into 30 seconds video, because when Selenium scrolls the page, the Python API doesn't provde a good control over the scrolling speed and it looks unnatural.
|
22 |
+
- use last frame to fill up another 90 seconds becuase your video should focus on your sales pitch, not the their website, their website is only grab the attention
|
23 |
+
- wola, you got a 2 minute video
|
24 |
+
|
25 |
+
### Step 3 - Prepare talking head video (One time thing, `Final Cut Pro`)
|
26 |
+
- manually by any video recorder
|
27 |
+
- crop your 2 minutes talking head video into a circle
|
28 |
+
- repositioned properly
|
29 |
+
- background should be pure black for overlay mask in the next step
|
30 |
+
|
31 |
+
### Step 4 - Overlay the talking head on the website video(`overlay.py`)
|
32 |
+
- Manual setup for the very first time
|
33 |
+
1. resize the talking head video and the website/profile video (the automated recording videos we got earlier) to same frame size so that they can be overlayed using `cv2`
|
34 |
+
2. inspect the video to set proper codec, fps, and frame size for the overlay output file
|
35 |
+
- Each batch
|
36 |
+
1. mask the black background of each talking head frame into transparent
|
37 |
+
2. merge each mased talking head frame with website frame
|
38 |
+
3. generate overlay video without audio
|
39 |
+
4. add audio from original talking head video into the genreated overlay video
|
40 |
+
5. I ran 100 videos each time which takes about 8 hours form URL to ready to send videos.
|
41 |
+
|
42 |
+
### Step 5 - Upload to Instantly or Gmails
|
43 |
+
|
44 |
+
** you need to track the view rate of your video, so you need a video host and tracking solution**
|
45 |
+
** only allows uploading 10 videos each time **
|
46 |
+
|
47 |
+
# Tech Challenges
|
48 |
+
1. find an unblocking screen recorder that works with Selenium interactions
|
49 |
+
2. sync audio back to the video after video is edited with CV2
|
50 |
+
|
51 |
+
|
52 |
+
# Final
|
53 |
+
|
54 |
+
This repo isn't actively mainteined, so if you encountered technical issues or have questions specific to your business operation, please join our [free community on skool](https://www.skool.com/ai-for-coaches-creators-2571/about). You can post your issues or attending free daily workshops to get the help you need.
|
clip/video/.DS_Store
ADDED
Binary file (8.2 kB). View file
|
|
clip/video/1-recording/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
clip/video/2-slowed/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
clip/video/3-elongated/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
clip/video/4-overlayed/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
clip/video/5-broken/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
clip/video/circle_head_3584x2160.mov
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:d9b818660ff228a9ee96af7b4e58297c36011118d4bb8740a2e6c52ad6246d7f
|
3 |
+
size 15515733
|