viddl 0.0.2

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.
@@ -0,0 +1,59 @@
1
+ module Viddl
2
+
3
+ module Video
4
+
5
+ class Clip
6
+
7
+ module Crop
8
+
9
+ extend self
10
+
11
+ # Crop options formatted for ffmpeg
12
+ # @param [Hash] options
13
+ # @option options [Hash] :crop The desired crop parameters (:x, :y, :width, :height)
14
+ # @return [Hash]
15
+ def options_formatted(options = {})
16
+ result = {}
17
+ unless options[:crop].nil?
18
+ crop = {}
19
+ [:x, :y, :width, :height].each do |property|
20
+ if options[:crop][property].nil?
21
+ raise "Crop is missing required #{property} property"
22
+ else
23
+ crop[property] = options[:crop][property].to_i
24
+ end
25
+ end
26
+ result[:crop] = crop
27
+ end
28
+ result
29
+ end
30
+
31
+ # Command line options for cropping
32
+ # @param [Hash] options
33
+ # @option options [Hash] :crop The desired crop parameters (:x, :y, :width, :height)
34
+ # @return [String]
35
+ def filter_args(options = {})
36
+ unless options[:crop].nil?
37
+ crop = options[:crop]
38
+ "crop=#{crop[:width]}:#{crop[:height]}:#{crop[:x]}:#{crop[:y]}"
39
+ end
40
+ end
41
+
42
+ # Token added to clip filename for crop args
43
+ # @param [Hash] options
44
+ # @option options [Hash] :crop The desired crop parameters (:x, :y, :width, :height)
45
+ # @return [String, nil]
46
+ def filename_token(options = {})
47
+ unless options[:crop].nil?
48
+ crop = options[:crop]
49
+ "cx#{crop[:x]}cy#{crop[:y]}cw#{crop[:width]}ch#{crop[:height]}"
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,83 @@
1
+ module Viddl
2
+
3
+ module Video
4
+
5
+ class Clip
6
+
7
+ module Cut
8
+
9
+ extend self
10
+
11
+ # Cut options formatted for ffmpeg
12
+ # @param [Hash] options
13
+ # @option options [Numeric] :start Time in the source file where the clip starts
14
+ # @option options [Numeric] :duration Duration of the clip
15
+ # @option options [Numeric] :end Time in the source file where the clip ends
16
+ # @return [Hash]
17
+ def options_formatted(options = {})
18
+ result = {}
19
+ result[:start] = options[:start]
20
+ result[:duration] = duration(options)
21
+ result
22
+ end
23
+
24
+ # Command line options for the given cut constraints
25
+ # @param [Hash] options
26
+ # @option options [Numeric] :start Time in the source file where the clip starts
27
+ # @option options [Numeric] :duration Duration of the clip
28
+ # @return [String, nil]
29
+ def args(options = {})
30
+ args = []
31
+ unless options[:start].nil?
32
+ args << "-ss #{options[:start]}"
33
+ end
34
+ unless options[:duration].nil?
35
+ args << "-t #{options[:duration]}"
36
+ end
37
+ args.join(" ") unless args.empty?
38
+ end
39
+
40
+ # Token added to clip filename for cut args
41
+ # @param [Hash] options
42
+ # @option options [Numeric] :start Time in the source file where the clip starts
43
+ # @option options [Numeric] :duration Duration of the clip
44
+ # @return [String, nil]
45
+ def filename_token(options = {})
46
+ if !options[:start].nil? || !options[:duration].nil?
47
+ args = ""
48
+ if !options[:start].nil?
49
+ args += "s#{options[:start]}"
50
+ end
51
+ if !options[:duration].nil?
52
+ args += "d#{options[:duration]}"
53
+ end
54
+ args
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # Numeric duration for the given options
61
+ # @param [Hash] options
62
+ # @option options [Numeric] :duration Duration of the clip
63
+ # @option options [Numeric] :end Time in the source file where the clip ends
64
+ # @return [Numeric]
65
+ def duration(options = {})
66
+ duration = nil
67
+ if !options[:duration].nil? && !options[:end].nil?
68
+ raise "Can not use both end time and duration"
69
+ elsif !options[:duration].nil? && options[:end].nil?
70
+ duration = options[:duration]
71
+ elsif options[:duration].nil? && !options[:end].nil?
72
+ duration = options[:end] - (options[:start] || 0)
73
+ end
74
+ duration
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,66 @@
1
+ module Viddl
2
+
3
+ module Video
4
+
5
+ class Clip
6
+
7
+ module Resize
8
+
9
+ extend self
10
+
11
+ # Resize options formatted for ffmpeg
12
+ # @param [Hash] options
13
+ # @option options [Integer, String] :width The desired width to resize to
14
+ # @option options [Integer, String] :height The desired height to resize to
15
+ # @return [Hash]
16
+ def options_formatted(options = {})
17
+ result = {}
18
+ [:width, :height].each do |property|
19
+ result[property] = options[property].to_i unless options[property].nil?
20
+ end
21
+ result
22
+ end
23
+
24
+ # Command line options for resize
25
+ # @param [Hash] options
26
+ # @option options [Integer] :width The desired width to resize to
27
+ # @option options [Integer] :height The desired height to resize to
28
+ # @return [String, nil]
29
+ def filter_args(options = {})
30
+ scale = if options[:width].nil? && !options[:height].nil?
31
+ "-1:#{options[:height]}"
32
+ elsif !options[:width].nil? && options[:height].nil?
33
+ "#{options[:width]}:-1"
34
+ elsif !options[:width].nil? && !options[:height].nil?
35
+ "#{options[:width]}:#{options[:height]}"
36
+ end
37
+ unless scale.nil?
38
+ "scale=#{scale}"
39
+ end
40
+ end
41
+
42
+ # Token added to clip filename for resize args
43
+ # @param [Hash] options
44
+ # @option options [Integer] :width The desired width to resize to
45
+ # @option options [Integer] :height The desired height to resize to
46
+ # @return [String, nil]
47
+ def filename_token(options = {})
48
+ if !options[:width].nil? || !options[:height].nil?
49
+ args = ""
50
+ if !options[:width].nil?
51
+ args += "w#{options[:width]}"
52
+ end
53
+ if !options[:height].nil?
54
+ args += "h#{options[:height]}"
55
+ end
56
+ args
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,49 @@
1
+ module Viddl
2
+
3
+ module Video
4
+
5
+ class Download
6
+
7
+ # download is stored to /tmp before processing
8
+ TEMPDIR = "/tmp"
9
+ # download format is forced to mp4 to optimize for quickness
10
+ FORMAT_ARG = "-f 'best[ext=mp4]'"
11
+
12
+ # Download the given video
13
+ # @param [Video::Instance] video
14
+ # @param [Hash] options
15
+ # @return [Download]
16
+ def self.process(video, options = {})
17
+ download = new(video)
18
+ download.process(options)
19
+ download
20
+ end
21
+
22
+ # @param [Video::Instance] video
23
+ def initialize(video)
24
+ @video = video
25
+ @video.download = self if @video.download.nil?
26
+ end
27
+
28
+ # Download the video file
29
+ # @param [Hash] options
30
+ # @return [Boolean]
31
+ def process(options = {})
32
+ result = Kernel.system(command_line)
33
+ raise(result.to_s) unless result
34
+ true
35
+ end
36
+
37
+ private
38
+
39
+ # Command line to download the video file
40
+ # @return [String]
41
+ def command_line
42
+ "youtube-dl #{@video.source_url} #{FORMAT_ARG} -o '#{TEMPDIR}/#{@video.id}s.%(ext)s'"
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,62 @@
1
+ module Viddl
2
+
3
+ module Video
4
+
5
+ class Instance
6
+
7
+ attr_accessor :download
8
+ attr_reader :id, :source_url
9
+
10
+ # @param [String] url The url of the video source
11
+ def initialize(url)
12
+ @source_url = url
13
+
14
+ populate_id
15
+ end
16
+
17
+ # Cut the video source using the given options
18
+ # @param [Hash] options
19
+ # @option options [Boolean] :audio Whether to include audio
20
+ # @option options [Numeric] :start Time in the source file where the clip starts
21
+ # @option options [Numeric] :duration Duration of the clip
22
+ # @option options [Numeric] :end Time in the source file where the clip ends
23
+ # @option options [Integer, String] :width The desired width to resize to
24
+ # @option options [Integer, String] :height The desired height to resize to
25
+ # @option options [Hash] :crop The desired crop parameters (:x, :y, :width, :height)
26
+ # @return [Array<Clip>]
27
+ def create_clip(options = {})
28
+ source_filenames.map do |filename|
29
+ Clip.process(filename, options)
30
+ end
31
+ end
32
+
33
+ # Download the video source
34
+ # @param [Hash] options
35
+ # @return [Download]
36
+ def process_download(options = {})
37
+ @download = Download.process(self, options)
38
+ end
39
+
40
+ # The downloaded source filenames
41
+ # @return [Array<String>]
42
+ def source_filenames
43
+ if @download.nil?
44
+ raise "File must be downloaded"
45
+ else
46
+ @source_filenames = Dir["#{Download::TEMPDIR}/#{@id}*"]
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # The video instance id
53
+ # @return [String]
54
+ def populate_id
55
+ @id = @source_url.scan(/youtube.com\/watch\?v\=(\S*)&?/).flatten.first
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join("..", "lib"))
2
+
3
+ require "rspec"
4
+ require "viddl"
5
+
6
+ #RSpec.configure do |c|
7
+ # c.mock_with :rspec
8
+ #end
@@ -0,0 +1,77 @@
1
+ require "helper"
2
+
3
+ describe Viddl::System do
4
+
5
+ context ".validate" do
6
+
7
+ context "passes" do
8
+
9
+ it "execs command line" do
10
+ expect(Kernel).to(receive(:system).twice.and_return(true))
11
+ @result = Viddl::System.send(:validate)
12
+ end
13
+
14
+ end
15
+
16
+ context "fails" do
17
+
18
+ it "raises" do
19
+ expect(Kernel).to(receive(:system).and_return(nil))
20
+ expect {
21
+ Viddl::System.send(:validate)
22
+ }.to(raise_error(RuntimeError))
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ context ".validate_ffmpeg" do
30
+
31
+ context "passes" do
32
+
33
+ it "execs command line" do
34
+ expect(Kernel).to(receive(:system).and_return(true))
35
+ @result = Viddl::System.send(:validate_ffmpeg)
36
+ end
37
+
38
+ end
39
+
40
+ context "fails" do
41
+
42
+ it "raises" do
43
+ expect(Kernel).to(receive(:system).and_return(nil))
44
+ expect {
45
+ Viddl::System.send(:validate_ffmpeg)
46
+ }.to(raise_error(RuntimeError))
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ context ".validate_youtube_dl" do
54
+
55
+ context "passes" do
56
+
57
+ it "execs command line" do
58
+ expect(Kernel).to(receive(:system).and_return(true))
59
+ @result = Viddl::System.send(:validate_youtube_dl)
60
+ end
61
+
62
+ end
63
+
64
+ context "fails" do
65
+
66
+ it "raises" do
67
+ expect(Kernel).to(receive(:system).and_return(nil))
68
+ expect {
69
+ Viddl::System.send(:validate_youtube_dl)
70
+ }.to(raise_error(RuntimeError))
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,81 @@
1
+ require "helper"
2
+
3
+ describe Viddl::Video::Clip::Audio do
4
+
5
+ context ".options_formatted" do
6
+
7
+ context "with no options" do
8
+
9
+ it "returns audio = true" do
10
+ options = {}
11
+ opts = Viddl::Video::Clip::Audio.send(:options_formatted, options)
12
+ expect(opts).to(include(audio: true))
13
+ end
14
+
15
+ end
16
+
17
+ context "with audio = false" do
18
+
19
+ it "returns audio = false" do
20
+ options = {
21
+ audio: false
22
+ }
23
+ opts = Viddl::Video::Clip::Audio.send(:options_formatted, options)
24
+ expect(opts).to(include(audio: false))
25
+ end
26
+
27
+ end
28
+
29
+ context "with audio = true" do
30
+
31
+ it "returns audio = true" do
32
+ options = {
33
+ audio: true
34
+ }
35
+ opts = Viddl::Video::Clip::Audio.send(:options_formatted, options)
36
+ expect(opts).to(include(audio: true))
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ context ".args" do
44
+
45
+ context "with no options" do
46
+
47
+ it "return blank string" do
48
+ options = {}
49
+ args = Viddl::Video::Clip::Audio.send(:args, options)
50
+ expect(args).to(be_nil)
51
+ end
52
+
53
+ end
54
+
55
+ context "with audio=false" do
56
+
57
+ it "turns off audio" do
58
+ options = {
59
+ audio: false
60
+ }
61
+ args = Viddl::Video::Clip::Audio.send(:args, options)
62
+ expect(args).to(eq("-an"))
63
+ end
64
+
65
+ end
66
+
67
+ context "with audio=true" do
68
+
69
+ it "returns blank string" do
70
+ options = {
71
+ audio: true
72
+ }
73
+ args = Viddl::Video::Clip::Audio.send(:args, options)
74
+ expect(args).to(be_nil)
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ end