nicoloid 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.
- 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:
|