eivid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|