audio_to_youtube 1.0.0

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/audio_to_youtube.rb +326 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 222b2488455a1f11c03853d3772db05f473aab73
4
+ data.tar.gz: 3d62868b920f7e694913b8cec93eff7b6e8a9b83
5
+ SHA512:
6
+ metadata.gz: 80e4dad589ee2596966310100151262afbe3f486c767159581ebbbcb1032b76ad803851c27ecbab33f8ad7718ba6955c16ff65b602722eb2a273d2a694d3ecc0
7
+ data.tar.gz: 96eac8616a78146fb75764e391fb6e8f5045a14d07ca8d156cf16c2d64d37ac07e04e03ac3269f529c41f7b0622cc1d5e2c738e64b23d5fff65ebde65be8db9b
@@ -0,0 +1,326 @@
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
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: audio_to_youtube
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Doan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Convert audio with background image to mp4 for uploading to youtube
14
+ email: quangkhoidoan@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/audio_to_youtube.rb
20
+ homepage: http://rubygems.org/gems/audio_to_youtube
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.2.2
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Convert audio with background image to mp4 for uploading to youtube
44
+ test_files: []