Remove harsh video restriction and add video duration (#1811)

* Remove harsh video restriction and add video duration

* Fix video meta data and tests

* Modify video tests

* Rescue video duration fetch error

* Change log to puts in test
This commit is contained in:
Ben Halpern 2019-02-16 17:42:44 -04:00 committed by GitHub
parent 28bac35db2
commit bce0b4fff6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 181 additions and 43 deletions

View file

@ -58,6 +58,7 @@ gem "jquery-rails", "~> 4.3"
gem "kaminari", "~> 1.1"
gem "libhoney", "~> 1.11"
gem "liquid", "~> 4.0"
gem "ffprober", "~> 0.5"
gem "nokogiri", "~> 1.10"
gem "octokit", "~> 4.13"
gem "omniauth", "~> 1.9"

View file

@ -311,6 +311,7 @@ GEM
loofah (>= 2.0)
sax-machine (>= 1.0)
ffi (1.9.25)
ffprober (0.5.3)
figaro (1.1.1)
thor (~> 0.14)
fission (0.5.0)
@ -979,6 +980,7 @@ DEPENDENCIES
fastly (~> 1.15)
fastly-rails (~> 0.8)
feedjira (~> 2.2)
ffprober (~> 0.5)
figaro (~> 1.1)
fix-db-schema-conflicts!
fog (~> 1.41)

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 491.858 491.858" style="enable-background:new 0 0 491.858 491.858;" xml:space="preserve" fill="white">
<g>
<g>
<g>
<path d="M357.714,423.331c0,9.328-10.676,16.891-23.847,16.891H23.847C10.676,440.222,0,432.659,0,423.331V203.735
c0-9.33,10.676-16.892,23.847-16.892h310.02c13.171,0,23.847,7.564,23.847,16.892V423.331L357.714,423.331z" fill="white" fill-opacity="1.0" />
<circle cx="89.428" cy="118.706" r="59.619" fill="white" fill-opacity="1.0" />
<circle cx="253.381" cy="103.801" r="74.524" fill="white" fill-opacity="1.0" />
<path d="M491.858,447.677c0,0-1.986,14.904-15.899,14.904c-13.912,0-103.34-83.42-103.34-94.397V258.882
c0-10.976,87.443-94.398,103.34-94.398c15.899,0,15.899,14.905,15.899,14.905V447.677z" fill-opacity="1.0" fill="white"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -98,7 +98,12 @@ function buildArticleHTML(article) {
} else if (article.class_name === "Article") {
readingTimeHTML = '<a href="'+article.path+'" class="article-reading-time">3 min read</a>'
}
var videoHTML = '';
if (article.cloudinary_video_url) {
videoHTML = '<a href="'+article.path+'" class="single-article-video-preview" style="background-image:url('+article.cloudinary_video_url+')"><div class="single-article-video-duration"><img src="<%= asset_path("video-camera.svg") %>" />'+article.video_duration_in_minutes+'</div></a>'
}
return '<div class="single-article single-article-small-pic">\
'+videoHTML+'\
'+orgHeadline+'\
<div class="small-pic">\
<a href="/'+profileUsername+'" class="small-pic-link-wrapper">\

View file

@ -233,6 +233,36 @@
overflow: hidden;
text-overflow: ellipsis;
}
.single-article-video-preview {
img {
width: 100%;
}
position: relative;
padding-top: 56%;
display: block;
background: $black no-repeat center center;
background-size: cover;
&:hover {
opacity: 1;
}
.single-article-video-duration {
position: absolute;
bottom: 8px;
right: 7px;
background: rgba(0,0,0,0.8);
color: white;
border-radius: 3px;
padding: 2px 5px 3px;
font-size: 0.75em;
font-weight: 500;
img {
width: 12px;
height: 12px;
margin-right: 4px;
vertical-align: -1px;
}
}
}
.article-organization-headline {
display:block;
margin-bottom:calc(-1px - 0.6vw);

View file

@ -55,22 +55,12 @@ class Internal::UsersController < Internal::ApplicationController
toggle_warn_user if user_params[:warn_user]
toggle_trust_user if user_params[:trusted_user]
toggle_ban_from_mentorship if user_params[:ban_from_mentorship]
toggle_video_permission if user_params[:video_permission]
end
def toggle_video_permission
if user_params[:video_permission] == "1"
@user.add_role :video_permission
else
@user.remove_role :video_permission
end
end
def toggle_ban_user
if user_params[:ban_user] == "1"
@user.add_role :banned
@user.remove_role :trusted
@user.remove_role :video_permission
create_note("banned", user_params[:note_for_current_role])
else
@user.remove_role :banned
@ -189,7 +179,6 @@ class Internal::UsersController < Internal::ApplicationController
:note_for_mentorship_ban,
:note_for_current_role,
:reason_for_mentorship_ban,
:trusted_user,
:video_permission)
:trusted_user)
end
end

