eivid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +215 -0
- data/Rakefile +18 -0
- data/app/controllers/eivid/application_controller.rb +20 -0
- data/app/controllers/eivid/concerns/video_validations.rb +42 -0
- data/app/controllers/eivid/videos_controller.rb +48 -0
- data/app/dump/eivid/video_mime_dump.rb +9 -0
- data/app/errors/eivid/incorrect_video_mime_type_error.rb +4 -0
- data/app/errors/eivid/main_app_record_not_found_error.rb +4 -0
- data/app/errors/eivid/maximum_vimeo_poll_reached_error.rb +4 -0
- data/app/errors/eivid/video_file_not_present_error.rb +4 -0
- data/app/errors/eivid/video_file_size_too_big_error.rb +4 -0
- data/app/errors/eivid/video_unavailable_error.rb +4 -0
- data/app/errors/eivid/video_urls_unavailable_error.rb +4 -0
- data/app/jobs/eivid/application_job.rb +4 -0
- data/app/jobs/eivid/check_vimeo_status_job.rb +46 -0
- data/app/jobs/eivid/get_vimeo_urls_job.rb +62 -0
- data/app/jobs/eivid/upload_vimeo_job.rb +55 -0
- data/app/mailers/eivid/application_mailer.rb +6 -0
- data/app/models/eivid/application_record.rb +5 -0
- data/app/models/eivid/concerns/main_app/owner.rb +17 -0
- data/app/models/eivid/concerns/main_app/user.rb +11 -0
- data/app/models/eivid/concerns/main_app/video_concerns.rb +16 -0
- data/app/models/eivid/concerns/main_app/video_resource.rb +11 -0
- data/app/models/eivid/concerns/request_service/delete_video.rb +17 -0
- data/app/models/eivid/concerns/request_service/get_video.rb +9 -0
- data/app/models/eivid/concerns/request_service/manage_folder.rb +29 -0
- data/app/models/eivid/concerns/request_service/poll_status.rb +9 -0
- data/app/models/eivid/concerns/request_service/testing_methods.rb +38 -0
- data/app/models/eivid/concerns/request_service/upload_video.rb +31 -0
- data/app/models/eivid/owner.rb +21 -0
- data/app/models/eivid/video.rb +21 -0
- data/app/models/eivid/video_resource.rb +8 -0
- data/app/services/eivid/notify_front_service.rb +14 -0
- data/app/services/eivid/request_service.rb +42 -0
- data/app/services/eivid/upload_service.rb +30 -0
- data/config/initializers/macro_initializer.rb +22 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20210325114212_create_eivid_videos.rb +11 -0
- data/db/migrate/20210325140804_add_uploaded_bool_to_videos.rb +5 -0
- data/db/migrate/20210330092643_create_eivid_owners.rb +9 -0
- data/db/migrate/20210330094604_add_external_id_to_owner.rb +5 -0
- data/db/migrate/20210330100147_add_folder_id_to_owner.rb +5 -0
- data/db/migrate/20210330103729_add_vimeo_id_to_videos.rb +5 -0
- data/db/migrate/20210331082454_add_status_poll_cnt_to_videos.rb +5 -0
- data/db/migrate/20210401091448_create_eivid_video_resources.rb +12 -0
- data/db/migrate/20210401123512_remove_status_poll_cnt_from_eivid_videos.rb +5 -0
- data/db/migrate/20210401132759_create_eivid_dev_envs.rb +11 -0
- data/db/migrate/20210401134700_drop.rb +5 -0
- data/db/migrate/20210419122001_change_url_to_embedded_url.rb +5 -0
- data/db/migrate/20210419122752_add_video_urls_to_videos.rb +7 -0
- data/db/migrate/20210420120017_add_user_id_to_eivid_videos.rb +5 -0
- data/lib/eivid.rb +6 -0
- data/lib/eivid/engine.rb +33 -0
- data/lib/eivid/version.rb +3 -0
- data/lib/tasks/eivid_tasks.rake +4 -0
- metadata +144 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
module Eivid
|
2
|
+
class GetVimeoUrlsJob < ApplicationJob
|
3
|
+
|
4
|
+
retry_on VideoUrlsUnavailableError, wait: 10.seconds, attempts: 50
|
5
|
+
|
6
|
+
def perform(video_record:, vimeo_id:)
|
7
|
+
@video_record = video_record
|
8
|
+
@vimeo_id = vimeo_id
|
9
|
+
|
10
|
+
set_response
|
11
|
+
set_url_sd
|
12
|
+
set_url_hd
|
13
|
+
set_url_thumbnail
|
14
|
+
|
15
|
+
validate_url_presence
|
16
|
+
validate_url_thumbnail
|
17
|
+
|
18
|
+
update_record
|
19
|
+
notify_front
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def set_response
|
25
|
+
@response = Eivid::RequestService.get_video @vimeo_id
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_url_sd
|
29
|
+
@url_sd = dig_video_url :sd
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_url_hd
|
33
|
+
@url_hd = dig_video_url :hd
|
34
|
+
end
|
35
|
+
|
36
|
+
def dig_video_url(definition)
|
37
|
+
@response[:files]&.find { |video| video[:quality] == definition.to_s }&.dig(:link)
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_url_thumbnail
|
41
|
+
@url_thumbnail = @response&.dig(:pictures, :sizes)&.find { |url| url[:width] == 640 && url[:height] == 360 }&.dig(:link_with_play_button)
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_url_presence
|
45
|
+
raise Eivid::VideoUrlsUnavailableError unless (@url_sd && @url_hd && @url_thumbnail)
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_url_thumbnail
|
49
|
+
raise Eivid::VideoUrlsUnavailableError if @url_thumbnail.include? 'video%2Fdefault'
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_record
|
53
|
+
@video_record.update(uploaded: true, url_sd: @url_sd, url_hd: @url_hd, url_thumbnail: @url_thumbnail)
|
54
|
+
end
|
55
|
+
|
56
|
+
def notify_front
|
57
|
+
data = { video: @video_record.slice(:id, :user_id), progress: { percentage: 100, step: "All video versions are available on Vimeo." } }
|
58
|
+
NotifyFrontService.progress('notify_method_on_versions_available', data)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Eivid
|
2
|
+
class UploadVimeoJob < ApplicationJob
|
3
|
+
|
4
|
+
def perform(video_record:, video_path:)
|
5
|
+
@video_record = video_record
|
6
|
+
@video_path = video_path
|
7
|
+
@video_file = File.open(video_path).read
|
8
|
+
|
9
|
+
upload_to_vimeo
|
10
|
+
set_attributes
|
11
|
+
update_record
|
12
|
+
add_to_folder
|
13
|
+
|
14
|
+
notify_front
|
15
|
+
check_status
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def upload_to_vimeo
|
21
|
+
@response = Eivid::RequestService.upload_video(video_path: @video_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_attributes
|
25
|
+
set_vimeo_url
|
26
|
+
set_vimeo_id
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_vimeo_url
|
30
|
+
@vimeo_url = @response.dig(:link)
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_vimeo_id
|
34
|
+
@vimeo_id = @vimeo_url.split('/').last
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_record
|
38
|
+
@video_record.update(url_embedded: @vimeo_url, vimeo_id: @vimeo_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_to_folder
|
42
|
+
Eivid::RequestService.add_video_to_folder(video_record: @video_record)
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_status
|
46
|
+
CheckVimeoStatusJob.perform_later(video_record: @video_record)
|
47
|
+
end
|
48
|
+
|
49
|
+
def notify_front
|
50
|
+
data = { video: @video_record.slice(:id, :user_id), progress: { percentage: 33, step: "The video has been uploaded to Vimeo." } }
|
51
|
+
NotifyFrontService.progress('notify_method_on_upload', data)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Eivid::Concerns::MainApp::Owner
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
included do
|
4
|
+
|
5
|
+
has_one :video_owner, class_name: 'Eivid::Owner', foreign_key: 'external_id'
|
6
|
+
has_many :videos, class_name: 'Eivid::Video', source: :video_owner, foreign_key: 'owner_id'
|
7
|
+
|
8
|
+
include Eivid::Concerns::MainApp::VideoConcerns
|
9
|
+
|
10
|
+
after_create :create_eivid_owner
|
11
|
+
|
12
|
+
def create_eivid_owner
|
13
|
+
Eivid::Owner.create(external_id: self.id)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Eivid::Concerns::MainApp::User
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
included do
|
4
|
+
|
5
|
+
has_many :videos, class_name: 'Eivid::Video'
|
6
|
+
has_many :video_resources, class_name: 'Eivid::VideoResource', through: :videos
|
7
|
+
|
8
|
+
include Eivid::Concerns::MainApp::VideoConcerns
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Eivid::Concerns::MainApp::VideoConcerns
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
included do
|
4
|
+
|
5
|
+
scope :has_video, -> { joins(:videos).distinct }
|
6
|
+
scope :has_not_video, -> { joins(:videos).where('eivid_videos.id' => nil) }
|
7
|
+
|
8
|
+
scope :has_pending_video, -> { joins(:videos).where('eivid_videos.uploaded' => false) }
|
9
|
+
scope :has_not_pending_video, -> { where(id: [ids - has_pending_video.ids]) }
|
10
|
+
|
11
|
+
# caution: due to the has_many with .distinct, the condition only has to be satisfied once
|
12
|
+
scope :has_processed_video, -> { joins(:videos).where('eivid_videos.uploaded': true).distinct }
|
13
|
+
scope :has_unprocessed_video, -> { joins(:videos).where('eivid_videos.uploaded' => false).distinct }
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Eivid::Concerns::MainApp::VideoResource
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
included do
|
4
|
+
|
5
|
+
has_many :video_resources, class_name: 'Eivid::VideoResource', as: :resource
|
6
|
+
has_many :videos, class_name: 'Eivid::Video', through: :video_resources, source: :video
|
7
|
+
|
8
|
+
include Eivid::Concerns::MainApp::VideoConcerns
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Eivid::Concerns::RequestService::DeleteVideo
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def destroy(video:)
|
5
|
+
@video = video
|
6
|
+
delete_from_vimeo
|
7
|
+
video.destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def delete_from_vimeo
|
13
|
+
return unless @video.uploaded
|
14
|
+
HTTParty.delete "#{Eivid::RequestService::PLAIN_VIDEO_URL}/#{@video.vimeo_id}", headers: default_headers
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Eivid::Concerns::RequestService::GetVideo
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def get_video(vimeo_id)
|
5
|
+
response = HTTParty.get "#{Eivid::RequestService::PLAIN_VIDEO_URL}/#{vimeo_id}", headers: default_headers
|
6
|
+
JSON.parse(response.body).deep_symbolize_keys
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Eivid::Concerns::RequestService::ManageFolder
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def get_all_folders
|
5
|
+
HTTParty.get Eivid::RequestService::FOLDER_URL, headers: default_headers
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_folder(namespace:, id:)
|
9
|
+
body = { "name" => "#{Rails.env}_#{namespace}_#{id}" }
|
10
|
+
|
11
|
+
@response = HTTParty.post Eivid::RequestService::FOLDER_URL, body: body.to_json, headers: default_headers
|
12
|
+
get_folder_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_video_to_folder(video_record:)
|
16
|
+
video_id = video_record.vimeo_id
|
17
|
+
folder_id = video_record.owner.folder_id
|
18
|
+
endpoint = Eivid::RequestService::ADD_TO_FOLDER_URL.call(folder_id, video_id)
|
19
|
+
|
20
|
+
HTTParty.put endpoint, headers: default_headers
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def get_folder_id
|
26
|
+
@response["uri"].split('/').last
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Eivid::Concerns::RequestService::PollStatus
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def get_status(vimeo_id:)
|
5
|
+
response = HTTParty.get "#{Eivid::RequestService::BASE_URL}/videos/#{vimeo_id}?fields=uri,status", headers: default_headers
|
6
|
+
JSON.parse(response)
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Eivid::Concerns::RequestService::TestingMethods
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def delete_all_videos
|
5
|
+
puts "are you sure you want to delete ALL videos from Vimeo? Confirm by typing: 'yup brah'"
|
6
|
+
confirmation = gets
|
7
|
+
return "operation cancelled" unless confirmation.chomp == "yup brah"
|
8
|
+
delete_all_videos_from_vimeo
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def get_all_video_ids
|
14
|
+
get_all_pages.map { |page| get_single_page_video_ids(page) }.flatten
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete_all_videos_from_vimeo
|
18
|
+
get_all_video_ids.map { |id| delete_single_video_from_vimeo(id) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_single_video_from_vimeo(vimeo_id)
|
22
|
+
HTTParty.delete "#{Eivid::RequestService::PLAIN_VIDEO_URL}/#{vimeo_id}", headers: default_headers
|
23
|
+
puts "deleted #{vimeo_id}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_single_page_video_ids(url)
|
27
|
+
response = HTTParty.get url, headers: default_headers
|
28
|
+
JSON.parse(response)["data"].map { |record| record["link"].split('/').last }
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_all_pages
|
32
|
+
response = HTTParty.get Eivid::RequestService::VIDEOS_URL, headers: default_headers
|
33
|
+
page_count = (JSON.parse(response)["total"] / 25.to_f).ceil
|
34
|
+
|
35
|
+
(1..page_count).map { |cnt| "#{Eivid::RequestService::VIDEOS_URL}?page=#{cnt}" }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Eivid::Concerns::RequestService::UploadVideo
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def upload_video(video_path:)
|
5
|
+
@file = File.open(video_path)
|
6
|
+
@request = HTTParty.post Eivid::RequestService::VIDEOS_URL, **upload_params
|
7
|
+
@response = JSON.parse(@request).deep_symbolize_keys
|
8
|
+
@upload_link = @response.dig(:upload, :upload_link)
|
9
|
+
|
10
|
+
tus_upload_to_vimeo
|
11
|
+
@response
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def tus_upload_to_vimeo
|
17
|
+
HTTParty.patch @upload_link, body: @file.read, headers: tus_headers
|
18
|
+
end
|
19
|
+
|
20
|
+
def upload_params
|
21
|
+
body = {
|
22
|
+
"upload" => {
|
23
|
+
"approach" => "tus",
|
24
|
+
"size" => @file.size.to_s,
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
{ body: body.to_json, headers: default_headers }
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Eivid
|
2
|
+
class Owner < ApplicationRecord
|
3
|
+
|
4
|
+
has_many :videos
|
5
|
+
|
6
|
+
validates :external_id, presence: true #, uniqueness: true # comment out for dev only
|
7
|
+
|
8
|
+
before_create :get_folder_id
|
9
|
+
|
10
|
+
belongs_to :"#{Eivid.owner_model}", foreign_key: "external_id"
|
11
|
+
|
12
|
+
def external_owner
|
13
|
+
send Eivid.owner_model
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_folder_id
|
17
|
+
self.folder_id = RequestService.create_folder(namespace: Eivid.owner_model, id: self.external_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Eivid
|
2
|
+
class Video < ApplicationRecord
|
3
|
+
|
4
|
+
belongs_to :owner
|
5
|
+
belongs_to :user, optional: true
|
6
|
+
has_many :video_resources
|
7
|
+
|
8
|
+
def owner_id # required for aliassing :owner_id
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def external_owner
|
13
|
+
owner.send Eivid.owner_model
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :"#{Eivid.owner_model}_id", :owner_id
|
17
|
+
alias_method :"#{Eivid.owner_model}", :external_owner
|
18
|
+
scope :"of_#{Eivid.owner_model}", -> (owner_id) { where(owner_id: owner_id) }
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Eivid::NotifyFrontService
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def progress(progress_method, progress_hash)
|
5
|
+
return unless Eivid.notify_front_enabled
|
6
|
+
|
7
|
+
main_app_proc = Eivid.send progress_method
|
8
|
+
main_app_meth = main_app_proc.call progress_hash.deep_symbolize_keys
|
9
|
+
|
10
|
+
eval main_app_meth
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# vimeo docs: https://developer.vimeo.com/api/guides/start
|
2
|
+
# wrapper docs: https://github.com/bo-oz/vimeo_me2
|
3
|
+
|
4
|
+
module Eivid::RequestService
|
5
|
+
|
6
|
+
VIMEO_ID = "136419353"
|
7
|
+
BASE_URL = "https://api.vimeo.com"
|
8
|
+
VIDEOS_URL = "#{BASE_URL}/me/videos"
|
9
|
+
FOLDER_URL = "#{BASE_URL}/me/projects"
|
10
|
+
PLAIN_VIDEO_URL = "#{BASE_URL}/videos"
|
11
|
+
ADD_TO_FOLDER_URL = -> (folder_id, video_id) { "#{FOLDER_URL}/#{folder_id}/videos/#{video_id}" }
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
include Eivid::Concerns::RequestService::ManageFolder
|
16
|
+
include Eivid::Concerns::RequestService::PollStatus
|
17
|
+
include Eivid::Concerns::RequestService::GetVideo
|
18
|
+
include Eivid::Concerns::RequestService::UploadVideo
|
19
|
+
include Eivid::Concerns::RequestService::DeleteVideo
|
20
|
+
include Eivid::Concerns::RequestService::TestingMethods
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default_headers
|
25
|
+
{
|
26
|
+
"Authorization" => "bearer #{Figaro.env.VIMEO_ACCESS_TOKEN}",
|
27
|
+
"Content-Type" => "application/json",
|
28
|
+
"Accept" => "application/vnd.vimeo.*+json;version=3.4"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def tus_headers
|
33
|
+
{
|
34
|
+
"Tus-Resumable" => "1.0.0",
|
35
|
+
"Upload-Offset" => "0",
|
36
|
+
"Content-Type" => "application/offset+octet-stream",
|
37
|
+
"Accept" => "application/vnd.vimeo.*+json;version=3.4"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|