access-derivatives 0.0.11
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/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +11 -0
- data/Rakefile +1 -0
- data/access-derivatives.gemspec +23 -0
- data/bin/poster_sprite +71 -0
- data/bin/poster_sprites_retrospective +22 -0
- data/bin/process_video +87 -0
- data/bin/upload_video_batch.sh +32 -0
- data/lib/access/derivatives.rb +7 -0
- data/lib/access/derivatives/version.rb +5 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6bfadafe61b4098608dde830f1b200e9992f5dd
|
4
|
+
data.tar.gz: 662d9471251f35920c4bd9ff3f24a6e948d3e955
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 610b63607385ec7728eaa6320d9503946d5277b696d3e44f865588964b12da0becf2fe278da6c4fb922487c9765141324935051f5f44b2a7cee01b8eb051b68f
|
7
|
+
data.tar.gz: a028814e83a97c109c62c2093101bfe00d307955143a25dba4bcf586998e5fe804b7421295ea31d35a3e06c2a07ee54fb1f9b7d9469858690ac8b5f617116e7f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jason Ronallo
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'access/derivatives/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "access-derivatives"
|
8
|
+
spec.version = Access::Derivatives::VERSION
|
9
|
+
spec.authors = ["Jason Ronallo"]
|
10
|
+
spec.email = ["jronallo@gmail.com"]
|
11
|
+
spec.summary = %q{Create access derivative files.}
|
12
|
+
spec.description = %q{Create access derivative files.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
data/bin/poster_sprite
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# poster_sprite /path/to/snapshots/directoryname
|
4
|
+
|
5
|
+
# Creates a poster sprite (and in the future maybe a WebVTT file) from snapshots in a directory.
|
6
|
+
|
7
|
+
# requires montage and identify from imagemagick
|
8
|
+
|
9
|
+
module AccessDerivatives
|
10
|
+
def self.seconds_to_webvtt_timestamp(total_seconds)
|
11
|
+
seconds = total_seconds % 60
|
12
|
+
minutes = (total_seconds / 60) % 60
|
13
|
+
hours = total_seconds / (60 * 60)
|
14
|
+
|
15
|
+
format("%02d:%02d:%02d.000", hours, minutes, seconds)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
directory = File.expand_path(ARGV[0])
|
20
|
+
directory_glob = File.join(directory, '*png')
|
21
|
+
directory_basename = File.basename(directory)
|
22
|
+
sprite_basename = directory_basename + '-sprite.jpg'
|
23
|
+
sprite_output_filename = File.join(directory, sprite_basename)
|
24
|
+
webvtt_output_filename = File.join(directory, directory_basename + '-sprite.vtt')
|
25
|
+
|
26
|
+
first_image = Dir.glob(directory_glob).sort.first
|
27
|
+
original_dimensions = `identify -format "%[fx:w]x%[fx:h]" #{first_image}`.chomp
|
28
|
+
|
29
|
+
original_width_str, original_height_str = original_dimensions.split('x')
|
30
|
+
original_height = original_height_str.to_i
|
31
|
+
original_width = original_width_str.to_i
|
32
|
+
|
33
|
+
width = 150
|
34
|
+
height = (original_height.to_f / original_width * width).to_i
|
35
|
+
|
36
|
+
tile_per_row = 5
|
37
|
+
|
38
|
+
`montage #{directory_glob} -tile #{tile_per_row}x -geometry #{width}x#{height}! #{sprite_output_filename}`
|
39
|
+
|
40
|
+
vtt_asset_base = File.join('http://siskel.lib.ncsu.edu/SCRC/', directory_basename, sprite_basename)
|
41
|
+
|
42
|
+
File.open(webvtt_output_filename, 'w') do |fh|
|
43
|
+
fh.puts "WEBVTT\n\n"
|
44
|
+
|
45
|
+
# figure out the coordinates when the images are 5 across at 150px wide.
|
46
|
+
# What height are they then?
|
47
|
+
x = 0
|
48
|
+
y = 0
|
49
|
+
number_in_row = 1
|
50
|
+
|
51
|
+
Dir.glob(directory_glob).sort.each_with_index do |file, index|
|
52
|
+
full_url = vtt_asset_base + "#xywh=#{x},#{y},#{width},#{height}"
|
53
|
+
|
54
|
+
start_time_integer = index * 5 # 5 seconds between each
|
55
|
+
end_time_integer = start_time_integer + 5
|
56
|
+
|
57
|
+
start_time = AccessDerivatives.seconds_to_webvtt_timestamp(start_time_integer)
|
58
|
+
end_time = AccessDerivatives.seconds_to_webvtt_timestamp(end_time_integer)
|
59
|
+
|
60
|
+
fh.puts start_time + ' --> ' + end_time
|
61
|
+
fh.puts full_url + "\n\n"
|
62
|
+
|
63
|
+
number_in_row += 1
|
64
|
+
x += width
|
65
|
+
if number_in_row > tile_per_row
|
66
|
+
number_in_row = 1
|
67
|
+
x = 0
|
68
|
+
y += height
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# poster_sprites_retrospective /path/to/directory/of/videos
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
directory = File.expand_path(ARGV[0])
|
8
|
+
Dir.glob(File.join(directory, '*.mp4')).each do |video|
|
9
|
+
basename = File.basename(video, '.mp4')
|
10
|
+
|
11
|
+
video_snapshots_directory = File.join(directory, basename)
|
12
|
+
Dir.mkdir(video_snapshots_directory) unless File.exist?(video_snapshots_directory)
|
13
|
+
video_snapshot_base_filename = File.join(video_snapshots_directory, basename)
|
14
|
+
|
15
|
+
# Create images every 5 seconds from the MP4 output.
|
16
|
+
`ffmpeg -i "#{video}" -f image2 -vf fps=fps=1/5 #{video_snapshot_base_filename}-%05d.png`
|
17
|
+
|
18
|
+
poster_sprite_command_path = File.join(File.expand_path(File.dirname(__FILE__)), 'poster_sprite')
|
19
|
+
`#{poster_sprite_command_path} #{video_snapshots_directory}`
|
20
|
+
|
21
|
+
FileUtils.rm(Dir.glob(File.join(video_snapshots_directory, '*png')))
|
22
|
+
end
|
data/bin/process_video
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# process_video /path/to/directory/of/video/files/
|
4
|
+
|
5
|
+
# Requires a recent ffmpeg and mail.
|
6
|
+
# requires montage and identify from imagemagick
|
7
|
+
require 'etc'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
# batch directory for processing
|
11
|
+
directory = File.expand_path(ARGV[0])
|
12
|
+
|
13
|
+
# Get the username of the owner of the batch directory. Used later for sending email.
|
14
|
+
uid = File.stat(directory).uid
|
15
|
+
username_for_batch = Etc.getpwuid(uid).name
|
16
|
+
|
17
|
+
# What directory should we place the output in?
|
18
|
+
base_output_directory = ENV['PROCESS_VIDEO_OUTPUT_DIRECTORY'] || '/html5/video/output'
|
19
|
+
batch_output_directory = File.basename(directory)
|
20
|
+
|
21
|
+
# Create output directory.
|
22
|
+
output_directory = File.join(base_output_directory, batch_output_directory)
|
23
|
+
Dir.mkdir(output_directory) unless File.exist?(output_directory)
|
24
|
+
|
25
|
+
# Create a directory for placing ffmpeg log files.
|
26
|
+
log_directory = File.join(output_directory, 'log')
|
27
|
+
Dir.mkdir(log_directory) unless File.exist?(log_directory)
|
28
|
+
|
29
|
+
# Create directory for snapshots.
|
30
|
+
snapshot_directory = File.join(output_directory, 'snapshots')
|
31
|
+
Dir.mkdir(snapshot_directory) unless File.exist?(snapshot_directory)
|
32
|
+
|
33
|
+
# Get the list of all the files that ought to be processed
|
34
|
+
source_video_files = []
|
35
|
+
['*.mp4','*.flv', '*.mov', '*.avi', '*.mkv','*.m4v', '*.f4v'].each do |source_extension|
|
36
|
+
source_video_files << Dir.glob(File.join(directory, source_extension))
|
37
|
+
end
|
38
|
+
source_video_files.flatten!.compact!
|
39
|
+
|
40
|
+
# iterate over each of the files that ought to be processed
|
41
|
+
source_video_files.each do |filepath|
|
42
|
+
# get the basename
|
43
|
+
extension = File.extname(filepath)
|
44
|
+
basename = File.basename(filepath, extension)
|
45
|
+
|
46
|
+
# Create the video output directory
|
47
|
+
video_output_directory = File.join(output_directory, basename)
|
48
|
+
Dir.mkdir(video_output_directory) unless File.exist?(video_output_directory)
|
49
|
+
|
50
|
+
# Create the snaptshots directory.
|
51
|
+
video_snapshots_directory = File.join(snapshot_directory, basename)
|
52
|
+
Dir.mkdir(video_snapshots_directory) unless File.exist?(video_snapshots_directory)
|
53
|
+
video_snapshots_base_filename = File.join(video_snapshots_directory, basename)
|
54
|
+
|
55
|
+
# determine filenames for each output format
|
56
|
+
video_output_filename_root = File.join(video_output_directory, basename)
|
57
|
+
video_output_filename_mp4 = video_output_filename_root + '.mp4'
|
58
|
+
video_output_filename_webm = video_output_filename_root + '.webm'
|
59
|
+
|
60
|
+
# determine the filenames for each of the log files created
|
61
|
+
log_file_mp4 = File.join(log_directory, basename + '-ffmpeg-mp4-%t.log')
|
62
|
+
log_file_webm = File.join(log_directory, basename + '-ffmpeg-webm-%t.log')
|
63
|
+
log_file_snapshot = File.join(log_directory, basename + '-snapshot-%t.log')
|
64
|
+
|
65
|
+
# Create MP4 suitable for web playback and log output.
|
66
|
+
# "-pix_fmt yuv420p" was added to work around an issue where the stream is yuv422p
|
67
|
+
`FFREPORT=file=#{log_file_mp4} ffmpeg -i #{filepath} -c:v libx264 -preset slow -crf 23 -profile:v baseline -level 3.0 -filter:v "scale=640:trunc(ow/a/2)*2" -acodec libfdk_aac -b:a 128k -movflags +faststart -pix_fmt yuv420p "#{video_output_filename_mp4}"`
|
68
|
+
|
69
|
+
# Create WebM and log output.
|
70
|
+
`FFREPORT=file=#{log_file_webm} ffmpeg -i "#{filepath}" -c:v libvpx -crf 10 -b:v 1M -filter:v "scale=640:trunc(ow/a/2)*2" -c:a libvorbis -b:a 128k "#{video_output_filename_webm}"`
|
71
|
+
|
72
|
+
# Create images every 5 seconds from the MP4 output.
|
73
|
+
`FFREPORT=file=#{log_file_snapshot} ffmpeg -i "#{video_output_filename_mp4}" -f image2 -vf fps=fps=1/5 #{video_snapshots_base_filename}-%05d.png`
|
74
|
+
|
75
|
+
# Create poster sprite and related webvtt file
|
76
|
+
poster_sprite_command_path = File.join(File.expand_path(File.dirname(__FILE__)), 'poster_sprite')
|
77
|
+
`#{poster_sprite_command_path} #{video_snapshots_directory}`
|
78
|
+
|
79
|
+
# Move poster sprite and WebVTT file into video output directory
|
80
|
+
sprite_files_glob = File.join(video_snapshots_directory, '*sprite*')
|
81
|
+
Dir.glob(sprite_files_glob).each do |file|
|
82
|
+
FileUtils.mv(file, video_output_directory)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sends an email when the job is done. This may only work this way with the -r option on redhat.
|
87
|
+
`echo "To get the files run: scp -r #{username_for_batch}@av1.lib.ncsu.edu:#{output_directory} ." | mail -s "Your transcoding job is done" -r jnronall@ncsu.edu #{username_for_batch}@ncsu.edu`
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# upload_video_batch.sh /path/to/batch/directory
|
4
|
+
|
5
|
+
# Setting for directory to where the batch directory ought to be uploaded
|
6
|
+
upload_batch_directory_base=/html5/video/uploads
|
7
|
+
|
8
|
+
# This only works if the local user
|
9
|
+
whoami=`whoami`
|
10
|
+
|
11
|
+
# Grab the first argument into a variable so that we can do the basename substitution.
|
12
|
+
argv0=$1
|
13
|
+
# The basename from whatever path was given
|
14
|
+
basename=`basename $argv0`
|
15
|
+
|
16
|
+
# full upload path
|
17
|
+
full_upload_path=${upload_batch_directory_base}/${basename}
|
18
|
+
full_upload_path_contents=${full_upload_path}/*
|
19
|
+
|
20
|
+
# Copy the files to the remote server under the upload batch directory.
|
21
|
+
echo -e 'Copying the files to av1... (May need to enter password).\n'
|
22
|
+
scp -r $1 $whoami@av1.lib.ncsu.edu:$upload_batch_directory_base
|
23
|
+
|
24
|
+
echo -e 'Changing group permissions of files. (May need to enter password).'
|
25
|
+
ssh $whoami@av1.lib.ncsu.edu "chgrp uploaders $full_upload_path && chmod g+rx $full_upload_path && chmod g+rw $full_upload_path_contents"
|
26
|
+
|
27
|
+
echo "Hit ENTER when you wish to continue with processing the video. (May need to enter password)."
|
28
|
+
read SOMETHIGNANYTHING
|
29
|
+
|
30
|
+
# Trigger the processing job on the remove server and background it using nohup.
|
31
|
+
# Output and errors will go into files. FFmpeg output goes to standard error.
|
32
|
+
ssh $whoami@av1.lib.ncsu.edu "PROCESS_VIDEO_OUTPUT_DIRECTORY=/html5/video/output nohup process_video $full_upload_path > upload_batch.out 2> upload_batch.err < /dev/null &"
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: access-derivatives
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.11
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason Ronallo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Create access derivative files.
|
42
|
+
email:
|
43
|
+
- jronallo@gmail.com
|
44
|
+
executables:
|
45
|
+
- poster_sprite
|
46
|
+
- poster_sprites_retrospective
|
47
|
+
- process_video
|
48
|
+
- upload_video_batch.sh
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- ".gitignore"
|
53
|
+
- Gemfile
|
54
|
+
- LICENSE.txt
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- access-derivatives.gemspec
|
58
|
+
- bin/poster_sprite
|
59
|
+
- bin/poster_sprites_retrospective
|
60
|
+
- bin/process_video
|
61
|
+
- bin/upload_video_batch.sh
|
62
|
+
- lib/access/derivatives.rb
|
63
|
+
- lib/access/derivatives/version.rb
|
64
|
+
homepage: ''
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.4.8
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Create access derivative files.
|
88
|
+
test_files: []
|