audio_to_youtube 1.0.0 → 1.0.1
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 +4 -4
- data/README.rdoc +30 -0
- metadata +3 -2
- data/lib/audio_to_youtube.rb +0 -326
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0e35ed366d88adb022bc5936ad38c8815b46d71
|
4
|
+
data.tar.gz: c81453f21b04751372ffbbd3b79be4527e3cc3ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 354cdcedabc8cc38e4168c2117cc96b37a438e13b8f2172fcbbb1049cf49018fa5746a30d386d743392a95e75f8c128007fff1bae933171fce17b78ec0cf7500
|
7
|
+
data.tar.gz: efde833ca0241f0b6a11415d7e5e3f1313746c608be69b62ac8335823130262c48febe40ea8862cc048c1a8a5fbf9f95ab7092881017dd2cbedd2670c43d36a6
|
data/README.rdoc
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
= Audio to youtube
|
2
|
+
Convert audio with background image to mp4 for uploading to youtube using ffmpeg.
|
3
|
+
|
4
|
+
Based on a python script: https://github.com/ianharmon/mp3-to-youtube
|
5
|
+
|
6
|
+
== Install
|
7
|
+
|
8
|
+
gem install audio_to_youtube
|
9
|
+
|
10
|
+
== Usage
|
11
|
+
|
12
|
+
require 'audio_to_youtube'
|
13
|
+
AudioToYoutube.generate audio, background, out
|
14
|
+
|
15
|
+
|
16
|
+
== Description
|
17
|
+
* *audio*: audio file name with path
|
18
|
+
|
19
|
+
* *background*: static background for the output video (will use *bg.jpg* if not specified)
|
20
|
+
|
21
|
+
* *out*: output filename (will use *out.mp4* as default)
|
22
|
+
|
23
|
+
== License
|
24
|
+
Copyright (C) 2015 *Brian Doan*
|
25
|
+
|
26
|
+
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
27
|
+
|
28
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
29
|
+
|
30
|
+
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: audio_to_youtube
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Doan
|
@@ -16,7 +16,7 @@ executables: []
|
|
16
16
|
extensions: []
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
|
-
-
|
19
|
+
- README.rdoc
|
20
20
|
homepage: http://rubygems.org/gems/audio_to_youtube
|
21
21
|
licenses:
|
22
22
|
- MIT
|
@@ -42,3 +42,4 @@ signing_key:
|
|
42
42
|
specification_version: 4
|
43
43
|
summary: Convert audio with background image to mp4 for uploading to youtube
|
44
44
|
test_files: []
|
45
|
+
has_rdoc:
|
data/lib/audio_to_youtube.rb
DELETED
@@ -1,326 +0,0 @@
|
|
1
|
-
class AudioToYoutube
|
2
|
-
@outputFile = 'out.mp4'
|
3
|
-
@tempFolder = 'temp'
|
4
|
-
@tempBg = 'bg.jpg'
|
5
|
-
@backgroundBase = 'media/bg-720.png'
|
6
|
-
@ffmpegDir = 'setme'
|
7
|
-
@imagemagickDir = 'setme'
|
8
|
-
private
|
9
|
-
def self.exitWithError(message)
|
10
|
-
puts ('\nError: ' + message)
|
11
|
-
end
|
12
|
-
|
13
|
-
################################################################################
|
14
|
-
# Generates the background art for the video
|
15
|
-
def self.generateBackground(audio, backgroundBase, output, blur, text)
|
16
|
-
|
17
|
-
blur ||= false
|
18
|
-
text ||= ""
|
19
|
-
|
20
|
-
# extract the album art
|
21
|
-
tempArt = "#{Dir.pwd}/art.png"
|
22
|
-
cmd = ["ffmpeg"]
|
23
|
-
cmd += ['-y']
|
24
|
-
cmd += ["-i #{audio}"]
|
25
|
-
cmd += ['-acodec none']
|
26
|
-
cmd += [tempArt]
|
27
|
-
puts 'Extracting album art...'
|
28
|
-
begin
|
29
|
-
unless Dir.exist?(@tempFolder) ||
|
30
|
-
Dir.mkdir(@tempFolder)
|
31
|
-
puts p cmd.join(' ')
|
32
|
-
end
|
33
|
-
rescue
|
34
|
-
exitWithError('Couldn\'t find album art in the audio file. ' +
|
35
|
-
'Use the -background option to select an image file.')
|
36
|
-
end
|
37
|
-
|
38
|
-
# now, build the background image
|
39
|
-
cmd = ["imagick"]
|
40
|
-
cmd += [backgroundBase]
|
41
|
-
|
42
|
-
# draw the blurred album art background
|
43
|
-
if blur
|
44
|
-
cmd += ['-gravity', 'center']
|
45
|
-
cmd += ['(']
|
46
|
-
cmd += [tempArt]
|
47
|
-
cmd += ['-resize', '1500x']
|
48
|
-
cmd += ['-modulate', '90']
|
49
|
-
cmd += ['-blur', '0x20']
|
50
|
-
cmd += [')']
|
51
|
-
cmd += ['-composite']
|
52
|
-
end
|
53
|
-
|
54
|
-
# set offsets depending on whether text is drawn
|
55
|
-
gravity = 'center'
|
56
|
-
offset = []
|
57
|
-
shadowOffset = []
|
58
|
-
unless text.empty?
|
59
|
-
gravity = 'west'
|
60
|
-
offset = ['-geometry', '+60+0']
|
61
|
-
shadowOffset = ['-geometry', '+30+0']
|
62
|
-
end
|
63
|
-
|
64
|
-
# set some settings used twice
|
65
|
-
mainArtFront = ['(']
|
66
|
-
mainArtFront += [tempArt]
|
67
|
-
mainArtFront += ['-resize', 'x720']
|
68
|
-
|
69
|
-
mainArtBack = [')']
|
70
|
-
mainArtBack += ['-gravity', gravity]
|
71
|
-
mainArtBack += ['-composite']
|
72
|
-
|
73
|
-
# draw shadow of the main album art
|
74
|
-
cmd += mainArtFront
|
75
|
-
cmd += ['-background', 'black']
|
76
|
-
cmd += ['-shadow', '50x15+0+0']
|
77
|
-
cmd += shadowOffset
|
78
|
-
cmd += mainArtBack
|
79
|
-
|
80
|
-
# draw the main album art
|
81
|
-
cmd += mainArtFront
|
82
|
-
cmd += offset
|
83
|
-
cmd += mainArtBack
|
84
|
-
|
85
|
-
# add in the track metadata text
|
86
|
-
unless text.empty?
|
87
|
-
labelText = text[0]
|
88
|
-
for i in 1..text.length
|
89
|
-
labelText += '\\n\\n' + text[i]
|
90
|
-
end
|
91
|
-
|
92
|
-
# set some settings used twice
|
93
|
-
labelFront = ['(']
|
94
|
-
labelFront += ['-size', '440x400']
|
95
|
-
labelFront += ['-background', 'none']
|
96
|
-
labelFront += ['-gravity', 'center']
|
97
|
-
labelFront += ['-font', 'Segoe-UI-Semibold']
|
98
|
-
|
99
|
-
labelMid = ['caption:' + labelText]
|
100
|
-
|
101
|
-
labelBack = ['-geometry', '+25+0']
|
102
|
-
labelBack += [')']
|
103
|
-
labelBack += ['-gravity', 'east']
|
104
|
-
labelBack += ['-composite']
|
105
|
-
|
106
|
-
# draw text shadow
|
107
|
-
cmd += labelFront.join(' ')
|
108
|
-
cmd += ['-fill', '#00000090']
|
109
|
-
cmd += labelMid.join(' ')
|
110
|
-
cmd += ['-blur', '0x6']
|
111
|
-
cmd += labelBack.join(' ')
|
112
|
-
|
113
|
-
# draw text itself
|
114
|
-
cmd += labelFront.join(' ')
|
115
|
-
cmd += ['-fill', 'white']
|
116
|
-
cmd += labelMid.join(' ')
|
117
|
-
cmd += labelBack.join(' ')
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
# put it all together
|
122
|
-
cmd += [output]
|
123
|
-
puts 'Generating video background...'
|
124
|
-
begin
|
125
|
-
puts Open3.capture3(cmd.join(' '))
|
126
|
-
rescue
|
127
|
-
exitWithError('Something went wrong when generating the background.')
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
################################################################################
|
132
|
-
# Splits text over multiple lines
|
133
|
-
def self.splitText(text, maximumLength)
|
134
|
-
|
135
|
-
maximumLength ||= 20
|
136
|
-
words = text.split(' ')
|
137
|
-
output = ''
|
138
|
-
currentLine = ''
|
139
|
-
words.each do |word|
|
140
|
-
if currentLine == ''
|
141
|
-
currentLine = word
|
142
|
-
else
|
143
|
-
temp = currentLine + ' ' + word
|
144
|
-
# with next word, still under maximum length - add normally
|
145
|
-
if len(temp) < maximumLength
|
146
|
-
currentLine = temp
|
147
|
-
# next word is longer than the maximum length - it gets its own line
|
148
|
-
elsif len(word) > maximumLength
|
149
|
-
if output != ''
|
150
|
-
output += r'\n'
|
151
|
-
end
|
152
|
-
output += currentLine
|
153
|
-
currentLine = ''
|
154
|
-
if output != ''
|
155
|
-
output += r'\n'
|
156
|
-
end
|
157
|
-
output += word
|
158
|
-
# we've overrun the maximum length - start a new line with the word
|
159
|
-
else
|
160
|
-
if output != ''
|
161
|
-
output += 'r\n'
|
162
|
-
end
|
163
|
-
output += currentLine
|
164
|
-
currentLine = word
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
if output != ''
|
169
|
-
output += r'\n'
|
170
|
-
end
|
171
|
-
output += currentLine
|
172
|
-
#print(output)
|
173
|
-
return output
|
174
|
-
end
|
175
|
-
|
176
|
-
################################################################################
|
177
|
-
# Retrieves the title and artist from the file
|
178
|
-
def self.getMetadata(file)
|
179
|
-
cmd = ["ffprobe"]
|
180
|
-
cmd += ['-i', file]
|
181
|
-
print('Extracting metadata...')
|
182
|
-
lines = ""
|
183
|
-
begin
|
184
|
-
output = p cmd.join(' ')
|
185
|
-
lines = output.split('\n')
|
186
|
-
rescue
|
187
|
-
exitWithError('Couldn\'t extract metadata from the file.')
|
188
|
-
end
|
189
|
-
|
190
|
-
title = ""
|
191
|
-
artist = ""
|
192
|
-
|
193
|
-
lines.each do |line|
|
194
|
-
match = line.scan(/^\s*(.*?)\s*: (.*?)\s*@/)
|
195
|
-
if match.length > 0
|
196
|
-
pair = match[0]
|
197
|
-
|
198
|
-
if len(pair) == 2
|
199
|
-
if pair[0] == 'title' and pair[1].length > 0
|
200
|
-
if title == "" # or len(title) < len(pair[1]):
|
201
|
-
title = pair[1]
|
202
|
-
print("Found title: " + title)
|
203
|
-
end
|
204
|
-
elsif pair[0] == 'artist' and pair[1].length > 0
|
205
|
-
if artist == "" # or len(artist) < len(pair[1]):
|
206
|
-
artist = pair[1]
|
207
|
-
print("Found artist: " + artist)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
return artist, title
|
214
|
-
end
|
215
|
-
################################################################################
|
216
|
-
# Creates the final video suitable for uploading
|
217
|
-
def self.encode(audio, background, output)
|
218
|
-
cmd = ["ffmpeg"]
|
219
|
-
cmd += ['-y']
|
220
|
-
cmd += ['-loop', '1']
|
221
|
-
cmd += ['-r', '1']
|
222
|
-
cmd += ['-i', background]
|
223
|
-
cmd += ['-i', audio]
|
224
|
-
cmd += ['-c:v', 'libx264']
|
225
|
-
cmd += ['-preset', 'veryfast']
|
226
|
-
cmd += ['-tune', 'stillimage']
|
227
|
-
cmd += ['-crf', '15']
|
228
|
-
cmd += ['-pix_fmt', 'yuv420p']
|
229
|
-
cmd += ['-strict', 'experimental']
|
230
|
-
cmd += ['-c:a', 'aac']
|
231
|
-
cmd += ['-b:a', '256k']
|
232
|
-
cmd += ['-shortest']
|
233
|
-
cmd += ['-threads', '0']
|
234
|
-
cmd += [output]
|
235
|
-
print('Encoding video...')
|
236
|
-
begin
|
237
|
-
system(cmd.join(' '))
|
238
|
-
rescue
|
239
|
-
exitWithError('Something went wrong when encoding the video.')
|
240
|
-
end
|
241
|
-
print('\nDone. Saved output as "%s".' % @outputFile)
|
242
|
-
end
|
243
|
-
|
244
|
-
################################################################################
|
245
|
-
# Set library locations
|
246
|
-
def self.setLibraryLocations()
|
247
|
-
|
248
|
-
# set FFmpeg location
|
249
|
-
if @ffmpegDir == 'setme' or @ffmpegDir == ''
|
250
|
-
@ffmpegDir = '/usr/local/bin'
|
251
|
-
end
|
252
|
-
|
253
|
-
# set ImageMagick location
|
254
|
-
if @imagemagickDir == 'setme' or @imagemagickDir == ''
|
255
|
-
@imagemagickDir = '/usr/local/bin'
|
256
|
-
end
|
257
|
-
|
258
|
-
# set full paths
|
259
|
-
ffprobe = Dir.pwd + " " + @ffmpegDir + ' ffprobe'
|
260
|
-
ffmpeg = Dir.pwd + " " + @ffmpegDir + ' ffmpeg'
|
261
|
-
imagick = Dir.pwd + " " + @imagemagickDir + ' convert'
|
262
|
-
|
263
|
-
# ensure libraries are actually installed
|
264
|
-
unless (os.path.exists(ffprobe) and os.path.exists(ffmpeg))
|
265
|
-
exitWithError("FFmpeg couldn\'t be found at \"#{@ffmpegDir}\". " +
|
266
|
-
'Please check your configuration.')
|
267
|
-
end
|
268
|
-
unless os.path.exists(imagick)
|
269
|
-
exitWithError("ImageMagick couldn\'t be found at \"#{@imagemagickDir}\". " +
|
270
|
-
'Please check your configuration.')
|
271
|
-
end
|
272
|
-
end
|
273
|
-
public
|
274
|
-
################################################################################
|
275
|
-
# Program entrypoint.
|
276
|
-
def self.generate *args
|
277
|
-
audio = nil
|
278
|
-
bg = nil
|
279
|
-
out = nil
|
280
|
-
if args.size < 1 || args.size > 3
|
281
|
-
puts 'This method takes either 1 to 3 arguments'
|
282
|
-
else
|
283
|
-
audio = args[0]
|
284
|
-
if args.size == 2
|
285
|
-
bg = args[1]
|
286
|
-
elsif args.size == 3
|
287
|
-
bg = args[1]
|
288
|
-
out = args[2]
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
bg ||= @tempBg
|
293
|
-
out ||= @outputFile
|
294
|
-
unless File.exist?(audio)
|
295
|
-
exitWithError("Input file \"#{audio}\" doesn\'t exist or can\'t be read.")
|
296
|
-
end
|
297
|
-
|
298
|
-
# determine arguments
|
299
|
-
custom = bg
|
300
|
-
doBlur = true
|
301
|
-
doText = false
|
302
|
-
|
303
|
-
background = Dir.pwd + " " + @tempFolder + " " + bg
|
304
|
-
|
305
|
-
# grab metadata if necessary
|
306
|
-
text = ""
|
307
|
-
if doText and custom == ""
|
308
|
-
metadata = getMetadata(audio)
|
309
|
-
metadata.each do |item|
|
310
|
-
if item != ""
|
311
|
-
#split = splitText(item)
|
312
|
-
text.append(item)
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
# generate the background unless a premade one is specified
|
318
|
-
if custom == ""
|
319
|
-
generateBackground(audio, backgroundBase, background, blur=doBlur, text=text)
|
320
|
-
else
|
321
|
-
background = custom
|
322
|
-
end
|
323
|
-
encode(audio, background, out)
|
324
|
-
end
|
325
|
-
|
326
|
-
end
|