David Chuan-En Lin commited on
Commit
5a11d0a
1 Parent(s): f58a6c0

Upload files

Browse files
Files changed (44) hide show
  1. .DS_Store +0 -0
  2. .gitattributes +3 -0
  3. README.md +5 -7
  4. files/.DS_Store +0 -0
  5. files/skydiving.npy +3 -0
  6. files/skydiving_features.npy +3 -0
  7. files/surfing.npy +3 -0
  8. files/surfing_features.npy +3 -0
  9. music/.DS_Store +0 -0
  10. music/and-it-sounds-like.mp3 +3 -0
  11. music/and-it-went-like.mp3 +3 -0
  12. music/comfort-chain.mp3 +3 -0
  13. music/coming-in-hot.mp3 +3 -0
  14. music/loop.mp3 +3 -0
  15. music/lovewave.mp3 +3 -0
  16. music/ready-set.mp3 +3 -0
  17. music/sheesh.mp3 +3 -0
  18. music/thinking-out-loud.mp3 +3 -0
  19. photos/.DS_Store +0 -0
  20. photos/skydiving/AdobeStock_10001953_Preview.jpeg +3 -0
  21. photos/skydiving/AdobeStock_120216166_Preview.jpeg +3 -0
  22. photos/skydiving/AdobeStock_138896480_Preview.jpeg +3 -0
  23. photos/skydiving/AdobeStock_166023598_Preview.jpeg +3 -0
  24. photos/skydiving/AdobeStock_279780585_Preview.jpeg +3 -0
  25. photos/skydiving/AdobeStock_33345390_Preview.jpeg +3 -0
  26. photos/skydiving/AdobeStock_348814707_Preview.jpeg +3 -0
  27. photos/skydiving/AdobeStock_350837731_Preview.jpeg +3 -0
  28. photos/skydiving/AdobeStock_7005042_Preview.jpeg +3 -0
  29. photos/skydiving/AdobeStock_96129011_Preview.jpeg +3 -0
  30. photos/surfing/AdobeStock_185663731_Preview.jpeg +3 -0
  31. photos/surfing/AdobeStock_211437413_Preview.jpeg +3 -0
  32. photos/surfing/AdobeStock_220162637_Preview.jpeg +3 -0
  33. photos/surfing/AdobeStock_220164473_Preview.jpeg +3 -0
  34. photos/surfing/AdobeStock_328826367_Preview.jpeg +3 -0
  35. photos/surfing/AdobeStock_415484898_Preview.jpeg +3 -0
  36. photos/surfing/AdobeStock_46444136_Preview.jpeg +3 -0
  37. photos/surfing/AdobeStock_495442848_Preview.jpeg +3 -0
  38. photos/surfing/AdobeStock_54024377_Preview.jpeg +3 -0
  39. photos/surfing/AdobeStock_70293058_Preview.jpeg +3 -0
  40. requirements.txt +11 -0
  41. videogenic.py +607 -0
  42. videos/.DS_Store +0 -0
  43. videos/skydiving.mp4 +3 -0
  44. videos/surfing.mp4 +3 -0
.DS_Store ADDED
Binary file (8.2 kB). View file
 
.gitattributes CHANGED
@@ -29,3 +29,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
29
  *.zip filter=lfs diff=lfs merge=lfs -text
30
  *.zst filter=lfs diff=lfs merge=lfs -text
31
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
29
  *.zip filter=lfs diff=lfs merge=lfs -text
30
  *.zst filter=lfs diff=lfs merge=lfs -text
31
  *tfevents* filter=lfs diff=lfs merge=lfs -text
32
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
33
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
34
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,10 @@
1
  ---
2
  title: Videogenic
3
- emoji: 📉
4
- colorFrom: blue
5
- colorTo: red
6
  sdk: streamlit
7
- sdk_version: 1.10.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
  ---
2
  title: Videogenic
3
+ emoji:
4
+ colorFrom: purple
5
+ colorTo: pink
6
  sdk: streamlit
7
+ sdk_version: 1.11.0
8
+ app_file: videogenic.py
9
  pinned: false
10
  ---
 
 
files/.DS_Store ADDED
Binary file (6.15 kB). View file
 
files/skydiving.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:62d9ee5c22ab5d17a4713ff64796d545b81bcfd40cb7d238cc7228434e5b8f3e
3
+ size 870930480
files/skydiving_features.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0ec76c93a1b5b4fd293ddd66340a9f6aff9f62a5c5adecfcfbbdd20f66de310
3
+ size 645248
files/surfing.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d246ebd959b39c63681a1ddd61912a5866b91990b01d31dcbde00a6db4e37036
3
+ size 859871040
files/surfing_features.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:20cb9e6d3818cc93280d14b53ff31c98c7eb4d09968d8e1e8992eae7e597a3c6
3
+ size 637056
music/.DS_Store ADDED
Binary file (6.15 kB). View file
 
