muvy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +54 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +172 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/muvy +5 -0
- data/lib/muvy.rb +5 -0
- data/lib/muvy/cli.rb +119 -0
- data/lib/muvy/download.rb +64 -0
- data/lib/muvy/errors.rb +9 -0
- data/lib/muvy/image.rb +125 -0
- data/lib/muvy/imagefolder.rb +56 -0
- data/lib/muvy/media.rb +62 -0
- data/lib/muvy/version.rb +3 -0
- data/lib/muvy/video.rb +60 -0
- data/muvy.gemspec +31 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 86d7b6a8c11d0ce8195f6d35bf9400028771c85a
|
4
|
+
data.tar.gz: a8c38340f7e011b1821f89f0e6ce7f040382dfd9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 209b168a3b90bd569a48504295a4a745adbaf4e08c46d742d73b9027d12624fa6327dc9369fe738fbaf8a65f27f4d8668b048279eefb7723c4bd930397677122
|
7
|
+
data.tar.gz: 9bbcaa4a1ff2a929d2d1983d493e387b3f9124c104a9e74e6c9b5b84c6670e6266eed611b69fad1097873c4d009b473a1c99214df8cdae231f8a11f6d62859d4
|
data/.gitignore
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/
|
10
|
+
/test/tmp/
|
11
|
+
/test/version_tmp/
|
12
|
+
/tmp/
|
13
|
+
|
14
|
+
# Used by dotenv library to load environment variables.
|
15
|
+
# .env
|
16
|
+
|
17
|
+
## Specific to RubyMotion:
|
18
|
+
.dat*
|
19
|
+
.repl_history
|
20
|
+
build/
|
21
|
+
*.bridgesupport
|
22
|
+
build-iPhoneOS/
|
23
|
+
build-iPhoneSimulator/
|
24
|
+
|
25
|
+
## Specific to RubyMotion (use of CocoaPods):
|
26
|
+
#
|
27
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
28
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
29
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
30
|
+
#
|
31
|
+
# vendor/Pods/
|
32
|
+
|
33
|
+
## Documentation cache and generated files:
|
34
|
+
/.yardoc/
|
35
|
+
/_yardoc/
|
36
|
+
/doc/
|
37
|
+
/rdoc/
|
38
|
+
|
39
|
+
## Environment normalization:
|
40
|
+
/.bundle/
|
41
|
+
/vendor/bundle
|
42
|
+
/lib/bundler/man/
|
43
|
+
|
44
|
+
# for a library or gem, you might want to ignore these files since the code is
|
45
|
+
# intended to run in multiple environments; otherwise, check them in:
|
46
|
+
Gemfile.lock
|
47
|
+
# .ruby-version
|
48
|
+
# .ruby-gemset
|
49
|
+
|
50
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
51
|
+
.rvmrc
|
52
|
+
|
53
|
+
# rspec failure tracking
|
54
|
+
.rspec_status
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Aaron Agarunov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
![muvy-header](https://i.imgur.com/Akc3Fh9.png)
|
2
|
+
|
3
|
+
**muvy** is a simple Ruby movie barcode generator.
|
4
|
+
You can feed it a youtube video, phone gallery, or any locally stored video files. It pulls most of the frames out, moves around the colors, and throws them back together in a neat montage.
|
5
|
+
|
6
|
+
------
|
7
|
+
* [Install](#install)
|
8
|
+
* [Notes](#notes)
|
9
|
+
* [Getting Started](#getting-started)
|
10
|
+
* [Usage](#usage)
|
11
|
+
* [Basics](#basics)
|
12
|
+
* [Options](#options)
|
13
|
+
* [Features](#features)
|
14
|
+
* [Examples](#examples)
|
15
|
+
* [Links](#links)
|
16
|
+
------
|
17
|
+
|
18
|
+
## Install
|
19
|
+
|
20
|
+
### Notes
|
21
|
+
|
22
|
+
* Currently version 0.2.0, with many [features still planned](#features).
|
23
|
+
|
24
|
+
### Getting Started
|
25
|
+
|
26
|
+
#### macOS
|
27
|
+
If you don't already have FFmpeg, ImageMagick, or youtube-dl installed, you can download all of them with [Homebrew](https://brew.sh/). You can use `ffmpeg -v`, `convert -v`, or `youtube-dl --version` at the terminal to check if you've already got them.
|
28
|
+
|
29
|
+
With Homewbrew, just bring up a terminal session and type:
|
30
|
+
```sh
|
31
|
+
$ brew install ffmpeg
|
32
|
+
$ brew install imagemagick
|
33
|
+
$ brew install youtube-dl
|
34
|
+
$ gem install muvy
|
35
|
+
```
|
36
|
+
|
37
|
+
#### Windows
|
38
|
+
1. You can [download Ruby here](https://rubyinstaller.org/).
|
39
|
+
2. Then you can grab [FFmpeg here](http://ffmpeg.zeranoe.com/builds/).
|
40
|
+
3. ..and then download [ImageMagick here](https://www.imagemagick.org/script/download.php#windows), **noting**:
|
41
|
+
* On the nth installation window, you need to check 2 boxes:
|
42
|
+
* [x] Add folder to your path variable
|
43
|
+
* [x] Download legacy binaries
|
44
|
+
4. And finally get [youtube-dl here](https://rg3.github.io/youtube-dl/download.html).
|
45
|
+
5. Then, you can install any gem like so:
|
46
|
+
```sh
|
47
|
+
$ gem install muvy
|
48
|
+
```
|
49
|
+
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
### Basics
|
54
|
+
|
55
|
+
| Type | Command: `muvy [Type] [Options]` | Support |
|
56
|
+
|--------|--------------------------------------------|-----------------------------------------------------------------------------------------------|
|
57
|
+
| URL | `muvy https://someVideoSite.com/someVidID` | [youtube-dl supported sites](https://rg3.github.io/youtube-dl/supportedsites.html) |
|
58
|
+
| Local | `muvy /Documents/Fave-Films/movie.mp4` | [FFmpeg supported formats](https://www.ffmpeg.org/general.html#File-Formats) |
|
59
|
+
| Folder | `muvy /Downloads/Phone-Backup-1/Photos` | [ImageMagick supported formats](https://www.imagemagick.org/script/formats.php) |
|
60
|
+
|
61
|
+
### Options
|
62
|
+
|
63
|
+
#### `-p, --path`
|
64
|
+
|
65
|
+
Optionally specify the path where your final image will be saved.
|
66
|
+
**Default**: your present working directory
|
67
|
+
|
68
|
+
#### `-s, --style`
|
69
|
+
Optionally specify currently supported styles: [solid](link) or [stretch](link).
|
70
|
+
**Default**: solid
|
71
|
+
|
72
|
+
#### `-g, --gradient`
|
73
|
+
Optionally add a gradient on top of the final image.
|
74
|
+
|
75
|
+
Choose one:
|
76
|
+
```
|
77
|
+
black:heavy black:medium black:light
|
78
|
+
white:heavy white:medium white:light
|
79
|
+
```
|
80
|
+
**Default**: none
|
81
|
+
|
82
|
+
[See examples](link)
|
83
|
+
|
84
|
+
#### `--arc`
|
85
|
+
Pass `--arc` to wrap all of the lines around a center point.
|
86
|
+
|
87
|
+
[See examples](link)
|
88
|
+
|
89
|
+
#### `--rotate`
|
90
|
+
Pass `--rotate` to rotate the final image 90 degrees, i.e. to draw horizontal lines,
|
91
|
+
where the top is the 'start' of your media file.
|
92
|
+
TODO - check if the last sentence is true
|
93
|
+
|
94
|
+
#### `-h, --height`
|
95
|
+
Optionally specify a custom height for the output image.
|
96
|
+
|
97
|
+
#### `--format`
|
98
|
+
Optionally force the download quality for `youtube-dl`.
|
99
|
+
This determines the height of your image when using `-s stretch` only if you didn't specify --height.
|
100
|
+
**Default**: 135 *(854x480 DASH at 24fps)*
|
101
|
+
|
102
|
+
See [youtube-dl docs on format selection](https://github.com/rg3/youtube-dl/blob/master/README.md#format-selection).
|
103
|
+
|
104
|
+
#### `--frame_rate`
|
105
|
+
Optionally specify the amount of frames to extract per second from the media.
|
106
|
+
This determines the width of the image.
|
107
|
+
|
108
|
+
You should run `muvy [..]` without this option once and check the stats printout
|
109
|
+
to get an idea of a better number.
|
110
|
+
For example, if the stats printout used "1.6 fps," passing `--frame_rate 3.2`
|
111
|
+
would double the amount of frames, lines, and subsequently the width.
|
112
|
+
|
113
|
+
> Setting this to an unreasonable number might cause hundreds of thousands
|
114
|
+
of files to be temporarily created in your system's temp files.
|
115
|
+
|
116
|
+
#### `--start` and `--end`
|
117
|
+
Optionally specify starting and ending times for processing videos.
|
118
|
+
If you only specify one of them, the other will default to the start/end.
|
119
|
+
|
120
|
+
[Examples](link)
|
121
|
+
|
122
|
+
### Features
|
123
|
+
- [x] Accepting image galleries, local videos, and online videos
|
124
|
+
- [x] Specifying start & end times for frame extraction
|
125
|
+
- [x] Vertical lines
|
126
|
+
- [x] Horizontal lines
|
127
|
+
- [x] Stretched output (average of each line of pixels)
|
128
|
+
- [x] Arc distortion
|
129
|
+
- [ ] Spotmap output ('QR' code)
|
130
|
+
- [ ] Slit scan output
|
131
|
+
- [ ] 'Bedforms' output
|
132
|
+
- [ ] Dominant color algorithms
|
133
|
+
- [ ] via ImageMagick histograms
|
134
|
+
- [ ] via k-means clustering
|
135
|
+
- [ ] Fade to black or white on edges
|
136
|
+
- [ ] Pixel thickness control
|
137
|
+
- [ ] Colorspace adjustments
|
138
|
+
- [ ] Accept music files
|
139
|
+
- [ ] Generate audio waveforms
|
140
|
+
- [ ] Randomize waveform colors
|
141
|
+
- [ ] Presets
|
142
|
+
|
143
|
+
## Examples
|
144
|
+
|
145
|
+
## Troubleshooting
|
146
|
+
|
147
|
+
Make sure you can access `ffmpeg -v`, `magick -v`, and `youtube-dl --version` on the command line. If you can't, you likely have to update your existing PATH environment variable [like this](https://video.stackexchange.com/questions/20495/how-do-i-set-up-and-use-ffmpeg-in-windows).
|
148
|
+
|
149
|
+
You might also want to update all three binaries.
|
150
|
+
|
151
|
+
If it's not working out, [I've linked more generators](#links) and methods that you can try out, most of them depending on some combination of `ffmpeg` and `ImageMagick`.
|
152
|
+
|
153
|
+
## Links
|
154
|
+
* Binaries · Gems
|
155
|
+
* [FFmpeg](https://www.ffmpeg.org/documentation.html) · [Streamio FFmpeg](https://github.com/streamio/streamio-ffmpeg)
|
156
|
+
* [ImageMagick](https://www.imagemagick.org/script/command-line-options.php) · [MiniMagick](https://github.com/minimagick/minimagick)
|
157
|
+
* [youtube-dl](https://github.com/rg3/youtube-dl) · [youtube-dl.rb](https://github.com/layer8x/youtube-dl.rb)
|
158
|
+
* Other Works & Inspirations
|
159
|
+
* [Zach Whalen's Barcoder](http://zachwhalen.net/pg/barcoder/)
|
160
|
+
* [arcanesanctum generator](http://arcanesanctum.net/movie-barcode-generator/)
|
161
|
+
* [moviebarcode on tumblr](http://moviebarcode.tumblr.com/)
|
162
|
+
* [/u/etherealpenguin on reddit](https://www.reddit.com/r/dataisbeautiful/comments/3rb8zi/the_average_color_of_every_frame_of_a_given_movie/)
|
163
|
+
* [Colors of Motion](http://thecolorsofmotion.com/films)
|
164
|
+
* Slit scanning
|
165
|
+
* [informal catalogue of research on this topic by Levin](http://www.flong.com/texts/lists/slit_scan/)
|
166
|
+
* K-means clustering as dominant color algorithms
|
167
|
+
* [k-means clustering on wikipedia](link)
|
168
|
+
* ImageMagick Histograms
|
169
|
+
* [Sparse color docs](http://www.imagemagick.org/Usage/canvas/#sparse-color)
|
170
|
+
* [Stackoverflow discussion (1)](https://stackoverflow.com/questions/40381273/apply-gradient-mask-on-image-that-already-has-transparency-with-imagemagick)
|
171
|
+
* ImageMagick support
|
172
|
+
* tremendous thank you to [fmw](http://www.fmwconcepts.com/imagemagick/index.php) & [snibgo](http://im.snibgo.com/index.htm)
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "muvy"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/muvy
ADDED
data/lib/muvy.rb
ADDED
data/lib/muvy/cli.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'muvy/media'
|
2
|
+
require 'muvy/errors'
|
3
|
+
require 'slop'
|
4
|
+
|
5
|
+
module Muvy
|
6
|
+
class CLI
|
7
|
+
attr_reader :media, :options
|
8
|
+
|
9
|
+
def start
|
10
|
+
parse
|
11
|
+
handle_media
|
12
|
+
handle_path
|
13
|
+
convert_options
|
14
|
+
read_media
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse
|
18
|
+
@options = Slop.parse do |o|
|
19
|
+
o.banner = "Usage: muvy [media link, file, or file path] [options]"
|
20
|
+
|
21
|
+
o.separator ""
|
22
|
+
o.separator "Optional adjustments:"
|
23
|
+
o.string "-p", "--path", "Directory to save final images, " +
|
24
|
+
"\n\t\t\s\s\s\sDefault: your pwd → #{Dir.pwd}",
|
25
|
+
default: Dir.pwd
|
26
|
+
o.string "-s", "--style", "Choose image style: solid, stretch " +
|
27
|
+
"\n\t\t\s\s\s\sDefault: solid",
|
28
|
+
default: "solid"
|
29
|
+
o.string "-g", "--gradient", "Add a gradient over the final image" +
|
30
|
+
"\n\t\t\s\s\s\sChoose one from: black:heavy black:medium black:light" +
|
31
|
+
"\n\t\t\s\s\s\s\t\t\s\s\s\s\swhite:heavy white:medium white:light" +
|
32
|
+
"\n\t\t\s\s\s\sDefault: none"
|
33
|
+
o.boolean "--arc", "Wrap all of the lines around a center point"
|
34
|
+
o.boolean "-r", "--rotate", "Image will have horizontal lines"
|
35
|
+
o.integer "-h", "--height", "Custom height of the final image"
|
36
|
+
o.string "--start", "Custom video start time"
|
37
|
+
o.string "--end", "Custom video end time"
|
38
|
+
o.string "--format", "Force youtube-dl to use a specific video quality"
|
39
|
+
o.string "--frame_rate", <<~FPS
|
40
|
+
Set a custom frame rate. Be careful!
|
41
|
+
\t\t\s\s\s\sSetting this to a high number might cause hundreds of
|
42
|
+
\t\t\s\s\s\sthousands of images to be generated in your sytem's temp dir.
|
43
|
+
\t\t\s\s\s\sThe specific frame_rate used by default for your file is printed
|
44
|
+
\t\t\s\s\s\safter generation. You can use that number to make a reasonable change.
|
45
|
+
FPS
|
46
|
+
|
47
|
+
o.separator "More:"
|
48
|
+
o.on "--help", "Shows this usage page" do
|
49
|
+
abort o.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
o.on "-v", "--version", "Displays the version" do
|
53
|
+
abort "muvy version #{VERSION}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue Slop::Error => e
|
57
|
+
abort <<~ERROR
|
58
|
+
#{e}.
|
59
|
+
Type `muvy --help` to see options, or visit the
|
60
|
+
github repo for extensive usage examples.
|
61
|
+
ERROR
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def handle_media
|
67
|
+
@media = options.arguments.shift
|
68
|
+
raise Muvy::Errors::NoMediaInput if media.nil?
|
69
|
+
rescue => e
|
70
|
+
abort input_error(e)
|
71
|
+
end
|
72
|
+
|
73
|
+
# if -path was specified but is invalid, raise an error
|
74
|
+
# if -path was not specified, it was set to the pwd by Slop defaults
|
75
|
+
def handle_path
|
76
|
+
raise Muvy::Errors::InvalidPathOption unless File.directory?(options[:path])
|
77
|
+
rescue => e
|
78
|
+
abort "#{e}: You specified a non-existent path '#{options[:path]}'"
|
79
|
+
end
|
80
|
+
|
81
|
+
# FileUtils.remove_entry_secure is called before ::mktmpdir returns
|
82
|
+
def read_media
|
83
|
+
Dir.mktmpdir do |tmp_dir|
|
84
|
+
options[:tmp_dir] = tmp_dir
|
85
|
+
puts "Using #{tmp_dir} to store jobs locally..."
|
86
|
+
|
87
|
+
Media.new(media, options).run
|
88
|
+
end
|
89
|
+
rescue => e
|
90
|
+
abort media_error(e)
|
91
|
+
ensure
|
92
|
+
puts "#{options[:tmp_dir]} was erased." if options[:tmp_dir]
|
93
|
+
end
|
94
|
+
|
95
|
+
def convert_options
|
96
|
+
@options = options.to_hash # finalize
|
97
|
+
end
|
98
|
+
|
99
|
+
def input_error(e)
|
100
|
+
<<~INPUT_ERROR
|
101
|
+
#{e}
|
102
|
+
You forgot to enter a URL, file, or folder with images.
|
103
|
+
|
104
|
+
#{options}
|
105
|
+
INPUT_ERROR
|
106
|
+
end
|
107
|
+
|
108
|
+
def media_error(e)
|
109
|
+
<<~MEDIA_ERROR
|
110
|
+
#{e}
|
111
|
+
Media is unrecognized.
|
112
|
+
The input was not a valid URL, file, or folder with images.
|
113
|
+
|
114
|
+
#{options}
|
115
|
+
MEDIA_ERROR
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'youtube-dl.rb'
|
4
|
+
|
5
|
+
module Muvy
|
6
|
+
class Download
|
7
|
+
attr_reader :media, :options, :settings
|
8
|
+
|
9
|
+
def initialize(media, options = {})
|
10
|
+
@media = media
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@settings = merge_settings
|
16
|
+
|
17
|
+
download_video
|
18
|
+
send_video
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def download_video
|
24
|
+
vid = YoutubeDL.download(media, settings)
|
25
|
+
add_options(vid)
|
26
|
+
|
27
|
+
puts <<~DOWNLOADED
|
28
|
+
Download complete.
|
29
|
+
Video title: #{vid.information[:title]}
|
30
|
+
Video URL: #{vid.information[:webpage_url]}
|
31
|
+
Video format: #{vid.information[:format]} saved as #{vid.information[:ext]}
|
32
|
+
as #{vid.information[:_filename]}
|
33
|
+
File size: #{(vid.information[:filesize] / 1.024e6).round(2)} MB.
|
34
|
+
DOWNLOADED
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add important settings to @options hash for use by FFmpeg
|
38
|
+
def add_options(vid)
|
39
|
+
options[:fps] = vid.information[:fps]
|
40
|
+
options[:media_length] = vid.information[:duration]
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_video
|
44
|
+
Video.new(settings[:output], options).run if File.exists?(settings[:output])
|
45
|
+
rescue => e
|
46
|
+
puts e
|
47
|
+
end
|
48
|
+
|
49
|
+
# defaults holds default values
|
50
|
+
# options holds command-line arguments
|
51
|
+
# settings merges defaults with options where appropriate
|
52
|
+
def merge_settings
|
53
|
+
defaults = {
|
54
|
+
continue: false,
|
55
|
+
format: 135,
|
56
|
+
output: "#{options[:tmp_dir]}/" +
|
57
|
+
Time.now.strftime("%d-%m-%Y-%H%M%S") +
|
58
|
+
".mp4"
|
59
|
+
}
|
60
|
+
|
61
|
+
@settings = defaults.merge!(options.select { |k, v| defaults.key?(k) && v })
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/muvy/errors.rb
ADDED
data/lib/muvy/image.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require "mini_magick"
|
2
|
+
|
3
|
+
module Muvy
|
4
|
+
class Image
|
5
|
+
attr_reader :media, :options
|
6
|
+
|
7
|
+
def initialize(media, options)
|
8
|
+
@media = media
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
montage
|
14
|
+
modification
|
15
|
+
printout
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def montage
|
21
|
+
MiniMagick::Tool::Montage.new do |montage|
|
22
|
+
montage << "#{media}/thumb*.png"
|
23
|
+
montage.mode("Concatenate")
|
24
|
+
montage.tile("x1")
|
25
|
+
options[:img] =
|
26
|
+
File.absolute_path(options[:path]) +
|
27
|
+
"/muvy-" +
|
28
|
+
Time.now.strftime('%d-%m-%H%M%S') +
|
29
|
+
".png"
|
30
|
+
montage << options[:img]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def modification
|
35
|
+
image = MiniMagick::Image.new(options[:img])
|
36
|
+
|
37
|
+
# TODO: Programatically trigger the methods based on options `nil` status
|
38
|
+
resize(image)
|
39
|
+
gradient(image)
|
40
|
+
rotate(image)
|
41
|
+
arc
|
42
|
+
end
|
43
|
+
|
44
|
+
# Arbitrary default height 720
|
45
|
+
def resize(image)
|
46
|
+
if options[:style] != "stretch"
|
47
|
+
image.resize "#{image.width}x#{options[:height] ||= 720}!"
|
48
|
+
elsif options[:height] # and style is stretch
|
49
|
+
image.resize "#{image.width}x#{options[:height]}!"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def gradient(image)
|
54
|
+
if options[:gradient]
|
55
|
+
choice = options[:gradient].split(":")
|
56
|
+
choice_path = "#{options[:tmp_dir]}/muvy-gradient.png"
|
57
|
+
|
58
|
+
weights = {
|
59
|
+
"heavy" => 1.6,
|
60
|
+
"medium" => 0.9,
|
61
|
+
"light" => 0.5
|
62
|
+
}
|
63
|
+
|
64
|
+
MiniMagick::Tool::Convert.new do |cmd|
|
65
|
+
cmd.size("#{image.width}x#{image.height}")
|
66
|
+
cmd << "gradient:"
|
67
|
+
cmd << "-function" << "Polynomial" << "-4,4,.1"
|
68
|
+
cmd << "-evaluate" << "Pow" << weights[choice[1]]
|
69
|
+
cmd.negate unless choice[0] == "white"
|
70
|
+
cmd.stack do |stack|
|
71
|
+
stack.merge! ["+clone", "-fill", "Black", "-colorize", "100"]
|
72
|
+
end
|
73
|
+
cmd << "+swap"
|
74
|
+
cmd << "-alpha" << "Off"
|
75
|
+
cmd.compose("CopyOpacity")
|
76
|
+
cmd << "-composite"
|
77
|
+
cmd.negate unless choice[0] == "black"
|
78
|
+
cmd << choice_path
|
79
|
+
end
|
80
|
+
|
81
|
+
gradient_image = MiniMagick::Image.new(choice_path)
|
82
|
+
apply_gradient = image.composite(gradient_image) do |composite|
|
83
|
+
composite.compose "Over"
|
84
|
+
end
|
85
|
+
|
86
|
+
apply_gradient.write(options[:img])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def rotate(image)
|
91
|
+
image.rotate(90) if options[:rotate]
|
92
|
+
end
|
93
|
+
|
94
|
+
def arc
|
95
|
+
if options[:arc]
|
96
|
+
MiniMagick::Tool::Convert.new do |cmd|
|
97
|
+
cmd << options[:img]
|
98
|
+
cmd << "-gravity" << "center"
|
99
|
+
cmd << "+repage"
|
100
|
+
cmd << "-virtual-pixel" << "Transparent"
|
101
|
+
cmd << "-distort" << "Arc" << "366"
|
102
|
+
cmd << options[:img]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def printout
|
108
|
+
image = MiniMagick::Image.open(options[:img])
|
109
|
+
puts <<~PRINTOUT_IMG
|
110
|
+
Saved to #{options[:img]}.
|
111
|
+
Width: #{image.dimensions[0]}, Height: #{image.dimensions[1]}
|
112
|
+
Type: #{image.type}
|
113
|
+
Colorspace: #{image.colorspace}
|
114
|
+
PRINTOUT_IMG
|
115
|
+
|
116
|
+
puts <<~PRINTOUT_VIDEO if options[:fps]
|
117
|
+
|
118
|
+
Thumbnails made at #{options[:frame_rate] ? options[:frame_rate] : (options[:fps] / (options[:media_length]**(1 / 1.99))).round(4) } frames per second.
|
119
|
+
Original video duration was #{options[:media_length]}.
|
120
|
+
#{'Start time: ' + options[:start].to_s if options[:start]}
|
121
|
+
#{'End time: ' + options[:end].to_s if options[:end]}
|
122
|
+
PRINTOUT_VIDEO
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "mini_magick"
|
2
|
+
|
3
|
+
module Muvy
|
4
|
+
class ImageFolder
|
5
|
+
attr_reader :media, :options, :photos
|
6
|
+
|
7
|
+
def initialize(media, options)
|
8
|
+
@media = media
|
9
|
+
@options = options
|
10
|
+
@photos = add_photos
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
average
|
15
|
+
send_thumbs
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def add_photos
|
21
|
+
Dir.glob("#{media}/*")
|
22
|
+
end
|
23
|
+
|
24
|
+
def average
|
25
|
+
# Store the average photos here
|
26
|
+
Dir.mkdir("#{options[:tmp_dir]}/muvy-img-folder/")
|
27
|
+
|
28
|
+
photos.each_with_index do |photo, i|
|
29
|
+
MiniMagick::Tool::Convert.new do |convert|
|
30
|
+
convert << photo
|
31
|
+
convert.resize("1x#{height}!")
|
32
|
+
convert << "#{options[:tmp_dir]}/muvy-img-folder/thumb#{i.to_s.rjust(6, '0')}.png"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def height
|
38
|
+
if options[:style] == "stretch"
|
39
|
+
options[:height] ? options[:height] : (abort <<~NO_HEIGHT)
|
40
|
+
You specified an image folder and 'stretch' with the --style flag.
|
41
|
+
You should also specify a uniform height with the --height option.
|
42
|
+
NO_HEIGHT
|
43
|
+
else
|
44
|
+
# Arbitrary default height 720
|
45
|
+
options[:height] ||= 720
|
46
|
+
|
47
|
+
# 1x1 to get average colors ('solid' style)
|
48
|
+
1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_thumbs
|
53
|
+
Image.new("#{options[:tmp_dir]}/muvy-img-folder", options).run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/muvy/media.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'muvy/download'
|
2
|
+
require 'muvy/video'
|
3
|
+
require 'muvy/imagefolder'
|
4
|
+
require 'muvy/errors'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Muvy
|
8
|
+
class Media
|
9
|
+
attr_reader :media, :options, :type
|
10
|
+
|
11
|
+
def initialize(media, options)
|
12
|
+
@media = media
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
get_type
|
18
|
+
send_type
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def send_type
|
24
|
+
Muvy.const_get(type.to_s).new(media, options).run
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks the first argument (store in :media, access via getter).
|
28
|
+
# Determines if it should be read by Download (type - online URL),
|
29
|
+
# by Video (type - local media), or by Image (type - local image files).
|
30
|
+
# Unrecognized inputs invoke the usage heredocs and banners at `CLI`
|
31
|
+
def get_type
|
32
|
+
if valid_url?(media)
|
33
|
+
@type = :Download
|
34
|
+
elsif file_exists?(media)
|
35
|
+
@type = :Video
|
36
|
+
elsif path_exists?(media)
|
37
|
+
@type = :ImageFolder
|
38
|
+
else
|
39
|
+
raise Muvy::Errors::InvalidMediaInput
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Accepts a string that behaves like a URL.
|
44
|
+
# The URL must have a valid URI scheme (e.g. http) to differentiate
|
45
|
+
# it from file paths. URI module doesn't recognize #host without it.
|
46
|
+
def valid_url?(url)
|
47
|
+
encoded_url = URI.escape(url)
|
48
|
+
parsed_url = URI.parse(encoded_url)
|
49
|
+
!parsed_url.host.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
def file_exists?(file)
|
53
|
+
file_path = File.absolute_path(file)
|
54
|
+
File.file?(file_path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def path_exists?(path)
|
58
|
+
full_path = File.absolute_path(path)
|
59
|
+
File.directory?(full_path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/muvy/version.rb
ADDED
data/lib/muvy/video.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'streamio-ffmpeg'
|
2
|
+
require 'muvy/image'
|
3
|
+
|
4
|
+
module Muvy
|
5
|
+
class Video
|
6
|
+
attr_reader :media, :options, :settings, :vid
|
7
|
+
|
8
|
+
def initialize(media, options = {})
|
9
|
+
@media = media
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
@vid = FFMPEG::Movie.new(media)
|
15
|
+
@settings = merge_settings
|
16
|
+
|
17
|
+
thumbs
|
18
|
+
send_thumbs
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Add important settings to @options hash for use by FFmpeg
|
24
|
+
def add_options
|
25
|
+
options[:fps] = vid.frame_rate
|
26
|
+
options[:media_length] = vid.duration
|
27
|
+
end
|
28
|
+
|
29
|
+
# defaults holds default values
|
30
|
+
# options holds command-line arguments
|
31
|
+
# settings merges defaults with options where appropriate
|
32
|
+
def merge_settings
|
33
|
+
add_options
|
34
|
+
|
35
|
+
defaults = {
|
36
|
+
vframes: (options[:fps] * options[:media_length]).floor,
|
37
|
+
frame_rate: options[:fps] / (options[:media_length]**(1 / 1.99)),
|
38
|
+
custom: %W{
|
39
|
+
-vf scale=1:#{options[:style] == 'stretch' ? vid.height : 1}
|
40
|
+
-ss #{options[:start] ? options[:start] : 0}
|
41
|
+
-to #{options[:end] ? options[:end] : options[:media_length]}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
@settings = defaults.merge!(options.select { |k, v| defaults.key?(k) && v })
|
46
|
+
end
|
47
|
+
|
48
|
+
def thumbs
|
49
|
+
vid.screenshot(
|
50
|
+
"#{options[:tmp_dir]}/thumb%06d.png",
|
51
|
+
settings,
|
52
|
+
validate: false
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def send_thumbs
|
57
|
+
Image.new(options[:tmp_dir], options).run
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/muvy.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "muvy/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "muvy"
|
8
|
+
spec.version = Muvy::VERSION
|
9
|
+
spec.authors = ["agarun"]
|
10
|
+
spec.email = ["19801205+agarun@users.noreply.github.com"]
|
11
|
+
|
12
|
+
spec.summary = "Ruby movie barcode generator."
|
13
|
+
spec.homepage = "https://github.com/agarun/ruby-muvy"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_runtime_dependency "youtube-dl.rb", "~> 0"
|
24
|
+
spec.add_runtime_dependency "streamio-ffmpeg", "~> 3.0"
|
25
|
+
spec.add_runtime_dependency "mini_magick", "~> 4.8"
|
26
|
+
spec.add_runtime_dependency "slop", "~> 4.6"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: muvy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- agarun
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: youtube-dl.rb
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: streamio-ffmpeg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mini_magick
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: slop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.6'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.15'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.15'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- 19801205+agarun@users.noreply.github.com
|
114
|
+
executables:
|
115
|
+
- muvy
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".rspec"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/console
|
127
|
+
- bin/setup
|
128
|
+
- exe/muvy
|
129
|
+
- lib/muvy.rb
|
130
|
+
- lib/muvy/cli.rb
|
131
|
+
- lib/muvy/download.rb
|
132
|
+
- lib/muvy/errors.rb
|
133
|
+
- lib/muvy/image.rb
|
134
|
+
- lib/muvy/imagefolder.rb
|
135
|
+
- lib/muvy/media.rb
|
136
|
+
- lib/muvy/version.rb
|
137
|
+
- lib/muvy/video.rb
|
138
|
+
- muvy.gemspec
|
139
|
+
homepage: https://github.com/agarun/ruby-muvy
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
metadata: {}
|
143
|
+
post_install_message:
|
144
|
+
rdoc_options: []
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 2.5.1
|
160
|
+
signing_key:
|
161
|
+
specification_version: 4
|
162
|
+
summary: Ruby movie barcode generator.
|
163
|
+
test_files: []
|