-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.coffee
385 lines (309 loc) · 10.9 KB
/
index.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
require('dotenv').config()
fs = require 'fs-extra'
url = require 'url'
path = require 'path'
spawn = require('child_process').spawn
request = require 'request'
cheerio = require 'cheerio'
async = require 'async'
imageSize = require 'image-size'
LineWrapper = require 'stream-line-wrapper'
batteryStatus = require './battery-status'
rankingURL = 'http://www.pixiv.net/ranking.php?mode=daily&content=illust'
maxRank = 50
maxPagePerManga = 5
currentDate = null
files = []
imageSizes = {}
threshold = 580
async.waterfall [
(done) ->
console.log "Getting #{rankingURL}..."
request rankingURL, done
(response, body, done) ->
return done(new Error 'Status not OK') if response.statusCode isnt 200
console.log 'Extracting URLs...'
$ = cheerio.load body
$ranks = $('.ranking-item').filter -> parseInt($(@).data('rank')) <= maxRank
currentDate = $('.sibling-items').eq(0).find('.current').text()
$ranks.each ->
$rank = $ this
rank = parseInt $rank.data 'rank'
thumbnail = url.parse $rank.find('._thumbnail').data 'src'
isManga = $rank.find('.work').hasClass 'multiple'
###
Parse pathname into parameters
Example:
URL: http://i3.pixiv.net/c/240x480/img-master/img/2015/07/16/00/15/45/51435066_p0_master1200.jpg
mark: c
size: 240x480
region: img-master
type: img
year: 2015
month: 07
day: 16
hour: 00
minute: 15
second: 45
filename: 51435066_p0_master1200.jpg
###
[_, mark, size, region, type, year, month, day, hour, minute, second, filename] =
thumbnail.pathname.split '/'
###
Parse filename into parametes
Example:
filename: 51435066_p0_master1200.jpg
id: 51435066
page: p0
type: master1200
extension: jpg
###
[id, page, imagetype, extension] = filename.split /[_\.]/
# Rebuild pathname (without extension and page)
region = 'img-original'
pathname = ['', region, type, year, month, day, hour, minute, second, id].join '/'
thumbnail.pathname = pathname
# Rebuild URL
originalURL = url.format thumbnail
region = 'img-master'
size = '500x1000_70'
thumbnail.pathname = ['', mark, size, region, type, year, month, day, hour, minute, second, filename].join '/'
thumbnail.host = 'i-mail.pximg.net'
# Rebuild URL
publicURL = url.format thumbnail
files.push
filename: id
url: originalURL
publicURL: publicURL
id: id
page: page
isManga: isManga
done null
(done) ->
async.parallel [
(done) ->
randomFile = files[Math.floor Math.random() * files.length]
request
url: "https://maker.ifttt.com/trigger/pixivwall/with/key/#{process.env.IFTTT_TOKEN}"
method: 'POST'
body: JSON.stringify value1: randomFile.publicURL
headers:
'Content-Type': 'application/json'
, done
(done) ->
fs.exists path.join(__dirname, "images/#{currentDate}"), (exists) ->
if exists
done null
else
fs.mkdir path.join(__dirname, "images/#{currentDate}"), done
], done
(_, done) ->
fs.readdir path.join(__dirname, "images/#{currentDate}"), done
(dirfiles, done) ->
files = files.filter (file) -> not dirfiles.some (dirfile) -> dirfile[...file.filename.length] is file.filename
async.eachLimit files, 5, (file, done) ->
console.log "Getting #{file.id}..."
if file.isManga
pages = [0...maxPagePerManga]
else
pages = [0]
async.detectSeries pages, (page, resultIn) ->
async.detectSeries ['jpg', 'png', 'gif', 'jpeg'], (extension, resultIn) ->
URL = "#{file.url}_p#{page}.#{extension}"
console.log "Trying #{URL}..."
request
url: URL
encoding: null
headers:
Cookie: 'pixiv_embed=pix'
, (error, response, body) ->
return done(error) if error
return resultIn(false) if response.statusCode isnt 200
filename = path.join __dirname, "images/#{currentDate}/#{file.filename}_p#{page}.#{extension}"
fs.writeFile filename, body, (error) ->
if error
done error
else
console.log "Saved #{file.filename}_p#{page}.#{extension}"
resultIn true
, (result) ->
if result is false and page is 0
console.error new Error "Suitable extension for #{file.filename} not found..."
resultIn not result
, (result) ->
done null
, done
(done) -> fs.readdir path.join(__dirname, "images/#{currentDate}"), done
(dirfiles, done) ->
# Filter images
dirfiles = dirfiles.filter (file) ->
['.jpg', '.png', '.gif', '.jpeg'].some (extension) ->
path.extname(file) is extension
.filter (file) -> path.basename(file).indexOf('-') is -1
dirfiles = dirfiles.map (file) -> path.join __dirname, "images/#{currentDate}", file
async.parallel [
(done) ->
randomImageCandidates = dirfiles.filter (file) ->
not path.basename(file).includes('-') and path.extname(file) is '.png'
randomImage = randomImageCandidates[Math.floor(Math.random() * randomImageCandidates.length)]
targetPath = path.join __dirname, 'images/random.png'
async.waterfall [
(done) -> fs.access targetPath, (error) -> done null, not error
(exists, done) ->
if exists
fs.unlink targetPath, done
else
done null
(done) ->
fs.link randomImage, targetPath, done
], done
(done) ->
async.map dirfiles, (file, done) ->
# Catch errors while detecting imagesizes... They just aren't needed actually
try
imageSize file, done
catch error
# Assume to be big enough not to need to resize it
done null,
width: Infinity
height: Infinity
, (error, imageSizeList) ->
if error then return done error
imageSizes = imageSizeList.reduce (previous, current, index) ->
previous[dirfiles[index]] = current
return previous
, imageSizes
done null
], done
(_, done) ->
async.waterfall [
(done) ->
fs.readdir path.join(__dirname, "images/#{currentDate}"), done
(dirfiles, done) ->
# Convert to full paths
fullpaths = dirfiles
.filter (file) -> file.match /^\d+_p\d+\.\w+$/
.map (dirfile) -> path.join __dirname, "images/#{currentDate}", dirfile
pixivwall = spawn path.join(__dirname, 'pixivwall'), fullpaths
prefixer = new LineWrapper prefix: 'pixivwall: '
pixivwall.stdout.pipe(prefixer).pipe(process.stdout)
pixivwall.on 'close', (code) ->
if code isnt 0
done new Error "pixivwall exit with code #{code}"
else
console.log 'Wallpaper successfully set!'
done null
], done
# We successfully set wallpaper with normal quality of image. Now improve quality of image using waifu2x!
# Crop Image with 16:9 by Imagemagick
(done) ->
# Skip if battery is not charging
if batteryStatus() isnt 1
console.log 'Battery is not charging. Skip scaling...'
return done null
async.forEachOfLimit imageSizes, 1, (size, image, done) ->
return done null if size.width is Infinity
imageFile = path.parse image
imageFile.base = "#{imageFile.name}-crop.png"
croppedImage = path.format imageFile
imageFile.base = "#{imageFile.name}-2x.png"
scaledImage = path.format imageFile
newSize =
width: Math.min size.width, Math.ceil size.height * (16 / 9)
height: Math.min size.height, Math.ceil size.width / (16 / 9)
# Skip if the image is big enough not to scale
return done null if newSize.width > threshold
offsetX = Math.floor (size.width - newSize.width) / 2
offsetY = Math.floor (size.height - newSize.height) / 2
# http://www.imagemagick.org/script/command-line-processing.php#geometry
geometry = "#{newSize.width}x#{newSize.height}+#{offsetX}+#{offsetY}"
async.waterfall [
(done) -> fs.access croppedImage, (error) -> done null, not error
(exists, done) ->
if exists
console.log "Skip cropping because #{path.basename croppedImage} already exists"
return done null
# Skip scaling if target dimension is totally the same with the original
if newSize.width is size.width and newSize.height is size.height
console.log "Copying #{path.basename image} to #{path.basename croppedImage}"
return fs.copy image, croppedImage, done
console.log "Cropping #{path.basename image} to #{path.basename croppedImage} with #{geometry}"
convert = spawn 'magick', [
image
'-crop', geometry
croppedImage
]
prefixer = new LineWrapper prefix: 'convert: '
convert.stdout.pipe(prefixer).pipe(process.stdout)
convert.stderr.pipe fs.createWriteStream path.join __dirname, 'log/convert.log'
convert.on 'close', (code) ->
if code isnt 0
done new Error "Imagemagick exit with code #{code}"
else
done null
(done) -> fs.access scaledImage, (error) -> done null, not error
(exists, done) ->
if exists
console.log "Skip scaling because #{path.basename scaledImage} already exists"
return done null
console.log "Scaling #{path.basename croppedImage} to #{path.basename scaledImage}"
if imageFile.ext is 'jpg' or imageFile.ext is 'jpeg'
mode = 'noise_scale'
else
mode = 'scale'
waifu2x = spawn 'C:\\Program Files\\waifu2x-caffe\\waifu2x-caffe-cui', [
'--process', 'gpu'
'--gpu', '0'
'--model_dir', 'C:\\Program Files\\waifu2x-caffe\\models\\upconv_7_anime_style_art_rgb'
'--mode', mode
'--input_path', croppedImage
'--output_path', scaledImage
]
prefixer = new LineWrapper prefix: 'waifu2x: '
waifu2x.stdout.pipe(prefixer).pipe(process.stdout)
waifu2x.stderr.pipe(prefixer).pipe(process.stdout)
waifu2x.on 'close', (code) ->
if code isnt 0
done new Error "waifu2x-converter exit with code #{code}"
else
done null
], done
, done
# Now, set wallpaper again to usefully use scaled images
(done) ->
async.waterfall [
(done) ->
fs.readdir path.join(__dirname, "images/#{currentDate}"), done
(dirfiles, done) ->
# Create rainbow table to store the images whose quality is the highest in the directory
imageTable = Object.create null
for file in dirfiles
if match = file.match /^(\d+_p\d+)(:?-(\d+)x)?\.(\w+)$/
[_, id, _, scale, ext] = match
scale = parseInt(scale, 10) or 1
if not imageTable[id] or
imageTable[id].scale < scale or
(ext is 'png' and imageTable[id].ext isnt 'png')
imageTable[id] =
file: file
scale: scale
ext: ext
# Convert to full paths
fullpaths = Object.keys imageTable
.map (id) -> path.join __dirname, "images/#{currentDate}", imageTable[id].file
pixivwall = spawn path.join(__dirname, 'pixivwall'), fullpaths
pixivwall.stdout.on 'data', (data) ->
data.toString().split('\n').forEach (line) ->
console.log "pixivwall: #{line}"
pixivwall.on 'close', (code) ->
if code isnt 0
done new Error "pixivwall exit with code #{code}"
else
console.log 'Wallpaper successfully set!'
done null
], done
], (error) ->
if error
throw error
else
console.log 'Operation successfull'