wco_models 3.1.0.313 → 3.1.0.314
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 +4 -4
- data/README.txt +19 -4
- data/app/assets/stylesheets/wco/main.scss +4 -0
- data/app/assets/stylesheets/wco/newsvideos.scss +10 -2
- data/app/controllers/wco/api/leads_controller.rb +5 -2
- data/app/controllers/wco/api/newspartials_controller.rb +15 -4
- data/app/controllers/wco/api/newsproducer_controller.rb +26 -0
- data/app/controllers/wco/api/newsvideos_controller.rb +17 -3
- data/app/controllers/wco/api/tags_controller.rb +2 -0
- data/app/controllers/wco/api/videos_controller.rb +3 -3
- data/app/controllers/wco/api_controller.rb +9 -9
- data/app/controllers/wco/newsvideos_controller.rb +102 -1
- data/app/models/wco/newspartial.rb +7 -3
- data/app/models/wco/newsvideo.rb +93 -78
- data/app/models/wco/tag.rb +4 -0
- data/app/models/wco_email/context.rb +11 -0
- data/app/models/wco_email/email_filter_condition.rb +17 -1
- data/app/views/wco/api/newsproducer/README.txt +2 -0
- data/app/views/wco/{newsproducer → api/newsproducer}/_studio_1.js +111 -104
- data/app/views/wco/{newsproducer → api/newsproducer}/studio_1.html.erb +4 -3
- data/app/views/wco/api/newsvideos/show.jbuilder +13 -0
- data/app/views/wco/newspartials/_form.haml +4 -2
- data/app/views/wco/newspartials/_show_in_newsvideo.haml +3 -3
- data/app/views/wco/newsvideos/_form.haml +19 -6
- data/app/views/wco/newsvideos/edit.haml +2 -1
- data/app/views/wco/newsvideos/show.haml +9 -2
- data/config/initializers/08_integrations.rb +2 -0
- data/config/initializers/08_integrations.rb-example +2 -0
- data/config/routes.rb +12 -4
- metadata +9 -8
- data/app/controllers/wco/api/obfuscated_redirects_controller.rb +0 -26
- data/app/controllers/wco/newsproducer_controller.rb +0 -22
- /data/app/views/wco/{newsproducer → api/newsproducer}/canvas.html.erb +0 -0
- /data/app/views/wco/{newsproducer → api/newsproducer}/canvas_minimal.html.erb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 702f87e01cf8d48d84cb1c0cc9d6c084f86663f4e685024229d862382f3ea744
|
|
4
|
+
data.tar.gz: daf2ffd2b25049f26b84501687413cd307d79b3cee5399bf3115f047ee6bbed9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8049284e53db198d9e4df32bf270b48b2441eeafce4d1a6461f9008f424cd15af375869dbd89ef54240847778359a7793ae78b0afe8aaf71cf05674dbb7f603a
|
|
7
|
+
data.tar.gz: 077aa431a75eb8c8231c6ebc4278922271c9b1ad6ee740d4999e32ab7e2c23cba61fdae3bd72afe5b2d2f7413ba71bfaffba5d0056bdb1f28ac043e07136242b
|
data/README.txt
CHANGED
|
@@ -18,15 +18,30 @@ Some infrastructure is driven by ansible - therefore, local python3 and ansible
|
|
|
18
18
|
|
|
19
19
|
= Develop / Use =
|
|
20
20
|
|
|
21
|
+
|
|
22
|
+
cmd = "#{cmd} [ -f newspartial_#{idx}.mp4 ] || ffmpeg -y -i newspartial_#{idx}.webm -r 24 -pix_fmt yuv420p newspartial_#{idx}.mp4 ; "
|
|
23
|
+
|
|
24
|
+
|
|
21
25
|
== Image to video ==
|
|
22
26
|
|
|
23
27
|
ffmpeg -loop 1 -i $inn -c:v libx264 -t 3 -pix_fmt yuv420p output.mp4
|
|
24
28
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
## illustrative image to video, sized
|
|
30
|
+
|
|
31
|
+
export width=848
|
|
32
|
+
export height=480
|
|
33
|
+
export duration_sec=3
|
|
34
|
+
magick "$inn" -resize "${width}x${height}^" -gravity center -extent "${width}x${height}" "$inn"
|
|
35
|
+
ffmpeg -loop 1 -i $inn -c:v libx264 -t $duration_sec -pix_fmt yuv420p $inn-out.mp4
|
|
36
|
+
|
|
37
|
+
## image in a news studio
|
|
29
38
|
|
|
39
|
+
export image=image.jpg ; \
|
|
40
|
+
export video_base=base_4_5sec.mp4 ; \
|
|
41
|
+
rm output.mp4 ; \
|
|
42
|
+
ffmpeg -i $video_base -i $image \
|
|
43
|
+
-filter_complex "[1:v]scale='min(iw,420)':-1,pad=iw+20:ih+20:10:10:black[img];[0:v][img]overlay=(W-w)/2:(H-h)/2-50" \
|
|
44
|
+
-c:v libx264 -pix_fmt yuv420p -c:a copy output.mp4
|
|
30
45
|
|
|
31
46
|
= Test =
|
|
32
47
|
|
|
@@ -30,8 +30,13 @@
|
|
|
30
30
|
|
|
31
31
|
.NewsoverlayTimeline {
|
|
32
32
|
border: 5px solid green;
|
|
33
|
+
background: white;
|
|
33
34
|
position: absolute;
|
|
34
35
|
left: 230px;
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
z-index: 5;
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
.NewspartialTimelineW {
|
|
@@ -47,8 +52,11 @@
|
|
|
47
52
|
width: 400px;
|
|
48
53
|
|
|
49
54
|
.full-text {
|
|
50
|
-
position: absolute;
|
|
51
|
-
left: 300px;
|
|
55
|
+
// position: absolute;
|
|
56
|
+
// left: 300px;
|
|
57
|
+
|
|
58
|
+
float: left;
|
|
59
|
+
margin-left: 300px;
|
|
52
60
|
|
|
53
61
|
.header {
|
|
54
62
|
background: #efefef;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
class Wco::Api::LeadsController < Wco::ApiController
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
before_action :check_credentials, only: [ :by_email ]
|
|
4
|
+
before_action :decode_secret, only: [ :by_email ]
|
|
6
5
|
|
|
7
6
|
def by_email
|
|
8
7
|
@lead = Wco::Lead.find_or_create_by_email( params[:email] )
|
|
@@ -10,6 +9,7 @@ class Wco::Api::LeadsController < Wco::ApiController
|
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
## select2-leads-ajax
|
|
12
|
+
## session is present
|
|
13
13
|
def index
|
|
14
14
|
authorize! :index, Wco::Lead
|
|
15
15
|
@leads = Wco::Lead.all
|
|
@@ -21,8 +21,11 @@ class Wco::Api::LeadsController < Wco::ApiController
|
|
|
21
21
|
{ name: /#{q}/i },
|
|
22
22
|
);
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
@leads = @leads.limit(100)
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
## session is present
|
|
26
29
|
def index_hash
|
|
27
30
|
authorize! :index, Wco::Lead
|
|
28
31
|
@leads = Wco::Lead.find( params[:lead_ids].split(',') )
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
class Wco::Api::NewspartialsController < Wco::ApiController
|
|
3
3
|
|
|
4
|
-
skip_before_action :decode_jwt
|
|
5
|
-
skip_before_action :verify_authenticity_token
|
|
6
4
|
before_action :decode_simple_api_key
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
##
|
|
7
|
+
## PUT config, actually
|
|
8
|
+
##
|
|
9
9
|
def add_config
|
|
10
10
|
puts! params, 'api newspartials#add_config params'
|
|
11
11
|
|
|
@@ -44,9 +44,20 @@ class Wco::Api::NewspartialsController < Wco::ApiController
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
## syncronous, everyone waits.
|
|
48
|
+
def generate_speech
|
|
49
|
+
@newspartial = Wco::Newspartial.unscoped.find params[:id]
|
|
50
|
+
@newspartial.generate_speech
|
|
51
|
+
render json: { status: :ok }
|
|
52
|
+
end
|
|
53
|
+
|
|
47
54
|
def show_config
|
|
48
55
|
@newspartial = Wco::Newspartial.find params[:id]
|
|
49
|
-
|
|
56
|
+
config = JSON.parse @newspartial.speech_json
|
|
57
|
+
config[:w_px] = @newspartial.newsvideo.w_px
|
|
58
|
+
config[:h_px] = @newspartial.newsvideo.h_px
|
|
59
|
+
config[:slug] = "#{@newspartial.newsvideo.slug}::#{@newspartial.slug}"
|
|
60
|
+
render json: config
|
|
50
61
|
end
|
|
51
62
|
|
|
52
63
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
class Wco::Api::NewsproducerController < Wco::ApiController
|
|
3
|
+
|
|
4
|
+
before_action :decode_simple_api_key
|
|
5
|
+
|
|
6
|
+
## trash
|
|
7
|
+
def canvas
|
|
8
|
+
authorize! :home, Wco
|
|
9
|
+
# render 'canvas_minimal'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
## trash
|
|
13
|
+
def canvas_minimal
|
|
14
|
+
authorize! :home, Wco
|
|
15
|
+
render 'canvas_minimal'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
## current
|
|
20
|
+
##
|
|
21
|
+
def studio_1
|
|
22
|
+
authorize! :home, Wco
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
2
|
class Wco::Api::NewsvideosController < Wco::ApiController
|
|
4
3
|
|
|
5
|
-
skip_before_action :decode_jwt
|
|
6
|
-
skip_before_action :verify_authenticity_token
|
|
7
4
|
before_action :decode_simple_api_key
|
|
8
5
|
|
|
6
|
+
##
|
|
7
|
+
## 2026-08-12 _vp_ continue
|
|
8
|
+
##
|
|
9
|
+
def generate
|
|
10
|
+
@newsvideo = Wco::Newsvideo.unscoped.find params[:id]
|
|
11
|
+
|
|
12
|
+
Rails.env.production? ?
|
|
13
|
+
Wco::NewsvideoGenerateJob.perform_async(@newsvideo.id.to_s) :
|
|
14
|
+
Wco::NewsvideoGenerateJob.perform_sync( @newsvideo.id.to_s)
|
|
15
|
+
|
|
16
|
+
render json: { status: :ok, message: 'Scheduled the generation' }
|
|
17
|
+
end
|
|
18
|
+
|
|
9
19
|
##
|
|
10
20
|
## the other one is used, not this one. Because I don't want to deal with api keys.
|
|
11
21
|
##
|
|
@@ -20,4 +30,8 @@ class Wco::Api::NewsvideosController < Wco::ApiController
|
|
|
20
30
|
# redirect_to action: 'show', newsvideo_id: params[:id]
|
|
21
31
|
end
|
|
22
32
|
|
|
33
|
+
def show
|
|
34
|
+
@newsvideo = Wco::Newsvideo.unscoped.find params[:id]
|
|
35
|
+
end
|
|
36
|
+
|
|
23
37
|
end
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
|
|
2
2
|
class Wco::Api::VideosController < Wco::ApiController
|
|
3
3
|
|
|
4
|
-
skip_before_action :decode_jwt
|
|
5
|
-
skip_before_action :verify_authenticity_token, only: [:create]
|
|
6
4
|
before_action :decode_simple_api_key
|
|
7
5
|
|
|
8
6
|
def create
|
|
9
7
|
# puts! params, 'api videos#create params'
|
|
8
|
+
authorize! :create, Wco::Video
|
|
9
|
+
old_videos = Wco::Video.where({ newspartial_id: params[:newspartial_id] }).map { |v| v.delete }
|
|
10
|
+
puts! old_videos, 'deleted old videos'
|
|
10
11
|
|
|
11
12
|
@video = Wco::Video.new({ name: params[:name],
|
|
12
13
|
thumb: params[:thumb],
|
|
13
14
|
video: params[:video],
|
|
14
15
|
newspartial_id: params[:newspartial_id],
|
|
15
16
|
})
|
|
16
|
-
authorize! :create, @video
|
|
17
17
|
|
|
18
18
|
if @video.save
|
|
19
19
|
puts! @video, 'Created @video.'
|
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
class Wco::ApiController < ActionController::Base
|
|
3
3
|
layout false
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
skip_before_action :verify_authenticity_token
|
|
6
6
|
|
|
7
7
|
##
|
|
8
8
|
## private
|
|
9
9
|
##
|
|
10
10
|
private
|
|
11
11
|
|
|
12
|
-
def
|
|
12
|
+
def decode_jwt
|
|
13
|
+
out = JWT.decode params[:jwt_token], nil, false
|
|
14
|
+
email = out[0]['email']
|
|
15
|
+
user = User.find_by({ email: email })
|
|
16
|
+
sign_in user
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def decode_secret
|
|
13
20
|
if params[:secret].blank? ||
|
|
14
21
|
params[:secret] != AWS_SES_LAMBDA_SECRET
|
|
15
22
|
|
|
@@ -18,13 +25,6 @@ class Wco::ApiController < ActionController::Base
|
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
27
|
|
|
21
|
-
def decode_jwt
|
|
22
|
-
out = JWT.decode params[:jwt_token], nil, false
|
|
23
|
-
email = out[0]['email']
|
|
24
|
-
user = User.find_by({ email: email })
|
|
25
|
-
sign_in user
|
|
26
|
-
end
|
|
27
|
-
|
|
28
28
|
def decode_simple_api_key
|
|
29
29
|
if params[:api_key].present? &&
|
|
30
30
|
params[:api_secret].present? &&
|
|
@@ -4,7 +4,82 @@ require 'pragmatic_segmenter'
|
|
|
4
4
|
class Wco::NewsvideosController < Wco::ApplicationController
|
|
5
5
|
|
|
6
6
|
before_action :set_lists
|
|
7
|
-
skip_before_action :verify_authenticity_token, only: [ :generate_illustration ]
|
|
7
|
+
skip_before_action :verify_authenticity_token, only: [ :add_image_as_overlay, :generate_illustration ]
|
|
8
|
+
|
|
9
|
+
def add_image_as_overlay
|
|
10
|
+
duration_sec = 3
|
|
11
|
+
|
|
12
|
+
@newsvideo = Wco::Newsvideo.unscoped.find params[:id]
|
|
13
|
+
authorize! :edit, @newsvideo
|
|
14
|
+
|
|
15
|
+
workdir = Rails.root.join('tmp', @newsvideo.id.to_s)
|
|
16
|
+
FileUtils.mkdir_p(workdir)
|
|
17
|
+
|
|
18
|
+
w_px = @newsvideo.w_px
|
|
19
|
+
h_px = @newsvideo.h_px
|
|
20
|
+
fps = @newsvideo.fps
|
|
21
|
+
|
|
22
|
+
input = workdir.join("overlay_#{@newsvideo.next_overlay_ms}.jpg")
|
|
23
|
+
URI.open(params[:image_url]) do |src|
|
|
24
|
+
File.binwrite(input, src.read)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
image_path = workdir.join("overlay_#{@newsvideo.next_overlay_ms}_#{w_px}x#{h_px}.jpg")
|
|
28
|
+
cmd = <<~CMD
|
|
29
|
+
magick #{input} \
|
|
30
|
+
-resize "#{w_px}x#{h_px}^" \
|
|
31
|
+
-gravity center \
|
|
32
|
+
-crop "#{w_px}x#{h_px}+0+0" \
|
|
33
|
+
+repage \
|
|
34
|
+
-quality 95 \
|
|
35
|
+
#{image_path}
|
|
36
|
+
CMD
|
|
37
|
+
puts "+++ magick cmd:"
|
|
38
|
+
puts cmd
|
|
39
|
+
out = `#{cmd}`
|
|
40
|
+
puts! out, 'out'
|
|
41
|
+
|
|
42
|
+
video_path = workdir.join("overlay_#{@newsvideo.next_overlay_ms}.mp4")
|
|
43
|
+
cmd = <<~CMD
|
|
44
|
+
ffmpeg -y \
|
|
45
|
+
-loop 1 \
|
|
46
|
+
-i #{image_path} \
|
|
47
|
+
-t #{duration_sec} \
|
|
48
|
+
-r #{fps} \
|
|
49
|
+
-c:v libx264 \
|
|
50
|
+
-pix_fmt yuv420p \
|
|
51
|
+
-vf "scale=#{w_px}:#{h_px}" \
|
|
52
|
+
#{video_path}
|
|
53
|
+
CMD
|
|
54
|
+
puts "+++ ffmpeg cmd:"
|
|
55
|
+
puts cmd
|
|
56
|
+
out = `#{cmd}`
|
|
57
|
+
puts! out, 'out'
|
|
58
|
+
|
|
59
|
+
@video = Wco::Video.new name: "#{@newsvideo.title} overlay #{@newsvideo.next_overlay_ms}"
|
|
60
|
+
@video.video = File.open(video_path)
|
|
61
|
+
@video.thumb = File.open(image_path)
|
|
62
|
+
@video.tags = [ Wco::Tag.illustration, Wco::Tag.find_or_create_by({ slug: @newsvideo.slug }) ]
|
|
63
|
+
flag = @video.save
|
|
64
|
+
if !flag
|
|
65
|
+
puts "Could not create video:"
|
|
66
|
+
puts @video.errors.full_messages.join(", ")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@overlay = Wco::Newsoverlay.new({ duration_ms: duration_sec * 1000,
|
|
70
|
+
newsvideo: @newsvideo,
|
|
71
|
+
start_at_ms: @newsvideo.next_overlay_ms,
|
|
72
|
+
video: @video,
|
|
73
|
+
});
|
|
74
|
+
if @overlay.save
|
|
75
|
+
@newsvideo.update( next_overlay_ms: @newsvideo.next_overlay_ms + @overlay.duration_ms )
|
|
76
|
+
else
|
|
77
|
+
puts "Could not create overlay:"
|
|
78
|
+
puts @overlay.errors.full_messages.join(", ")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
render json: { status: :ok }
|
|
82
|
+
end
|
|
8
83
|
|
|
9
84
|
def create
|
|
10
85
|
params[:newsvideo][:tag_ids]&.delete ''
|
|
@@ -39,6 +114,32 @@ class Wco::NewsvideosController < Wco::ApplicationController
|
|
|
39
114
|
render json: { status: :ok, message: 'Scheduled the generation' }
|
|
40
115
|
end
|
|
41
116
|
|
|
117
|
+
def generate_all_audio
|
|
118
|
+
@newsvideo = Wco::Newsvideo.unscoped.find params[:id]
|
|
119
|
+
authorize! :edit, @newsvideo
|
|
120
|
+
|
|
121
|
+
cmd = "cd #{PUPPETEER_ROOT} ; yarn run newsvideo_generate_all_audio #{@newsvideo.id}"
|
|
122
|
+
puts "+++ puppeteer cmd:"
|
|
123
|
+
puts cmd
|
|
124
|
+
out = `#{cmd}`
|
|
125
|
+
puts! out, 'out'
|
|
126
|
+
|
|
127
|
+
render json: { status: :ok }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def generate_all_video
|
|
131
|
+
@newsvideo = Wco::Newsvideo.unscoped.find params[:id]
|
|
132
|
+
authorize! :edit, @newsvideo
|
|
133
|
+
|
|
134
|
+
cmd = "cd #{PUPPETEER_ROOT} ; yarn run newsvideo_generate_all_video #{@newsvideo.id}"
|
|
135
|
+
puts "+++ puppeteer cmd:"
|
|
136
|
+
puts cmd
|
|
137
|
+
out = `#{cmd}`
|
|
138
|
+
puts! out, 'out'
|
|
139
|
+
|
|
140
|
+
render json: { status: :ok }
|
|
141
|
+
end
|
|
142
|
+
|
|
42
143
|
def generate_illustration
|
|
43
144
|
@newsvideo = Wco::Newsvideo.unscoped.find params[:id]
|
|
44
145
|
authorize! :edit, @newsvideo
|
|
@@ -10,7 +10,7 @@ class Wco::Newspartial
|
|
|
10
10
|
|
|
11
11
|
PAGE_PARAM_NAME = 'newspartials_page'
|
|
12
12
|
|
|
13
|
-
MAX_WORDS =
|
|
13
|
+
MAX_WORDS = 30
|
|
14
14
|
|
|
15
15
|
field :title
|
|
16
16
|
# validates :title, presence: true, uniqueness: true
|
|
@@ -23,6 +23,8 @@ class Wco::Newspartial
|
|
|
23
23
|
belongs_to :newsvideo
|
|
24
24
|
has_one :video
|
|
25
25
|
|
|
26
|
+
field :slug, type: :string
|
|
27
|
+
|
|
26
28
|
has_mongoid_attached_file :audio,
|
|
27
29
|
:storage => :s3,
|
|
28
30
|
:s3_credentials => ::S3_CREDENTIALS,
|
|
@@ -66,13 +68,15 @@ class Wco::Newspartial
|
|
|
66
68
|
'Accept' => 'application/json',
|
|
67
69
|
}, body: {
|
|
68
70
|
input: body,
|
|
69
|
-
voice: "
|
|
71
|
+
# voice: "am_fenrir", # good?
|
|
72
|
+
# "af_bella" # bad
|
|
73
|
+
voice: "af_jessica", # good
|
|
70
74
|
language: "en-us",
|
|
71
75
|
audioEncoding: "wav",
|
|
72
76
|
}.to_json
|
|
73
77
|
);
|
|
74
78
|
out = out.body
|
|
75
|
-
|
|
79
|
+
puts! out, 'out'
|
|
76
80
|
|
|
77
81
|
self[:config_json] = out
|
|
78
82
|
tmp = JSON.parse( out )
|
data/app/models/wco/newsvideo.rb
CHANGED
|
@@ -31,8 +31,10 @@ class Wco::Newsvideo
|
|
|
31
31
|
field :y, :type => Float
|
|
32
32
|
field :z, :type => Float
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
field :next_overlay_ms, type: :integer, default: 1000
|
|
35
|
+
field :fps, type: :integer, default: 30
|
|
36
|
+
|
|
37
|
+
belongs_to :generated_video, class_name: 'Wco::Video', optional: true
|
|
36
38
|
|
|
37
39
|
belongs_to :author, class_name: 'Wco::Profile'
|
|
38
40
|
|
|
@@ -46,6 +48,7 @@ class Wco::Newsvideo
|
|
|
46
48
|
has_many :videos
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
|
|
49
52
|
has_many :newsoverlays
|
|
50
53
|
def newsoverlays
|
|
51
54
|
Wco::Newsoverlay.where( newsvideo_id: self.id )
|
|
@@ -57,8 +60,8 @@ class Wco::Newsvideo
|
|
|
57
60
|
sentences = PragmaticSegmenter::Segmenter.new(text: @newsvideo.body).segment
|
|
58
61
|
phrases = sentences_to_phrases(sentences)
|
|
59
62
|
|
|
60
|
-
phrases.
|
|
61
|
-
newspartial = Wco::Newspartial.new body: phrase, newsvideo: @newsvideo
|
|
63
|
+
phrases.each_with_index do |phrase, idx|
|
|
64
|
+
newspartial = Wco::Newspartial.new body: phrase, newsvideo: @newsvideo, slug: "#{sprintf("%02d", idx)}0"
|
|
62
65
|
newspartial.save!
|
|
63
66
|
end
|
|
64
67
|
end
|
|
@@ -66,12 +69,13 @@ class Wco::Newsvideo
|
|
|
66
69
|
|
|
67
70
|
def generate
|
|
68
71
|
@newsvideo = self
|
|
72
|
+
workdir = Rails.root.join('tmp', @newsvideo.id.to_s)
|
|
73
|
+
partials = @newsvideo.newspartials.order_by(slug: :asc).to_a
|
|
69
74
|
|
|
70
75
|
## put together config, first thing
|
|
71
76
|
cmd = "cd #{Rails.root.join('tmp')} ; rm -rf #{@newsvideo.id} ; mkdir -p #{@newsvideo.id} ; cd #{@newsvideo.id} ; "
|
|
72
|
-
|
|
73
|
-
cmd = "#{cmd} echo \"file '
|
|
74
|
-
cmd = "#{cmd} echo \"file 'newspartial_#{idx}.wav' \" >> audiolist.txt ; "
|
|
77
|
+
partials.each_with_index do |_part, idx|
|
|
78
|
+
cmd = "#{cmd} echo \"file 'synced_#{idx}.mp4'\" >> syncedlist.txt ; "
|
|
75
79
|
end
|
|
76
80
|
puts "+++ config cmd:"
|
|
77
81
|
puts cmd
|
|
@@ -79,10 +83,10 @@ class Wco::Newsvideo
|
|
|
79
83
|
puts! out, 'out'
|
|
80
84
|
|
|
81
85
|
## get base files locally
|
|
82
|
-
cmd = "cd #{
|
|
83
|
-
|
|
86
|
+
cmd = "cd #{workdir} ; "
|
|
87
|
+
partials.each_with_index do |part, idx|
|
|
84
88
|
cmd = "#{cmd} wget -nc -O newspartial_#{idx}.webm #{part.video.video.url} ; "
|
|
85
|
-
cmd = "#{cmd} [ -f newspartial_#{idx}.mp4 ] || ffmpeg -y -i newspartial_#{idx}.webm newspartial_#{idx}.mp4 ; "
|
|
89
|
+
cmd = "#{cmd} [ -f newspartial_#{idx}.mp4 ] || ffmpeg -y -i newspartial_#{idx}.webm -r 24 -pix_fmt yuv420p newspartial_#{idx}.mp4 ; "
|
|
86
90
|
cmd = "#{cmd} wget -nc -O newspartial_#{idx}.wav #{part.audio.url} ; "
|
|
87
91
|
end
|
|
88
92
|
puts "+++ base files cmd:"
|
|
@@ -90,85 +94,94 @@ class Wco::Newsvideo
|
|
|
90
94
|
out = `#{cmd}`
|
|
91
95
|
puts! out, 'out'
|
|
92
96
|
|
|
93
|
-
##
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
## Sync each newspartial's sound to its video, then we'll merge those clips
|
|
98
|
+
partials.each_with_index do |_part, idx|
|
|
99
|
+
cmd = <<~AOL
|
|
100
|
+
cd #{workdir} ;
|
|
101
|
+
rm -f synced_#{idx}.mp4 ;
|
|
102
|
+
ffmpeg -y -i newspartial_#{idx}.mp4 -i newspartial_#{idx}.wav \
|
|
103
|
+
-map 0:v:0 \
|
|
104
|
+
-map 1:a:0 \
|
|
105
|
+
-c:v libx264 \
|
|
106
|
+
-pix_fmt yuv420p \
|
|
107
|
+
-c:a aac \
|
|
108
|
+
-shortest \
|
|
109
|
+
synced_#{idx}.mp4 ;
|
|
110
|
+
AOL
|
|
111
|
+
puts "+++ per-partial #{idx} sync cmd:"
|
|
112
|
+
puts cmd
|
|
113
|
+
out = `#{cmd}`
|
|
114
|
+
puts! out, 'out'
|
|
97
115
|
end
|
|
98
|
-
puts "+++ overlays cmd:"
|
|
99
|
-
puts cmd
|
|
100
|
-
out = `#{cmd}`
|
|
101
|
-
puts! out, 'out'
|
|
102
116
|
|
|
103
|
-
##
|
|
117
|
+
## merge synced newspartials into combined_base
|
|
104
118
|
cmd = <<~AOL
|
|
105
|
-
cd #{
|
|
106
|
-
rm -f
|
|
107
|
-
ffmpeg -y -f concat -safe 0 -i
|
|
119
|
+
cd #{workdir} ;
|
|
120
|
+
rm -f combined_base.mp4 ;
|
|
121
|
+
ffmpeg -y -f concat -safe 0 -i syncedlist.txt -c:v libx264 -pix_fmt yuv420p \
|
|
122
|
+
-c:a aac \
|
|
123
|
+
-vsync cfr \
|
|
124
|
+
-af "aresample=async=1:first_pts=0" \
|
|
125
|
+
combined_base.mp4 ;
|
|
108
126
|
AOL
|
|
109
|
-
puts "+++
|
|
127
|
+
puts "+++ synced concat cmd:"
|
|
110
128
|
puts cmd
|
|
111
129
|
out = `#{cmd}`
|
|
112
130
|
puts! out, 'out'
|
|
113
131
|
|
|
114
|
-
## audio concat
|
|
115
|
-
audio_filenames = (0...@newsvideo.newspartials.length).map { |i| "newspartial_#{i}.wav" }.join("|")
|
|
116
|
-
cmd = <<~AOL
|
|
117
|
-
cd #{Rails.root.join('tmp', @newsvideo.id)} ;
|
|
118
|
-
rm -f audio_concat.wav ;
|
|
119
|
-
ffmpeg -y -f concat -safe 0 -i audiolist.txt -c copy audio_concat.wav ;
|
|
120
|
-
AOL
|
|
121
|
-
puts "+++ audio concat cmd:"
|
|
122
|
-
puts cmd
|
|
123
|
-
out = `#{cmd}`
|
|
124
|
-
puts! out, 'out'
|
|
125
132
|
|
|
126
|
-
## combine base
|
|
127
|
-
cmd = <<~AOL
|
|
128
|
-
cd #{Rails.root.join('tmp', @newsvideo.id)} ;
|
|
129
|
-
rm -f output.mp4 ;
|
|
130
|
-
ffmpeg -y -i video_concat.mp4 -i audio_concat.wav -c:v copy -c:a aac combined_base.mp4 ;
|
|
131
|
-
AOL
|
|
132
|
-
puts "+++ combine base cmd:"
|
|
133
|
-
puts cmd
|
|
134
|
-
out = `#{cmd}`
|
|
135
|
-
puts! out, 'out'
|
|
136
133
|
|
|
137
|
-
## combine overlays
|
|
138
|
-
nn = @newsvideo.newsoverlays.map { |ol| ol.start_at_ms }
|
|
139
|
-
puts! nn, 'nn'
|
|
140
|
-
ffmpeg_cmd = [ "ffmpeg -y -i combined_base.mp4 \\" ]
|
|
141
|
-
nn.each_with_index do |ms, idx|
|
|
142
|
-
ffmpeg_cmd.push " -i overlay_#{idx}.mp4 \\"
|
|
143
|
-
end
|
|
144
|
-
ffmpeg_cmd.push "-filter_complex \"\\"
|
|
145
|
-
#
|
|
146
|
-
nn.each_with_index do |ms, idx|
|
|
147
|
-
ffmpeg_cmd.push "[#{idx+1}:v]setpts=PTS-STARTPTS+#{ms.to_f/1000}/TB[v#{idx+1}]; \\"
|
|
148
|
-
end
|
|
149
|
-
#
|
|
150
|
-
curr_s = "0:v"
|
|
151
|
-
n = nil
|
|
152
|
-
nn.each_with_index do |ms, idx|
|
|
153
|
-
n = idx+1
|
|
154
|
-
ffmpeg_cmd.push "[#{curr_s}][v#{n}]overlay=0:0:eof_action=pass[tmp#{n}]#{idx+1<nn.length ? ';' : ''} \\"
|
|
155
|
-
curr_s = "tmp#{n}"
|
|
156
|
-
end
|
|
157
|
-
ffmpeg_cmd.push " \" -map \"[#{curr_s}]\" -map 0:a? -c:v libx264 -c:a copy combined_fin.mp4 "
|
|
158
|
-
ffmpeg_cmd = ffmpeg_cmd.join("\n")
|
|
159
|
-
puts "+++ ffmpeg_cmd:"
|
|
160
|
-
puts ffmpeg_cmd
|
|
161
134
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
135
|
+
## overlays
|
|
136
|
+
if @newsvideo.newsoverlays.length > 0
|
|
137
|
+
## get overlays
|
|
138
|
+
cmd = "cd #{Rails.root.join('tmp', @newsvideo.id)} ; "
|
|
139
|
+
@newsvideo.newsoverlays.each_with_index do |overlay, idx|
|
|
140
|
+
cmd = "#{cmd} wget -nc -O overlay_#{idx}.mp4 #{overlay.video.video.url} ; "
|
|
141
|
+
end
|
|
142
|
+
puts "+++ overlays cmd:"
|
|
143
|
+
puts cmd
|
|
144
|
+
out = `#{cmd}`
|
|
145
|
+
puts! out, 'out'
|
|
146
|
+
|
|
147
|
+
## combine overlays
|
|
148
|
+
nn = @newsvideo.newsoverlays.map { |ol| ol.start_at_ms }
|
|
149
|
+
puts! nn, 'nn'
|
|
150
|
+
ffmpeg_cmd = [ "ffmpeg -y -i combined_base.mp4 \\" ]
|
|
151
|
+
nn.each_with_index do |ms, idx|
|
|
152
|
+
ffmpeg_cmd.push " -i overlay_#{idx}.mp4 \\"
|
|
153
|
+
end
|
|
154
|
+
ffmpeg_cmd.push "-filter_complex \"\\"
|
|
155
|
+
|
|
156
|
+
nn.each_with_index do |ms, idx|
|
|
157
|
+
ffmpeg_cmd.push "[#{idx+1}:v]setpts=PTS-STARTPTS+#{ms.to_f/1000}/TB[v#{idx+1}]; \\"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
curr_s = "0:v"
|
|
161
|
+
n = nil
|
|
162
|
+
nn.each_with_index do |ms, idx|
|
|
163
|
+
n = idx+1
|
|
164
|
+
ffmpeg_cmd.push "[#{curr_s}][v#{n}]overlay=0:0:eof_action=pass[tmp#{n}]#{idx+1<nn.length ? ';' : ''} \\"
|
|
165
|
+
curr_s = "tmp#{n}"
|
|
166
|
+
end
|
|
167
|
+
ffmpeg_cmd.push " \" -map \"[#{curr_s}]\" -map 0:a? -c:v libx264 -c:a copy combined_fin.mp4 "
|
|
168
|
+
ffmpeg_cmd = ffmpeg_cmd.join("\n")
|
|
169
|
+
puts "+++ ffmpeg_cmd:"
|
|
170
|
+
puts ffmpeg_cmd
|
|
171
|
+
|
|
172
|
+
## combine overlays 2
|
|
173
|
+
cmd = <<~AOL
|
|
174
|
+
cd #{Rails.root.join('tmp', @newsvideo.id)} ;
|
|
175
|
+
rm -f combined_fin.mp4 ;
|
|
176
|
+
#{ffmpeg_cmd} ;
|
|
177
|
+
AOL
|
|
178
|
+
puts "+++ ffmpeg cmd 2:"
|
|
179
|
+
puts cmd
|
|
180
|
+
out = `#{cmd}`
|
|
181
|
+
puts! out, 'out'
|
|
182
|
+
else
|
|
183
|
+
cmd = `cd #{workdir} ; mv combined_base.mp4 combined_fin.mp4`
|
|
184
|
+
end
|
|
172
185
|
|
|
173
186
|
## upload the video.
|
|
174
187
|
@video = Wco::Video.new name: @newsvideo.title
|
|
@@ -179,6 +192,8 @@ class Wco::Newsvideo
|
|
|
179
192
|
puts "Could not create video:"
|
|
180
193
|
puts @video.errors.full_messages.join(", ")
|
|
181
194
|
end
|
|
195
|
+
|
|
196
|
+
@newsvideo.update( generated_video: @video )
|
|
182
197
|
end
|
|
183
198
|
|
|
184
199
|
def sentences_to_phrases sentences
|
data/app/models/wco/tag.rb
CHANGED