music/and-it-sounds-like.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1147e33873dd2ddfb8414c50e9b91fd6f88813a8fd4493a66d4d45d21107bb61
3
+ size 287354
music/and-it-went-like.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c9ea37f421d2ebea3a35876e3b3a710496aa0c80adbeea6152169adef29cba90
3
+ size 297360
music/comfort-chain.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fd12feddd5ca89f8b3ac5cc5332d0f9eae6287fe10040b57225de75a67ac3b63
3
+ size 295632
music/coming-in-hot.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:25d27fa1d10ed3ed59a2a6ed706a9df319645a0b3208066fcabd2225c2f67814
3
+ size 229719
music/loop.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9655e2b062b6aaf54e709b2c6e53f7774686fd04f93629730bcc95946696e3d9
3
+ size 61269030
music/lovewave.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d3b788ea8a983b36f01cd31a765e840e1bff906d84e16efd7b641351021a8d6
3
+ size 219936
music/ready-set.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6d11d286777853692d0ce7f88306af9e119335d9ddc40dd9dea4ee03361fb7ba
3
+ size 380324
music/sheesh.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e370d0a9f0e261cdd2e0fbfece30a2a35b5c23e30b11964d812bfb615a017c90
3
+ size 317043
music/thinking-out-loud.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0839c8ced02007f098b2295c177ecb8d0335d55dbec77ae33bab06de9346afea
3
+ size 134543
photos/.DS_Store ADDED
Binary file (6.15 kB). View file
 
photos/skydiving/AdobeStock_10001953_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 0a315ef51f608d01d67a911edb137c0c225af197b59ddeaf20b98ed114bac319
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
photos/skydiving/AdobeStock_120216166_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 72925aae1b640e4ec4d3de9af99135f8f6081f400af126af6acbacfbf7c9fdd0
  • Pointer size: 131 Bytes
  • Size of remote file: 234 kB
photos/skydiving/AdobeStock_138896480_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 564303243268fff80ffb21421421b686ef488b5ae4cfbd24bb21d243f819ef19
  • Pointer size: 131 Bytes
  • Size of remote file: 228 kB
photos/skydiving/AdobeStock_166023598_Preview.jpeg ADDED

Git LFS Details

  • SHA256: bd586ef2ad1421067e8a6d4d0179b7891782c456bd6ea546d78afb83ba0e532f
  • Pointer size: 131 Bytes
  • Size of remote file: 230 kB
photos/skydiving/AdobeStock_279780585_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 94c8d8c4d9ea258aaf3a5fc9ecb9efd5f8cc2a12dfef69b3ae21893155cf4c99
  • Pointer size: 131 Bytes
  • Size of remote file: 275 kB
photos/skydiving/AdobeStock_33345390_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 7f10b904e67ab0398c0bd633ebdb5061627b0a3d3aaa1e56b57d4663af05240d
  • Pointer size: 131 Bytes
  • Size of remote file: 229 kB
photos/skydiving/AdobeStock_348814707_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 555d3e22eca3dafd8de0266252c288823925b28b0f830a2fcb5728f6eaf5e6a6
  • Pointer size: 131 Bytes
  • Size of remote file: 206 kB
photos/skydiving/AdobeStock_350837731_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 63e3513159602dc7e24bfb023a8d810dd2d24e5f230efaeb399b9d52d81b4bab
  • Pointer size: 131 Bytes
  • Size of remote file: 109 kB
photos/skydiving/AdobeStock_7005042_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 6675396bfc2ccd8006ff54efa9ff9428ecd36912ca223d530facb9dde8579acd
  • Pointer size: 131 Bytes
  • Size of remote file: 312 kB
photos/skydiving/AdobeStock_96129011_Preview.jpeg ADDED

Git LFS Details

  • SHA256: eff50fd1d4bdc12ef4c6c2eb1e4b15c7d0b096b36af2869ad583b1257b72ea45
  • Pointer size: 131 Bytes
  • Size of remote file: 168 kB
photos/surfing/AdobeStock_185663731_Preview.jpeg ADDED

Git LFS Details

  • SHA256: c8bd75ca95b75641bff10d2cb8327dde4073416e1af66183f2dab6767422d90f
  • Pointer size: 131 Bytes
  • Size of remote file: 427 kB
photos/surfing/AdobeStock_211437413_Preview.jpeg ADDED

Git LFS Details

  • SHA256: eab1a3c219bf7d6da517a302605f40cd932d06f4206907b7ad115f7000f00cad
  • Pointer size: 131 Bytes
  • Size of remote file: 312 kB
photos/surfing/AdobeStock_220162637_Preview.jpeg ADDED

Git LFS Details

  • SHA256: b60eebd3b8b2c3d02344e0656a5dc57259e2296b369deb941f5da89c00b31551
  • Pointer size: 131 Bytes
  • Size of remote file: 316 kB
photos/surfing/AdobeStock_220164473_Preview.jpeg ADDED

Git LFS Details

  • SHA256: da3ef19dcccbb8b61a605f83cafb0a803bd0a0aed2e9d638084584758d5a9bc3
  • Pointer size: 131 Bytes
  • Size of remote file: 391 kB
photos/surfing/AdobeStock_328826367_Preview.jpeg ADDED

Git LFS Details

  • SHA256: ad051a7a2adf85234d1e56dab267033e9d8c962c0430ab208595dc44bb96ed7e
  • Pointer size: 131 Bytes
  • Size of remote file: 295 kB
photos/surfing/AdobeStock_415484898_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 52917b362d75a89d79337ad84eae406d1900b84a233fd4dc6b43b66e1abb169d
  • Pointer size: 131 Bytes
  • Size of remote file: 248 kB
photos/surfing/AdobeStock_46444136_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 85986bbdf57872395a11011acb44a40fc41b6febd6de2be6317f225bb78e4995
  • Pointer size: 131 Bytes
  • Size of remote file: 477 kB
