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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.txt +19 -4
  3. data/app/assets/stylesheets/wco/main.scss +4 -0
  4. data/app/assets/stylesheets/wco/newsvideos.scss +10 -2
  5. data/app/controllers/wco/api/leads_controller.rb +5 -2
  6. data/app/controllers/wco/api/newspartials_controller.rb +15 -4
  7. data/app/controllers/wco/api/newsproducer_controller.rb +26 -0
  8. data/app/controllers/wco/api/newsvideos_controller.rb +17 -3
  9. data/app/controllers/wco/api/tags_controller.rb +2 -0
  10. data/app/controllers/wco/api/videos_controller.rb +3 -3
  11. data/app/controllers/wco/api_controller.rb +9 -9
  12. data/app/controllers/wco/newsvideos_controller.rb +102 -1
  13. data/app/models/wco/newspartial.rb +7 -3
  14. data/app/models/wco/newsvideo.rb +93 -78
  15. data/app/models/wco/tag.rb +4 -0
  16. data/app/models/wco_email/context.rb +11 -0
  17. data/app/models/wco_email/email_filter_condition.rb +17 -1
  18. data/app/views/wco/api/newsproducer/README.txt +2 -0
  19. data/app/views/wco/{newsproducer → api/newsproducer}/_studio_1.js +111 -104
  20. data/app/views/wco/{newsproducer → api/newsproducer}/studio_1.html.erb +4 -3
  21. data/app/views/wco/api/newsvideos/show.jbuilder +13 -0
  22. data/app/views/wco/newspartials/_form.haml +4 -2
  23. data/app/views/wco/newspartials/_show_in_newsvideo.haml +3 -3
  24. data/app/views/wco/newsvideos/_form.haml +19 -6
  25. data/app/views/wco/newsvideos/edit.haml +2 -1
  26. data/app/views/wco/newsvideos/show.haml +9 -2
  27. data/config/initializers/08_integrations.rb +2 -0
  28. data/config/initializers/08_integrations.rb-example +2 -0
  29. data/config/routes.rb +12 -4
  30. metadata +9 -8
  31. data/app/controllers/wco/api/obfuscated_redirects_controller.rb +0 -26
  32. data/app/controllers/wco/newsproducer_controller.rb +0 -22
  33. /data/app/views/wco/{newsproducer → api/newsproducer}/canvas.html.erb +0 -0
  34. /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: c622a73c401d1bb78a1ce1ffe72ae4923d2fa3d452d6011e5ad5968345e66300
4
- data.tar.gz: f09b8bf4a8ccc5974979e9c399108b83b6d2f69ca9a8214cb9b026cb10b373b2
3
+ metadata.gz: 702f87e01cf8d48d84cb1c0cc9d6c084f86663f4e685024229d862382f3ea744
4
+ data.tar.gz: daf2ffd2b25049f26b84501687413cd307d79b3cee5399bf3115f047ee6bbed9
5
5
  SHA512:
6
- metadata.gz: 213f70b468caee15cd916d996778954a8f8471a6c4feb0854fdc8d7d2d79d931629573d6c4b38b057309065fa769c38764240097fbda173d5b514917d56da037
7
- data.tar.gz: b416d7abe72e3dc78e429cd570a0d5a358942879680f5d6094c9d2c26651a52dff3d9755df8d9b009e6c2b38b2aae7d952bbaaa765d7fbc203c05871a9535ceb
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
- ## unnecessary:
26
- ffmpeg -loop 1 -i "$inn" -vf "scale=iw:ih" -c:v libx264 -t 3 -pix_fmt yuv420p output.mp4
27
- ## odd pixel:
28
- ffmpeg -loop 1 -i "$inn" -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -c:v libx264 -t 3 -pix_fmt yuv420p output.mp4
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
 
@@ -28,6 +28,10 @@ textarea {
28
28
  background: pink;
29
29
  }
30
30
 
31
+ .align-right {
32
+ text-align: right;
33
+ }
34
+
31
35
  /* B */
32
36
 
33
37
  .blue {
@@ -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
- skip_before_action :decode_jwt
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
- render json: @newspartial.speech_json
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,6 +1,8 @@
1
1
 
2
2
  class Wco::Api::TagsController < Wco::ApiController
3
3
 
4
+ before_action :decode_jwt
5
+
4
6
  def index
5
7
  authorize! :index, Wco::Tag
6
8
  @tags = Wco::Tag.all()
@@ -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
- before_action :decode_jwt
5
+ skip_before_action :verify_authenticity_token
6
6
 
7
7
  ##
8
8
  ## private
9
9
  ##
10
10
  private
11
11
 
12
- def check_credentials
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 = 20
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: "af_bella",
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
- # puts! out, 'out'
79
+ puts! out, 'out'
76
80
 
77
81
  self[:config_json] = out
78
82
  tmp = JSON.parse( out )
@@ -31,8 +31,10 @@ class Wco::Newsvideo
31
31
  field :y, :type => Float
32
32
  field :z, :type => Float
33
33
 
34
- # has_one :image_thumb
35
- # has_one :image_hero
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.each do |phrase|
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
- @newsvideo.newspartials.each_with_index do |part, idx|
73
- cmd = "#{cmd} echo \"file 'newspartial_#{idx}.mp4' \" >> videolist.txt ; "
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 #{Rails.root.join('tmp', @newsvideo.id)} ; "
83
- @newsvideo.newspartials.each_with_index do |part, idx|
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
- ## get overlays
94
- cmd = "cd #{Rails.root.join('tmp', @newsvideo.id)} ; "
95
- @newsvideo.newsoverlays.each_with_index do |overlay, idx|
96
- cmd = "#{cmd} wget -nc -O overlay_#{idx}.mp4 #{overlay.video.video.url} ; "
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
- ## video concat
117
+ ## merge synced newspartials into combined_base
104
118
  cmd = <<~AOL
105
- cd #{Rails.root.join('tmp', @newsvideo.id)} ;
106
- rm -f video_concat.mp4 ;
107
- ffmpeg -y -f concat -safe 0 -i videolist.txt -c copy video_concat.mp4 ;
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 "+++ video concat cmd:"
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
- # combine overlays 2
163
- cmd = <<~AOL
164
- cd #{Rails.root.join('tmp', @newsvideo.id)} ;
165
- rm -f combined_fin.mp4 ;
166
- #{ffmpeg_cmd} ;
167
- AOL
168
- puts "+++ ffmpeg cmd 2:"
169
- puts cmd
170
- out = `#{cmd}`
171
- puts! out, 'out'
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
@@ -45,6 +45,10 @@ class Wco::Tag
45
45
  find_or_create_by({ slug: DONOTSEND })
46
46
  end
47
47
 
48
+ def self.illustration
49
+ find_or_create_by({ slug: 'Illustration.' })
50
+ end
51
+
48
52
  INBOX = 'inbox'
49
53
  def self.inbox
50
54
  find_or_create_by({ slug: INBOX })