nicoloid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +6 -0
- data/README.mkd +95 -0
- data/Rakefile +1 -0
- data/bin/nicoloid +6 -0
- data/lib/nicoloid.rb +286 -0
- data/lib/nicoloid/version.rb +3 -0
- data/nicoloid.gemspec +20 -0
- metadata +56 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.mkd
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Nicoloid - Download video from nicovideo and convert to mp3
|
2
|
+
|
3
|
+
__OWN YOUR RISK__
|
4
|
+
|
5
|
+
## Note
|
6
|
+
|
7
|
+
* This tool is optimized for VOCALOID.
|
8
|
+
* If video name contains VOCALOID names, this tool will put to artist tag in ID3.
|
9
|
+
|
10
|
+
## Requirement
|
11
|
+
|
12
|
+
* Ruby 1.9+ (Ruby 1.9.2+ is supported)
|
13
|
+
* ffmpeg
|
14
|
+
|
15
|
+
## How to use
|
16
|
+
|
17
|
+
1. write config in yaml, format is below
|
18
|
+
2. run `nicoloid /path/to/config.yml`
|
19
|
+
|
20
|
+
## Configuration
|
21
|
+
|
22
|
+
account:
|
23
|
+
mail: your_mail
|
24
|
+
password: your_password
|
25
|
+
|
26
|
+
directories:
|
27
|
+
temporary: temporary_directory
|
28
|
+
output: mp3_directory
|
29
|
+
|
30
|
+
ffmpeg: path_to_ffmpeg
|
31
|
+
|
32
|
+
# if you use ranking as source
|
33
|
+
source:
|
34
|
+
from: ranking
|
35
|
+
method: ranking_method
|
36
|
+
span: ranking_span
|
37
|
+
category: ranking_category
|
38
|
+
|
39
|
+
# if you use mylist as source
|
40
|
+
# (tbd)
|
41
|
+
|
42
|
+
# the followings are optional.
|
43
|
+
|
44
|
+
proxy:
|
45
|
+
host: proxy_host
|
46
|
+
port: proxy_port
|
47
|
+
|
48
|
+
limit: limit_count_of_videos
|
49
|
+
|
50
|
+
### Note
|
51
|
+
|
52
|
+
#### output directory
|
53
|
+
|
54
|
+
* __output directory will be wiped__ all its including files before converting. please move file which you need.
|
55
|
+
* output mp3-s will be saved in output directory.
|
56
|
+
|
57
|
+
#### Temporary directory
|
58
|
+
|
59
|
+
* it will be created automatically before converting, and deleted after converting.
|
60
|
+
|
61
|
+
#### What's a ranking_method and span?
|
62
|
+
|
63
|
+
* Niconico ranking url includes method, span and category.
|
64
|
+
|
65
|
+
http://www.nicovideo.jp/ranking/__METHOD__/__SPAN__/__CATEGORY__
|
66
|
+
|
67
|
+
Example: <http://www.nicovideo.jp/ranking/fav/daily/vocaloid>
|
68
|
+
|
69
|
+
source:
|
70
|
+
method: fav
|
71
|
+
span: daily
|
72
|
+
category: vocaloid
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
MIT Licence
|
77
|
+
|
78
|
+
(c) Shota Fukumori 2011
|
79
|
+
|
80
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
81
|
+
> of this software and associated documentation files (the "Software"), to deal
|
82
|
+
> in the Software without restriction, including without limitation the rights
|
83
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
84
|
+
> copies of the Software, and to permit persons to whom the Software is
|
85
|
+
> furnished to do so, subject to the following conditions:
|
86
|
+
> The above copyright notice and this permission notice shall be included in
|
87
|
+
> all copies or substantial portions of the Software.
|
88
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
89
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
90
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
91
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
92
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
93
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
94
|
+
> THE SOFTWARE.
|
95
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/nicoloid
ADDED
data/lib/nicoloid.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
#-*- coding:utf-8 -*-
|
2
|
+
|
3
|
+
# nicoloid.rb
|
4
|
+
# Author: Shota Fukumori (sora_h) <sorah@tubusu.net>
|
5
|
+
# License: MIT Licence
|
6
|
+
# The MIT License {{{
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
15
|
+
# all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
# THE SOFTWARE.
|
24
|
+
#}}}
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'niconico'
|
28
|
+
require 'yaml'
|
29
|
+
require 'fileutils'
|
30
|
+
require 'taglib'
|
31
|
+
require 'termcolor'
|
32
|
+
|
33
|
+
frame_factory = TagLib::ID3v2::FrameFactory.instance
|
34
|
+
frame_factory.default_text_encoding = TagLib::String::UTF8
|
35
|
+
|
36
|
+
class File
|
37
|
+
NULL ||= /mswin/u =~ RUBY_PLATFORM ? 'NUL' : '/dev/null'
|
38
|
+
end
|
39
|
+
|
40
|
+
class Nicoloid
|
41
|
+
|
42
|
+
VOCALOIDS = %w(初音ミク
|
43
|
+
鏡音リン
|
44
|
+
鏡音レン
|
45
|
+
巡音ルカ
|
46
|
+
KAITO
|
47
|
+
MEIKO
|
48
|
+
重音テト
|
49
|
+
GUMI
|
50
|
+
めぐっぽいど
|
51
|
+
Megpoid
|
52
|
+
がくっぽいど
|
53
|
+
神威がくぽ
|
54
|
+
がくぽ
|
55
|
+
VY1
|
56
|
+
歌愛ユキ
|
57
|
+
)
|
58
|
+
VOCALOIDS_ALIAS = {"Megpoid" => "GUMI",
|
59
|
+
"めぐっぽいど" => "GUMI",
|
60
|
+
"がくぽ" => "神威がくぽ",
|
61
|
+
"がくっぽいど" => "神威がくぽ",
|
62
|
+
"megpoid" => "GUMI"}
|
63
|
+
|
64
|
+
VOCALOIDS_SORTING = {"初音ミク" => "Hatsune Miku",
|
65
|
+
"鏡音リン" => "Kagamine Rin",
|
66
|
+
"鏡音レン" => "Kagamine Ren",
|
67
|
+
"巡音ルカ" => "Megurine Ruka",
|
68
|
+
"KAITO" => "Kaito",
|
69
|
+
"MEIKO" => "Meiko",
|
70
|
+
"重音テト" => "Kasane Teto",
|
71
|
+
"GUMI" => "Gumi",
|
72
|
+
"神威がくぽ" => "Kamui Gakupo",
|
73
|
+
"VY1" => "VY1",
|
74
|
+
"歌愛ユキ" => "Kaai Yuki"}
|
75
|
+
|
76
|
+
class << self
|
77
|
+
def puts_progress(str)
|
78
|
+
puts "<bold><blue> *</blue> <white>#{str}</white></bold>".termcolor
|
79
|
+
yield
|
80
|
+
end
|
81
|
+
|
82
|
+
def run(argv)
|
83
|
+
puts "Usage: #{File.basename(__FILE__)} [config_file=config.yml]" if argv[0] == "--help"
|
84
|
+
|
85
|
+
config = YAML.load_file(argv[0] || "config.yml")
|
86
|
+
|
87
|
+
nv = Niconico.new(config["account"]["mail"], config["account"]["password"])
|
88
|
+
|
89
|
+
ffmpeg = config["ffmpeg"] || "ffmpeg"
|
90
|
+
|
91
|
+
cache_dir = File.expand_path(config["directories"]["cache"])
|
92
|
+
output_dir = File.expand_path(config["directories"]["output"])
|
93
|
+
tmpdir = File.expand_path(config["directories"]["temporary"])
|
94
|
+
|
95
|
+
if File.exist?(tmpdir)
|
96
|
+
FileUtils.remove_entry_secure(tmpdir)
|
97
|
+
end
|
98
|
+
FileUtils.mkdir(tmpdir)
|
99
|
+
|
100
|
+
converted = []
|
101
|
+
if File.exist?(output_dir)
|
102
|
+
nicoloid_converted = "#{output_dir}/nicoloid_converted"
|
103
|
+
|
104
|
+
if File.exist?(nicoloid_converted)
|
105
|
+
converted = open(nicoloid_converted,&:readlines).map(&:chomp)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
FileUtils.mkdir output_dir
|
109
|
+
end
|
110
|
+
|
111
|
+
nv.agent.set_proxy(config["proxy"]["host"],config["proxy"]["port"]) if config["proxy"]
|
112
|
+
|
113
|
+
max = config["limit"] ? config["limit"].to_i : 10
|
114
|
+
|
115
|
+
puts "<bold><blue>=></blue> <white>Loading list...</white></bold>".termcolor
|
116
|
+
|
117
|
+
videos = []
|
118
|
+
|
119
|
+
case config["source"]["from"]
|
120
|
+
when nil, "ranking"
|
121
|
+
config["source"]["category"] ||= config["source"]["category"].to_sym \
|
122
|
+
|| :vocaloid
|
123
|
+
%w(method span category).each do |k|
|
124
|
+
config["source"][k.to_sym] = config["source"][k].to_sym if config["source"][k]
|
125
|
+
end
|
126
|
+
|
127
|
+
videos = nv.ranking(config["source"]["category"], config["source"])
|
128
|
+
when "vocaran"
|
129
|
+
latest_vocaran = nv.mylist(9352163)[-1]
|
130
|
+
if /(PL|PL) ?[::] ?mylist\/(\d+)/ =~ latest_vocaran.description
|
131
|
+
mylist_id = $2
|
132
|
+
videos = nv.mylist(mylist_id)
|
133
|
+
sleep 1
|
134
|
+
else
|
135
|
+
warn "<bold><red>=></red> <white>Failed to detect mylist</white></bold>".termcolor
|
136
|
+
end
|
137
|
+
else
|
138
|
+
warn "WARNING: source name is wrong or not supported. you gave #{config["source"]["from"]} as source name."
|
139
|
+
end
|
140
|
+
|
141
|
+
videos[0...max].each_with_index do |v, i|
|
142
|
+
puts "<bold><blue>=></blue> <white>#{i+1}. (#{v.id}) #{v.title}</white><bold>".termcolor
|
143
|
+
begin
|
144
|
+
skip_message = "<bold><green> *</green> <white>Skipped</white></bold>".termcolor
|
145
|
+
(puts skip_message.termcolor; next) if converted.include?(v.id)
|
146
|
+
(puts skip_message; sleep 7; next) if v.type == :swf
|
147
|
+
|
148
|
+
videoname = "#{cache_dir}/#{v.id}.#{v.type}"
|
149
|
+
thumbname = "#{tmpdir}/#{v.id}.jpg"
|
150
|
+
cookie_jar = "#{tmpdir}/cookie.txt"
|
151
|
+
|
152
|
+
video_downloaded = false
|
153
|
+
puts_progress "Downloading video" do
|
154
|
+
unless Dir["#{cache_dir}/#{v.id}.*"].empty?
|
155
|
+
puts "<green> *</green> <white>Using from cache</white>".termcolor
|
156
|
+
break
|
157
|
+
end
|
158
|
+
puts "<bold><red> *</red> <white>Seems economy</white></bold>".termcolor if v.economy?
|
159
|
+
if (`curl --help` rescue nil)
|
160
|
+
a = v.get_video_by_other
|
161
|
+
cookies = a[:cookie]
|
162
|
+
url = a[:url]
|
163
|
+
|
164
|
+
open(cookie_jar, "w") do |io|
|
165
|
+
io.puts cookies.map { |cookie|
|
166
|
+
[cookie.domain, "TRUE", cookie.path,
|
167
|
+
cookie.secure.inspect.upcase, cookie.expires.to_i,
|
168
|
+
cookie.name, cookie.value].join("\t")
|
169
|
+
}.join("\n")
|
170
|
+
end
|
171
|
+
|
172
|
+
unless system("curl", "-#", "-o", videoname, "-b", cookie_jar, url)
|
173
|
+
puts "<bold><red>=></red> <white>Failed...</white></bold>".termcolor
|
174
|
+
end
|
175
|
+
else
|
176
|
+
puts "<bold><red> *</red> <white>if you have a curl, you can download videos with progress bar.</white></bold>".termcolor
|
177
|
+
open(videoname,"wb") do |f|
|
178
|
+
f.print v.get_video
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
video_downloaded = true
|
183
|
+
|
184
|
+
filename = nil
|
185
|
+
puts_progress "Converting to mp3" do
|
186
|
+
#outext = `ffmpeg -i #{videoname} 2>&1`[/^ +Stream.+?: Audio: (.+?),/, 1]
|
187
|
+
outext = nil
|
188
|
+
basename = "#{i+1}_#{v.id}_#{v.title.gsub(/\//,"-")}.#{outext||"mp3"}"
|
189
|
+
filename = "#{output_dir}/#{basename}"
|
190
|
+
|
191
|
+
cmd = if outext
|
192
|
+
[ffmpeg, "-loglevel", "quiet", "-i", videoname, "-acodec", "copy", filename]
|
193
|
+
else
|
194
|
+
[ffmpeg, "-loglevel", "quiet", "-i", videoname, "-ab", "320k", filename]
|
195
|
+
end
|
196
|
+
unless system(*cmd)
|
197
|
+
puts "<bold><red>=></red> <white>Failed...</white></bold>".termcolor
|
198
|
+
exit 1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
artists=artist_sorting=nil
|
203
|
+
puts_progress "Detecting artists" do
|
204
|
+
vt = v.title
|
205
|
+
|
206
|
+
artists = v.tags.select{|tag| VOCALOIDS.include?(tag) }
|
207
|
+
|
208
|
+
if artists.empty?
|
209
|
+
puts "<green>=></green> <white>Detection by tags failed. try detecting from the title...</white>".termcolor
|
210
|
+
artists = VOCALOIDS.inject([]){|r,i| vt.include?(i) ? r << i : r }
|
211
|
+
end
|
212
|
+
|
213
|
+
artists.map!{|i| VOCALOIDS_ALIAS.key?(i) ? VOCALOIDS_ALIAS[i] : i }
|
214
|
+
artists.uniq!
|
215
|
+
|
216
|
+
artist_sorting = artists.map{|i| VOCALOIDS_SORTING[i] || i}.join(', ')
|
217
|
+
artists = artists.join(', ')
|
218
|
+
|
219
|
+
puts
|
220
|
+
puts " #{artists}"
|
221
|
+
puts " #{artist_sorting}"
|
222
|
+
puts
|
223
|
+
end
|
224
|
+
|
225
|
+
puts_progress "Exporting thumbnail" do
|
226
|
+
unless system(ffmpeg, "-loglevel", "quiet", "-i", videoname, *%w(-f image2 -vframes 1 -ss 10 -an -deinterlace), thumbname)
|
227
|
+
puts "<bold><red>=></red> <white>Failed...</white></bold>".termcolor
|
228
|
+
exit 1
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
puts_progress "Setting ID3 Tags..." do
|
233
|
+
file = TagLib::MPEG::File.new(filename)
|
234
|
+
tag = file.id3v2_tag
|
235
|
+
tag.artist = artists
|
236
|
+
tag.title = v.title
|
237
|
+
tag.album = v.title
|
238
|
+
|
239
|
+
cover = TagLib::ID3v2::AttachedPictureFrame.new
|
240
|
+
cover.mime_type = "image/jpeg"
|
241
|
+
cover.description = "cover"
|
242
|
+
cover.type = TagLib::ID3v2::AttachedPictureFrame::FrontCover
|
243
|
+
cover.picture = File.open(thumbname, "rb").read
|
244
|
+
|
245
|
+
sort = TagLib::ID3v2::TextIdentificationFrame.new("TSOP",TagLib::String::UTF8)
|
246
|
+
sort.text = artist_sorting
|
247
|
+
|
248
|
+
tag.add_frame(cover)
|
249
|
+
tag.add_frame(sort)
|
250
|
+
|
251
|
+
file.save
|
252
|
+
end
|
253
|
+
rescue Exception => e
|
254
|
+
files = [filename,tmpdir]
|
255
|
+
files << videoname unless video_downloaded
|
256
|
+
files.each do |f|
|
257
|
+
FileUtils.remove_entry_secure f if File.exist?(f)
|
258
|
+
end
|
259
|
+
if e.class == Interrupt
|
260
|
+
puts
|
261
|
+
puts_progress("Interrupt."){}
|
262
|
+
exit
|
263
|
+
else
|
264
|
+
raise e
|
265
|
+
end
|
266
|
+
else
|
267
|
+
converted << v.id
|
268
|
+
open(nicoloid_converted,"w") do |f|
|
269
|
+
f.print converted.join("\n")
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
puts_progress "Wait..." do
|
274
|
+
5.times {print "."; sleep 1}
|
275
|
+
puts
|
276
|
+
puts
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
puts_progress "Removing temporary directory" do
|
281
|
+
FileUtils.remove_entry_secure tmpdir
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
data/nicoloid.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "nicoloid/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "nicoloid"
|
7
|
+
s.version = Nicoloid::VERSION
|
8
|
+
s.authors = ["Shota Fukumori (sora_h)"]
|
9
|
+
s.email = ["sorah@tubusu.net"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = "Download video from nicovideo and convert to mp3"
|
12
|
+
s.description = "nicoloid downloads video from nicovideo.jp, and convert videos to mp3 using ffmpeg."
|
13
|
+
|
14
|
+
s.rubyforge_project = "nicoloid"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nicoloid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Shota Fukumori (sora_h)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-17 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: nicoloid downloads video from nicovideo.jp, and convert videos to mp3
|
15
|
+
using ffmpeg.
|
16
|
+
email:
|
17
|
+
- sorah@tubusu.net
|
18
|
+
executables:
|
19
|
+
- nicoloid
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- .gitignore
|
24
|
+
- Gemfile
|
25
|
+
- README.mkd
|
26
|
+
- Rakefile
|
27
|
+
- bin/nicoloid
|
28
|
+
- lib/nicoloid.rb
|
29
|
+
- lib/nicoloid/version.rb
|
30
|
+
- nicoloid.gemspec
|
31
|
+
homepage: ''
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project: nicoloid
|
51
|
+
rubygems_version: 1.8.11
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Download video from nicovideo and convert to mp3
|
55
|
+
test_files: []
|
56
|
+
has_rdoc:
|