View file

@ -49,6 +49,7 @@ class Article < ApplicationRecord
before_save :set_all_dates
before_save :calculate_base_scores
before_save :set_caches
before_save :fetch_video_duration
after_save :async_score_calc, if: :published
after_save :bust_cache
after_save :update_main_image_background_hex
@ -75,7 +76,7 @@ class Article < ApplicationRecord
:main_image, :main_image_background_hex_color, :updated_at, :slug,
:video, :user_id, :organization_id, :video_source_url, :video_code,
:video_thumbnail_url, :video_closed_caption_track_url,
:published_at, :crossposted_at, :boost_states, :description, :reading_time)
:published_at, :crossposted_at, :boost_states, :description, :reading_time, :video_duration_in_seconds)
}
scope :limited_columns_internal_select, -> {
@ -137,7 +138,8 @@ class Article < ApplicationRecord
enqueue: :trigger_delayed_index do
attributes :title, :path, :class_name, :comments_count, :reading_time,
:tag_list, :positive_reactions_count, :id, :hotness_score, :score,
:readable_publish_date, :flare_tag, :user_id, :organization_id
:readable_publish_date, :flare_tag, :user_id, :organization_id,
:cloudinary_video_url, :video_duration_in_minutes
attribute :published_at_int do
published_at.to_i
end
@ -384,6 +386,25 @@ class Article < ApplicationRecord
user&.collections&.pluck(:slug)
end
def cloudinary_video_url
ApplicationController.helpers.cloudinary(video_thumbnail_url, 880)
end
def video_duration_in_minutes
minutes = (video_duration_in_seconds.to_i / 60) % 60
seconds = video_duration_in_seconds.to_i % 60
"#{minutes}:#{seconds}"
end
def fetch_video_duration
if video.present? && video_duration_in_seconds.zero?
info = Ffprober::Parser.from_url video
self.video_duration_in_seconds = info.json[:format][:duration].to_f
end
rescue StandardError => e
puts e.message
end
private
def update_notifications
@ -454,7 +475,7 @@ class Article < ApplicationRecord
if published && video_state == "PROGRESSING"
return errors.add(:published, "cannot be set to true if video is still processing")
end
if video.present? && !user.has_role?(:video_permission)
if video.present? && user.created_at > 2.weeks.ago
return errors.add(:video, "cannot be added member without permission")
end
end

View file

@ -25,7 +25,6 @@ class Role < ApplicationRecord
level_2_member
level_1_member
workshop_pass
video_permission
chatroom_beta_tester
banned_from_mentorship
comment_banned

View file

@ -3,7 +3,7 @@ class User < ApplicationRecord
attr_accessor :scholar_email, :note, :ban_from_mentorship, :quick_match, :ban_user, :warn_user,
:note_for_mentorship_ban, :reason_for_mentorship_ban,
:note_for_current_role, :add_mentor, :add_mentee, :trusted_user, :video_permission
:note_for_current_role, :add_mentor, :add_mentee, :trusted_user
rolify
include AlgoliaSearch

View file

@ -1,9 +1,9 @@
class VideoPolicy < ApplicationPolicy
def new?
user.has_role?(:video_permission)
user.created_at < 2.weeks.ago if user.created_at
end
def create?
user.has_role?(:video_permission)
user.created_at < 2.weeks.ago if user.created_at
end
end

View file

@ -1,13 +1,15 @@
<div class="single-article single-article-small-pic">
<% if story.video.present? %>
<%= render "articles/video_player", meta_tags: false, article: story %>
<% if story.video.present? && story.video_thumbnail_url.present? %>
<a class="single-article-video-preview" style="background-image:url(<%= story.cloudinary_video_url %>)" >
<div class="single-article-video-duration"><img src="<%= asset_path("video-camera.svg") %>" /><%= story.video_duration_in_minutes %></div>
</a>
<% end %>
<% organization = story.organization %>
<% if organization && !@organization_article_index %>
<div class="article-organization-headline">
<a href="/<%= organization.slug %>" class="article-organization-headline-inner"><img src="<%= organization.profile_image_90 %>"><%= organization.name %></a><a class="org-headline-filler" href="<%= story.path %>">&nbsp;</a></div>
<% end %>
<a href="<%=story.user.path%>" class="small-pic-link-wrapper">
<a href="<%= story.user.path%>" class="small-pic-link-wrapper">
<div class="small-pic" >
<% if story.cached_tag_list_array.include?("hiring") && story.organization_id.present? && !@organization_article_index %>
<img src="<%= ProfileImage.new(story.organization).get(90) %>" alt="<%= story.organization.username %> profile" />
@ -16,7 +18,7 @@
<% end %>
</div>
</a>
<a href="<%=story.path%>" class="small-pic-link-wrapper index-article-link" data-preload-image="<%=cloud_cover_url(story.main_image) %>" id="article-link-<%=story.id%>">
<a href="<%= story.path %>" class="small-pic-link-wrapper index-article-link" data-preload-image="<%=cloud_cover_url(story.main_image) %>" id="article-link-<%= story.id%>">
<div class="content">
<h3>
<%= render "articles/tag_identifier", story: story, tag: @tag %>
@ -24,7 +26,7 @@
</div>
</a>
<h4>
<a href="<%=story.user.path%>"><%= story.user.name %>・<%= story.readable_publish_date %></a>
<a href="<%= story.user.path %>"><%= story.user.name %>・<%= story.readable_publish_date %></a>
</h4>
<div class="tags">
@ -34,13 +36,13 @@
</div>
<% if story.comments_count > 0 %>
<div class="article-engagement-count comments-count">
<a href="<%=story.path%>#comments">
<a href="<%= story.path %>#comments">
<img src="<%= asset_path("comments-bubble.png") %>" alt="chat" /><span class="engagement-count-number"><%= story.comments_count %></span>
</a>
</div>
<% end %>
<div class="article-engagement-count reactions-count" data-reaction-count data-reactable-id="<%= story.id %>">
<a href="<%=story.path%>">
<a href="<%= story.path %>">
<img src="<%= asset_path("reactions-stack.png") %>" alt="Reactions" /><span id="engagement-count-number-<%= story.id %>" class="engagement-count-number">
<%= story.positive_reactions_count %>
</span>

View file

@ -1,10 +1,11 @@
<div <% "itemscope itemtype=\"http://schema.org/VideoObject\"" if meta_tags %> class="video-player-header">
<div temscope itemtype="http://schema.org/VideoObject" class="video-player-header">
<% if meta_tags %>
<meta itemprop="uploadDate" content="<%= @article.published_at %>" />
<meta itemprop="name" content="<%= @article.title %>" />
<meta itemprop="description" content="<%= @article.description %>" />
<meta itemprop="thumbnailUrl" content="<%= cloudinary(article.video_thumbnail_url, 880) %>" />
<meta itemprop="contentUrl" content="<%= article.video_source_url %>" />
<meta itemprop="duration" content="<%= "PT%sM%sS" % article.video_duration_in_minutes.split(':') %>" />
<% end %>
<script src="//content.jwplatform.com/libraries/b1zWy2iv.js" async> </script>
<div id="video-player-<%= article.id %>" class="video-player"></div>
@ -40,6 +41,7 @@
playerInstance.setup({
file: "<%= article.video_source_url %>",
mediaid: "<%= article.video_code %>",
autostart: <%= internal_navigation? %>,
image: "<%= cloudinary(article.video_thumbnail_url, 880) %>",
playbackRateControls: true,
tracks: [{

View file

@ -2,15 +2,15 @@
<div class="dashboard-container" id="user-dashboard">
<div class="actions">
<a class="action <%= 'active' if params[:which] == "organization" || params[:which].blank? %>" href="/dashboard">
<a class="action <%= "active" if params[:which] == "organization" || params[:which].blank? %>" href="/dashboard">
<span>POSTS</span>
<span>(<%= @user.articles_count %>)</span>
</a>
<a class="action <%= 'active' if params[:which] == "user_followers" || params[:which] == "organization_user_followers" %>" href="/dashboard/user_followers">
<a class="action <%= "active" if params[:which] == "user_followers" || params[:which] == "organization_user_followers" %>" href="/dashboard/user_followers">
<span>FOLLOWERS</span>
<span>(<%= @user.followers_count %>)</span>
</a>
<a class="action <%= 'active' if params[:which].to_s.include?("following") %>" href="/dashboard/following">
<a class="action <%= "active" if params[:which].to_s.include?("following") %>" href="/dashboard/following">
<span>FOLLOWING</span>
<span>(<%= @user.following_users_count + @user.following_tags_count %>)</span>
</a>
@ -78,7 +78,7 @@
<% end %>
<% elsif @articles.any? %>
<%= render "analytics" %>
<% if current_user.has_role?(:video_permission) %>
<% if current_user.created_at < 2.weeks.ago %>
<a class="video-upload-cta" href="/videos/new" data-no-instant>
Upload a Video 🎥
</a>

View file

@ -52,8 +52,6 @@
<%= form_for [:internal, @user] do |f| %>
<p>Add Trusted Role (Community Moderator)
<%= f.check_box :trusted_user, checked: @user.trusted %></p>
<p>Allow Video Uploads
<%= f.check_box :video_permission, checked: @user.has_role?(:video_permission) %> </p>
<%= f.submit "Update Privileges" %>
<% end %>
<% else %>

View file

@ -18,7 +18,7 @@
key: "video-upload__#{SecureRandom.hex}",
key_starts_with: "video-upload__",
acl: "public-read",
max_file_size: (current_user.has_role?(:super_admin) ? 15000 : 3500).megabytes,
max_file_size: (current_user.has_role?(:super_admin) ? 20000 : 6000).megabytes,
id: "s3-uploader",
class: "upload-form",
data: {:key => :val} do %>

View file

@ -0,0 +1,5 @@
class AddVideoDurationInSecondsToArticles < ActiveRecord::Migration[5.1]
def change
add_column :articles, :video_duration_in_seconds, :float, default: 0
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190206222055) do
ActiveRecord::Schema.define(version: 20190216185753) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -117,6 +117,7 @@ ActiveRecord::Schema.define(version: 20190206222055) do
t.string "video"
t.string "video_closed_caption_track_url"
t.string "video_code"
t.float "video_duration_in_seconds", default: 0.0
t.string "video_source_url"
t.string "video_state"
t.string "video_thumbnail_url"

View file

@ -37,8 +37,7 @@ FactoryBot.define do
trait :video do
after(:build) do |article|
article.video = "https://video.com"
article.user.add_role :video_permission
article.video = "https://s3.amazonaws.com/dev-to-input-v0/video-upload__2d7dc29e39a40c7059572bca75bb646b"
article.save
end
end

View file

@ -39,7 +39,7 @@ FactoryBot.define do
end
trait :video_permission do
after(:build) { |user| user.add_role :video_permission }
after(:build) { |user| user.created_at = 3.weeks.ago }
end
trait :ignore_after_callback do

View file

@ -206,7 +206,7 @@ RSpec.describe Article, type: :model do
describe "#video" do
it "must be a url" do
article.user.add_role(:video_permission)
article.user.created_at = 3.weeks.ago
article.video = "hey"
expect(article).not_to be_valid
article.video = "http://hey.com"
@ -216,7 +216,14 @@ RSpec.describe Article, type: :model do
it "must belong to permissioned user" do
article.video = "http://hey.com"
expect(article).not_to be_valid
article.user.add_role(:video_permission)
article.user.created_at = 3.weeks.ago
expect(article).to be_valid
end
it "saves with video" do
article.user.created_at = 3.weeks.ago
article.video = "https://s3.amazonaws.com/dev-to-input-v0/video-upload__2d7dc29e39a40c7059572bca75bb646b"
article.save
expect(article).to be_valid
end
end

View file

@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe Comment, type: :model do
let(:user) { create(:user) }
let(:user) { create(:user, created_at: 3.weeks.ago) }
let(:user2) { create(:user) }
let(:article) { create(:article, user_id: user.id, published: true) }
let(:article_with_video) { create(:article, :video, user_id: user.id, published: true) }

View file

@ -17,10 +17,10 @@ RSpec.describe VideoPolicy do
it { is_expected.to forbid_actions(%i[new create]) }
end
context "when user does not have video permission" do
context "when does have video permission" do
let(:user) { build(:user) }
before { user.add_role :video_permission }
before { user.created_at = 3.weeks.ago }
it { is_expected.to permit_actions(%i[new create]) }
end