photos/surfing/AdobeStock_495442848_Preview.jpeg ADDED

Git LFS Details

  • SHA256: b1fcdf9c2a1708c2435a3be71bd097d2c3dbeb0428e4f414a013457306ef8800
  • Pointer size: 131 Bytes
  • Size of remote file: 399 kB
photos/surfing/AdobeStock_54024377_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 5df855e201893f1d751d8c3b5cf1c5a2fe3f8ac11061d300ce58d70cf5325ebb
  • Pointer size: 131 Bytes
  • Size of remote file: 440 kB
photos/surfing/AdobeStock_70293058_Preview.jpeg ADDED

Git LFS Details

  • SHA256: 2838f0b9f681254fe0a66faf1d932ff5ade35a770d3c150fed7f738ef8012629
  • Pointer size: 131 Bytes
  • Size of remote file: 328 kB
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ streamlit_vega_lite
3
+ opencv-python
4
+ Pillow
5
+ torch
6
+ numpy
7
+ decord
8
+ moviepy
9
+ altair
10
+ pandas
11
+ glob2
videogenic.py ADDED
@@ -0,0 +1,607 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ # from pytube import YouTube
3
+ # from pytube import extract
4
+ import cv2
5
+ from PIL import Image
6
+ import clip as openai_clip
7
+ import torch
8
+ import math
9
+ import numpy as np
10
+ import tempfile
11
+ # from humanfriendly import format_timespan
12
+ import json
13
+ import sys
14
+ from random import randrange
15
+ import logging
16
+ # from pyunsplash import PyUnsplash
17
+ import requests
18
+ import io
19
+ from io import BytesIO
20
+ import base64
21
+ import altair as alt
22
+ from streamlit_vega_lite import altair_component
23
+ import pandas as pd
24
+ from datetime import timedelta
25
+ import math
26
+ from decord import VideoReader, cpu, gpu
27
+ from moviepy.video.io.VideoFileClip import VideoFileClip
28
+ from moviepy.audio.io.AudioFileClip import AudioFileClip
29
+ from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
30
+ from moviepy.editor import *
31
+ import glob
32
+
33
+ def fetch_video(url):
34
+ yt = YouTube(url)
35
+ streams = yt.streams.filter(adaptive=True, subtype='mp4', resolution='360p', only_video=True)
36
+ length = yt.length
37
+ if length >= 300:
38
+ st.error('Please find a YouTube video shorter than 5 minutes. Sorry about this, the server capacity is limited for the time being.')
39
+ st.stop()
40
+ video = streams[0]
41
+ return video, video.url
42
+
43
+ # @st.cache()
44
+ # def extract_frames(video):
45
+ # frames = []
46
+ # capture = cv2.VideoCapture(video)
47
+ # fps = capture.get(cv2.CAP_PROP_FPS)
48
+ # current_frame = 0
49
+ # while capture.isOpened():
50
+ # ret, frame = capture.read()
51
+ # if ret == True:
52
+ # frames.append(Image.fromarray(frame[:, :, ::-1]))
53
+ # else:
54
+ # break
55
+ # current_frame += fps
56
+ # capture.set(cv2.CAP_PROP_POS_FRAMES, current_frame)
57
+ # # print(f'Frames extracted: {len(frames)}')
58
+
59
+ # return frames, fps
60
+
61
+ # @st.cache()
62
+ def video_to_frames(video):
63
+ vr = VideoReader(video)
64
+ frames = []
65
+ frame_count = len(vr)
66
+ fps = vr.get_avg_fps()
67
+ for i in range(0, frame_count, int(fps)):
68
+ # for i in range(0, frame_count):
69
+ frame = vr[i].asnumpy()
70
+ y_dim = frame.shape[0]
71
+ x_dim = frame.shape[1]
72
+ frames.append(Image.fromarray(frame))
73
+ return frames, fps, x_dim, y_dim
74
+
75
+ def video_to_info(video):
76
+ vr = VideoReader(video)
77
+ frames = []
78
+ frame_count = len(vr)
79
+ fps = vr.get_avg_fps()
80
+ frame = vr[0].asnumpy()
81
+ y_dim = frame.shape[0]
82
+ x_dim = frame.shape[1]
83
+ return fps, x_dim, y_dim
84
+
85
+ # @st.cache()
86
+ def encode_frames(video_frames):
87
+ batch_size = 256
88
+ batches = math.ceil(len(video_frames) / batch_size)
89
+ video_features = torch.empty([0, 512], dtype=torch.float16).to(st.session_state.device)
90
+ for i in range(batches):
91
+ batch_frames = video_frames[i*batch_size : (i+1)*batch_size]
92
+ batch_preprocessed = torch.stack([st.session_state.preprocess(frame) for frame in batch_frames]).to(st.session_state.device)
93
+ with torch.no_grad():
94
+ batch_features = st.session_state.model.encode_image(batch_preprocessed)
95
+ batch_features /= batch_features.norm(dim=-1, keepdim=True)
96
+ video_features = torch.cat((video_features, batch_features))
97
+ # print(f'Features: {video_features.shape}')
98
+ return video_features
99
+
100
+ def classify_activity(video_features, activities_list):
101
+ text = torch.cat([openai_clip.tokenize(
102
+ f'{activity}') for activity in activities_list]).to(st.session_state.device)
103
+ with torch.no_grad():
104
+ text_features = st.session_state.model.encode_text(text)
105
+ text_features /= text_features.norm(dim=-1, keepdim=True)
106
+ logit_scale = st.session_state.model.logit_scale.exp()
107
+ video_features = torch.from_numpy(video_features)
108
+ similarities = (logit_scale * video_features @
109
+ text_features.t()).softmax(dim=-1)
110
+ probs, word_idxs = similarities[0].topk(5)
111
+ primary_activity = []
112
+ for prob, word_idx in zip(probs, word_idxs):
113
+ primary_activity.append(activities_list[word_idx])
114
+ # primary_activity = activities_list[word_idx]
115
+ return primary_activity
116
+
117
+ def encode_photos(photos):
118
+ batch_size = 256
119
+ batches = math.ceil(len(photos) / batch_size)
120
+ video_features = torch.empty([0, 512], dtype=torch.float16).to(st.session_state.device)
121
+ for i in range(batches):
122
+ batch_frames = photos[i*batch_size : (i+1)*batch_size]
123
+ batch_preprocessed = torch.stack([st.session_state.preprocess(Image.open(frame)) for frame in batch_frames]).to(st.session_state.device)
124
+ with torch.no_grad():
125
+ batch_features = st.session_state.model.encode_image(batch_preprocessed)
126
+ batch_features /= batch_features.norm(dim=-1, keepdim=True)
127
+ video_features = torch.cat((video_features, batch_features))
128
+ # print(f'Features: {video_features.shape}')
129
+ return video_features
130
+
131
+ def img_to_bytes(img):
132
+ img_byte_arr = io.BytesIO()
133
+ img.save(img_byte_arr, format='JPEG')
134
+ img_byte_arr = img_byte_arr.getvalue()
135
+ return img_byte_arr
136
+
137
+ def normalize(vector):
138
+ return (vector - np.min(vector)) / (np.max(vector) - np.min(vector))
139
+
140
+ def format_img(img):
141
+ size = 150, 150
142
+ # img = Image.fromarray(img)
143
+ img.thumbnail(size, Image.Resampling.LANCZOS)
144
+ output = io.BytesIO()
145
+ img.save(output, format='PNG')
146
+ encoded_string = f'data:image/png;base64,{base64.b64encode(output.getvalue()).decode()}'
147
+ return encoded_string
148
+
149
+ def get_photos(keyword):
150
+ photo_collection = []
151
+ for filename in glob.glob(f'photos/{st.session_state.domain.lower()}/*.jpeg'):
152
+ photo = Image.open(filename)
153
+ photo_collection.append(photo)
154
+ return photo_collection
155
+
156
+ # # api_key = 'hzcKZ0e4we95wSd8_ip2zTB3m2DrOMWehAxrYjqjwg0'
157
+ # api_key = 'fZ1nE7Y4NC-iYGmqgv-WuyM8m9p0LroCdAOZOR6tyho'
158
+ # unsplash_search = PyUnsplash(api_key=api_key)
159
+ # logging.getLogger('pyunsplash').setLevel(logging.DEBUG)
160
+ # search = unsplash_search.search(type_='photos', query=keyword) # per_page
161
+ # photo_collection = []
162
+ # # st.markdown(f'**Unsplash photos for `{keyword}`**')
163
+ # for result in search.entries:
164
+ # photo_url = result.link_download
165
+ # response = requests.get(photo_url)
166
+ # photo = Image.open(BytesIO(response.content))
167
+ # # st.image(photo, width=200)
168
+ # photo_collection.append(photo)
169
+ # return photo_collection
170
+
171
+ def display_results(best_photo_idx):
172
+ st.markdown('**Top 10 highlights**')
173
+ result_arr = []
174
+ for frame_id in best_photo_idx:
175
+ result = st.session_state.video_frames[frame_id]
176
+ st.image(result)
177
+ return result_arr
178
+
179
+ def make_df(similarities):
180
+ similarities = similarities
181
+ df = pd.DataFrame()
182
+ df['keyword'] = [keyword] * len(similarities)
183
+ df['x'] = [i for i, _ in enumerate(similarities)]
184
+ df['y'] = normalize(np.power(similarities, 8))
185
+ df['image'] = [format_img(frame) for frame in st.session_state.video_frames]
186
+ return df
187
+
188
+ # @st.cache()
189
+ def compute_scores(search_query, video_features, text_query, display_results_count=10):
190
+ sum_photo = torch.zeros(1, 512)
191
+ for photo in search_query:
192
+ with torch.no_grad():
193
+ image_features = st.session_state.model.encode_image(st.session_state.preprocess(photo).unsqueeze(0).to(st.session_state.device))
194
+ image_features /= image_features.norm(dim=-1, keepdim=True)
195
+ sum_photo += sum_photo + image_features
196
+ avg_photo = sum_photo / len(search_query)
197
+ video_features = torch.from_numpy(video_features)
198
+ similarities = (100.0 * video_features @ avg_photo.T)
199
+ # values, best_photo_idx = similarities.topk(display_results_count, dim=0)
200
+ # display_results(best_photo_idx)
201
+ return similarities.cpu().numpy()
202
+
203
+ def avenir():
204
+ font = 'Avenir'
205
+ return {
206
+ 'config' : {
207
+ 'title': {'font': font},
208
+ 'axis': {
209
+ 'labelFont': font,
210
+ 'titleFont': font
211
+ }
212
+ }
213
+ }
214
+
215
+ alt.themes.register('avenir', avenir)
216
+ alt.themes.enable('avenir')
217
+
218
+ # TODO: Make playhead scores and average according to keyword
219
+ # TODO: Maximum interval selection
220
+ # TODO: Interactive legend https://altair-viz.github.io/gallery/interactive_legend.html
221
+ # TODO: Multi-line highlight https://altair-viz.github.io/gallery/multiline_highlight.html
222
+ @st.cache
223
+ def draw_chart(df, mode):
224
+ if st.session_state.mode == 'Automatic':
225
+ nearest = alt.selection(type='single', nearest=True, on='mouseover', empty='none')
226
+ line = alt.Chart(df).mark_line().encode(
227
+ x=alt.X('x:Q', axis=alt.Axis(labels=True, tickSize=0, title='')),
228
+ y=alt.Y('y', axis=alt.Axis(labels=False, tickSize=0, title='')),
229
+ # color=alt.Color('keyword:N', scale=alt.Scale(scheme='tableau20')),
230
+ color=alt.value('#00C7BE'),
231
+ # color=alt.Color('#9b59b6'),
232
+ )
233
+ selectors = alt.Chart(df).mark_point().encode(
234
+ x='x:Q',
235
+ opacity=alt.value(0),
236
+ ).add_selection(
237
+ nearest
238
+ )
239
+ rules = alt.Chart(df).mark_rule(color='black').encode(
240
+ x='x:Q',
241
+ ).transform_filter(
242
+ nearest
243
+ )
244
+ points = line.mark_point().encode(
245
+ opacity=alt.condition(nearest, alt.value(1), alt.value(0))
246
+ )
247
+ text = line.mark_text(align='center', yOffset=-110, fontSize=16).encode(
248
+ text=alt.condition(nearest, 'y:N', alt.value(' ')),
249
+ color=alt.value('#000000'),
250
+ # fontSize=30
251
+ ).transform_calculate(y=f'format(datum.y, ".2f")')
252
+ image = line.mark_image(align='center', width=150, height=150, yOffset=-60).encode(
253
+ url=alt.condition(nearest, 'image', alt.value(' '))
254
+ )
255
+ chart = alt.layer(line, selectors, points, rules, text, image)
256
+ elif st.session_state.mode == 'brush':
257
+ brush = alt.selection(type='interval', encodings=['x'])
258
+ line = alt.Chart(df).mark_line().encode( # https://www.rdocumentation.org/packages/vegalite/versions/0.6.1/topics/mark_line
259
+ x=alt.X('x:Q', axis=alt.Axis(labels=True, tickSize=0, title='')),
260
+ y=alt.Y('y:Q', axis=alt.Axis(labels=False, tickSize=0, title='')),
261
+ # color=alt.Color('keyword:N', scale=alt.Scale(scheme='tableau20')),
262
+ color=alt.value('#00C7BE'),
263
+ ).add_selection(
264
+ brush
265
+ )
266
+ text = alt.Chart(df).transform_filter(brush).mark_text(
267
+ align='right',
268
+ # baseline='top',
269
+ # dx=1500
270
+ dx=750,
271
+ dy=-12,
272
+ fontSize=24,
273
+ fontWeight=800,
274
+ ).encode(
275
+ # x='max(x):Q',
276
+ y='mean(y):Q',
277
+ # dy=alt.value(10),
278
+ text=alt.Text('mean(y):Q', format='.2f'),
279
+ )
280
+ average = alt.Chart(df).mark_rule(color='black', strokeDash=[5, 5]).encode(
281
+ y='mean(y):Q',
282
+ # size=alt.SizeValue(3),
283
+ ).transform_filter(
284
+ brush
285
+ )
286
+ # chart = alt.layer(line, average, text)
287
+ chart = line
288
+ elif st.session_state.mode == 'User selection':
289
+ brush = alt.selection(type='interval', encodings=['x'])
290
+ line = alt.Chart(df).mark_line().encode( # https://www.rdocumentation.org/packages/vegalite/versions/0.6.1/topics/mark_line
291
+ x=alt.X('x:Q', axis=alt.Axis(labels=True, tickSize=0, title='')),
292
+ y=alt.Y('y:Q', axis=alt.Axis(labels=False, tickSize=0, title='')),
293
+ # color=alt.Color('keyword:N', scale=alt.Scale(scheme='tableau20')),
294
+ color=alt.value('#00C7BE'),
295
+ ).add_selection(
296
+ brush
297
+ )
298
+ text = alt.Chart(df).transform_filter(brush).mark_text(
299
+ align='right',
300
+ # baseline='top',
301
+ # dx=1500
302
+ dx=750,
303
+ dy=-12,
304
+ fontSize=24,
305
+ fontWeight=800,
306
+ ).encode(
307
+ # x='max(x):Q',
308
+ y='mean(y):Q',
309
+ # dy=alt.value(10),
310
+ text=alt.Text('mean(y):Q', format='.2f'),
311
+ )
312
+ average = alt.Chart(df).mark_rule(color='black', strokeDash=[5, 5]).encode(
313
+ y='mean(y):Q',
314
+ # size=alt.SizeValue(3),
315
+ ).transform_filter(
316
+ brush
317
+ )
318
+ # chart = alt.layer(line, average, text)
319
+ chart = line
320
+ return chart.properties(width=1250, height=500).configure_axis(grid=False, domain=False).configure_view(strokeOpacity=0)
321
+ # return line
322
+
323
+ def max_subarray(arr, k):
324
+ n = len(arr)
325
+ if (n < k):
326
+ st.write('Video too short')
327
+ res = 0
328
+ left = 0
329
+ right = k
330
+ for i in range(k):
331
+ res += arr[i]
332
+ curr_sum = res
333
+ for i in range(k, n):
334
+ curr_sum += arr[i] - arr[i - k]
335
+ if curr_sum > res:
336
+ res = curr_sum
337
+ left = i - k
338
+ right = i
339
+ return res, left, right
340
+
341
+ def edit_video(template, df_all):
342
+ video_path = f'videos/{st.session_state.domain.lower()}.mp4'
343
+ if template == 'Coming In Hot by Andy Mineo & Lecrae (hype, 7 seconds)':
344
+ res, left, right = max_subarray(df_all['y'].tolist(), 7)
345
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
346
+ fps = video.fps
347
+ x_dim = st.session_state.x_dim
348
+ y_dim = st.session_state.y_dim
349
+ music_path = 'music/coming-in-hot.mp3'
350
+ blank1 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.6)
351
+ flash1 = video.subclip(t_start=0, t_end=1.2)
352
+ blank2 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
353
+ flash2 = video.subclip(t_start=1.3, t_end=1.4)
354
+ blank3 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
355
+ flash3 = video.subclip(t_start=1.5, t_end=3.3)
356
+ blank4 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
357
+ flash4 = video.subclip(t_start=3.4, t_end=3.5)
358
+ blank5 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
359
+ flash5 = video.subclip(t_start=3.6, t_end=4.6)
360
+ blank6 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
361
+ flash6 = video.subclip(t_start=4.7, t_end=4.8)
362
+ blank7 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
363
+ highlight = video.subclip(t_start=4.9, t_end=6.384)
364
+ output = concatenate_videoclips([blank1, flash1, blank2, flash2, blank3, flash3, blank4, flash4, blank5, flash5, blank6, flash6, blank7, highlight])
365
+ elif template == 'Thinking Out Loud Cypher by Jermsego (hype, 8 seconds)':
366
+ res, left, right = max_subarray(df_all['y'].tolist(), 7)
367
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
368
+ fps = video.fps
369
+ x_dim = st.session_state.x_dim
370
+ y_dim = st.session_state.y_dim
371
+ music_path = 'music/thinking-out-loud.mp3'
372
+ blank = ColorClip((x_dim, y_dim), (0, 0, 0), duration=1.6)
373
+ highlight = video.subclip(t_start=0, t_end=6.852)
374
+ output = concatenate_videoclips([blank, highlight])
375
+ elif template == 'Sheesh by Surfaces (upbeat, 10 seconds)':
376
+ res, left, right = max_subarray(df_all['y'].tolist(), 8)
377
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
378
+ fps = video.fps
379
+ x_dim = st.session_state.x_dim
380
+ y_dim = st.session_state.y_dim
381
+ music_path = 'music/sheesh.mp3'
382
+ blank1 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=3.5)
383
+ flash1 = video.subclip(t_start=0, t_end=0.1)
384
+ blank2 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
385
+ flash2 = video.subclip(t_start=0.2, t_end=0.3)
386
+ blank3 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
387
+ flash3 = video.subclip(t_start=0.4, t_end=0.5)
388
+ blank4 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.1)
389
+ flash4 = video.subclip(t_start=0.6, t_end=0.7)
390
+ blank5 = ColorClip((x_dim, y_dim), (0, 0, 0), duration=0.9)
391
+ highlight = video.subclip(t_start=1.6, t_end=7.18408163265)
392
+ output = concatenate_videoclips([blank1, flash1, blank2, flash2, blank3, flash3, blank4, flash4, blank5, highlight])
393
+ elif template == 'Moon by Kid Francescoli (tranquil, 10 seconds)':
394
+ res, left, right = max_subarray(df_all['y'].tolist(), 9)
395
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
396
+ fps = video.fps
397
+ x_dim = st.session_state.x_dim
398
+ y_dim = st.session_state.y_dim
399
+ music_path = 'music/and-it-went-like.mp3'
400
+ blank = ColorClip((x_dim, y_dim), (0, 0, 0), duration=1.9)
401
+ highlight = video.subclip(t_start=0, t_end=8.132)
402
+ output = concatenate_videoclips([blank, highlight])
403
+ elif template == 'Ready Set by Joey Valence & Brae (old school, 10 seconds)':
404
+ res, left, right = max_subarray(df_all['y'].tolist(), 11)
405
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
406
+ fps = video.fps
407
+ x_dim = st.session_state.x_dim
408
+ y_dim = st.session_state.y_dim
409
+ music_path = 'music/ready-set.mp3'
410
+ highlight = video.subclip(t_start=0, t_end=10.512)
411
+ output = highlight
412
+ elif template == 'Lovewave by The 1-800 (tranquil, 13 seconds)':
413
+ res, left, right = max_subarray(df_all['y'].tolist(), 12)
414
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
415
+ fps = video.fps
416
+ x_dim = st.session_state.x_dim
417
+ y_dim = st.session_state.y_dim
418
+ music_path = 'music/lovewave.mp3'
419
+ blank = ColorClip((x_dim, y_dim), (0, 0, 0), duration=2.1)
420
+ highlight = video.subclip(t_start=0, t_end=11.58)
421
+ output = concatenate_videoclips([blank, highlight])
422
+ elif template == 'And It Sounds Like by Forrest Nolan (tranquil, 17 seconds)':
423
+ res, left, right = max_subarray(df_all['y'].tolist(), 16)
424
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
425
+ fps = video.fps
426
+ x_dim = st.session_state.x_dim
427
+ y_dim = st.session_state.y_dim
428
+ music_path = 'music/and-it-sounds-like.mp3'
429
+ blank = ColorClip((x_dim, y_dim), (0, 0, 0), duration=2)
430
+ highlight = video.subclip(t_start=0, t_end=15.928)
431
+ output = concatenate_videoclips([blank, highlight])
432
+ elif template == 'Comfort Chain by Instupendo (lofi, 18 seconds)':
433
+ res, left, right = max_subarray(df_all['y'].tolist(), 19)
434
+ video = VideoFileClip(video_path).subclip(t_start=left, t_end=right)
435
+ fps = video.fps
436
+ x_dim = st.session_state.x_dim
437
+ y_dim = st.session_state.y_dim
438
+ music_path = 'music/comfort-chain.mp3'
439
+ highlight = video.subclip(t_start=0, t_end=18.432000000000002)
440
+ output = highlight
441
+ # st.write(res, left, right)
442
+ song = AudioFileClip(music_path)
443
+ output = output.set_audio(song)
444
+ output.write_videofile('output.mp4', temp_audiofile='temp.m4a', remove_temp=True, audio_codec='aac', logger=None, fps=fps)
445
+ st.video('output.mp4')
446
+ # return output
447
+
448
+ def crop_video(df_all, left, right):
449
+ video_path = f'videos/{st.session_state.domain.lower()}.mp4'
450
+ video = VideoFileClip(video_path)
451
+ fps = video.fps
452
+ music_path = 'music/loop.mp3'
453
+ song = AudioFileClip(music_path)
454
+ video = video.set_audio(song)
455
+ output = video.subclip(t_start=left, t_end=right)
456
+ output.write_videofile('output.mp4', temp_audiofile='temp.m4a', remove_temp=True, audio_codec='aac', logger=None, fps=fps)
457
+ st.video('output.mp4')
458
+ # return output
459
+
460
+ st.set_page_config(page_title='Videogenic', page_icon = '✨', layout = 'wide', initial_sidebar_state = 'collapsed')
461
+
462
+ hide_streamlit_style = """
463
+ <style>
464
+ #MainMenu {visibility: hidden;}
465
+ footer {visibility: hidden;}
466
+ * {font-family: Avenir; cursor: pointer;}
467
+ .css-gma2qf {display: flex; justify-content: center; font-size: 42px; font-weight: bold;}
468
+ a:link {text-decoration: none;}
469
+ a:hover {text-decoration: none;}
470
+ .st-ba {font-family: Avenir;}
471
+ </style>
472
+ """
473
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)
474
+
475
+ # clustrmaps = """
476
+ # <a href="https://clustrmaps.com/site/1bham" target="_blank" title="Visit tracker"><img src="//www.clustrmaps.com/map_v2.png?d=NhNk5g9hy6Y06nqo7RirhHvZSr89uSS8rPrt471wAXw&cl=ffffff" width="0" height="0"></a>
477
+ # """
478
+
479
+ # st.markdown(clustrmaps, unsafe_allow_html=True)
480
+
481
+ # ss = SessionState.get(url=None, id=None, input=None, file_name=None, video=None, video_name=None, video_frames=None, video_features=None, fps=None, mode=None, query=None, progress=1)
482
+
483
+ st.title('Videogenic ✨')
484
+ if 'progress' not in st.session_state:
485
+ st.session_state.progress = 1
486
+
487
+ # mode = 'play'
488
+ # mode = 'brush'
489
+ # mode = 'select'
490
+
491
+ if st.session_state.progress == 1:
492
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
493
+ model, preprocess = openai_clip.load('ViT-B/32', device=device)
494
+ if 'model' not in st.session_state:
495
+ st.session_state.model = model
496
+ st.session_state.preprocess = preprocess
497
+ st.session_state.device = device
498
+ st.session_state.model = model
499
+ st.session_state.preprocess = preprocess
500
+ st.session_state.device = device
501
+ domain = st.selectbox('Select video',('Skydiving', 'Surfing')) # Entire journey, montage, vlog
502
+ if 'domain' not in st.session_state:
503
+ st.session_state.domain = domain
504
+ st.session_state.domain = domain
505
+ if st.button('Process video'):
506
+ video_name = f'videos/{st.session_state.domain.lower()}.mp4'
507
+ video_file = open(video_name, 'rb')
508
+ video_bytes = video_file.read()
509
+ if 'video' not in st.session_state:
510
+ st.session_state.video = video_bytes
511
+ st.session_state.video = video_bytes
512
+ # st.video(st.session_state.video)
513
+ # video_frames, fps, x_dim, y_dim = video_to_frames(video_name) # first run; video_to_info
514
+ # np.save(f'files/{st.session_state.domain.lower()}.npy', video_frames)
515
+ fps, x_dim, y_dim = video_to_info(video_name)
516
+ video_frames = np.load(f'files/{st.session_state.domain.lower()}.npy', allow_pickle=True)
517
+ if 'video_frames' not in st.session_state:
518
+ st.session_state.video_frames = video_frames
519
+ st.session_state.fps = fps
520
+ st.session_state.x_dim = x_dim
521
+ st.session_state.y_dim = y_dim
522
+ st.session_state.video_frames = video_frames
523
+ st.session_state.fps = fps
524
+ st.session_state.x_dim = x_dim
525
+ st.session_state.y_dim = y_dim
526
+ print('Extracted frames')
527
+ # encoded_frames = encode_frames(video_frames) # first run
528
+ # np.save(f'files/{st.session_state.domain.lower()}_features.npy', encoded_frames)
529
+ encoded_frames = np.load(f'files/{st.session_state.domain.lower()}_features.npy', allow_pickle=True)
530
+ if 'video_features' not in st.session_state:
531
+ # st.session_state.video_features = encoded_frames
532
+ st.session_state.video_features = encoded_frames
533
+ st.session_state.video_features = encoded_frames
534
+ print('Encoded frames')
535
+ st.session_state.progress = 2
536
+
537
+ # with open('activities.txt') as f:
538
+ # activities_list = [line.rstrip('\n') for line in f]
539
+ # keywords = classify_activity(st.session_state.video_features, activities_list)
540
+ # st.write(keywords)
541
+
542
+ if st.session_state.progress == 2:
543
+ mode = st.radio('Select mode', ('Automatic', 'User selection'))
544
+ if 'mode' not in st.session_state:
545
+ st.session_state.mode = mode
546
+ st.session_state.mode = mode
547
+ # keywords = list(st.text_input('Enter topic').split(','))
548
+ # if st.button('Compute scores') and keywords is not None:
549
+ keyword = st.session_state.domain.lower()
550
+ df_list = []
551
+ # for keyword in keywords:
552
+ img_set = get_photos(keyword)
553
+ similarities = compute_scores(img_set, st.session_state.video_features, keyword)
554
+ # st.write(similarities)
555
+ df = make_df(similarities)
556
+ df_list.append(df)
557
+ df_all = pd.concat(df_list, ignore_index=True, sort=False)
558
+ if 'df_all' not in st.session_state:
559
+ st.session_state.df_all = df_all
560
+ st.session_state.df_all = df_all
561
+ # st.write(df_all)
562
+ # highlight_length = 7.033
563
+ # st.write(st.session_state.fps)
564
+ selection = altair_component(draw_chart(df_all, st.session_state.mode))
565
+ print(selection)
566
+ # if '_vgsid_' in selection:
567
+ # # the ids start at 1
568
+ # st.write(df.iloc[[selection['_vgsid_'][0] - 1]])
569
+ # else:
570
+ # st.info('Hover over the chart above to see details about the Penguin here.')
571
+ # if 'x' in selection:
572
+ # # the ids start at 1
573
+ # st.write(selection['x'])
574
+ # chart = draw_chart(df_all, mode)
575
+ # st.altair_chart(chart, use_container_width=False)
576
+ # st.session_state.progress = 3
577
+
578
+ # if st.session_state.progress == 3:
579
+ if st.session_state.mode == 'Automatic':
580
+ # template = st.selectbox('Select template', ['Coming In Hot by Andy Mineo & Lecrae (hype, 7 seconds)', 'Thinking Out Loud Cypher by Jermsego (hype, 8 seconds)', 'Sheesh by Surfaces (upbeat, 10 seconds)',
581
+ # 'Moon by Kid Francescoli (tranquil, 10 seconds)', 'Ready Set by Joey Valence & Brae (old school, 10 seconds)', 'Lovewave by The 1-800 (tranquil, 13 seconds)',
582
+ # 'And It Sounds Like by Forrest Nolan (tranquil, 17 seconds)', 'Comfort Chain by Instupendo (lofi, 18 seconds)'])
583
+ template = st.selectbox('Select template', ['Coming In Hot by Andy Mineo & Lecrae (hype, 7 seconds)', 'Sheesh by Surfaces (upbeat, 10 seconds)', 'Lovewave by The 1-800 (tranquil, 13 seconds)'])
584
+
585
+ if st.button('Generate video'):
586
+ edit_video(template, st.session_state.df_all)
587
+ elif st.session_state.mode == 'User selection':
588
+ if st.button('Generate video'):
589
+ left = selection['x'][0]
590
+ right = selection['x'][1]
591
+ crop_video(st.session_state.df_all, left, right)
592
+ # res, left, right = max_subarray(df_all['y'].tolist(), 8)
593
+
594
+ # if 'left' not in st.session_state:
595
+ # st.session_state.left = left
596
+ # st.session_state.right = right
597
+ # video_path = f'videos/{domain.lower()}.mp4'
598
+ # music_path = 'music/sheesh.wav'
599
+ # video = VideoFileClip(video_path).subclip(t_start=st.session_state.left, t_end=st.session_state.right)
600
+ # fps = video.fps
601
+ # x_dim = st.session_state.x_dim
602
+ # y_dim = st.session_state.y_dim
603
+ # song = AudioFileClip(music_path)
604
+ # output = edit_video(video, template)
605
+ # st.video('output.mp4')
606
+ # np.save('skydiving_features', st.session_state.video_features)
607
+ # np.save('skydiving_frames', st.session_state.video_frames)
videos/.DS_Store ADDED
Binary file (6.15 kB). View file
 
videos/skydiving.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:96534459fdba80dd076a7c4de5e6d9553db55640baf3ff5956450de3efd0586b
3
+ size 79814669
videos/surfing.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b566c3d0c6894193302096f069b2970f402f0048b4640a6f764889ebe3dfa817
3
+ size 81639070