Compare commits
79 Commits
Author | SHA1 | Date |
---|---|---|
Kurt Bestor | 018cf8405c | |
maboroshin | 1b39bb7700 | |
Kurt Bestor | 8b97a60861 | |
Kurt Bestor | 5e53ab1691 | |
Kurt Bestor | b6f12454df | |
Kurt Bestor | 92d769f0a4 | |
lukemin | 8b602b6bfb | |
lukemin | e34d1814cc | |
Blank-1973 | cb5538d2bc | |
Kurt Bestor | 00bc7e0d60 | |
Kurt Bestor | 468fb0210f | |
Kurt Bestor | 9473eb6680 | |
Kurt Bestor | b5effb248b | |
Blank-1973 | 532692ab60 | |
Kurt Bestor | e79e6c0bcb | |
KurtBestor | 34f23f18c8 | |
KurtBestor | 7952f9173e | |
Kurt Bestor | ff397445a9 | |
Oliwer Stryjewski | 947f617624 | |
Oliwer Stryjewski | 8593fd422d | |
Oliwer Stryjewski | 468e3fd1f7 | |
Kurt Bestor | a3cec35bc4 | |
Oliwer | 2af331ab67 | |
Oliwer Stryjewski | cbfab862af | |
Kurt Bestor | a8b7a0fa46 | |
ZG | 2c23b2d3b8 | |
Kurt Bestor | 1fb3f9bfa6 | |
abc0922001 | 7f7f39c408 | |
KurtBestor | 27ae4180e9 | |
Kurt Bestor | fd40ac2d1c | |
Blank-1973 | 2d800d4de1 | |
KurtBestor | f68a837ddf | |
KurtBestor | f0a8e5914d | |
Kurt Bestor | 71e08d25e7 | |
bog_4t | 8c48305382 | |
KurtBestor | de7728691e | |
Kurt Bestor | 116ef29afe | |
Ryu juheon | 07b0b9416e | |
Ryu juheon | af5dae6f11 | |
Kurt Bestor | aeff19c32a | |
ZG | df8a01a59b | |
KurtBestor | 65ef86e414 | |
KurtBestor | c2247b082b | |
Kurt Bestor | 27f397fcff | |
Blank-1973 | 08faa47ddc | |
KurtBestor | 4c4a8fbd0c | |
Kurt Bestor | adf33e1150 | |
Ryu juheon | c29857243e | |
Ryu juheon | ad5f99e040 | |
Ryu juheon | 2a21584e28 | |
Ryu juheon | d9663229d9 | |
Ryu juheon | 1c89435723 | |
Ryu juheon | 9b6fd24f88 | |
Kurt Bestor | 3a5f80353d | |
Ryu juheon | 7fb542942f | |
Ryu juheon | 5bbd6030b0 | |
Ryu juheon | 0675e8fe64 | |
Ryu juheon | ace7f3d07c | |
Ryu juheon | f3c3c6053d | |
Ryu juheon | 6b32261de8 | |
Ryu juheon | 356af051ca | |
Ryu juheon | 4268150ff4 | |
Ryu juheon | 649a2bf28b | |
Ryu juheon | fa499b7785 | |
Ryu juheon | f9cf582a69 | |
Kurt Bestor | b9eccc70c4 | |
Kurt Bestor | c71c96cf9c | |
Christian Clauss | af933eba71 | |
Christian Clauss | 7ce6fb6314 | |
Kurt Bestor | d58897fe9f | |
Bharathi | 3070fc49b6 | |
KurtBestor | 01e41d0f17 | |
Kurt Bestor | eb76f6814a | |
bog_4t | 0c2a1248d7 | |
bog_4t | ff96316bef | |
bog_4t | ec1581926f | |
Kurt Bestor | ba8b763654 | |
ZG | 442e1ab88b | |
KurtBestor | d53aa1e4b4 |
|
@ -35,7 +35,6 @@
|
|||
| **4chan** | <https://4chan.org> |
|
||||
| **AfreecaTV** | <https://afreecatv.com> |
|
||||
| **ArtStation** | <https://artstation.com> |
|
||||
| **AsianSister** | <https://asiansister.com> |
|
||||
| **AsmHentai** | <https://asmhentai.com> |
|
||||
| **Avgle** | <https://avgle.com> |
|
||||
| **baraag.net** | <https://baraag.net> |
|
||||
|
@ -56,13 +55,11 @@
|
|||
| **hanime.tv** | <https://hanime.tv> |
|
||||
| **Hentai Foundry** | <https://hentai-foundry.com> |
|
||||
| **Hitomi.la** | <https://hitomi.la> |
|
||||
| **Hiyobi.me** | <https://hiyobi.me> |
|
||||
| **Imgur** | <https://imgur.com> |
|
||||
| **Instagram** | <https://instagram.com> |
|
||||
| **Iwara** | <https://iwara.tv><br><https://ecchi.iwara.tv> |
|
||||
| **Jmana** | <https://jmana.net> |
|
||||
| **カクヨム** | <https://kakuyomu.jp> |
|
||||
| **LHScan** | <https://loveheaven.net> |
|
||||
| **Likee** | <https://likee.video> |
|
||||
| **Luscious** | <https://luscious.net> |
|
||||
| **MyReadingManga** | <https://myreadingmanga.info> |
|
||||
|
@ -100,4 +97,4 @@
|
|||
| **Yande.re** | <https://yande.re> |
|
||||
| **Youku** | <https://youku.com> |
|
||||
| **YouTube** | <https://youtube.com> |
|
||||
| **and more...** | [Supported sites by youtube-dl](http://ytdl-org.github.io/youtube-dl/supportedsites.html) |
|
||||
| **and more...** | [Supported sites by yt-dlp](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md) |
|
||||
|
|
|
@ -8,15 +8,14 @@ class Image:
|
|||
self._url = url
|
||||
self.url = LazyUrl(ref, self.get, self)
|
||||
self.filename = '{:04}{}'.format(n, get_ext(url))
|
||||
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(2, 1)
|
||||
def get(self, _):
|
||||
return self._url
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
|
||||
class Downloader_4chan(Downloader):
|
||||
type = '4chan'
|
||||
URLS = [r'regex:boards.(4chan|4channel).org']
|
||||
|
|
|
@ -9,7 +9,7 @@ from m3u8_tools import playlist2stream, M3u8_stream
|
|||
import errors
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
|
||||
def __init__(self, stream, referer, id, title, url_thumb):
|
||||
self.url = LazyUrl(referer, lambda x: stream, self)
|
||||
|
@ -21,7 +21,7 @@ class Video(object):
|
|||
downloader.download(url_thumb, buffer=self.thumb)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_afreeca(Downloader):
|
||||
type = 'afreeca'
|
||||
URLS = ['afreecatv.com']
|
||||
|
@ -31,14 +31,14 @@ class Downloader_afreeca(Downloader):
|
|||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
return url.rstrip(' /')
|
||||
|
||||
|
||||
def read(self):
|
||||
session = Session()
|
||||
video = get_video(self.url, session, self.cw)
|
||||
self.urls.append(video.url)
|
||||
|
||||
self.setIcon(video.thumb)
|
||||
|
||||
|
||||
self.title = video.title
|
||||
|
||||
|
||||
|
@ -64,9 +64,16 @@ def get_video(url, session, cw):
|
|||
print_('url_thumb: {}'.format(url_thumb))
|
||||
params = re.find('VodParameter *= *[\'"]([^\'"]+)[\'"]', html, err='No VodParameter')
|
||||
params += '&adultView=ADULT_VIEW&_={}'.format(int(time()*1000))
|
||||
url_xml = 'http://stbbs.afreecatv.com:8080/api/video/get_video_info.php?' + params
|
||||
print(url_xml)
|
||||
html = downloader.read_html(url_xml, session=session, referer=url)
|
||||
for subdomain in ['afbbs', 'stbbs']: #4758
|
||||
url_xml = 'http://{}.afreecatv.com:8080/api/video/get_video_info.php?'.format(subdomain) + params
|
||||
print_(url_xml)
|
||||
try:
|
||||
html = downloader.read_html(url_xml, session=session, referer=url)
|
||||
break
|
||||
except Exception as e:
|
||||
e_ = e
|
||||
else:
|
||||
raise e_
|
||||
soup = Soup(html)
|
||||
if '<flag>PARTIAL_ADULT</flag>' in html:
|
||||
raise errors.LoginRequired()
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#coding:utf8
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader, json, os
|
||||
from error_printer import print_error
|
||||
from translator import tr_
|
||||
|
@ -8,7 +7,7 @@ from utils import Downloader, Soup, get_print, lazy, Session, try_n, LazyUrl, cl
|
|||
import clf2
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, post_url, date, url, page):
|
||||
self.post_url = post_url
|
||||
|
@ -22,15 +21,16 @@ class Image(object):
|
|||
return 'Image({})'.format(self.filename)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_artstation(Downloader):
|
||||
type = 'artstation'
|
||||
URLS = ['artstation.com']
|
||||
display_name = 'ArtStation'
|
||||
ACCEPT_COOKIES = [r'(.*\.)?artstation\.(com|co)']
|
||||
|
||||
def init(self):
|
||||
self.url_main = 'https://www.artstation.com/{}'.format(self.id.replace('artstation_', '', 1).replace('/', '/'))
|
||||
|
||||
|
||||
if '/artwork/' in self.url or '/projects/' in self.url:
|
||||
pass#raise NotImplementedError('Single post')
|
||||
else:
|
||||
|
@ -67,7 +67,7 @@ class Downloader_artstation(Downloader):
|
|||
imgs = get_imgs_page(id_art, self.session, cw=self.cw)
|
||||
else:
|
||||
imgs = get_imgs(id, self.title, self.session, type=type, cw=self.cw)
|
||||
|
||||
|
||||
for img in imgs:
|
||||
self.urls.append(img.url)
|
||||
|
||||
|
@ -135,7 +135,7 @@ def get_id_art(post_url):
|
|||
|
||||
|
||||
def get_id(url, cw=None):
|
||||
print_ = get_print(cw)
|
||||
print_ = get_print(cw)
|
||||
|
||||
url = url.split('?')[0].split('#')[0]
|
||||
|
||||
|
@ -143,7 +143,7 @@ def get_id(url, cw=None):
|
|||
id_art = get_id_art(url)
|
||||
imgs = get_imgs_page(id_art, session=Session(), cw=cw)
|
||||
return imgs[0].data['user']['username']
|
||||
|
||||
|
||||
if '.artstation.' in url and 'www.artstation.' not in url:
|
||||
id = url.split('.artstation')[0].split('//')[-1]
|
||||
type = None
|
||||
|
@ -214,10 +214,8 @@ def get_imgs_page(id_art, session, date=None, cw=None):
|
|||
img = video
|
||||
else:
|
||||
img = Image(post_url, date, url, page)
|
||||
|
||||
|
||||
img.data = data#
|
||||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
|
||||
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
import downloader
|
||||
from utils import Soup, urljoin, LazyUrl, Downloader, try_n, clean_title
|
||||
from timee import sleep
|
||||
import os
|
||||
import ree as re
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_asiansister(Downloader):
|
||||
type = 'asiansister'
|
||||
URLS = ['asiansister.com']
|
||||
display_name = 'AsianSister'
|
||||
|
||||
@try_n(4)
|
||||
def init(self):
|
||||
html = downloader.read_html(self.url)
|
||||
self.soup = Soup(html)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return clean_title(self.soup.find('title').text.replace('- ASIANSISTER.COM', '').strip())
|
||||
|
||||
def read(self):
|
||||
imgs = get_imgs(self.url, self.soup, self.name)
|
||||
|
||||
for img in imgs:
|
||||
if img.type == 'video':
|
||||
self.single = True
|
||||
self.urls.append(img.url)
|
||||
|
||||
self.title = self.name
|
||||
|
||||
|
||||
class Image(object):
|
||||
def __init__(self, url, referer, p, type='image'):
|
||||
self.url = LazyUrl(referer, lambda x: url, self)
|
||||
ext = os.path.splitext(url.split('?')[0])[1]
|
||||
self.filename = u'{:04}{}'.format(p, ext)
|
||||
self.type = type
|
||||
|
||||
|
||||
@try_n(4)
|
||||
def get_imgs(url, soup=None, name=None):
|
||||
if soup is None:
|
||||
html = downloader.read_html(url)
|
||||
soup = Soup(html)
|
||||
|
||||
view = soup.findAll('div', class_='rootContant')[:2][-1]
|
||||
|
||||
v = view.find('video')
|
||||
if v:
|
||||
img = v.find('source').attrs['src']
|
||||
img = urljoin(url, img)
|
||||
img = Image(img, url, 0, 'video')
|
||||
ext = os.path.splitext(img.url().split('?')[0])[1]
|
||||
img.filename = u'{}{}'.format(name, ext)
|
||||
return [img]
|
||||
|
||||
imgs = []
|
||||
for img in view.findAll('img'):
|
||||
img = img.attrs['dataurl']
|
||||
img = urljoin(url, img)
|
||||
img = re.sub('/[a-z]+images/', '/images/', img).replace('_t.', '.')
|
||||
img = Image(img, url, len(imgs))
|
||||
imgs.append(img)
|
||||
|
||||
return imgs
|
|
@ -1,8 +1,10 @@
|
|||
#coding: utf8
|
||||
import downloader
|
||||
import ree as re
|
||||
from utils import Soup, urljoin, Downloader, join
|
||||
from utils import Soup, urljoin, Downloader, join, LazyUrl, Session, get_print
|
||||
import os
|
||||
from timee import sleep
|
||||
from translator import tr_
|
||||
|
||||
|
||||
|
||||
|
@ -16,15 +18,15 @@ def get_id(url):
|
|||
return int(re.find('/g/([0-9]+)', url))
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_asmhentai(Downloader):
|
||||
type = 'asmhentai'
|
||||
URLS = ['asmhentai.com']
|
||||
MAX_CORE = 8
|
||||
display_name = 'AsmHentai'
|
||||
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
self.session = Session()
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -32,7 +34,7 @@ class Downloader_asmhentai(Downloader):
|
|||
return 'https://asmhentai.com/g/{}/'.format(id_)
|
||||
|
||||
def read(self):
|
||||
info, imgs = get_imgs(self.url)
|
||||
info = get_info(self.url, self.session, self.cw)
|
||||
|
||||
# 1225
|
||||
artist = join(info['artists'])
|
||||
|
@ -42,51 +44,39 @@ class Downloader_asmhentai(Downloader):
|
|||
series = info['parodies'][0] if info['parodies'] else u'N/A'
|
||||
title = self.format_title(info['category'][0], info['id'], info['title'], artist, group, series, lang)
|
||||
|
||||
self.urls += imgs
|
||||
self.urls += [img.url for img in info['imgs']]
|
||||
|
||||
self.title = title
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, url, referer):
|
||||
self.url = LazyUrl(referer, lambda _:url, self)
|
||||
self.filename = os.path.basename(url)
|
||||
|
||||
def get_imgs(url):
|
||||
html = downloader.read_html(url)
|
||||
|
||||
def get_info(url, session, cw):
|
||||
print_ = get_print(cw)
|
||||
html = downloader.read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
|
||||
info = get_info(url, soup)
|
||||
|
||||
view = soup.find('div', class_='gallery')
|
||||
|
||||
imgs = []
|
||||
for img in view.findAll('div', class_='preview_thumb'):
|
||||
img = img.find('img').attrs.get('data-src') or img.find('img').attrs.get('src')
|
||||
img = urljoin(url, img).replace('t.jpg', '.jpg')
|
||||
imgs.append(img)
|
||||
|
||||
return info, imgs
|
||||
|
||||
|
||||
def get_info(url, soup=None):
|
||||
if soup is None:
|
||||
html = downloader.read_html(url)
|
||||
soup = Soup(html)
|
||||
|
||||
info = {}
|
||||
|
||||
|
||||
info['id'] = get_id(url)
|
||||
|
||||
|
||||
title = soup.find('h1').text.strip()
|
||||
info['title'] = title
|
||||
|
||||
for tag in soup.findAll('span', class_='tag'):
|
||||
href = tag.parent.attrs['href']
|
||||
href = urljoin(url, href).strip('/')
|
||||
|
||||
|
||||
key = href.split('/')[3]
|
||||
value = href.split('/')[-1]
|
||||
|
||||
|
||||
if key == 'language' and value == 'translated':
|
||||
continue
|
||||
|
||||
|
||||
if key in info:
|
||||
info[key].append(value)
|
||||
else:
|
||||
|
@ -95,6 +85,40 @@ def get_info(url, soup=None):
|
|||
for key in ['artists', 'groups', 'parodies', 'tags', 'characters']:
|
||||
if key not in info:
|
||||
info[key] = []
|
||||
|
||||
|
||||
info['imgs'] = []
|
||||
def read_imgs(soup):
|
||||
c = 0
|
||||
for img in soup.findAll('div', class_='preview_thumb'):
|
||||
img = img.find('img').attrs.get('data-src') or img.find('img').attrs.get('src')
|
||||
img = urljoin(url, img).replace('t.jpg', '.jpg')
|
||||
img = Image(img, url)
|
||||
info['imgs'].append(img)
|
||||
c += 1
|
||||
if not c:
|
||||
raise Exception('no imgs')
|
||||
|
||||
read_imgs(soup)
|
||||
|
||||
csrf = soup.find('meta', {'name':'csrf-token'})['content']
|
||||
print_(f'csrf: {csrf}')
|
||||
t_pages = int(soup.find('input', type='hidden', id='t_pages')['value'])
|
||||
print_(f't_pages: {t_pages}')
|
||||
|
||||
while len(info['imgs']) < t_pages: #4971
|
||||
print_('imgs: {}'.format(len(info['imgs'])))
|
||||
sleep(1, cw)
|
||||
cw.setTitle('{} {} - {} / {}'.format(tr_('읽는 중...'), info['title'], len(info['imgs']), t_pages))
|
||||
data = {
|
||||
'_token': csrf,
|
||||
'id': str(info['id']),
|
||||
'dir': soup.find('input', type='hidden', id='dir')['value'],
|
||||
'v_pages': len(info['imgs']),
|
||||
't_pages': str(t_pages),
|
||||
'type': '1',
|
||||
}
|
||||
r = session.post('https://asmhentai.com/inc/thumbs_loader.php', data=data)
|
||||
soup_more = Soup(r.text)
|
||||
read_imgs(soup_more)
|
||||
|
||||
return info
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import webbrowser
|
|||
import errors
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_avgle(Downloader):
|
||||
type = 'avgle'
|
||||
single = True
|
||||
|
@ -29,14 +29,14 @@ class Downloader_avgle(Downloader):
|
|||
self.urls.append(video.url)
|
||||
|
||||
self.setIcon(video.thumb)
|
||||
|
||||
|
||||
self.title = video.title
|
||||
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_video(url, cw=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
|
||||
check_alive(cw)
|
||||
|
||||
data = cw.data_
|
||||
|
@ -61,13 +61,13 @@ def get_video(url, cw=None):
|
|||
|
||||
url_thumb = soup.find('meta', {'property': 'og:image'}).attrs['content']
|
||||
title = soup.find('meta', {'property': 'og:title'}).attrs['content'].strip()
|
||||
|
||||
|
||||
video = Video(stream, url_thumb, url, title)
|
||||
|
||||
return video
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, url, url_thumb, referer, title):
|
||||
self.url = LazyUrl(referer, lambda x: url, self)
|
||||
self.url_thumb = url_thumb
|
||||
|
@ -76,5 +76,3 @@ class Video(object):
|
|||
self.title = title
|
||||
ext = '.mp4'
|
||||
self.filename = u'{}{}'.format(clean_title(title, n=-len(ext)), ext)
|
||||
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@ def get_id(url):
|
|||
return re.find('baraag.net/([^/]+)', url.lower())
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_baraag(Downloader):
|
||||
type = 'baraag'
|
||||
URLS = ['baraag.net']
|
||||
display_name = 'baraag.net'
|
||||
|
||||
ACCEPT_COOKIES = [r'(.*\.)?baraag\.net']
|
||||
|
||||
def init(self):
|
||||
self.referer = self.url
|
||||
|
||||
|
@ -49,6 +50,3 @@ class Downloader_baraag(Downloader):
|
|||
self.filenames[img.url] = img.filename
|
||||
|
||||
self.title = self.name
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#coding:utf8
|
||||
from __future__ import print_function
|
||||
import downloader
|
||||
from utils import Soup, cut_pair, LazyUrl, Downloader, get_print, get_max_range, try_n, clean_title, check_alive
|
||||
import json
|
||||
|
@ -8,13 +7,13 @@ import os
|
|||
from translator import tr_
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_bcy(Downloader):
|
||||
type = 'bcy'
|
||||
URLS = ['bcy.net/item/detail/', 'bcy.net/u/']
|
||||
MAX_CORE = 8
|
||||
display_name = '半次元'
|
||||
|
||||
|
||||
def init(self):
|
||||
self.html = downloader.read_html(self.url)
|
||||
self.info = get_info(self.url, self.html)
|
||||
|
@ -49,7 +48,7 @@ def get_ssr_data(html):
|
|||
def get_imgs(url, html=None, cw=None):
|
||||
if '/detail/' not in url:
|
||||
return get_imgs_channel(url, html, cw)
|
||||
|
||||
|
||||
if html is None:
|
||||
html = downloader.read_html(url)
|
||||
|
||||
|
@ -68,7 +67,7 @@ def get_imgs(url, html=None, cw=None):
|
|||
return imgs
|
||||
|
||||
|
||||
class Image_single(object):
|
||||
class Image_single:
|
||||
def __init__(self, url ,referer, p):
|
||||
self._url = url
|
||||
self.p = p
|
||||
|
@ -80,7 +79,7 @@ class Image_single(object):
|
|||
return self._url
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, referer, id, p):
|
||||
self.id = id
|
||||
self.p = p
|
||||
|
@ -109,7 +108,7 @@ def get_info(url, html):
|
|||
info['artist'] = uname.text.strip()
|
||||
|
||||
j = get_ssr_data(html)
|
||||
|
||||
|
||||
if '/detail/' in url:
|
||||
info['uid'] = j['detail']['detail_user']['uid']
|
||||
info['id'] = j['detail']['post_data']['item_id']
|
||||
|
@ -117,17 +116,17 @@ def get_info(url, html):
|
|||
info['uid'] = j['homeInfo']['uid']
|
||||
|
||||
return info
|
||||
|
||||
|
||||
|
||||
def get_imgs_channel(url, html=None, cw=None):
|
||||
print_ = get_print(cw)
|
||||
if html is None:
|
||||
html = downloader.read_html(url)
|
||||
info = get_info(url, html)
|
||||
|
||||
|
||||
# Range
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
|
||||
ids = set()
|
||||
imgs = []
|
||||
for p in range(1000):
|
||||
|
@ -170,4 +169,3 @@ def get_imgs_channel(url, html=None, cw=None):
|
|||
print('over max_pid:', max_pid)
|
||||
break
|
||||
return imgs[:max_pid]
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import clf2
|
|||
import errors
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_bdsmlr(Downloader):
|
||||
type = 'bdsmlr'
|
||||
URLS = ['bdsmlr.com']
|
||||
|
@ -20,7 +20,7 @@ class Downloader_bdsmlr(Downloader):
|
|||
def init(self):
|
||||
if u'bdsmlr.com/post/' in self.url:
|
||||
raise errors.Invalid(tr_(u'개별 다운로드는 지원하지 않습니다: {}').format(self.url))
|
||||
|
||||
|
||||
self.url = 'https://{}.bdsmlr.com'.format(self.id_)
|
||||
self.session = Session()
|
||||
clf2.solve(self.url, session=self.session, cw=self.cw)
|
||||
|
@ -38,14 +38,14 @@ class Downloader_bdsmlr(Downloader):
|
|||
|
||||
def read(self):
|
||||
info = get_imgs(self.id_, session=self.session, cw=self.cw)
|
||||
|
||||
|
||||
for post in info['posts']:
|
||||
self.urls.append(post.url)
|
||||
|
||||
self.title = u'{} (bdsmlr_{})'.format(clean_title(info['username']), self.id_)
|
||||
|
||||
|
||||
class Post(object):
|
||||
|
||||
class Post:
|
||||
def __init__(self, url, referer, id, p):
|
||||
self.id = id
|
||||
self.url = LazyUrl(referer, lambda x: url, self)
|
||||
|
@ -71,7 +71,7 @@ def foo(url, soup, info, reblog=False):
|
|||
post = Post(mag.attrs['href'], url, id, p)
|
||||
info['posts'].append(post)
|
||||
info['c'] += 20 if info['c'] else 5
|
||||
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_imgs(user_id, session, cw=None):
|
||||
|
@ -89,7 +89,7 @@ def get_imgs(user_id, session, cw=None):
|
|||
username = soup.find('title').text.strip()###
|
||||
print('username:', username)
|
||||
info['username'] = username
|
||||
|
||||
|
||||
token = soup.find('meta', {'name': 'csrf-token'}).attrs['content']
|
||||
print_(u'token: {}'.format(token))
|
||||
|
||||
|
@ -136,9 +136,8 @@ def get_imgs(user_id, session, cw=None):
|
|||
cw.setTitle(s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
|
||||
if len(info['posts']) > max_pid:
|
||||
break
|
||||
|
||||
return info
|
||||
|
||||
|
|
|
@ -29,9 +29,9 @@ RESOLS[80] = '1080p'
|
|||
RESOLS[64] = '720p'
|
||||
RESOLS[32] = '480p'
|
||||
RESOLS[16] = '360p'
|
||||
|
||||
|
||||
class Video(object):
|
||||
|
||||
class Video:
|
||||
|
||||
def __init__(self, url, referer, id, p):
|
||||
ext = os.path.splitext(url.split('?')[0])[1]
|
||||
|
@ -61,10 +61,10 @@ def fix_url(url, cw=None):
|
|||
return url_new
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_bili(Downloader):
|
||||
type = 'bili'
|
||||
URLS = ['bilibili.com', 'bilibili.tv']
|
||||
URLS = [r'regex:'+_VALID_URL]
|
||||
lock = True
|
||||
detect_removed = False
|
||||
detect_local_lazy = False
|
||||
|
@ -154,10 +154,11 @@ def get_resolution_(quality):
|
|||
return RESOLS[quality]
|
||||
|
||||
|
||||
@try_n(4)
|
||||
def get_videos(url, cw=None, depth=0):
|
||||
print_ = get_print(cw)
|
||||
res = get_resolution()
|
||||
|
||||
|
||||
mobj = re.match(_VALID_URL, url)
|
||||
video_id = mobj.group('id')
|
||||
anime_id = mobj.group('anime_id')
|
||||
|
@ -179,9 +180,9 @@ def get_videos(url, cw=None, depth=0):
|
|||
print_('cid: {}'.format(cid))
|
||||
headers = {'Referer': url}
|
||||
entries = []
|
||||
|
||||
|
||||
RENDITIONS = ['qn={}&quality={}&type='.format(qlt, qlt) for qlt in RESOLS.keys()]# + ['quality=2&type=mp4']
|
||||
|
||||
|
||||
for num, rendition in enumerate(RENDITIONS, start=1):
|
||||
print('####', num, rendition)
|
||||
payload = 'appkey=%s&cid=%s&otype=json&%s' % (_APP_KEY, cid, rendition)
|
||||
|
@ -208,7 +209,7 @@ def get_videos(url, cw=None, depth=0):
|
|||
if int(re.find('([0-9]+)p', resolution)) > res:
|
||||
print_('skip resolution')
|
||||
continue
|
||||
|
||||
|
||||
for idx, durl in enumerate(video_info['durl']):
|
||||
# 1343
|
||||
if idx == 0:
|
||||
|
@ -216,19 +217,19 @@ def get_videos(url, cw=None, depth=0):
|
|||
if size < 1024 * 1024 and depth == 0:
|
||||
print_('size is too small')
|
||||
return get_videos(url, cw, depth+1)
|
||||
|
||||
|
||||
formats = [
|
||||
{'url': durl['url'],
|
||||
{'url': durl['url'],
|
||||
'filesize': int_or_none(durl['size'])}]
|
||||
for backup_url in durl.get('backup_url', []):
|
||||
formats.append({'url': backup_url,
|
||||
formats.append({'url': backup_url,
|
||||
'preference': -2 if 'hd.mp4' in backup_url else -3})
|
||||
|
||||
for a_format in formats:
|
||||
a_format.setdefault('http_headers', {}).update({'Referer': url})
|
||||
|
||||
entries.append({'id': '%s_part%s' % (video_id, idx),
|
||||
'duration': float_or_none(durl.get('length'), 1000),
|
||||
entries.append({'id': '%s_part%s' % (video_id, idx),
|
||||
'duration': float_or_none(durl.get('length'), 1000),
|
||||
'formats': formats})
|
||||
|
||||
break
|
||||
|
@ -239,7 +240,7 @@ def get_videos(url, cw=None, depth=0):
|
|||
video = Video(url_video, url, cid, len(videos))
|
||||
videos.append(video)
|
||||
|
||||
info = {'title': clean_title(title),
|
||||
info = {'title': clean_title(title),
|
||||
'url_thumb': url_thumb}
|
||||
return (
|
||||
videos, info)
|
||||
|
@ -251,4 +252,3 @@ def get_pages(html):
|
|||
data = json.loads(data_raw)
|
||||
pages = data['videoData']['pages']
|
||||
return pages
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@ import os
|
|||
def decode(s, hash):
|
||||
# generateKey
|
||||
key = int(hash[:16], 16)
|
||||
|
||||
filter = [int((key>>i*8)%256) for i in range(8)][::-1] #
|
||||
|
||||
filter = [int((key>>i*8)%256) for i in range(8)][::-1] #
|
||||
s2 = bytes(x^y for x, y in zip(s, cycle(filter)))
|
||||
return s2
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, src, hash, p, page):
|
||||
def f(_):
|
||||
f = BytesIO()
|
||||
|
@ -36,13 +36,13 @@ class Image(object):
|
|||
self.filename = u'{}/{:04}.jpg'.format(page.title, p)
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
def __init__(self, url, title):
|
||||
self.url = url
|
||||
self.title = clean_title(title)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_comicwalker(Downloader):
|
||||
type = 'comicwalker'
|
||||
URLS = ['comic-walker.com/contents/detail/', 'comic-walker.jp/contents/detail/']
|
||||
|
@ -134,7 +134,7 @@ def f(url):
|
|||
def get_imgs(url, soup=None, cw=None):
|
||||
if soup is None:
|
||||
html = downloader.read_html(url)
|
||||
soup = Soup(hrml)
|
||||
soup = Soup(html)
|
||||
|
||||
title = get_title(soup, cw)
|
||||
|
||||
|
@ -147,13 +147,12 @@ def get_imgs(url, soup=None, cw=None):
|
|||
if imgs_already:
|
||||
imgs += imgs_already
|
||||
continue
|
||||
|
||||
|
||||
if cw is not None:
|
||||
if not cw.alive:
|
||||
return
|
||||
cw.setTitle(u'{} {} / {} ({} / {})'.format(tr_(u'읽는 중...'), title, page.title, i+1, len(pages)))
|
||||
|
||||
|
||||
imgs += get_imgs_page(page)
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ def get_id(url):
|
|||
return re.find(r'/view/([0-9a-z]+)', url, err='no id')
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_coub(Downloader):
|
||||
type = 'coub'
|
||||
URLS = ['coub.com', r'regex:'+PATTEN_IMAGIZER]
|
||||
|
@ -38,9 +38,9 @@ class Downloader_coub(Downloader):
|
|||
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self, pp=self.pp)
|
||||
self.cw = cw
|
||||
|
@ -49,7 +49,7 @@ class Video(object):
|
|||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
fs = [f for f in info['formats'] if f['ext'] == 'mp4']
|
||||
|
@ -57,7 +57,7 @@ class Video(object):
|
|||
self._url = f['url']
|
||||
## fs = [f for f in info['formats'] if f['ext'] == 'mp3']
|
||||
## self.f_audio = sorted(fs, key=lambda f: int(f.get('filesize', 0)))[-1]
|
||||
|
||||
|
||||
self.thumb_url = info['thumbnails'][0]['url']
|
||||
self.thumb = IO()
|
||||
downloader.download(self.thumb_url, buffer=self.thumb)
|
||||
|
|
|
@ -9,7 +9,6 @@ from ratelimit import limits, sleep_and_retry
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_danbooru(Downloader):
|
||||
type='danbooru'
|
||||
URLS = ['danbooru.donmai.us']
|
||||
|
@ -35,7 +34,8 @@ class Downloader_danbooru(Downloader):
|
|||
if 'donmai.us/favorites' in self.url:
|
||||
id = qs.get('user_id', [''])[0]
|
||||
print('len(id) =', len(id), '"{}"'.format(id))
|
||||
assert len(id) > 0, '[Fav] User id is not specified'
|
||||
if not id:
|
||||
raise AssertionError('[Fav] User id is not specified')
|
||||
id = 'fav_{}'.format(id)
|
||||
elif 'donmai.us/explore/posts/popular' in self.url: #4160
|
||||
soup = read_soup(self.url, self.cw)
|
||||
|
@ -56,11 +56,11 @@ class Downloader_danbooru(Downloader):
|
|||
|
||||
for img in imgs:
|
||||
self.urls.append(img.url)
|
||||
|
||||
self.title = self.name
|
||||
|
||||
|
||||
class Image(object):
|
||||
self.title = self.name
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, id, url, cw):
|
||||
self._cw = cw
|
||||
self.id = id
|
||||
|
@ -73,8 +73,13 @@ class Image(object):
|
|||
img = ori.find('a')['href']
|
||||
else:
|
||||
img = soup.find('li', id='post-info-size').find('a')['href']
|
||||
|
||||
if get_ext(img) == '.zip': #4635
|
||||
img = soup.find('section', id='content').find('video')['src']
|
||||
|
||||
img = urljoin(url, img)
|
||||
ext = get_ext(img)
|
||||
|
||||
self.filename = '{}{}'.format(self.id, ext)
|
||||
return img
|
||||
|
||||
|
@ -84,8 +89,8 @@ class Image(object):
|
|||
@limits(2, 1)
|
||||
def wait(cw):
|
||||
check_alive(cw)
|
||||
|
||||
|
||||
|
||||
|
||||
def setPage(url, page):
|
||||
# Always use HTTPS
|
||||
url = url.replace('http://', 'https://')
|
||||
|
@ -99,7 +104,7 @@ def setPage(url, page):
|
|||
url = re.sub('page=[0-9]*', 'page={}'.format(page), url)
|
||||
else:
|
||||
url += '&page={}'.format(page)
|
||||
|
||||
|
||||
return url
|
||||
|
||||
|
||||
|
@ -120,7 +125,7 @@ def get_imgs(url, title=None, range_=None, cw=None):
|
|||
|
||||
# Range
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
|
||||
if range_ is None:
|
||||
range_ = range(1, 1001)
|
||||
print(range_)
|
||||
|
@ -150,13 +155,13 @@ def get_imgs(url, title=None, range_=None, cw=None):
|
|||
|
||||
if empty_count_global >= 6:
|
||||
break
|
||||
|
||||
|
||||
for article in articles:
|
||||
id = article.attrs['data-id']
|
||||
|
||||
#url_img = article.attrs['data-file-url'].strip()
|
||||
url_img = urljoin(url, article.find('a', class_='post-preview-link')['href']) #4160
|
||||
|
||||
|
||||
#print(url_img)
|
||||
if url_img not in url_imgs:
|
||||
url_imgs.add(url_img)
|
||||
|
@ -165,9 +170,9 @@ def get_imgs(url, title=None, range_=None, cw=None):
|
|||
|
||||
if len(imgs) >= max_pid:
|
||||
break
|
||||
|
||||
|
||||
if cw is not None:
|
||||
cw.setTitle('{} {} - {}'.format(tr_('읽는 중...'), title, len(imgs)))
|
||||
i += 1
|
||||
|
||||
|
||||
return imgs[:max_pid]
|
||||
|
|
|
@ -30,7 +30,7 @@ import requests
|
|||
import errors
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class DownloaderDiscordEmoji(Downloader):
|
||||
type = "discord"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import downloader
|
||||
import ytdl
|
||||
from utils import Downloader, Session, try_n, LazyUrl, get_ext, format_filename, clean_title, get_print
|
||||
from utils import Downloader, Session, try_n, LazyUrl, get_ext, format_filename, clean_title, get_print, get_resolution
|
||||
from io import BytesIO
|
||||
import ree as re
|
||||
from m3u8_tools import playlist2stream, M3u8_stream
|
||||
|
@ -8,10 +8,10 @@ import utils
|
|||
import ffmpeg
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_etc(Downloader):
|
||||
type = 'etc'
|
||||
URLS = []
|
||||
URLS = ['thisvid.com'] #5153
|
||||
single = True
|
||||
MAX_PARALLEL = 8
|
||||
display_name = 'Etc'
|
||||
|
@ -20,8 +20,8 @@ class Downloader_etc(Downloader):
|
|||
self.session = Session()
|
||||
name = ytdl.get_extractor_name(self.url)
|
||||
self.print_('extractor: {}'.format(name))
|
||||
if name == 'generic':
|
||||
raise NotImplementedError()
|
||||
#if name == 'generic':
|
||||
# raise NotImplementedError()
|
||||
|
||||
def read(self):
|
||||
video = get_video(self.url, self.session, self.cw)
|
||||
|
@ -51,19 +51,26 @@ def int_or_none(s):
|
|||
def format_(f):
|
||||
if f is None:
|
||||
return 'None'
|
||||
return '{} - {} - {} - {}'.format(f['format'], f['_resolution'], f['_audio'], f['url'])
|
||||
return 'format:{} - resolution:{} - vbr:{} - audio:{} - url:{}'.format(f['format'], f['_resolution'], f['_vbr'], f['_audio'], f['url'])
|
||||
|
||||
|
||||
class UnSupportedError(Exception):pass
|
||||
|
||||
|
||||
def get_video(url, session, cw, ie_key=None):
|
||||
print_ = get_print(cw)
|
||||
try:
|
||||
video = _get_video(url, session, cw, ie_key, allow_m3u8=True)
|
||||
if isinstance(video, Exception):
|
||||
raise video
|
||||
if isinstance(video.url(), M3u8_stream):
|
||||
c = video.url().segs[0].download(cw)
|
||||
if not c:
|
||||
raise Exception('invalid m3u8')
|
||||
return video
|
||||
except Exception as e:
|
||||
if isinstance(e, UnSupportedError):
|
||||
raise e
|
||||
print_(e)
|
||||
return _get_video(url, session, cw, ie_key, allow_m3u8=False)
|
||||
|
||||
|
@ -77,7 +84,12 @@ def _get_video(url, session, cw, ie_key=None, allow_m3u8=True):
|
|||
'playlistend': 1,
|
||||
}
|
||||
ydl = ytdl.YoutubeDL(options, cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
try:
|
||||
info = ydl.extract_info(url)
|
||||
except Exception as e:
|
||||
if 'ERROR: Unsupported URL' in str(e):
|
||||
return UnSupportedError(str(e))
|
||||
raise e
|
||||
if not ie_key:
|
||||
ie_key = ytdl.get_extractor_name(url)
|
||||
info['ie_key'] = ie_key
|
||||
|
@ -105,11 +117,17 @@ def _get_video(url, session, cw, ie_key=None, allow_m3u8=True):
|
|||
fs = []
|
||||
for i, f in enumerate(formats):
|
||||
f['_index'] = i
|
||||
f['_resolution'] = f.get('vbr') or int_or_none(re.find('([0-9]+)p', f['format'], re.IGNORECASE)) or f.get('height') or f.get('width') or int(f.get('vcodec', 'none') != 'none')
|
||||
f['_resolution'] = int_or_none(re.find('([0-9]+)p', f['format'], re.IGNORECASE)) or f.get('height') or f.get('width') or int(f.get('vcodec', 'none') != 'none')
|
||||
f['_vbr'] = f.get('vbr') or 0
|
||||
f['_audio'] = f.get('abr') or f.get('asr') or int(f.get('acodec', 'none') != 'none')
|
||||
print_(format_(f))
|
||||
fs.append(f)
|
||||
|
||||
#4773
|
||||
res = max(get_resolution(), min(f['_resolution'] for f in fs))
|
||||
print_(f'res: {res}')
|
||||
fs = [f for f in fs if f['_resolution'] <= res]
|
||||
|
||||
if not fs:
|
||||
raise Exception('No videos')
|
||||
|
||||
|
@ -123,13 +141,13 @@ def _get_video(url, session, cw, ie_key=None, allow_m3u8=True):
|
|||
print_('invalid url: {}'.format(f['url']))
|
||||
return list(fs)[0]#
|
||||
|
||||
f_video = filter_f(reversed(sorted(fs, key=lambda f:(f['_resolution'], f['_index']))))
|
||||
f_video = filter_f(reversed(sorted(fs, key=lambda f:(f['_resolution'], f['_vbr'], f['_index']))))
|
||||
print_('video0: {}'.format(format_(f_video)))
|
||||
|
||||
if f_video['_audio']:
|
||||
f_audio = None
|
||||
else:
|
||||
fs_audio = sorted([f_audio for f_audio in fs if (not f_audio['_resolution'] and f_audio['_audio'])], key=lambda f:(f['_audio'], f['_index']))
|
||||
fs_audio = sorted([f_audio for f_audio in fs if (not f_audio['_resolution'] and f_audio['_audio'])], key=lambda f:(f['_audio'], f['_vbr'], f['_index']))
|
||||
if fs_audio:
|
||||
f_audio = fs_audio[-1]
|
||||
else:
|
||||
|
@ -163,7 +181,7 @@ def get_ext_(url, session, referer):
|
|||
return ext
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, f, f_audio, info, session, referer, cw=None):
|
||||
self.f_audio = f_audio
|
||||
self.cw = cw
|
||||
|
@ -174,6 +192,7 @@ class Video(object):
|
|||
self.header = utils.capitalize(get_ie_key(info))
|
||||
self.session = session
|
||||
self.referer = referer
|
||||
self.subs = ytdl.get_subtitles(info)
|
||||
|
||||
self.url_thumb = info.get('thumbnail')
|
||||
self.thumb = BytesIO()
|
||||
|
@ -190,8 +209,9 @@ class Video(object):
|
|||
ext = '.mp3'
|
||||
|
||||
if ext.lower() == '.m3u8':
|
||||
res = get_resolution() #4773
|
||||
try:
|
||||
url = playlist2stream(self.url, referer, session=session, n_thread=4)
|
||||
url = playlist2stream(self.url, referer, session=session, n_thread=4, res=res)
|
||||
except:
|
||||
url = M3u8_stream(self.url, referer=referer, session=session, n_thread=4)
|
||||
ext = '.mp4'
|
||||
|
@ -205,4 +225,5 @@ class Video(object):
|
|||
f = BytesIO()
|
||||
downloader.download(self.f_audio['url'], buffer=f, referer=self.referer, session=self.session)
|
||||
ffmpeg.merge(filename, f, cw=self.cw)
|
||||
utils.pp_subtitle(self, filename, self.cw)
|
||||
return filename
|
||||
|
|
|
@ -6,7 +6,7 @@ from io import BytesIO
|
|||
PATTERN_ID = r'/content/([^/]+)'
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_fc2(Downloader):
|
||||
type = 'fc2'
|
||||
single = True
|
||||
|
@ -34,11 +34,11 @@ class Downloader_fc2(Downloader):
|
|||
f = BytesIO()
|
||||
downloader.download(video.url_thumb, referer=self.url, buffer=f)
|
||||
self.setIcon(f)
|
||||
|
||||
|
||||
self.title = info['title']
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
|
||||
def __init__(self, url, url_thumb, referer, title, id_, session):
|
||||
self._url = url
|
||||
|
|
|
@ -5,7 +5,7 @@ from timee import sleep
|
|||
from hashlib import md5
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_file(Downloader):
|
||||
type = 'file'
|
||||
single = True
|
||||
|
@ -15,7 +15,7 @@ class Downloader_file(Downloader):
|
|||
def fix_url(cls, url):
|
||||
if '://' not in url:
|
||||
url = 'https://' + url.lstrip('/')
|
||||
return 'file_' + url
|
||||
return url
|
||||
|
||||
def read(self):
|
||||
qs = query_url(self.url)
|
||||
|
@ -35,15 +35,15 @@ class Downloader_file(Downloader):
|
|||
ext = ''
|
||||
if not ext:
|
||||
ext = get_ext(name)
|
||||
|
||||
|
||||
name = os.path.splitext(name)[0]
|
||||
|
||||
self.urls.append(self.url)
|
||||
|
||||
|
||||
id_ = md5(self.url.encode('utf8')).hexdigest()[:8]
|
||||
tail = ' ({}){}'.format(id_, ext)
|
||||
filename = clean_title(name, n=-len(tail)) + tail
|
||||
|
||||
|
||||
self.filenames[self.url] = filename
|
||||
|
||||
|
||||
self.title = filename
|
||||
|
|
|
@ -22,7 +22,7 @@ def b58decode(s):
|
|||
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, photo):
|
||||
self.photo = photo
|
||||
self.id = photo.id
|
||||
|
@ -51,12 +51,12 @@ def find_ps(url):
|
|||
return user, ps
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_flickr(Downloader):
|
||||
type = 'flickr'
|
||||
URLS = ['flickr.com']
|
||||
_name = None
|
||||
|
||||
|
||||
def init(self):
|
||||
if 'flickr.com' in self.url.lower():
|
||||
self.url = self.url.replace('http://', 'https://')
|
||||
|
@ -94,14 +94,14 @@ def get_imgs(url, title=None, cw=None):
|
|||
if not flickr_auth.isAuth:
|
||||
raise Exception('No Auth')
|
||||
|
||||
|
||||
|
||||
if '/albums/' in url:
|
||||
user, ps = find_ps(url)
|
||||
handle = ps
|
||||
else:
|
||||
user = flickr_api.Person.findByUrl(url)
|
||||
handle = user
|
||||
|
||||
|
||||
photos = []
|
||||
|
||||
per_page = 500
|
||||
|
@ -125,4 +125,3 @@ def get_imgs(url, title=None, cw=None):
|
|||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -28,12 +28,13 @@ def get_tags(url):
|
|||
return id
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_gelbooru(Downloader):
|
||||
type = 'gelbooru'
|
||||
URLS = ['gelbooru.com']
|
||||
MAX_CORE = 8
|
||||
_name = None
|
||||
ACCEPT_COOKIES = [r'(.*\.)?gelbooru\.com']
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -79,8 +80,8 @@ class LazyUrl_gelbooru(LazyUrl):
|
|||
img = Image(data['id'], data['url'])
|
||||
return img.url
|
||||
|
||||
|
||||
class Image(object):
|
||||
|
||||
class Image:
|
||||
def __init__(self, id_, url):
|
||||
self.id_ = id_
|
||||
self._url = url
|
||||
|
@ -134,7 +135,7 @@ def get_imgs(url, title=None, cw=None):
|
|||
else:
|
||||
cookies = {'fringeBenefits': 'yup'}
|
||||
print_('user_id: {}'.format(user_id))
|
||||
|
||||
|
||||
# Range
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
|
@ -167,7 +168,7 @@ def get_imgs(url, title=None, cw=None):
|
|||
if count_no_imgs > 1:
|
||||
print('break')
|
||||
break
|
||||
|
||||
|
||||
if len(imgs) >= max_pid:
|
||||
break
|
||||
|
||||
|
@ -175,5 +176,5 @@ def get_imgs(url, title=None, cw=None):
|
|||
if not cw.alive:
|
||||
break
|
||||
cw.setTitle(u'{} {} - {}'.format(tr_(u'읽는 중...'), title, len(imgs)))
|
||||
|
||||
|
||||
return imgs[:max_pid]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#coding: utf8
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
import os
|
||||
import utils
|
||||
|
@ -11,12 +10,12 @@ from translator import tr_
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_hameln(Downloader):
|
||||
type = 'hameln'
|
||||
URLS = ['syosetu.org']
|
||||
MAX_CORE = 2
|
||||
detect_removed = False
|
||||
ACCEPT_COOKIES = [r'(.*\.)?syosetu\.org']
|
||||
|
||||
def init(self):
|
||||
id_ = re.find('/novel/([^/]+)', self.url)
|
||||
|
@ -59,7 +58,7 @@ class Downloader_hameln(Downloader):
|
|||
self.cw.pbar.setFormat('[%v/%m]')
|
||||
|
||||
|
||||
class Text(object):
|
||||
class Text:
|
||||
def __init__(self, page, p):
|
||||
self.page = page
|
||||
self.url = LazyUrl(page.url, self.get, self)
|
||||
|
@ -71,9 +70,9 @@ class Text(object):
|
|||
f.write(text.encode('utf8'))
|
||||
f.seek(0)
|
||||
return f
|
||||
|
||||
|
||||
class Page(object):
|
||||
|
||||
class Page:
|
||||
def __init__(self, title, url):
|
||||
self.title = clean_title(title)
|
||||
self.url = url
|
||||
|
@ -114,7 +113,7 @@ def get_pages(url, soup=None):
|
|||
def read_page(page):
|
||||
html = read_html(page.url)
|
||||
soup = Soup(html)
|
||||
|
||||
|
||||
text_top = get_text(soup.find('div', id='maegaki'))
|
||||
print(text_top.count('\n'))
|
||||
text_mid = get_text(soup.find('div', id='honbun'))
|
||||
|
@ -149,6 +148,5 @@ def get_info(url, soup=None):
|
|||
info['artist'] = soup.find('span', {'itemprop':'author'}).text.strip()
|
||||
info['title'] = soup.find('span', {'itemprop':'name'}).text.strip()
|
||||
sss = get_sss(soup)
|
||||
info['novel_ex'] = get_text(sss[-2], '')
|
||||
info['novel_ex'] = get_text(sss[-2])
|
||||
return info
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from m3u8_tools import M3u8_stream
|
|||
from random import randrange
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
|
||||
def __init__(self, info, stream):
|
||||
self.info = info
|
||||
|
@ -34,7 +34,7 @@ class Video(object):
|
|||
return ('Video({})').format(self.id)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_hanime(Downloader):
|
||||
type = 'hanime'
|
||||
URLS = ['hanime.tv/hentai-videos/', 'hanime.tv/videos/']
|
||||
|
@ -44,7 +44,7 @@ class Downloader_hanime(Downloader):
|
|||
def read(self):
|
||||
video, session = get_video(self.url, cw=self.cw)
|
||||
self.video = video
|
||||
|
||||
|
||||
self.urls.append(video.url)
|
||||
self.filenames[video.url] = video.filename
|
||||
|
||||
|
@ -116,10 +116,7 @@ def get_video(url, session=None, cw=None):
|
|||
stream = sorted(steams_filtered, key=lambda _: _['height'])[-1]
|
||||
else:
|
||||
stream = sorted(streams_good, key=lambda _: _['height'])[0]
|
||||
|
||||
|
||||
print_('Final stream:')
|
||||
print_stream(stream)
|
||||
return Video(info, stream), session
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#coding: utf8
|
||||
import downloader
|
||||
from utils import Downloader, Session, Soup, LazyUrl, urljoin, get_ext, clean_title
|
||||
from utils import Downloader, Session, Soup, LazyUrl, urljoin, get_ext, clean_title, try_n
|
||||
import ree as re
|
||||
from translator import tr_
|
||||
import clf2
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
from m3u8_tools import M3u8_stream
|
||||
from timee import sleep
|
||||
|
||||
|
||||
|
||||
|
@ -17,22 +19,38 @@ class Image:
|
|||
self.session = session
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(2, 1)
|
||||
@limits(1, 1)
|
||||
def get(self, referer):
|
||||
soup = downloader.read_soup(self._url, referer, session=self.session)
|
||||
div = soup.find('div', id='display_image_detail')
|
||||
url = urljoin(self._url, div.find('img').parent['href'])
|
||||
div = soup.find('div', id='display_image_detail') or soup.find('ul', id='detail_list')
|
||||
parent = div.find('img').parent
|
||||
while not parent.get('href'):
|
||||
parent = parent.parent
|
||||
url = urljoin(self._url, parent['href'])
|
||||
ext = get_ext(url)
|
||||
self.filename = '{:04}{}'.format(self._p, ext)
|
||||
return url, self._url
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Video:
|
||||
|
||||
def __init__(self, src, referer, title, session):
|
||||
ext = get_ext(src)
|
||||
if ext == '.m3u8':
|
||||
_src = src
|
||||
src = M3u8_stream(_src, referer=referer, session=session)
|
||||
ext = '.mp4'
|
||||
self.url = LazyUrl(referer, lambda _: src, self)
|
||||
self.filename = '{}{}'.format(clean_title(title), ext)
|
||||
|
||||
|
||||
|
||||
class Downloader_hentaicosplay(Downloader):
|
||||
type = 'hentaicosplay'
|
||||
URLS = ['hentai-cosplays.com']
|
||||
URLS = ['hentai-cosplays.com', 'porn-images-xxx.com']
|
||||
icon = None
|
||||
display_name = 'Hentai Cosplay'
|
||||
MAX_PARALLEL = 1 # must be 1
|
||||
MAX_CORE = 4
|
||||
|
||||
@classmethod
|
||||
|
@ -40,19 +58,41 @@ class Downloader_hentaicosplay(Downloader):
|
|||
url = re.sub(r'/page/[0-9]+', '', url)
|
||||
url = re.sub(r'/attachment/[0-9]+', '', url)
|
||||
url = re.sub(r'([a-zA-Z]+\.)hentai-cosplays\.com', 'hentai-cosplays.com', url)
|
||||
url = re.sub(r'.com/story/', '.com/image/', url)
|
||||
return url
|
||||
|
||||
def init(self):
|
||||
self.session = Session()
|
||||
|
||||
@try_n(2)
|
||||
def read(self):
|
||||
#4961
|
||||
ua = downloader.random_ua()
|
||||
self.print_(f'read start ua: {ua}')
|
||||
downloader.REPLACE_UA[r'hentai-cosplays\.com'] = ua
|
||||
downloader.REPLACE_UA[r'porn-images-xxx\.com'] = ua
|
||||
|
||||
if '/video/' in self.url:
|
||||
res = clf2.solve(self.url, session=self.session, cw=self.cw)
|
||||
soup = Soup(res['html'])
|
||||
title = (soup.find('h1', id='post_title') or soup.find('div', id='page').find('h2')).text.strip()
|
||||
self.title = title
|
||||
view = soup.find('div', id='post') or soup.find('div', class_='video-container')
|
||||
video = view.find('video')
|
||||
src = video.find('source')['src']
|
||||
src = urljoin(self.url, src)
|
||||
video = Video(src, self.url, title, self.session)
|
||||
self.urls.append(video.url)
|
||||
self.single = True
|
||||
return
|
||||
|
||||
if '/image/' not in self.url:
|
||||
raise NotImplementedError('Not a post')
|
||||
|
||||
res = clf2.solve(self.url, session=self.session, cw=self.cw)
|
||||
soup = Soup(res['html'])
|
||||
title = soup.find('h2').text
|
||||
paginator = soup.find('div', id='paginator')
|
||||
title = (soup.find('h2') or soup.find('h3')).text
|
||||
paginator = soup.find('div', id='paginator') or soup.find('div', class_='paginator_area')
|
||||
pages = [self.url]
|
||||
for a in paginator.findAll('a'):
|
||||
href = a.get('href')
|
||||
|
@ -61,23 +101,27 @@ class Downloader_hentaicosplay(Downloader):
|
|||
href = urljoin(self.url, href)
|
||||
if href not in pages:
|
||||
pages.append(href)
|
||||
self.print_(f'pages: {len(pages)}')
|
||||
|
||||
imgs = []
|
||||
for i, page in enumerate(pages):
|
||||
sleep(2, self.cw)
|
||||
if page == self.url:
|
||||
soup_page = soup
|
||||
else:
|
||||
soup_page = downloader.read_soup(page, session=self.session)
|
||||
view = soup_page.find('div', id='post')
|
||||
view = soup_page.find('div', id='post') or soup_page.find('ul', id='detail_list')
|
||||
for img in view.findAll('img'):
|
||||
href = img.parent['href']
|
||||
href = img.parent.get('href') or img.parent.parent.get('href')
|
||||
if not href:
|
||||
continue
|
||||
href = urljoin(page, href)
|
||||
img = Image(href, page, len(imgs), self.session)
|
||||
imgs.append(img)
|
||||
self.print_(f'imgs: {len(imgs)}')
|
||||
self.cw.setTitle('{} {} ({} / {})'.format(tr_('읽는 중...'), title, i+1, len(pages)))
|
||||
|
||||
for img in imgs:
|
||||
self.urls.append(img.url)
|
||||
|
||||
self.title = clean_title(title)
|
||||
|
||||
|
|
|
@ -9,18 +9,18 @@ URL_ENTER = 'https://www.hentai-foundry.com/site/index?enterAgree=1&size=1550'
|
|||
URL_FILTER = 'https://www.hentai-foundry.com/site/filters'
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, session):
|
||||
@try_n(4)
|
||||
def f(_):
|
||||
html = downloader.read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
|
||||
|
||||
box = soup.find('section', id='picBox')
|
||||
img = box.find('img')
|
||||
if img is None:
|
||||
raise Exception('No img')
|
||||
|
||||
|
||||
onclick = img.attrs.get('onclick', '')
|
||||
if onclick and '.src' in onclick:
|
||||
print('onclick', onclick)
|
||||
|
@ -28,14 +28,14 @@ class Image(object):
|
|||
else:
|
||||
img = img.attrs['src']
|
||||
img = urljoin(url, img)
|
||||
|
||||
|
||||
filename = clean_title(os.path.basename(img.split('?')[0]))
|
||||
name, ext = os.path.splitext(filename)
|
||||
|
||||
# https://www.hentai-foundry.com/pictures/user/DrGraevling/74069/Eversong-Interrogation-pg.-13
|
||||
if ext.lower() not in ['.bmp', '.png', '.gif', '.jpg', '.jpeg', '.webp', '.webm', '.avi', '.mp4', '.mkv', '.wmv']:
|
||||
filename = u'{}.jpg'.format(name)
|
||||
|
||||
|
||||
self.filename = filename
|
||||
return img
|
||||
self.url = LazyUrl(url, f, self)
|
||||
|
@ -47,13 +47,13 @@ def get_username(url):
|
|||
return username
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_hf(Downloader):
|
||||
type = 'hf'
|
||||
URLS = ['hentai-foundry.com']
|
||||
MAX_CORE = 16
|
||||
display_name = 'Hentai Foundry'
|
||||
|
||||
|
||||
def init(self):
|
||||
self.session = enter()
|
||||
|
||||
|
@ -78,7 +78,7 @@ class Downloader_hf(Downloader):
|
|||
def enter():
|
||||
print('enter')
|
||||
session = Session()
|
||||
|
||||
|
||||
r = session.get(URL_ENTER)
|
||||
|
||||
# 862
|
||||
|
@ -104,10 +104,10 @@ def enter():
|
|||
})
|
||||
r = session.post(URL_FILTER, data=data, headers={'Referer': r.url})
|
||||
print(r)
|
||||
|
||||
|
||||
return session
|
||||
|
||||
|
||||
|
||||
def get_imgs(username, title, session, cw=None):
|
||||
url = 'https://www.hentai-foundry.com/pictures/user/{}'.format(username)
|
||||
|
||||
|
@ -153,4 +153,3 @@ def get_imgs(username, title, session, cw=None):
|
|||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
# uncompyle6 version 3.5.0
|
||||
# Python bytecode 2.7 (62211)
|
||||
# Decompiled from: Python 2.7.16 (v2.7.16:413a49145e, Mar 4 2019, 01:30:55) [MSC v.1500 32 bit (Intel)]
|
||||
# Embedded file name: imgur_downloader.pyo
|
||||
# Compiled at: 2019-10-07 05:58:14
|
||||
import downloader
|
||||
from utils import Downloader, Soup, try_n, urljoin, get_max_range, clean_title, cut_pair
|
||||
import ree as re, json, os
|
||||
from timee import sleep
|
||||
from translator import tr_
|
||||
|
||||
@Downloader.register
|
||||
|
||||
|
||||
class Downloader_imgur(Downloader):
|
||||
type = 'imgur'
|
||||
URLS = ['imgur.com']
|
||||
|
@ -81,7 +77,7 @@ def get_imgs(url, info=None, cw=None):
|
|||
imgs_ = info['media']
|
||||
else: # legacy
|
||||
imgs_ = [info]
|
||||
|
||||
|
||||
for img in imgs_:
|
||||
img_url = img.get('url') # new
|
||||
if not img_url: # legacy
|
||||
|
@ -91,7 +87,7 @@ def get_imgs(url, info=None, cw=None):
|
|||
if img_url in imgs:
|
||||
continue
|
||||
imgs.append(img_url)
|
||||
|
||||
|
||||
elif info['type'] == 'r':
|
||||
urls = set()
|
||||
for p in range(100):
|
||||
|
@ -99,7 +95,7 @@ def get_imgs(url, info=None, cw=None):
|
|||
print(url_api)
|
||||
html = downloader.read_html(url_api, referer=url)
|
||||
soup = Soup(html)
|
||||
|
||||
|
||||
c = 0
|
||||
for post in soup.findAll('div', class_='post'):
|
||||
a = post.find('a', class_='image-list-link')
|
||||
|
@ -122,10 +118,9 @@ def get_imgs(url, info=None, cw=None):
|
|||
return []
|
||||
else:
|
||||
print(s)
|
||||
|
||||
|
||||
if c == 0:
|
||||
print('same; break')
|
||||
break
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -1,592 +0,0 @@
|
|||
#coding:utf8
|
||||
import downloader
|
||||
from timee import sleep, clock
|
||||
from constants import clean_url
|
||||
from utils import Downloader, LazyUrl, urljoin, get_max_range, Soup, Session, update_url_query, get_print, cut_pair, get_ext, clean_title, lazy, try_n, generate_csrf_token, check_alive
|
||||
import urllib
|
||||
from error_printer import print_error
|
||||
import os, requests
|
||||
from translator import tr_
|
||||
import json
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import ree as re
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
import clf2
|
||||
import errors
|
||||
FORMAT_PIN = r'/p/([0-9a-zA-Z-_]+)'
|
||||
|
||||
|
||||
def get_session(url, cw=None):
|
||||
#res = clf2.solve(url, cw=cw)
|
||||
#return res['session']
|
||||
session = Session()
|
||||
sessionid = session.cookies._cookies.get('.instagram.com', {}).get('/',{}).get('sessionid')
|
||||
if sessionid is None or sessionid.is_expired():
|
||||
raise errors.LoginRequired()
|
||||
session.headers['User-Agent'] = downloader.hdr['User-Agent']
|
||||
if not session.cookies.get('csrftoken', domain='.instagram.com'):
|
||||
csrf_token = generate_csrf_token()
|
||||
print('csrf:', csrf_token)
|
||||
session.cookies.set("csrftoken", csrf_token, domain='.instagram.com')
|
||||
return session
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_insta(Downloader):
|
||||
type = 'insta'
|
||||
URLS = ['instagram.com']
|
||||
MAX_CORE = 8
|
||||
display_name = 'Instagram'
|
||||
|
||||
def init(self):
|
||||
self.session = get_session(self.url, self.cw)
|
||||
if '/p/' in self.url:
|
||||
self.print_('single post')
|
||||
elif '/stories/' in self.url:
|
||||
self.print_('stories')
|
||||
elif 'instagram.com' in self.url:
|
||||
self.url = u'https://www.instagram.com/{}'.format(self.username)
|
||||
|
||||
@lazy
|
||||
def username(self):
|
||||
return get_username(self.url)
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
if 'instagram.com' not in url:
|
||||
url = u'https://www.instagram.com/{}'.format(url)
|
||||
return url.split('?')[0].split('#')[0].strip('/')
|
||||
|
||||
@classmethod
|
||||
def key_id(cls, url):
|
||||
return url.replace('://www.', '://')
|
||||
|
||||
@lazy
|
||||
def name(self):
|
||||
return get_name(self.url)
|
||||
|
||||
@property
|
||||
def id_(self):
|
||||
return u'{} (insta_{})'.format(clean_title(self.name), self.username)
|
||||
|
||||
def read(self):
|
||||
cw = self.cw
|
||||
title = self.id_
|
||||
self.artist = self.name
|
||||
ui_setting = self.ui_setting
|
||||
|
||||
if '/p/' in self.url:
|
||||
self.print_('single')
|
||||
iter = get_imgs_single(self.url, self.session, cw=cw)
|
||||
elif '/stories/highlights/' in self.url:
|
||||
iter = get_stories_single(self.url, session=self.session, cw=cw)
|
||||
else:
|
||||
s = ui_setting.instaStories.isChecked()
|
||||
self.print_('stories: {}'.format(s))
|
||||
iter = get_imgs_all(self.url, title, session=self.session, cw=cw, d=self, stories=s)
|
||||
|
||||
imgs = []
|
||||
for img in iter:
|
||||
if cw and not cw.alive:
|
||||
return
|
||||
self.urls.append(img.url)
|
||||
|
||||
self.title = title
|
||||
|
||||
|
||||
def get_j(script):
|
||||
s = script.string
|
||||
if not s:
|
||||
return
|
||||
|
||||
try:
|
||||
s = s.replace('window._sharedData', '').strip()[1:-1].strip()
|
||||
j = json.loads(s)
|
||||
return j
|
||||
except ValueError as e:
|
||||
pass
|
||||
|
||||
|
||||
def read_html(url, session, cw):
|
||||
#res = clf2.solve(url, session=session, cw=cw)#
|
||||
#return res['html']
|
||||
return downloader.read_html(url, session=session)
|
||||
|
||||
|
||||
def check_error(soup, cw, wait):
|
||||
print_ = get_print(cw)
|
||||
|
||||
if len(soup.html) < 1000: #4014
|
||||
raise errors.LoginRequired(soup.html)
|
||||
|
||||
err = soup.find('div', class_='error-container')
|
||||
if err:
|
||||
err = err.text.strip()
|
||||
if wait:
|
||||
print_('err: {}'.format(err))
|
||||
sleep(60*30, cw)
|
||||
else:
|
||||
raise Exception(err)
|
||||
|
||||
|
||||
def get_sd(url, session=None, html=None, cw=None, wait=True):
|
||||
print_ = get_print(cw)
|
||||
|
||||
if html:
|
||||
soup = Soup(html)
|
||||
check_error(soup, cw, wait)
|
||||
for script in soup.findAll('script'):
|
||||
j = get_j(script)
|
||||
if j:
|
||||
break
|
||||
else:
|
||||
raise Exception('no _sharedData!!')
|
||||
else:
|
||||
for try_ in range(4):
|
||||
_wait(cw)
|
||||
html = read_html(url, session, cw)
|
||||
soup = Soup(html)
|
||||
check_error(soup, cw, wait)
|
||||
for script in soup.findAll('script'):
|
||||
j = get_j(script)
|
||||
if j:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise Exception('no _sharedData')
|
||||
for script in soup.findAll('script'):
|
||||
s = script.string
|
||||
if s and 'window.__additionalDataLoaded(' in s:
|
||||
s = cut_pair(s)
|
||||
j_add = json.loads(s)
|
||||
try:
|
||||
j['entry_data']['PostPage'][0].update(j_add)
|
||||
except:
|
||||
j['entry_data']['ProfilePage'][0].update(j_add) #2900
|
||||
|
||||
# Challenge
|
||||
challenge = j['entry_data'].get('Challenge')
|
||||
if challenge:
|
||||
try:
|
||||
for cont in challenge[0]['extraData']['content']:
|
||||
title = cont.get('title')
|
||||
if title:
|
||||
break
|
||||
else:
|
||||
raise Exception('no title')
|
||||
except:
|
||||
title = 'Err'
|
||||
raise errors.LoginRequired(title)
|
||||
|
||||
# LoginAndSignupPage
|
||||
login = j['entry_data'].get('LoginAndSignupPage')
|
||||
if login:
|
||||
raise errors.LoginRequired()
|
||||
|
||||
return j
|
||||
|
||||
|
||||
def get_id(url):
|
||||
j = get_sd(url)
|
||||
if '/p/' in url:
|
||||
id = j['entry_data']['PostPage'][0]['graphql']['shortcode_media']['owner']['id']
|
||||
elif '/stories/' in url:
|
||||
id = j['entry_data']['StoriesPage'][0]['user']['username'] # ???
|
||||
else:
|
||||
id = j['entry_data']['ProfilePage'][0]['graphql']['user']['id']
|
||||
return id
|
||||
|
||||
|
||||
|
||||
def get_username(url):
|
||||
j = get_sd(url, wait=False)
|
||||
if '/p/' in url:
|
||||
post = j['entry_data']['PostPage'][0]
|
||||
if 'graphql' in post:
|
||||
id = post['graphql']['shortcode_media']['owner']['full_name']
|
||||
else:
|
||||
id = post['items'][0]['user']['username'] #4336
|
||||
elif '/stories/' in url:
|
||||
id = j['entry_data']['StoriesPage'][0]['user']['username']
|
||||
else:
|
||||
id = j['entry_data']['ProfilePage'][0]['graphql']['user']['username']
|
||||
return id
|
||||
|
||||
|
||||
def get_name(url):
|
||||
j = get_sd(url)
|
||||
if '/p/' in url:
|
||||
post = j['entry_data']['PostPage'][0]
|
||||
if 'graphql' in post:
|
||||
name = post['graphql']['shortcode_media']['owner']['full_name']
|
||||
else:
|
||||
name = post['items'][0]['user']['full_name'] #4336
|
||||
elif '/stories/' in url:
|
||||
id = get_id(url)
|
||||
url = 'https://www.instagram.com/{}/'.format(id)
|
||||
return get_name(url)
|
||||
else:
|
||||
name = j['entry_data']['ProfilePage'][0]['graphql']['user']['full_name']
|
||||
return name
|
||||
|
||||
|
||||
class Image(object):
|
||||
|
||||
def __init__(self, url, referer, filename, id=None):
|
||||
self._url = url
|
||||
self.url = LazyUrl(referer, self.get, self)
|
||||
self.filename = filename
|
||||
self.id = id
|
||||
|
||||
def get(self, referer):
|
||||
wait_download()
|
||||
return self._url
|
||||
|
||||
|
||||
class Image_lazy(object):
|
||||
|
||||
def __init__(self, url, session=None, cw=None):
|
||||
self.url = url
|
||||
self.session = session
|
||||
self.cw = cw
|
||||
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
|
||||
@try_n(4)
|
||||
def get(self, url):
|
||||
cw = self.cw
|
||||
if cw and not cw.alive:
|
||||
raise Exception('cw is dead')
|
||||
node = Node(url, session=self.session, cw=cw)
|
||||
img = node.imgs[0]
|
||||
ext = os.path.splitext(url)[1]
|
||||
wait_download()
|
||||
url_img = img.url()
|
||||
self.filename = img.filename
|
||||
return url_img
|
||||
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(1, 10)
|
||||
def _wait(cw=None):
|
||||
if cw and not cw.alive:
|
||||
raise Exception('cw is dead while waiting')
|
||||
|
||||
|
||||
##@sleep_and_retry
|
||||
##@limits(1, 1)
|
||||
def wait_download():
|
||||
pass
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_query(query_hash, variables, session, cw=None):
|
||||
_wait(cw)
|
||||
print_ = get_print(cw)
|
||||
csrf_token = session.cookies.get('csrftoken', domain='.instagram.com')
|
||||
if not csrf_token:
|
||||
raise Exception('no csrftoken')
|
||||
hdr = {
|
||||
"X-CSRFToken" : csrf_token, #2849
|
||||
"X-IG-App-ID" : "936619743392459",
|
||||
"X-IG-WWW-Claim" : "0",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
}
|
||||
url_ = update_url_query('https://www.instagram.com/graphql/query/', {'query_hash': query_hash, 'variables': json.dumps(variables)})
|
||||
#print(len(edges), url_)
|
||||
r = session.get(url_, headers=hdr)
|
||||
try:
|
||||
j = json.loads(r.text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
j = {}
|
||||
if not j or j.get('status') == 'fail':
|
||||
msg = 'Fail: {} {}'.format(j.get('message') or 'Please wait a few minutes before you try again.', variables)
|
||||
print_(msg)
|
||||
sleep(60*30, cw)
|
||||
raise Exception(msg)
|
||||
return j
|
||||
|
||||
|
||||
def get_imgs(url, n_max=2000, title=None, cw=None, session=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
for try_ in range(4):
|
||||
try:
|
||||
html = read_html(url, session, cw)
|
||||
m = re.search('"edge_owner_to_timeline_media":{"count":([0-9]+)', html)
|
||||
if m is None:
|
||||
raise Exception('Invalid page')
|
||||
break
|
||||
except Exception as e:
|
||||
e_ = e
|
||||
print_(print_error(e)[0])
|
||||
else:
|
||||
raise e_
|
||||
n = int(m.groups()[0])
|
||||
n = min(n, n_max)
|
||||
|
||||
data = get_sd(url, html=html, cw=cw)
|
||||
|
||||
uploader_id = data['entry_data']['ProfilePage'][0]['graphql']['user']['id']
|
||||
csrf_token = data['config']['csrf_token']#
|
||||
session.cookies.set(name='ig_pr', value='1', path='/', domain='.instagram.com')
|
||||
|
||||
cursor = ''
|
||||
edges = []
|
||||
bad = 0
|
||||
while True:
|
||||
check_alive(cw)
|
||||
|
||||
variables = {
|
||||
'id': uploader_id,
|
||||
'first': 12,
|
||||
}
|
||||
if cursor:
|
||||
variables['after'] = cursor
|
||||
#print_(variables)#
|
||||
|
||||
media = None
|
||||
try:
|
||||
j = get_query('003056d32c2554def87228bc3fd9668a', variables, session, cw)
|
||||
media = j['data']['user']['edge_owner_to_timeline_media']
|
||||
sleep(2)#
|
||||
except Exception as e:
|
||||
if bad > 10:
|
||||
raise Exception('no media')
|
||||
else:
|
||||
print_(u'no media.. retry... ({}) {}'.format(bad+1, print_error(e)[0]))
|
||||
sleep(12*bad, cw)
|
||||
bad += 1
|
||||
continue
|
||||
bad = 0
|
||||
|
||||
edges_new = media.get('edges')
|
||||
if not edges_new or not isinstance(edges_new, list):
|
||||
print('no edges_new')
|
||||
break
|
||||
|
||||
edges += edges_new
|
||||
|
||||
s = u'{} {} - {} / {}'.format(tr_(u'읽는 중...'), title, len(edges), n)
|
||||
if cw is not None:
|
||||
cw.setTitle(s)
|
||||
if not cw.alive:
|
||||
return []
|
||||
else:
|
||||
print(s)
|
||||
|
||||
if len(edges) >= n:
|
||||
break
|
||||
|
||||
page_info = media.get('page_info')
|
||||
if not page_info:
|
||||
break
|
||||
if not page_info.get('has_next_page'):
|
||||
break
|
||||
cursor = page_info.get('end_cursor')
|
||||
if not cursor:
|
||||
break
|
||||
|
||||
if len(edges) <= n/2:
|
||||
raise Exception(u'Too short: {} / {}'.format(len(edges), n))
|
||||
|
||||
imgs = []
|
||||
for edge in edges:
|
||||
node = edge['node']
|
||||
type = node['__typename']
|
||||
id = node['shortcode']
|
||||
url = u'https://www.instagram.com/p/{}/'.format(id)
|
||||
## if type in ['GraphVideo', 'GraphImage']:
|
||||
## single = True
|
||||
## else:
|
||||
## single = False
|
||||
for img in Node(url, session=session, cw=cw, media=node).imgs:
|
||||
imgs.append(img)
|
||||
if len(imgs) >= n_max:
|
||||
break
|
||||
|
||||
return imgs
|
||||
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, url, format=u'[%y-%m-%d] id_ppage', session=None, cw=None, media=None):
|
||||
print('Node', url)
|
||||
print_ = get_print(cw)
|
||||
self.id = re.search(FORMAT_PIN, url).groups()[0]
|
||||
self.imgs = []
|
||||
self.session = session
|
||||
|
||||
if not media:
|
||||
if False: # Original
|
||||
j = get_sd(url, self.session, cw=cw)
|
||||
data = j['entry_data']['PostPage'][0]['graphql']
|
||||
else:
|
||||
variables = {
|
||||
"shortcode" : self.id,
|
||||
"child_comment_count" : 3,
|
||||
"fetch_comment_count" : 40,
|
||||
"parent_comment_count" : 24,
|
||||
"has_threaded_comments": True,
|
||||
}
|
||||
j = get_query('a9441f24ac73000fa17fe6e6da11d59d', variables, session, cw)
|
||||
data = j['data']
|
||||
media = data['shortcode_media']
|
||||
|
||||
if 'video_url' in media:
|
||||
urls = [
|
||||
media['video_url']]
|
||||
elif 'edge_sidecar_to_children' in media:
|
||||
edges = media['edge_sidecar_to_children']['edges']
|
||||
urls = []
|
||||
for edge in edges:
|
||||
node = edge['node']
|
||||
if 'video_url' in node:
|
||||
url_ = node['video_url']
|
||||
else:
|
||||
url_ = node['display_resources'][(-1)]['src']
|
||||
urls.append(url_)
|
||||
else:
|
||||
urls = [media['display_resources'][(-1)]['src']]
|
||||
time = media['taken_at_timestamp']
|
||||
|
||||
self.date = datetime.fromtimestamp(time)
|
||||
self.timeStamp = self.date.strftime(format).replace(':', u'\uff1a')
|
||||
for p, img in enumerate(urls):
|
||||
ext = os.path.splitext(img.split('?')[0].split('#')[0])[1]
|
||||
filename = ('{}{}').format(self.timeStamp, ext).replace('id', str(self.id)).replace('page', str(p))
|
||||
img = Image(img, url, filename)
|
||||
self.imgs.append(img)
|
||||
|
||||
|
||||
def get_imgs_all(url, title=None, cw=None, d=None, session=None, stories=True):
|
||||
max_pid = get_max_range(cw)
|
||||
url = clean_url(url)
|
||||
if stories:
|
||||
imgs_str = get_stories(url, title, cw=cw, session=session)
|
||||
else:
|
||||
imgs_str = []
|
||||
max_pid = max(0, max_pid - len(imgs_str))
|
||||
imgs = get_imgs(url, max_pid, title=title, cw=cw, session=session)
|
||||
|
||||
return imgs_str + imgs[:max_pid]
|
||||
|
||||
|
||||
def get_imgs_single(url, session=None, cw=None):
|
||||
node = Node(url, session=session, cw=cw)
|
||||
return node.imgs
|
||||
|
||||
|
||||
def get_stories(url, title=None, cw=None, session=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
html = downloader.read_html(url, session=session)
|
||||
|
||||
data = get_sd(url, html=html, cw=cw)
|
||||
|
||||
uploader_id = data['entry_data']['ProfilePage'][0]['graphql']['user']['id']
|
||||
csrf_token = data['config']['csrf_token']#
|
||||
session.cookies.set(name='ig_pr', value='1', path='/', domain='.instagram.com')
|
||||
|
||||
print('uploader_id:', uploader_id)
|
||||
variables = {
|
||||
'user_id': uploader_id,
|
||||
'include_chaining': True,
|
||||
'include_reel': True,
|
||||
'include_suggested_users': False,
|
||||
'include_logged_out_extras': False,
|
||||
'include_highlight_reels': True,
|
||||
'include_live_status': True,
|
||||
}
|
||||
j = get_query('d4d88dc1500312af6f937f7b804c68c3', variables, session, cw)
|
||||
|
||||
imgs = []
|
||||
ids = set()
|
||||
|
||||
data = j['data']
|
||||
hs = data['user']['edge_highlight_reels']
|
||||
edges = hs['edges']
|
||||
edges.insert(0, str(uploader_id))
|
||||
for i, edge in enumerate(edges):
|
||||
if isinstance(edge, str):
|
||||
id = edge
|
||||
hid = None
|
||||
url_str = url
|
||||
else:
|
||||
id = None
|
||||
hid = edge['node']['id']
|
||||
url_str = 'https://www.instagram.com/stories/highlights/{}/'.format(hid)
|
||||
try:
|
||||
imgs_new = get_stories_single(url_str, id=id, cw=cw, session=session)
|
||||
for img in imgs_new:
|
||||
if img.id in ids:
|
||||
print('duplicate: {}'.format(img.id))
|
||||
continue
|
||||
ids.add(img.id)
|
||||
imgs.append(img)
|
||||
print_('stories: {}'.format(hid))
|
||||
except Exception as e:
|
||||
print_(u'Failed to get stories: {}'.format(hid))
|
||||
print(e)
|
||||
msg = u'{} {} - {} / {}'.format(tr_(u'스토리 읽는 중...'), title, i+1, len(edges))
|
||||
if cw:
|
||||
if not cw.alive:
|
||||
return
|
||||
cw.setTitle(msg)
|
||||
else:
|
||||
print(msg)
|
||||
imgs = sort_str(imgs)
|
||||
return imgs
|
||||
|
||||
|
||||
def sort_str(imgs):
|
||||
imgs = sorted(imgs, key=lambda img: int(img.id), reverse=True)
|
||||
return imgs
|
||||
|
||||
|
||||
def get_stories_single(url, id=None, cw=None, session=None):
|
||||
j = get_sd(url, session=session, cw=cw)
|
||||
hid = re.find('/stories/highlights/([0-9]+)', url)
|
||||
reel_ids = []
|
||||
highlight_reel_ids = []
|
||||
if hid is None:
|
||||
if id is None:
|
||||
id = get_id(url) # ???
|
||||
reel_ids.append(str(id))
|
||||
else:
|
||||
highlight_reel_ids.append(str(hid))
|
||||
print(id, hid)
|
||||
variables = {
|
||||
"reel_ids":reel_ids,
|
||||
"tag_names":[],
|
||||
"location_ids":[],
|
||||
"highlight_reel_ids":highlight_reel_ids,
|
||||
"precomposed_overlay":False,
|
||||
"show_story_viewer_list":True,
|
||||
"story_viewer_fetch_count":50,
|
||||
"story_viewer_cursor":"",
|
||||
"stories_video_dash_manifest":False
|
||||
}
|
||||
print(variables)
|
||||
j = get_query('f5dc1457da7a4d3f88762dae127e0238', variables, session, cw)
|
||||
data = j['data']
|
||||
m = data['reels_media'][0]
|
||||
items = m['items']
|
||||
if not items:
|
||||
raise Exception('no items')
|
||||
imgs = []
|
||||
for item in items:
|
||||
id = item['id']
|
||||
rs = item.get('video_resources') or item['display_resources']
|
||||
r = rs[-1]
|
||||
src = r['src']
|
||||
ext = get_ext(src)
|
||||
filename = u'stories_{}{}'.format(id, ext)
|
||||
img = Image(src, url, filename, id=id)
|
||||
imgs.append(img)
|
||||
imgs = sort_str(imgs)
|
||||
return imgs
|
|
@ -1,10 +1,7 @@
|
|||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
from utils import Soup, urljoin, Downloader, LazyUrl, get_print, clean_url, clean_title, check_alive, Session, try_n, format_filename
|
||||
from utils import Soup, urljoin, Downloader, LazyUrl, get_print, clean_url, clean_title, check_alive, Session, try_n, format_filename, tr_, get_ext
|
||||
import ree as re
|
||||
import json
|
||||
import os
|
||||
from timee import sleep
|
||||
from io import BytesIO
|
||||
import errors
|
||||
TIMEOUT = 300
|
||||
|
@ -12,17 +9,17 @@ PATTERN_ID = r'videos/([0-9a-zA-Z_-]+)'
|
|||
|
||||
|
||||
|
||||
class File(object):
|
||||
class File:
|
||||
thumb = None
|
||||
|
||||
def __init__(self, type, url, title, referer, p=0, multi_post=False):
|
||||
id_ = re.find('videos/([0-9a-zA-Z_-]+)', referer, err='no video id')
|
||||
self.type = type
|
||||
self.url = LazyUrl(referer, lambda _: url, self)
|
||||
ext = os.path.splitext(url.split('?')[0])[1]
|
||||
ext = get_ext(url)
|
||||
if ext.lower() == '.php':
|
||||
ext = '.mp4'
|
||||
if type == 'video':
|
||||
id_ = re.find('videos/([0-9a-zA-Z_-]+)', referer, err='no video id')
|
||||
self.filename = format_filename(title, id_, ext) #4287
|
||||
elif type == 'image':
|
||||
name = '{}_p{}'.format(clean_title(title), p) if multi_post else p
|
||||
|
@ -32,10 +29,10 @@ class File(object):
|
|||
self.title = title
|
||||
|
||||
|
||||
class LazyFile(object):
|
||||
class LazyFile:
|
||||
_url = None
|
||||
thumb = None
|
||||
|
||||
|
||||
def __init__(self, url, type_, session):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.type = {'videos': 'video', 'images': 'image'}.get(type_) or type_
|
||||
|
@ -50,9 +47,9 @@ class LazyFile(object):
|
|||
self.filename = file.filename
|
||||
self._url = file.url()
|
||||
return self._url
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
|
||||
class Downloader_iwara(Downloader):
|
||||
type = 'iwara'
|
||||
URLS = ['iwara.tv']
|
||||
|
@ -87,8 +84,12 @@ class Downloader_iwara(Downloader):
|
|||
if type_ == 'videos':
|
||||
files = [LazyFile(url, type_, self.session) for url in urls]
|
||||
file = self.process_playlist('[Channel] [{}] {}'.format(type_.capitalize(), title), files)
|
||||
elif type_ == 'images':
|
||||
files = [LazyFile(url, type_, self.session) for url in urls]
|
||||
elif type_ == 'images': #4499
|
||||
files = []
|
||||
for i, url in enumerate(urls):
|
||||
check_alive(self.cw)
|
||||
files += get_files(url, self.session, multi_post=True, cw=self.cw) #4728
|
||||
self.title = '{} {} - {} / {}'.format(tr_('읽는 중...'), title, i, len(urls))
|
||||
title = '[Channel] [{}] {}'.format(type_.capitalize(), title)
|
||||
else:
|
||||
raise NotImplementedError(type_)
|
||||
|
@ -102,14 +103,14 @@ class Downloader_iwara(Downloader):
|
|||
|
||||
if file.type == 'youtube':
|
||||
raise errors.Invalid('[iwara] Youtube: {}'.format(self.url))
|
||||
|
||||
|
||||
if file.type == 'image':
|
||||
self.single = False
|
||||
title = title or file.title
|
||||
if not self.single:
|
||||
title = clean_title(title)
|
||||
self.title = title
|
||||
|
||||
|
||||
if file.thumb is not None:
|
||||
self.setIcon(file.thumb)
|
||||
|
||||
|
@ -141,11 +142,11 @@ def read_channel(url, type_, session, cw=None):
|
|||
if p == 0:
|
||||
title = soup.find('h1', class_='page-title').text
|
||||
info['title'] = title.replace("'s videos", '').replace("'s images", '').strip()
|
||||
|
||||
|
||||
view = soup.find('div', class_='view-content')
|
||||
if view is None:
|
||||
break
|
||||
|
||||
|
||||
urls_new = []
|
||||
for div in view.findAll('div', class_='views-column'):
|
||||
href = div.find('a')['href']
|
||||
|
@ -173,7 +174,7 @@ def get_files(url, session, multi_post=False, cw=None):
|
|||
video = content.find('video')
|
||||
if youtube:
|
||||
type = 'youtube'
|
||||
elif video:
|
||||
elif video and video.attrs.get('poster'): #4901
|
||||
type = 'video'
|
||||
else:
|
||||
type = 'image'
|
||||
|
@ -181,8 +182,12 @@ def get_files(url, session, multi_post=False, cw=None):
|
|||
files = []
|
||||
if type == 'image':
|
||||
urls = set()
|
||||
for img in content.findAll('img'):
|
||||
img = urljoin(url, img.parent.attrs['href'])
|
||||
for img in content.findAll(['img', 'video']):
|
||||
if img.source: #4901
|
||||
img = img.source['src']
|
||||
else:
|
||||
img = img.parent.attrs['href']
|
||||
img = urljoin(url, img)
|
||||
if '/files/' not in img:
|
||||
continue
|
||||
if img in urls:
|
||||
|
|
|
@ -13,7 +13,7 @@ PATTERN_ALL = r'jmana[0-9]*.*/(comic_list_title|book|bookdetail)\?book'
|
|||
PATTERN_ID = '[?&]bookdetailid=([0-9]+)'
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, page, p):
|
||||
self.url = LazyUrl(page.url, lambda _: url, self)
|
||||
|
@ -22,7 +22,7 @@ class Image(object):
|
|||
self.filename = (u'{}/{}').format(page.title, name)
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
|
||||
def __init__(self, title, url):
|
||||
self.title = clean_title(title)
|
||||
|
@ -30,7 +30,7 @@ class Page(object):
|
|||
self.id = int(re.find(PATTERN_ID, url))
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_jmana(Downloader):
|
||||
type = 'jmana'
|
||||
URLS = ['regex:'+PATTERN_ALL]
|
||||
|
@ -55,7 +55,7 @@ class Downloader_jmana(Downloader):
|
|||
raise Exception('list not found')
|
||||
self.url = self.fix_url(url)
|
||||
self._soup = None
|
||||
|
||||
|
||||
for i, page in enumerate(get_pages(self.url, self.session, self.soup)):
|
||||
if page.id == int(op['value']):
|
||||
break
|
||||
|
@ -106,7 +106,7 @@ def get_title(soup):
|
|||
|
||||
def get_artist(soup):
|
||||
return re.find(r'작가 *: *(.+)', soup.text, default='').strip() or 'N/A'
|
||||
|
||||
|
||||
|
||||
@try_n(4, sleep=60)
|
||||
def get_imgs_page(page, referer, session, cw=None):
|
||||
|
@ -118,15 +118,15 @@ def get_imgs_page(page, referer, session, cw=None):
|
|||
print_('inserted: {}'.format(inserted))
|
||||
|
||||
inserted = set(int(i) for i in inserted.split(',')) if inserted else set()
|
||||
|
||||
|
||||
soup = Soup(html)
|
||||
|
||||
view = soup.find(class_='pdf-wrap')
|
||||
|
||||
|
||||
imgs = []
|
||||
for i, img in enumerate(child for child in view.children if isinstance(child, bs4.element.Tag)):
|
||||
src = img.get('data-src') or img.get('src') or ''
|
||||
|
||||
|
||||
if i in inserted:
|
||||
print_('remove: {}'.format(src))
|
||||
continue
|
||||
|
@ -140,7 +140,7 @@ def get_imgs_page(page, referer, session, cw=None):
|
|||
if '/notice' in src:
|
||||
print('notice:', src)
|
||||
continue
|
||||
|
||||
|
||||
img = Image(src, page, len(imgs))
|
||||
imgs.append(img)
|
||||
|
||||
|
@ -195,7 +195,7 @@ def get_imgs(url, title, session, soup=None, cw=None):
|
|||
if imgs_already:
|
||||
imgs += imgs_already
|
||||
continue
|
||||
|
||||
|
||||
imgs += get_imgs_page(page, url, session, cw)
|
||||
if cw is not None:
|
||||
if not cw.alive:
|
||||
|
@ -206,4 +206,3 @@ def get_imgs(url, title, session, soup=None, cw=None):
|
|||
raise Exception('no imgs')
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import downloader
|
||||
import ree as re
|
||||
from utils import Session, LazyUrl, Soup, Downloader, try_n, get_print, clean_title, print_error, urljoin, get_imgs_already
|
||||
from utils import Session, LazyUrl, Soup, Downloader, try_n, get_print, clean_title, print_error, urljoin, get_imgs_already, check_alive
|
||||
from time import sleep
|
||||
from translator import tr_
|
||||
import page_selector
|
||||
import json
|
||||
import clf2
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
|
||||
def __init__(self, id_, title):
|
||||
self.id_ = id_
|
||||
|
@ -16,21 +17,28 @@ class Page(object):
|
|||
self.url = 'https://page.kakao.com/viewer?productId={}'.format(id_)
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, page, p):
|
||||
self.url = LazyUrl('https://page.kakao.com/', lambda _: url, self)
|
||||
self._url = url
|
||||
self.url = LazyUrl('https://page.kakao.com/', self.get, self)
|
||||
ext = '.jpg'
|
||||
self.filename = '{}/{:04}{}'.format(clean_title(page.title), p, ext)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
@sleep_and_retry
|
||||
@limits(5, 1)
|
||||
def get(self, _):
|
||||
return self._url
|
||||
|
||||
|
||||
|
||||
class Downloader_kakaopage(Downloader):
|
||||
type = 'kakaopage'
|
||||
URLS = ['page.kakao.com/home']
|
||||
MAX_CORE = 4
|
||||
MAX_SPEED = 4.0
|
||||
display_name = 'KakaoPage'
|
||||
ACCEPT_COOKIES = [r'(.*\.)?kakao\.com']
|
||||
|
||||
def init(self):
|
||||
self.session = Session()
|
||||
|
@ -56,20 +64,21 @@ class Downloader_kakaopage(Downloader):
|
|||
|
||||
self.title = info['title']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_id(url):
|
||||
id_ = re.find('seriesId=([0-9]+)', url, err='No seriesId')
|
||||
return id_
|
||||
|
||||
|
||||
|
||||
def get_pages(url, session):
|
||||
def get_pages(url, session, cw=None):
|
||||
id_ = get_id(url)
|
||||
|
||||
pages = []
|
||||
ids = set()
|
||||
for p in range(500): #2966
|
||||
check_alive(cw)
|
||||
url_api = 'https://api2-page.kakao.com/api/v5/store/singles'
|
||||
data = {
|
||||
'seriesid': id_,
|
||||
|
@ -124,7 +133,7 @@ def get_imgs_page(page, session):
|
|||
imgs = []
|
||||
for file in data['downloadData']['members']['files']:
|
||||
url = file['secureUrl']
|
||||
url = urljoin('https://page-edge-jz.kakao.com/sdownload/resource/', url)
|
||||
url = 'https://page-edge.kakao.com/sdownload/resource?kid=' + url #5176
|
||||
img = Image(url, page, len(imgs))
|
||||
imgs.append(img)
|
||||
return imgs
|
||||
|
@ -132,13 +141,13 @@ def get_imgs_page(page, session):
|
|||
|
||||
def get_info(url, session, cw=None):
|
||||
print_ = get_print(cw)
|
||||
pages = get_pages(url, session)
|
||||
pages = get_pages(url, session, cw)
|
||||
pages = page_selector.filter(pages, cw)
|
||||
if not pages:
|
||||
raise Exception('no pages')
|
||||
|
||||
info = {}
|
||||
|
||||
|
||||
html = read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
|
||||
|
@ -164,9 +173,8 @@ def get_info(url, session, cw=None):
|
|||
imgs = []
|
||||
|
||||
for i, page in enumerate(pages):
|
||||
check_alive(cw)
|
||||
if cw is not None:
|
||||
if not cw.alive:
|
||||
return
|
||||
cw.setTitle('{} {} / {} ({} / {})'.format(tr_('읽는 중...'), info['title'], page.title, i + 1, len(pages)))
|
||||
|
||||
#3463
|
||||
|
@ -174,7 +182,7 @@ def get_info(url, session, cw=None):
|
|||
if imgs_already:
|
||||
imgs += imgs_already
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
_imgs = get_imgs_page(page, session)
|
||||
e_msg = None
|
||||
|
@ -184,7 +192,7 @@ def get_info(url, session, cw=None):
|
|||
print_('{} {}'.format(page.title, len(_imgs)))
|
||||
if e_msg:
|
||||
print_(e_msg)
|
||||
|
||||
|
||||
imgs += _imgs
|
||||
sleep(.2)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from io import BytesIO as IO
|
|||
from m3u8_tools import M3u8_stream
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_vlive(Downloader):
|
||||
type = 'kakaotv'
|
||||
URLS = ['tv.kakao']
|
||||
|
@ -14,6 +14,7 @@ class Downloader_vlive(Downloader):
|
|||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
url = url.replace('.kakao.com/m/', '.kakao.com/')
|
||||
return url.split('?')[0].strip('/')
|
||||
|
||||
def read(self):
|
||||
|
@ -29,9 +30,9 @@ class Downloader_vlive(Downloader):
|
|||
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
@ -40,13 +41,13 @@ class Video(object):
|
|||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
fs = [f for f in info['formats'] if f['ext'] == 'mp4']
|
||||
f = sorted(fs, key=lambda f: f['height'])[-1]
|
||||
self._url = f['url']
|
||||
|
||||
|
||||
self.thumb_url = info['thumbnails'][0]['url']
|
||||
self.thumb = IO()
|
||||
downloader.download(self.thumb_url, buffer=self.thumb)
|
||||
|
|
|
@ -9,7 +9,7 @@ from translator import tr_
|
|||
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
def __init__(self, url, title, date, p):
|
||||
self.url = url
|
||||
self.title = clean_title(u'[{:04}] {}'.format(p, title), n=-4)
|
||||
|
@ -26,7 +26,7 @@ class Page(object):
|
|||
return f
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_kakuyomu(Downloader):
|
||||
type = 'kakuyomu'
|
||||
URLS = ['kakuyomu.jp']
|
||||
|
@ -36,13 +36,13 @@ class Downloader_kakuyomu(Downloader):
|
|||
|
||||
def init(self):
|
||||
self.info = get_info(self.url, cw=self.cw)
|
||||
|
||||
|
||||
def read(self):
|
||||
outdir = get_outdir('kakuyomu')
|
||||
|
||||
self.artist = self.info['artist']
|
||||
title_dir = clean_title(u'[{}] {}'.format(self.artist, self.info['title']))
|
||||
|
||||
|
||||
for page in self.info['pages']:
|
||||
file = os.path.join(outdir, title_dir, page.filename)
|
||||
if os.path.isfile(file):
|
||||
|
@ -85,7 +85,7 @@ def get_text(page):
|
|||
|
||||
{}'''.format(page.title, page.date, story)
|
||||
return text
|
||||
|
||||
|
||||
|
||||
def get_info(url, soup=None, cw=None):
|
||||
print_ = get_print(cw)
|
||||
|
@ -103,7 +103,12 @@ def get_info(url, soup=None, cw=None):
|
|||
if button:
|
||||
print('decompose button')
|
||||
button.decompose()
|
||||
catch = desc.find('span', id='catchphrase-body').text.strip()
|
||||
catch = desc.find('span', id='catchphrase-body')
|
||||
if catch is None: #4445
|
||||
print_('no catch')
|
||||
catch = ''
|
||||
else:
|
||||
catch = catch.text.strip()
|
||||
intro = desc.find('p', id='introduction')
|
||||
if intro is None: #4262
|
||||
print_('no intro')
|
||||
|
@ -112,7 +117,7 @@ def get_info(url, soup=None, cw=None):
|
|||
intro = intro.text.strip()
|
||||
desc = u' {}{}'.format(catch, ('\n\n\n'+intro) if intro else '')
|
||||
info['description'] = desc
|
||||
|
||||
|
||||
pages = []
|
||||
for a in soup.findAll('a', class_='widget-toc-episode-episodeTitle'):
|
||||
href = urljoin(url, a.attrs['href'])
|
||||
|
@ -124,4 +129,3 @@ def get_info(url, soup=None, cw=None):
|
|||
info['pages'] = pages
|
||||
|
||||
return info
|
||||
|
||||
|
|
|
@ -1,47 +1,56 @@
|
|||
import downloader
|
||||
from utils import Soup, urljoin, Downloader, LazyUrl, Session, try_n, format_filename, clean_title
|
||||
from utils import Soup, urljoin, Downloader, LazyUrl, Session, try_n, format_filename, clean_title, get_resolution, get_print
|
||||
from timee import sleep
|
||||
import ree as re
|
||||
from io import BytesIO
|
||||
import clf2
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_kissjav(Downloader):
|
||||
type = 'kissjav'
|
||||
URLS = ['kissjav.com']
|
||||
URLS = ['kissjav.com', 'kissjav.li'] #4835
|
||||
single = True
|
||||
display_name = 'KissJAV'
|
||||
ACCEPT_COOKIES = [r'(.*\.)?kissjav\.(com|li)']
|
||||
|
||||
def read(self):
|
||||
self.session = None#get_session(self.url, cw=self.cw)
|
||||
|
||||
video = get_video(self.url, self.session)
|
||||
self.session = Session()#get_session(self.url, cw=self.cw)
|
||||
|
||||
video = get_video(self.url, self.session, self.cw)
|
||||
self.urls.append(video.url)
|
||||
self.setIcon(video.thumb)
|
||||
self.enableSegment(1024*1024//2)
|
||||
|
||||
|
||||
self.title = video.title
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_video(url, session):
|
||||
def get_video(url, session, cw):
|
||||
print_ = get_print(cw)
|
||||
soup = downloader.read_soup(url, session=session)
|
||||
|
||||
view = soup.find('div', id='player-container-fluid')
|
||||
src_best = None
|
||||
res_best = -1
|
||||
fs = []
|
||||
for source in view.findAll('source'):
|
||||
src = urljoin(url, source.attrs['src'])
|
||||
res = re.find('([0-9]+)p', source.attrs['title'])
|
||||
res = int(res) if res else 0
|
||||
if res > res_best:
|
||||
src_best = src
|
||||
res_best = res
|
||||
f = {'res': res, 'src': src}
|
||||
fs.append(f)
|
||||
print_(f)
|
||||
|
||||
if src_best is None:
|
||||
if not fs:
|
||||
raise Exception('No source')
|
||||
|
||||
#4773
|
||||
res = max(get_resolution(), min(f['res'] for f in fs))
|
||||
print_(f'res: {res}')
|
||||
fs = sorted([f for f in fs if f['res'] <= res], key=lambda f: f['res'])
|
||||
f = fs[-1]
|
||||
print_(f'best: {f}')
|
||||
src_best = f['src']
|
||||
|
||||
title = soup.find('h1').text.strip()
|
||||
id = soup.find('div', id='video').attrs['data-id']
|
||||
|
||||
|
@ -53,7 +62,7 @@ def get_video(url, session):
|
|||
return video
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, url, url_thumb, referer, title, id, session):
|
||||
self.title = title
|
||||
self.filename = format_filename(title, id, '.mp4')
|
||||
|
@ -69,4 +78,3 @@ def get_session(url, cw=None):
|
|||
session = Session()
|
||||
clf2.solve(url, session=session, cw=cw)
|
||||
return session
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import errors
|
|||
##from image_reader import QPixmap
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, page, p):
|
||||
self._url = url
|
||||
self.url = LazyUrl(page.url, self.get, self)#, pp=self.pp)
|
||||
|
@ -30,7 +30,7 @@ class Image(object):
|
|||
## return filename
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
def __init__(self, title, url):
|
||||
self.title = clean_title(title)
|
||||
self.url = url
|
||||
|
@ -46,7 +46,7 @@ def get_soup_session(url, cw=None):
|
|||
return Soup(res['html']), session
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_lhscan(Downloader):
|
||||
type = 'lhscan'
|
||||
URLS = [
|
||||
|
@ -103,7 +103,7 @@ class Downloader_lhscan(Downloader):
|
|||
def get_imgs_page(page, referer, session, cw=None):
|
||||
print_ = get_print(cw)
|
||||
print_(page.title)
|
||||
|
||||
|
||||
html = downloader.read_html(page.url, referer, session=session)
|
||||
if clf2._is_captcha(Soup(html)): #4124
|
||||
html = clf2.solve(page.url, session, cw)['html']
|
||||
|
@ -137,8 +137,10 @@ def get_imgs_page(page, referer, session, cw=None):
|
|||
continue
|
||||
if '/uploads/lazy_loading.gif' in src:
|
||||
continue
|
||||
src = 'https://welovekai.com/proxy.php?link=' + src.replace('\n', '').replace('\r', '') #5238
|
||||
if not imgs:
|
||||
print_(src0)
|
||||
print_(src)
|
||||
img = Image(src, page, len(imgs))
|
||||
imgs.append(img)
|
||||
|
||||
|
@ -149,7 +151,7 @@ def get_pages(url, session, soup=None, cw=None):
|
|||
if soup is None:
|
||||
html = downloader.read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
|
||||
|
||||
tab = soup.find('ul', class_='list-chapters')
|
||||
|
||||
pages = []
|
||||
|
|
|
@ -6,7 +6,7 @@ from io import BytesIO
|
|||
from translator import tr_
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_likee(Downloader):
|
||||
type = 'likee'
|
||||
URLS = ['likee.video']
|
||||
|
@ -39,7 +39,7 @@ def get_info(url, session, cw=None):
|
|||
|
||||
info = {}
|
||||
info['videos'] = []
|
||||
|
||||
|
||||
if '/video/' in url:
|
||||
info['type'] = 'single'
|
||||
video = Video(url, session)
|
||||
|
@ -68,7 +68,7 @@ def get_info(url, session, cw=None):
|
|||
videos = data['data']['videoList']
|
||||
if not videos:
|
||||
break
|
||||
|
||||
|
||||
for data in videos:
|
||||
url_post = 'https://likee.video/@{}/video/{}'.format(data['likeeId'], data['postId'])
|
||||
if url_post in urls:
|
||||
|
@ -87,11 +87,11 @@ def get_info(url, session, cw=None):
|
|||
cw.setTitle(msg)
|
||||
else:
|
||||
print(msg)
|
||||
|
||||
|
||||
return info
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, url, session, data=None):
|
||||
self.id_ = re.find('/video/([0-9]+)', url, err='no id')
|
||||
self._session = session
|
||||
|
@ -107,7 +107,7 @@ class Video(object):
|
|||
|
||||
data = json.loads(r.text)
|
||||
video = data['data']['videoList'][0]
|
||||
|
||||
|
||||
url_video = video['videoUrl']
|
||||
self.url_thumb = video['coverUrl']
|
||||
self.artist = video['nickname']
|
||||
|
@ -116,4 +116,3 @@ class Video(object):
|
|||
self.filename = '{}{}'.format(self.id_, ext)
|
||||
|
||||
return url_video
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
#coding:utf8
|
||||
import downloader
|
||||
from utils import Soup, Downloader, LazyUrl, urljoin, try_n, get_outdir, clean_title
|
||||
from utils import Soup, Downloader, LazyUrl, urljoin, try_n, get_outdir, clean_title, get_max_range
|
||||
import ree as re
|
||||
import os
|
||||
from timee import sleep
|
||||
from translator import tr_
|
||||
from io import BytesIO
|
||||
import json
|
||||
import clf2
|
||||
downloader.REPLACE_UA[r'\.luscious\.net'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36'
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, item, referer):
|
||||
self.item = item
|
||||
self.id = str(item['id'])
|
||||
|
@ -23,7 +25,7 @@ class Image(object):
|
|||
return img
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, url, title, url_thumb):
|
||||
self.url = url
|
||||
self.title = title
|
||||
|
@ -32,9 +34,9 @@ class Video(object):
|
|||
self.url_thumb = url_thumb
|
||||
self.thumb = BytesIO()
|
||||
downloader.download(self.url_thumb, buffer=self.thumb)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
|
||||
class Downloader_luscious(Downloader):
|
||||
type = 'luscious'
|
||||
URLS = ['luscious.net']
|
||||
|
@ -45,28 +47,36 @@ class Downloader_luscious(Downloader):
|
|||
url = url.replace('members.luscious.', 'www.luscious.')
|
||||
return url
|
||||
|
||||
@classmethod
|
||||
def key_id(cls, url):
|
||||
return '/'.join(url.split('/')[3:])
|
||||
|
||||
def read(self):
|
||||
url = fix_url(self.url)
|
||||
for try_ in range(8):
|
||||
def f(html, browser=None):
|
||||
soup = Soup(html)
|
||||
if soup.find('input', type='password'):
|
||||
browser.show()
|
||||
return False
|
||||
browser.hide()
|
||||
try:
|
||||
html = downloader.read_html(url)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.print_('retry...')
|
||||
else:
|
||||
raise
|
||||
soup = Soup(html)
|
||||
get_title(soup)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
res = clf2.solve(self.url, f=f, cw=self.cw, show=True)
|
||||
self.url = res['url']
|
||||
soup = Soup(res['html'])
|
||||
|
||||
title = clean_title(get_title(soup))
|
||||
|
||||
|
||||
self.title = tr_(u'읽는 중... {}').format(title)
|
||||
|
||||
if '/videos/' in url:
|
||||
video = get_video(url, soup)
|
||||
if '/videos/' in self.url:
|
||||
video = get_video(self.url, soup)
|
||||
imgs = [video]
|
||||
self.setIcon(video.thumb)
|
||||
else:
|
||||
imgs = get_imgs(url, soup, self.cw)
|
||||
imgs = get_imgs(self.url, soup, self.cw)
|
||||
|
||||
dir = os.path.join(get_outdir(self.type), title)
|
||||
names = {}
|
||||
|
@ -88,31 +98,32 @@ class Downloader_luscious(Downloader):
|
|||
|
||||
|
||||
def update(cw, title, imgs):
|
||||
s = u'{} {} ({})'.format(tr_(u'읽는 중...'), title, len(imgs))
|
||||
s = u'{} {} - {}'.format(tr_(u'읽는 중...'), title, len(imgs))
|
||||
if cw is not None:
|
||||
cw.setTitle(s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
def fix_url(url):
|
||||
url = re.sub(r'[^./]+\.luscious', 'legacy.luscious', url)
|
||||
return url
|
||||
|
||||
def get_imgs(url, soup=None, cw=None):
|
||||
url = fix_url(url)
|
||||
if soup is None:
|
||||
html = downloader.read_html(url)
|
||||
soup = Soup(html)
|
||||
title = get_title(soup)
|
||||
|
||||
|
||||
n = get_max_range(cw)
|
||||
|
||||
imgs = []
|
||||
for p in range(1, 81):
|
||||
imgs_new = get_imgs_p(url, p)
|
||||
p = 1
|
||||
while True:
|
||||
imgs_new, has_next_page = get_imgs_p(url, p)
|
||||
if not imgs_new:
|
||||
break
|
||||
imgs += imgs_new
|
||||
update(cw, title, imgs)
|
||||
return imgs
|
||||
p += 1
|
||||
if len(imgs) >= n or not has_next_page:
|
||||
break
|
||||
return imgs[:n]
|
||||
|
||||
|
||||
@try_n(4, sleep=30)
|
||||
|
@ -128,12 +139,12 @@ def get_imgs_p(url, p=1):
|
|||
img = Image(item, url)
|
||||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
return imgs, data['data']['picture']['list']['info']['has_next_page']
|
||||
|
||||
|
||||
def get_video(url, soup):
|
||||
url_thumb = soup.find('meta', {'property': 'og:image'}).attrs['content']
|
||||
|
||||
|
||||
title = re.find('videos/([^/]+)', url)
|
||||
video = soup.find('video')
|
||||
url = video.source.attrs['src']
|
||||
|
@ -142,4 +153,4 @@ def get_video(url, soup):
|
|||
|
||||
|
||||
def get_title(soup):
|
||||
return soup.find('h2').text.strip()
|
||||
return soup.find('h1').text.strip()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from utils import Downloader, LazyUrl, clean_title
|
||||
from utils import Downloader, LazyUrl, clean_title, Session
|
||||
import utils
|
||||
from m3u8_tools import playlist2stream, M3u8_stream
|
||||
import os
|
||||
|
@ -7,13 +7,13 @@ from translator import tr_
|
|||
DEFAULT_N_THREAD = 2
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_m3u8(Downloader):
|
||||
type = 'm3u8'
|
||||
URLS = ['.m3u8']
|
||||
single = True
|
||||
display_name = 'M3U8'
|
||||
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
if '://' not in url:
|
||||
|
@ -25,25 +25,36 @@ class Downloader_m3u8(Downloader):
|
|||
self.print_('n_thread: {}'.format(n_thread))
|
||||
video = Video(self.url, n_thread)
|
||||
self.urls.append(video.url)
|
||||
self.title = '{} ({})'.format(video.title, video.id_)
|
||||
self.title = os.path.splitext(os.path.basename(video.filename))[0].replace(b'\xef\xbc\x9a'.decode('utf8'), ':')
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, url, n_thread):
|
||||
session = Session()
|
||||
session.purge([r'(.*\.)?{}'.format(utils.domain(url))])
|
||||
try:
|
||||
m = playlist2stream(url, n_thread=n_thread)
|
||||
m = playlist2stream(url, n_thread=n_thread, session=session)
|
||||
except:
|
||||
m = M3u8_stream(url, n_thread=n_thread)
|
||||
m = M3u8_stream(url, n_thread=n_thread, session=session)
|
||||
if m.live is not None: #5110
|
||||
m = m.live
|
||||
live = True
|
||||
else:
|
||||
live = False
|
||||
self.url = LazyUrl(url, lambda _: m, self)
|
||||
self.title = os.path.splitext(os.path.basename(url))[0]
|
||||
self.id_ = md5(url.encode('utf8')).hexdigest()[:8]
|
||||
tail = ' ({}).mp4'.format(self.id_)
|
||||
if live: #5110
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
tail = clean_title(now.strftime(' %Y-%m-%d %H:%M')) + tail
|
||||
self.filename = clean_title(self.title, n=-len(tail)) + tail
|
||||
|
||||
|
||||
import selector
|
||||
@selector.options('m3u8')
|
||||
def options():
|
||||
def options(urls):
|
||||
def f(urls):
|
||||
n_thread, ok = utils.QInputDialog.getInt(Downloader.mainWindow, tr_('Set number of threads'), tr_('Number of threads?'), value=DEFAULT_N_THREAD, min=1, max=4, step=1)
|
||||
if not ok:
|
||||
|
|
|
@ -7,29 +7,49 @@ from time import sleep
|
|||
import clf2
|
||||
import ree as re
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
from PIL import Image as Image_
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, page, p):
|
||||
ext = get_ext(url)
|
||||
if ext.lower()[1:] not in ['jpg', 'jpeg', 'bmp', 'png', 'gif', 'webm', 'webp']:
|
||||
ext = '.jpg'
|
||||
self.filename = '{}/{:04}{}'.format(page.title, p, ext)
|
||||
self.url = LazyUrl(page.url, lambda _: url, self)
|
||||
self._url = url
|
||||
self.url = LazyUrl(page.url, self.get, self, pp=self.pp)
|
||||
self.url.show_pp = False
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(2, 1)
|
||||
def get(self, _):
|
||||
return self._url
|
||||
|
||||
def pp(self, filename): #5233
|
||||
img = Image_.open(filename)
|
||||
nf = getattr(img, 'n_frames', 1)
|
||||
loop = img.info.get('loop')
|
||||
if nf > 1 and loop:
|
||||
img.seek(nf-1)
|
||||
img.save(filename)
|
||||
img.close()
|
||||
return filename
|
||||
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
def __init__(self, title, url):
|
||||
self.title = clean_title(title)
|
||||
self.url = url
|
||||
self.id = int(re.find(r'/(comic|webtoon)/([0-9]+)', url, err='no id')[1])
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_manatoki(Downloader):
|
||||
type = 'manatoki'
|
||||
URLS = [r'regex:(mana|new)toki[0-9]*\.(com|net)']
|
||||
MAX_CORE = 4
|
||||
ACCEPT_COOKIES = [r'(.*\.)?(mana|new)toki[0-9]*\.(com|net)']
|
||||
|
||||
@try_n(2)
|
||||
def init(self):
|
||||
|
@ -49,7 +69,7 @@ class Downloader_manatoki(Downloader):
|
|||
raise Exception('no selected option')
|
||||
self.session, self.soup, url = get_soup(url)
|
||||
url_page = self.fix_url(url)
|
||||
|
||||
|
||||
for i, page in enumerate(get_pages(url_page, self.soup)):
|
||||
if page.id == int(op['value']):
|
||||
break
|
||||
|
@ -83,7 +103,7 @@ class Downloader_manatoki(Downloader):
|
|||
self.artist = get_artist(self.soup)
|
||||
|
||||
imgs = get_imgs(self.url, self.name, self.soup, self.session, self.cw)
|
||||
|
||||
|
||||
for img in imgs:
|
||||
if isinstance(img, Image):
|
||||
self.urls.append(img.url)
|
||||
|
@ -106,28 +126,62 @@ def get_artist(soup):
|
|||
def get_soup(url, session=None):
|
||||
if session is None:
|
||||
session = Session()
|
||||
res = clf2.solve(url, session=session)
|
||||
def f(html, browser=None):
|
||||
soup = Soup(html)
|
||||
if soup.find('form', {'name':'fcaptcha'}): #4660
|
||||
browser.show()
|
||||
return False
|
||||
browser.hide()
|
||||
return True
|
||||
res = clf2.solve(url, session=session, f=f)
|
||||
soup = Soup(res['html'], apply_css=True)
|
||||
|
||||
|
||||
return session, soup, res['url']
|
||||
|
||||
|
||||
def get_pages(url, soup):
|
||||
def get_pages(url, soup, sub=False):
|
||||
list = soup.find('ul', class_='list-body')
|
||||
pages = []
|
||||
titles = {}
|
||||
for item in list.findAll('div', 'wr-subject')[::-1]:
|
||||
for item in list.findAll('div', 'wr-subject'):
|
||||
for span in item.a.findAll('span'):
|
||||
span.decompose()
|
||||
title = item.a.text.strip()
|
||||
title = utils.fix_dup(title, titles) #4161
|
||||
href = item.a['href']
|
||||
href = urljoin(url, href)
|
||||
page = Page(title, href)
|
||||
pages.append(page)
|
||||
pages.append((title, href))
|
||||
|
||||
if not pages:
|
||||
raise Exception('no pages')
|
||||
return pages
|
||||
|
||||
## if sub: #4909
|
||||
## return pages
|
||||
## else:
|
||||
## pg = soup.find('ul', class_='pagination')
|
||||
## as_ = pg.findAll('a')
|
||||
## for a in as_:
|
||||
## href = a.get('href')
|
||||
## if not href:
|
||||
## continue
|
||||
## href = urljoin(url, href)
|
||||
## for try_ in range(2):
|
||||
## try:
|
||||
## session, soup2, href = get_soup(href)
|
||||
## pages += get_pages(href, soup2, sub=True)
|
||||
## break
|
||||
## except Exception as e:
|
||||
## e_ = e
|
||||
## print(e)
|
||||
## else:
|
||||
## raise e_
|
||||
|
||||
titles = {}
|
||||
pages_ = []
|
||||
for title, href in pages[::-1]:
|
||||
title = utils.fix_dup(title, titles) #4161
|
||||
page = Page(title, href)
|
||||
pages_.append(page)
|
||||
|
||||
return pages_
|
||||
|
||||
|
||||
@page_selector.register('manatoki')
|
||||
|
@ -142,7 +196,7 @@ def f(url):
|
|||
|
||||
def get_imgs(url, title, soup=None, session=None, cw=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
|
||||
if soup is None or session is None:
|
||||
session, soup, url = get_soup(url, session)
|
||||
|
||||
|
@ -155,7 +209,7 @@ def get_imgs(url, title, soup=None, session=None, cw=None):
|
|||
if imgs_already:
|
||||
imgs += imgs_already
|
||||
continue
|
||||
|
||||
|
||||
imgs_ = get_imgs_page(page, title, url, session, cw)
|
||||
imgs += imgs_
|
||||
|
||||
|
@ -185,17 +239,17 @@ def get_imgs_page(page, title, referer, session, cw):
|
|||
if page.title != title_page:
|
||||
print_('{} -> {}'.format(page.title, title_page))
|
||||
page.title = title_page
|
||||
|
||||
|
||||
views = soup.findAll('div', class_='view-content')\
|
||||
+ soup.findAll('div', class_='view-padding')
|
||||
if not views:
|
||||
raise Exception('no views')
|
||||
|
||||
hash = re.find(r'''data_attribute *: *['"](.+?)['"]''', soup.html)
|
||||
hash = re.find(r'''data_attribute\s*:\s*['"](.+?)['"]''', soup.html)
|
||||
print_('hash: {}'.format(hash))
|
||||
if hash is None:
|
||||
raise Exception('no hash')
|
||||
|
||||
|
||||
imgs = []
|
||||
for view in views:
|
||||
if view is None:
|
||||
|
@ -223,7 +277,7 @@ def get_imgs_page(page, title, referer, session, cw):
|
|||
|
||||
def isVisible(tag):
|
||||
while tag:
|
||||
if re.search(r'display: *none', tag.get('style', ''), re.IGNORECASE):
|
||||
if re.search(r'display:\s*none', tag.get('style', ''), re.IGNORECASE):
|
||||
return False
|
||||
tag = tag.parent
|
||||
return True
|
||||
|
|
|
@ -9,7 +9,7 @@ import ree as re
|
|||
import clf2#
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, p, page, cw):
|
||||
self.cw = cw
|
||||
ext = get_ext(url)
|
||||
|
@ -23,23 +23,22 @@ class Image(object):
|
|||
return self._url#'tmp://' + clf2.download(self._url, cw=self.cw)
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
def __init__(self, title, url, soup=None):
|
||||
self.title = clean_title(title)
|
||||
self.url = url
|
||||
self.soup = soup
|
||||
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_mrm(Downloader):
|
||||
type = 'mrm'
|
||||
URLS = ['myreadingmanga.info']
|
||||
_soup = None
|
||||
MAX_CORE = 4
|
||||
display_name = 'MyReadingManga'
|
||||
|
||||
ACCEPT_COOKIES = [r'(.*\.)?myreadingmanga\.info']
|
||||
|
||||
def init(self):
|
||||
self.session = get_session(self.url, self.cw)
|
||||
|
||||
|
@ -76,7 +75,7 @@ class Downloader_mrm(Downloader):
|
|||
self.urls.append(img.url)
|
||||
|
||||
self.title = self.name
|
||||
|
||||
|
||||
|
||||
def get_title(soup):
|
||||
title = soup.find('h1', class_='entry-title').text.strip()
|
||||
|
@ -102,7 +101,7 @@ def get_imgs(url, soup=None, session=None, cw=None):
|
|||
imgs = []
|
||||
for i, page in enumerate(pages):
|
||||
s = '{} {} / {} ({} / {})'.format(tr_('읽는 중...'), title, page.title, i+1, len(pages))
|
||||
|
||||
|
||||
if cw:
|
||||
if not cw.alive:
|
||||
return
|
||||
|
@ -157,7 +156,7 @@ def get_imgs_page(page, session=None, cw=None):
|
|||
html = read_html(url, session=session, cw=None)
|
||||
soup = Soup(html)
|
||||
page.soup = soup
|
||||
|
||||
|
||||
view = soup.find('div', class_='entry-content')
|
||||
|
||||
imgs = []
|
||||
|
@ -209,4 +208,3 @@ def get_session(url, cw=None):
|
|||
session = r['session']
|
||||
|
||||
return session
|
||||
|
||||
|
|
|
@ -30,12 +30,12 @@ def get_id(url):
|
|||
return username, pid
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_naver(Downloader):
|
||||
type = 'naver'
|
||||
URLS = ['blog.naver.', '.blog.me']
|
||||
display_name = 'Naver Blog'
|
||||
|
||||
|
||||
def init(self):
|
||||
username, pid = get_id(self.url)
|
||||
if username is None:
|
||||
|
@ -58,22 +58,22 @@ class Downloader_naver(Downloader):
|
|||
self.urls.append(img.url)
|
||||
|
||||
self.title = self.name
|
||||
|
||||
|
||||
class Image(object):
|
||||
|
||||
class Image:
|
||||
def __init__(self, url, referer, p):
|
||||
self.url = LazyUrl(referer, lambda _: url, self)
|
||||
#3788, #3817
|
||||
ext = get_ext(url)
|
||||
self.filename = '{:04}{}'.format(p, ext)
|
||||
|
||||
|
||||
class Video(object):
|
||||
|
||||
class Video:
|
||||
def __init__(self, url, referer, p):
|
||||
self.url = LazyUrl(referer, lambda _: url, self)
|
||||
self.filename = 'video_{}.mp4'.format(p)
|
||||
|
||||
|
||||
|
||||
def read_page(url, depth=0):
|
||||
print('read_page', url, depth)
|
||||
if depth > 10:
|
||||
|
@ -108,7 +108,7 @@ def get_imgs(url):
|
|||
print('view', view is not None)
|
||||
|
||||
imgs_ = view.findAll('span', class_='_img') + view.findAll('img')
|
||||
|
||||
|
||||
for img in imgs_:
|
||||
url = img.attrs.get('src', None)
|
||||
if url is None:
|
||||
|
@ -116,7 +116,7 @@ def get_imgs(url):
|
|||
if url is None:
|
||||
print(u'invalid img: {}'.format(url))
|
||||
continue
|
||||
|
||||
|
||||
if 'ssl.pstatic.net' in url: #
|
||||
continue
|
||||
|
||||
|
@ -128,7 +128,7 @@ def get_imgs(url):
|
|||
|
||||
if 'storep-phinf.pstatic.net' in url: # emoticon
|
||||
continue
|
||||
|
||||
|
||||
url = url.replace('mblogthumb-phinf', 'blogfiles')
|
||||
#url = re.sub('\?type=[a-zA-Z0-9]*', '?type=w1@2x', url)
|
||||
#url = re.sub('\?type=[a-zA-Z0-9]*', '', url)
|
||||
|
@ -137,7 +137,7 @@ def get_imgs(url):
|
|||
if url in urls:
|
||||
print('### Duplicate:', url)
|
||||
continue
|
||||
|
||||
|
||||
urls.add(url)
|
||||
#url = url.split('?type=')[0]
|
||||
img = Image(url, referer, len(imgs))
|
||||
|
@ -170,4 +170,3 @@ def get_imgs(url):
|
|||
videos.append(video)
|
||||
|
||||
return imgs + videos
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from utils import Downloader, get_print, urljoin, Soup, get_ext, LazyUrl, clean_title, downloader, re, try_n, errors, json
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_navercafe(Downloader):
|
||||
type = 'navercafe'
|
||||
URLS = ['cafe.naver.com']
|
||||
|
@ -45,7 +45,7 @@ def get_info(url, cw=None):
|
|||
info['cafename'] = j['result']['cafe']['url']
|
||||
info['cafeid'] = clubid
|
||||
info['id'] = articleid
|
||||
|
||||
|
||||
html_content = j['result']['article']['contentHtml']
|
||||
soup = Soup(html_content)
|
||||
|
||||
|
@ -75,7 +75,7 @@ def get_info(url, cw=None):
|
|||
fs = sorted(fs, key=lambda f: f['size'], reverse=True)
|
||||
video = Image(fs[0]['source'], url_article, len(imgs))
|
||||
imgs.append(video)
|
||||
|
||||
|
||||
for img in soup.findAll('img'):
|
||||
img = Image(urljoin(url_article, img['src']), url, len(imgs))
|
||||
imgs.append(img)
|
||||
|
|
|
@ -42,13 +42,13 @@ import page_selector
|
|||
from utils import Downloader, Soup, clean_title
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
def __init__(self, title, url) -> None:
|
||||
self.title = clean_title(title)
|
||||
self.url = url
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class DownloaderNaverPost(Downloader):
|
||||
type = "naverpost" # 타입
|
||||
URLS = ["m.post.naver.com", "post.naver.com"]
|
||||
|
|
|
@ -8,7 +8,7 @@ from translator import tr_
|
|||
import json
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
|
||||
def __init__(self, url, title, p):
|
||||
self.url = url
|
||||
|
@ -16,7 +16,7 @@ class Page(object):
|
|||
self.p = p
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, page, p):
|
||||
ext = get_ext(url)
|
||||
|
@ -25,7 +25,7 @@ class Image(object):
|
|||
self.url = LazyUrl(page.url, lambda _: url, self)
|
||||
|
||||
|
||||
class Info(object):
|
||||
class Info:
|
||||
|
||||
def __init__(self, id, title, artist):
|
||||
self.id = id
|
||||
|
@ -33,7 +33,7 @@ class Info(object):
|
|||
self.artist = artist
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_navertoon(Downloader):
|
||||
type = 'navertoon'
|
||||
URLS = ['comic.naver.com']
|
||||
|
@ -167,7 +167,7 @@ def get_imgs(page, cw=None):
|
|||
|
||||
type_ = re.find('''webtoonType *: *['"](.+?)['"]''', html)
|
||||
print_('type: {}'.format(type_))
|
||||
|
||||
|
||||
imgs = []
|
||||
if type_ == 'DEFAULT': # https://m.comic.naver.com/webtoon/detail.nhn?titleId=715772
|
||||
view = soup.find('div', class_='toon_view_lst')
|
||||
|
@ -235,4 +235,3 @@ def get_imgs_all(url, title, cw=None):
|
|||
break
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -9,13 +9,12 @@ import ytdl
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_navertv(Downloader):
|
||||
type = 'navertv'
|
||||
single = True
|
||||
URLS = ['tv.naver.com']
|
||||
display_name = 'Naver TV'
|
||||
|
||||
|
||||
def init(self):
|
||||
if not re.match('https?://.+', self.url, re.IGNORECASE):
|
||||
self.url = 'https://tv.naver.com/v/{}'.format(self.url)
|
||||
|
@ -33,9 +32,9 @@ class Downloader_navertv(Downloader):
|
|||
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
@ -44,7 +43,7 @@ class Video(object):
|
|||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
fs = [f for f in info['formats'] if f['protocol'] in ['http', 'https']]
|
||||
|
@ -53,7 +52,7 @@ class Video(object):
|
|||
raise Exception('No MP4 videos')
|
||||
f = fs[0]
|
||||
self._url = f['url']
|
||||
|
||||
|
||||
self.thumb_url = info['thumbnails'][0]['url']
|
||||
self.thumb = IO()
|
||||
downloader.download(self.thumb_url, buffer=self.thumb)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#coding:utf8
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
import ree as re
|
||||
from utils import Soup, urljoin, LazyUrl, Downloader, try_n, join, clean_title
|
||||
|
@ -7,13 +6,13 @@ import os
|
|||
import json
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_nhentai_com(Downloader):
|
||||
type = 'nhentai_com'
|
||||
URLS = [r'regex:https?://nhentai.com']
|
||||
MAX_CORE = 16
|
||||
display_name = 'nhentai.com'
|
||||
|
||||
|
||||
def init(self):
|
||||
self.info = get_info(self.url)
|
||||
self.url = self.info['url']
|
||||
|
@ -56,9 +55,9 @@ class LazyUrl_nhentai_com(LazyUrl):
|
|||
url = data['url']
|
||||
img = Image(referer, url, data['p'])
|
||||
return img.url
|
||||
|
||||
|
||||
class Image(object):
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, url_page, url_img, p):
|
||||
self.p = p
|
||||
self.referer = url_page
|
||||
|
@ -82,7 +81,7 @@ def get_info(url):
|
|||
|
||||
info = {}
|
||||
info['url'] = url
|
||||
|
||||
|
||||
info['id'] = int(data['id'])
|
||||
info['type'] = data['category']['name']
|
||||
info['title'] = data['title']
|
||||
|
@ -97,7 +96,5 @@ def get_info(url):
|
|||
img = Image(url, img, len(imgs))
|
||||
imgs.append(img)
|
||||
info['imgs'] = imgs
|
||||
|
||||
|
||||
return info
|
||||
|
||||
|
||||
|
|
|
@ -1,31 +1,36 @@
|
|||
#coding:utf8
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
import ree as re
|
||||
from utils import Soup, urljoin, LazyUrl, Downloader, try_n, join, get_ext
|
||||
import os
|
||||
import json
|
||||
import clf2
|
||||
|
||||
|
||||
def get_id(url):
|
||||
try:
|
||||
return int(url)
|
||||
except:
|
||||
return int(re.find('/g/([0-9]+)', url))
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_nhentai(Downloader):
|
||||
type = 'nhentai'
|
||||
URLS = ['nhentai.net']
|
||||
MAX_CORE = 16
|
||||
display_name = 'nhentai'
|
||||
|
||||
def init(self):
|
||||
self.url = 'https://nhentai.net/g/{}/'.format(self.id_)
|
||||
ACCEPT_COOKIES = [r'(.*\.)?nhentai\.net']
|
||||
|
||||
@property
|
||||
def id_(self):
|
||||
try:
|
||||
return int(self.url)
|
||||
except:
|
||||
return int(re.find('/g/([0-9]+)', self.url))
|
||||
def init(self):
|
||||
self.session = clf2.solve(self.url, cw=self.cw)['session'] #4541
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
return 'https://nhentai.net/g/{}/'.format(get_id(url))
|
||||
|
||||
def read(self):
|
||||
info, imgs = get_imgs(self.id_)
|
||||
info, imgs = get_imgs(get_id(self.url), self.session)
|
||||
|
||||
# 1225
|
||||
artist = join(info.artists)
|
||||
|
@ -58,16 +63,16 @@ class LazyUrl_nhentai(LazyUrl):
|
|||
url = data['url']
|
||||
img = Image(referer, url, data['p'])
|
||||
return img.url
|
||||
|
||||
|
||||
class Image(object):
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, url_page, url_img, p):
|
||||
self.p = p
|
||||
self.url = LazyUrl_nhentai(url_page, lambda _: url_img, self)
|
||||
self.filename = '{:04}{}'.format(p, get_ext(url_img))
|
||||
|
||||
|
||||
class Info(object):
|
||||
class Info:
|
||||
def __init__(self, host, id, id_media, title, p, artists, groups, seriess, lang, type, formats):
|
||||
self.host = host
|
||||
self.id = id
|
||||
|
@ -83,15 +88,15 @@ class Info(object):
|
|||
|
||||
|
||||
@try_n(4)
|
||||
def get_info(id):
|
||||
def get_info(id, session):
|
||||
url = 'https://nhentai.net/g/{}/1/'.format(id)
|
||||
referer = 'https://nhentai.net/g/{}/'.format(id)
|
||||
html = downloader.read_html(url, referer=referer)
|
||||
html = downloader.read_html(url, referer=referer, session=session)
|
||||
|
||||
data = html.split('JSON.parse(')[1].split(');')[0]
|
||||
gal = json.loads(json.loads(data))
|
||||
host = 'https://i.nhentai.net'#re.find('''media_url: *['"]([^'"]+)''', html, err='no host')
|
||||
|
||||
|
||||
id = int(gal['id'])
|
||||
id_media = int(gal['media_id'])
|
||||
title = gal['title']['english']
|
||||
|
@ -120,8 +125,8 @@ def get_info(id):
|
|||
return info
|
||||
|
||||
|
||||
def get_imgs(id):
|
||||
info = get_info(id)
|
||||
def get_imgs(id, session):
|
||||
info = get_info(id, session)
|
||||
|
||||
imgs = []
|
||||
for p in range(1, info.p+1):
|
||||
|
@ -132,5 +137,3 @@ def get_imgs(id):
|
|||
imgs.append(img)
|
||||
|
||||
return info, imgs
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ def get_id(url):
|
|||
return re.find('/watch/([a-zA-Z0-9]+)', url)
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, session, info, format, cw):
|
||||
self.session = session
|
||||
self.info = info
|
||||
|
@ -27,9 +27,9 @@ class Video(object):
|
|||
self.username = info['uploader']
|
||||
self.url = LazyUrl('https://www.nicovideo.jp/watch/{}'.format(self.id), lambda _: info['url'], self, pp=self.pp)
|
||||
self.cw = cw
|
||||
|
||||
|
||||
self.filename = format_filename(self.title, self.id, self.ext)
|
||||
|
||||
|
||||
self.url_thumb = info['thumbnail_url']
|
||||
print('thumb:', self.url_thumb)
|
||||
self.thumb = BytesIO()
|
||||
|
@ -60,7 +60,7 @@ def suitable(url):
|
|||
return get_id(url) is not None
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_nico(Downloader):
|
||||
type = 'nico'
|
||||
single = True
|
||||
|
@ -94,7 +94,7 @@ class Downloader_nico(Downloader):
|
|||
else:
|
||||
username = ''
|
||||
password = ''
|
||||
|
||||
|
||||
try:
|
||||
session = login(username, password)
|
||||
except Exception as e:
|
||||
|
@ -133,7 +133,7 @@ def get_video(session, url, format, cw=None):
|
|||
|
||||
import selector
|
||||
@selector.options('nico')
|
||||
def options():
|
||||
def options(urls):
|
||||
return [
|
||||
{'text': 'MP4 (동영상)', 'format': 'mp4'},
|
||||
{'text': 'MP3 (음원)', 'format': 'mp3'},
|
||||
|
|
|
@ -21,13 +21,13 @@ def isLogin(soup):
|
|||
return False
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_nijie(Downloader):
|
||||
type = 'nijie'
|
||||
URLS = ['nijie.info']
|
||||
MAX_CORE = 4
|
||||
display_name = 'ニジエ'
|
||||
|
||||
|
||||
def init(self):
|
||||
if 'members.php' not in self.url and 'members_illust.php' not in self.url:
|
||||
raise NotImplementedError()
|
||||
|
@ -61,7 +61,7 @@ class Downloader_nijie(Downloader):
|
|||
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, id, url, p, lazy=True, img=None):
|
||||
self.id = id
|
||||
self.p = p
|
||||
|
@ -77,7 +77,7 @@ class Image(object):
|
|||
ext = get_ext(img)
|
||||
self.filename = '{}_p{}{}'.format(self.id, self.p, ext)
|
||||
return img
|
||||
|
||||
|
||||
|
||||
@try_n(8, sleep=10)
|
||||
def get_imgs_post(id, url):
|
||||
|
@ -92,7 +92,7 @@ def get_imgs_post(id, url):
|
|||
img = Image(id, url, len(imgs), False, url_img)
|
||||
imgs.append(img)
|
||||
return imgs
|
||||
|
||||
|
||||
|
||||
def setPage(url, page):
|
||||
# Always use HTTPS
|
||||
|
@ -142,7 +142,7 @@ def get_imgs(url, title=None, cw=None):
|
|||
imgs_ = get_imgs_post(id, url_img)#
|
||||
else:
|
||||
imgs_ = [Image(id, url_img, 0)]
|
||||
|
||||
|
||||
imgs += imgs_
|
||||
c += 1
|
||||
|
||||
|
@ -160,5 +160,3 @@ def get_imgs(url, title=None, cw=None):
|
|||
if len(imgs) >= max_pid or c == 0:
|
||||
break
|
||||
return imgs
|
||||
|
||||
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
from io import BytesIO
|
||||
from urllib.parse import urlparse
|
||||
from typing import List, cast
|
||||
|
||||
from requests.sessions import session
|
||||
|
||||
from errors import LoginRequired
|
||||
from utils import Downloader, Soup, Session, clean_title
|
||||
|
||||
from bs4.element import Tag
|
||||
import requests
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_novelpia(Downloader):
|
||||
type = "novelpia"
|
||||
URLS = ["novelpia.com"]
|
||||
|
||||
def __get_number(self, url: str) -> str:
|
||||
return url.replace("/viewer/", "")
|
||||
|
||||
def __get_cookie(self) -> Session:
|
||||
session = requests.Session()
|
||||
user_key = Session().cookies.get("USERKEY", domain=".novelpia.com")
|
||||
login_key = Session().cookies.get("LOGINKEY", domain=".novelpia.com")
|
||||
|
||||
if user_key and login_key:
|
||||
session.cookies.set("USERKEY", user_key, domain=".novelpia.com")
|
||||
session.cookies.set("LOGINKEY", login_key, domain=".novelpia.com")
|
||||
return session
|
||||
|
||||
def init(self) -> None:
|
||||
self.parsed_url = urlparse(self.url) # url 나눔
|
||||
self.soup = Soup(requests.get(self.url).text)
|
||||
|
||||
def read(self):
|
||||
session = self.__get_cookie()
|
||||
f = BytesIO()
|
||||
|
||||
title_element = self.soup.find("b", {"class": "cut_line_one"})
|
||||
|
||||
if not title_element:
|
||||
raise LoginRequired
|
||||
|
||||
# Maybe NavigableString?
|
||||
assert isinstance(title_element, Tag)
|
||||
self.title = title_element.text
|
||||
|
||||
# css selecter is not working :(
|
||||
ep_num = self.soup.find(
|
||||
"span",
|
||||
{
|
||||
"style": "background-color:rgba(155,155,155,0.5);padding: 1px 6px;border-radius: 3px;font-size: 11px; margin-right: 3px;"
|
||||
},
|
||||
)
|
||||
assert isinstance(ep_num, Tag)
|
||||
|
||||
ep_name = self.soup.find("span", {"class": "cut_line_one"})
|
||||
assert isinstance(ep_name, Tag)
|
||||
|
||||
# Dirty but for clean filename
|
||||
self.print_(ep_name.text)
|
||||
ep_name.text.replace(ep_num.text, "")
|
||||
self.print_(ep_name.text)
|
||||
self.print_(ep_num.text)
|
||||
|
||||
self.filenames[f] = clean_title(f"{ep_num.text}: {ep_name.text}.txt", "safe")
|
||||
|
||||
# https://novelpia.com/viewer/:number:
|
||||
numbers: List[str] = []
|
||||
numbers.append(self.__get_number(self.parsed_url[2]))
|
||||
|
||||
# Get real contents
|
||||
# https://novelpia.com/proc/viewer_data/:number:
|
||||
# {"s": [{"text": ""}]}
|
||||
viewer_datas = map(
|
||||
lambda number: f"https://novelpia.com/proc/viewer_data/{number}", numbers
|
||||
)
|
||||
for viewer_data in viewer_datas:
|
||||
response = session.get(viewer_data)
|
||||
if response.text:
|
||||
response = response.json()
|
||||
for text_dict in response["s"]:
|
||||
text = text_dict["text"]
|
||||
if "img" in text:
|
||||
soup = Soup(text)
|
||||
img = soup.find("img")
|
||||
# Maybe NavigableString here too?
|
||||
assert isinstance(img, Tag)
|
||||
src = img.attrs["src"]
|
||||
filename = img.attrs["data-filename"]
|
||||
f.write(f"[{filename}]".encode("UTF-8"))
|
||||
self.urls.append(f"https:{src}")
|
||||
else:
|
||||
f.write(text_dict["text"].encode("UTF-8"))
|
||||
f.seek(0)
|
||||
self.urls.append(f)
|
||||
else:
|
||||
raise LoginRequired
|
|
@ -4,32 +4,42 @@ from io import BytesIO
|
|||
from utils import Downloader, query_url, LazyUrl, get_ext, urljoin, clean_title, check_alive, lock, get_print, get_max_range
|
||||
import errors
|
||||
from translator import tr_
|
||||
from multiprocessing.pool import ThreadPool
|
||||
from math import ceil
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
|
||||
|
||||
class Image:
|
||||
|
||||
def __init__(self, id, referer):
|
||||
self._id = id
|
||||
self.url = LazyUrl(referer, self.get, self)
|
||||
|
||||
def get(self, referer):
|
||||
# https://j.nozomi.la/nozomi.js
|
||||
s_id = str(self._id)
|
||||
url_post = 'https://j.nozomi.la/post/{}/{}/{}.json'.format(s_id[-1], s_id[-3:-1], self._id)
|
||||
j = downloader.read_json(url_post, referer)
|
||||
url = urljoin(referer, j['imageurl'])
|
||||
def __init__(self, id, url, referer, p):
|
||||
self.url = LazyUrl(referer, lambda _: url, self)
|
||||
ext = get_ext(url)
|
||||
self.filename = '{}{}'.format(self._id, ext)
|
||||
return url
|
||||
self.filename = '{}{}{}'.format(id, f'_p{p}' if p else '', ext)
|
||||
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(4, 1)
|
||||
def read_post(id, referer):
|
||||
# https://j.nozomi.la/nozomi.js
|
||||
s_id = str(id)
|
||||
url_post = 'https://j.nozomi.la/post/{}/{}/{}.json'.format(s_id[-1], s_id[-3:-1], s_id)
|
||||
j = downloader.read_json(url_post, referer)
|
||||
imgs = []
|
||||
for p, url in enumerate(j['imageurls']):
|
||||
url = urljoin(referer, url['imageurl'])
|
||||
img = Image(id, url, referer, p)
|
||||
imgs.append(img)
|
||||
return imgs
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_nozomi(Downloader):
|
||||
type = 'nozomi'
|
||||
URLS = ['nozomi.la']
|
||||
display_name = 'Nozomi.la'
|
||||
MAX_CORE = 15
|
||||
ACC_MTIME = True
|
||||
ACCEPT_COOKIES = [r'(.*\.)?nozomi\.la']
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -50,13 +60,20 @@ class Downloader_nozomi(Downloader):
|
|||
self.title = clean_title(self.name)
|
||||
qs = query_url(self.url)
|
||||
q = qs['q'][0]
|
||||
for id in get_ids_multi(q, self._popular, self.cw):
|
||||
img = Image(id, self.url)
|
||||
self.urls.append(img.url)
|
||||
ids = get_ids_multi(q, self._popular, self.cw)
|
||||
p = ThreadPool(6)
|
||||
step = 10
|
||||
for i in range(int(ceil(len(ids)/step))):
|
||||
for imgs in p.map(lambda id: read_post(id, self.url), ids[i*step:(i+1)*step]):
|
||||
self.urls += [img.url for img in imgs]
|
||||
s = '{} {} - {} / {}'.format(tr_('읽는 중...'), self.name, i*step, len(ids))
|
||||
self.cw.setTitle(s)
|
||||
self.title = clean_title(self.name)
|
||||
|
||||
|
||||
@lock
|
||||
def get_ids(q, popular, cw):
|
||||
print_ = get_print(cw)
|
||||
check_alive(cw)
|
||||
if q is None:
|
||||
if popular:
|
||||
|
@ -64,11 +81,12 @@ def get_ids(q, popular, cw):
|
|||
else:
|
||||
url_api = 'https://j.nozomi.la/index.nozomi'
|
||||
else:
|
||||
q = q.replace('/', '') #5146
|
||||
if popular:
|
||||
url_api = 'https://j.nozomi.la/nozomi/popular/{}-Popular.nozomi'.format(quote(q))
|
||||
else:
|
||||
url_api = 'https://j.nozomi.la/nozomi/{}.nozomi'.format(quote(q))
|
||||
print(url_api)
|
||||
#print_(url_api)
|
||||
f = BytesIO()
|
||||
downloader.download(url_api, referer='https://nozomi.la/', buffer=f)
|
||||
data = f.read()
|
||||
|
|
|
@ -10,7 +10,7 @@ import errors
|
|||
class EmbedUrlError(Exception): pass
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_pandoratv(Downloader):
|
||||
type = 'pandoratv'
|
||||
URLS = ['pandora.tv']
|
||||
|
@ -48,9 +48,9 @@ def extract(name, html, cw=None):
|
|||
return value
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url_video = None
|
||||
|
||||
|
||||
def __init__(self, url, format='title', cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.format = format
|
||||
|
@ -68,7 +68,7 @@ class Video(object):
|
|||
embedUrl = extract('embedUrl', html, cw)
|
||||
if embedUrl:
|
||||
raise EmbedUrlError('[pandoratv] EmbedUrl: {}'.format(embedUrl))
|
||||
|
||||
|
||||
uid = extract('strLocalChUserId', html, cw)
|
||||
pid = extract('nLocalPrgId', html, cw)
|
||||
fid = extract('strFid', html, cw)
|
||||
|
@ -98,13 +98,12 @@ class Video(object):
|
|||
self._url_video = data['src']
|
||||
|
||||
self.title = soup.find('meta', {'property': 'og:description'})['content']
|
||||
|
||||
|
||||
ext = get_ext(self._url_video)
|
||||
self.filename = format_filename(self.title, pid, ext)
|
||||
|
||||
self.url_thumb = soup.find('meta', {'property': 'og:image'})['content']
|
||||
self.thumb = BytesIO()
|
||||
downloader.download(self.url_thumb, buffer=self.thumb)
|
||||
|
||||
return self._url_video
|
||||
|
||||
return self._url_video
|
||||
|
|
|
@ -7,15 +7,16 @@ from mastodon import get_imgs
|
|||
import json
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_pawoo(Downloader):
|
||||
type = 'pawoo'
|
||||
URLS = ['pawoo.net']
|
||||
|
||||
ACCEPT_COOKIES = [r'(.*\.)?pawoo\.net']
|
||||
|
||||
def init(self):
|
||||
self.url = 'https://pawoo.net/{}'.format(self.id_)
|
||||
self.referer = self.url
|
||||
|
||||
|
||||
@property
|
||||
def id_(self):
|
||||
return re.find('pawoo.net/([^/]+)', self.url.lower(), default=self.url)
|
||||
|
@ -41,5 +42,3 @@ class Downloader_pawoo(Downloader):
|
|||
self.filenames[img.url] = img.filename
|
||||
|
||||
self.title = self.name
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import downloader
|
||||
from utils import Session, Downloader, LazyUrl, clean_url, try_n, Soup, clean_title, get_ext, get_max_range
|
||||
from utils import Session, Downloader, LazyUrl, clean_url, try_n, Soup, clean_title, get_ext, get_max_range, get_print
|
||||
import json, os, ree as re
|
||||
from timee import sleep
|
||||
from translator import tr_
|
||||
|
@ -10,7 +10,7 @@ from m3u8_tools import playlist2stream, M3u8_stream
|
|||
BASE_URL = 'https://www.pinterest.com'
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_pinter(Downloader):
|
||||
type = 'pinter'
|
||||
URLS = ['pinterest.']
|
||||
|
@ -30,6 +30,8 @@ class Downloader_pinter(Downloader):
|
|||
self.print_('type: {}'.format(self.type_pinter))
|
||||
if self.type_pinter in ['board', 'section']:
|
||||
self.info = get_info(username, board, self.api)
|
||||
elif self.type_pinter == 'pin':
|
||||
pass #5132
|
||||
else:
|
||||
raise NotImplementedError(self.type_pinter)
|
||||
|
||||
|
@ -50,7 +52,7 @@ class Downloader_pinter(Downloader):
|
|||
def read(self):
|
||||
if self.type_pinter == 'pin':
|
||||
self.single = True
|
||||
id = int(self._pin_id)
|
||||
id = self._pin_id
|
||||
else:
|
||||
id = self.info['id']
|
||||
self.title = self.name
|
||||
|
@ -82,15 +84,18 @@ def get_info(username, board, api):
|
|||
|
||||
|
||||
class PinterestAPI:
|
||||
HEADERS = {'Accept': 'application/json, text/javascript, */*, q=0.01',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'X-Pinterest-AppState': 'active',
|
||||
'X-APP-VERSION': 'cb1c7f9',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Origin': BASE_URL + '/'}
|
||||
HEADERS = {
|
||||
'Accept': 'application/json, text/javascript, */*, q=0.01',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Referer': BASE_URL + '/',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-APP-VERSION' : '31461e0',
|
||||
'X-Pinterest-AppState': 'active',
|
||||
'Origin': BASE_URL,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.session = Session()
|
||||
self.session = Session('chrome')
|
||||
self.session.headers.update(self.HEADERS)
|
||||
|
||||
def pin(self, pin_id):
|
||||
|
@ -130,15 +135,17 @@ class PinterestAPI:
|
|||
print('_call: {}, {}'.format(url, params))
|
||||
r = self.session.get(url, params=params)
|
||||
print(r)
|
||||
global R
|
||||
R = r
|
||||
s = r.text
|
||||
status_code = r.status_code
|
||||
try:
|
||||
data = json.loads(s)
|
||||
except ValueError:
|
||||
data = {}
|
||||
else:
|
||||
if status_code < 400 and not r.history:
|
||||
return data
|
||||
|
||||
if status_code < 400 and not r.history:
|
||||
return data
|
||||
|
||||
if status_code == 404 or r.history:
|
||||
raise Exception('Not Found')
|
||||
|
@ -159,7 +166,7 @@ class PinterestAPI:
|
|||
return
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, img):
|
||||
self.id = img['id']
|
||||
|
@ -184,6 +191,7 @@ class Image(object):
|
|||
|
||||
|
||||
def get_imgs(id, api, cw=None, title=None, type='board'):
|
||||
print_ = get_print(cw)
|
||||
n = get_max_range(cw)
|
||||
imgs = []
|
||||
ids = set()
|
||||
|
@ -201,6 +209,8 @@ def get_imgs(id, api, cw=None, title=None, type='board'):
|
|||
print('skip img:', img['id'])
|
||||
continue
|
||||
img = Image(img)
|
||||
if type == 'pin' and img.id != id:
|
||||
raise AssertionError('id mismatch')
|
||||
if img.id in ids:
|
||||
print('duplicate:', img.id)
|
||||
continue
|
||||
|
@ -228,4 +238,3 @@ def get_username_board(url):
|
|||
board = board[:-1].strip()
|
||||
|
||||
return (username, board)
|
||||
|
||||
|
|
|
@ -7,10 +7,11 @@ import page_selector, clf2
|
|||
from hashlib import md5
|
||||
from datetime import datetime
|
||||
import errors
|
||||
import utils
|
||||
SALT = 'mAtW1X8SzGS880fsjEXlM73QpS1i4kUMBhyhdaYySk8nWz533nrEunaSplg63fzT'
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, page, p):
|
||||
ext = get_ext(url)
|
||||
|
@ -19,14 +20,14 @@ class Image(object):
|
|||
self.url = LazyUrl(page.url, lambda _: url, self)
|
||||
|
||||
|
||||
class Page(object):
|
||||
class Page:
|
||||
|
||||
def __init__(self, url, title):
|
||||
self.title = clean_title(title)
|
||||
self.url = url
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_pixiv_comic(Downloader):
|
||||
type = 'pixiv_comic'
|
||||
URLS = ['comic.pixiv.net/works', 'comic.pixiv.net/viewer/']
|
||||
|
@ -93,13 +94,14 @@ def get_artist(soup):
|
|||
if artist:
|
||||
return artist.text.strip()
|
||||
else:
|
||||
artist = re.find(r'"author" *: *(".+?")', soup.html)
|
||||
html = soup.html.replace('\\"', utils.esc('"')) #4936
|
||||
artist = re.find(r'"author" *: *(".*?")', html) # 4389
|
||||
if artist:
|
||||
return json.loads(artist)
|
||||
else:
|
||||
return 'N/A'
|
||||
artist = json.loads(artist).replace(utils.esc('"'), '"')
|
||||
return artist or None
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_pages(soup, url):
|
||||
pages = []
|
||||
hrefs = set()
|
||||
|
@ -109,12 +111,16 @@ def get_pages(soup, url):
|
|||
if href in hrefs:
|
||||
continue
|
||||
hrefs.add(href)
|
||||
divs = a.findAll('div', recursive=False)
|
||||
divs = a.div.findAll('div', recursive=False) #5158
|
||||
if not divs: #5158
|
||||
continue
|
||||
if len(divs) < 2:
|
||||
divs = divs[0].findAll('div', recursive=False) #4861
|
||||
if len(divs) < 2:
|
||||
continue
|
||||
right = divs[1]
|
||||
number = right.findAll('span')[0].text.strip()
|
||||
title = right.findAll('span')[1].text.strip()
|
||||
number = list(right.children)[0].text.strip() #5158
|
||||
title = list(right.children)[1].text.strip() #5158
|
||||
title = ' - '.join(x for x in [number, title] if x)
|
||||
if title in titles:
|
||||
title0 = title
|
||||
|
@ -159,7 +165,7 @@ def get_imgs(url, title, soup=None, session=None, cw=None):
|
|||
if cw is not None:
|
||||
if not cw.alive:
|
||||
return
|
||||
cw.setTitle((u'{} {} / {} ({} / {})').format(tr_(u'\uc77d\ub294 \uc911...'), title, page.title, i + 1, len(pages)))
|
||||
cw.setTitle('{} {} / {} ({} / {})'.format(tr_('읽는 중...'), title, page.title, i + 1, len(pages)))
|
||||
imgs += get_imgs_page(page, session)
|
||||
|
||||
return imgs
|
||||
|
@ -184,7 +190,7 @@ def get_imgs_page(page, session):
|
|||
|
||||
if not pages:
|
||||
raise Exception('No pages')
|
||||
|
||||
|
||||
imgs = []
|
||||
for p in pages:
|
||||
img = p['url']
|
||||
|
@ -193,4 +199,3 @@ def get_imgs_page(page, session):
|
|||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import downloader
|
||||
from utils import Downloader, Session, urljoin, clean_title, LazyUrl, get_ext, get_print, try_n, compatstr, get_max_range, check_alive, query_url, get_outdir
|
||||
from utils import Downloader, Session, urljoin, clean_title, LazyUrl, get_ext, get_print, try_n, compatstr, get_max_range, check_alive, query_url, get_outdir, Soup
|
||||
import ffmpeg
|
||||
import utils
|
||||
import os
|
||||
|
@ -19,8 +19,8 @@ from collections import deque
|
|||
from locker import lock
|
||||
import threading
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
import clf2
|
||||
##import asyncio
|
||||
FORCE_LOGIN = True
|
||||
LIMIT = 48
|
||||
for header in ['pixiv_illust', 'pixiv_bmk', 'pixiv_search', 'pixiv_following', 'pixiv_following_r18']:
|
||||
if header not in constants.available_extra:
|
||||
|
@ -28,12 +28,23 @@ for header in ['pixiv_illust', 'pixiv_bmk', 'pixiv_search', 'pixiv_following', '
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_pixiv(Downloader):
|
||||
type = 'pixiv'
|
||||
MAX_CORE = 16
|
||||
MAX_CORE = 4
|
||||
MAX_PARALLEL = 2
|
||||
keep_date = True
|
||||
STEP = 8, 32
|
||||
STEP = 4, 16
|
||||
ACCEPT_COOKIES = [r'(.*\.)?pixiv\.(com|co|net)']
|
||||
|
||||
def init(self):
|
||||
res = clf2.solve(self.url, cw=self.cw)
|
||||
self.session = res['session'] #5105
|
||||
|
||||
soup = Soup(res['html'])
|
||||
err = soup.find('p', class_='error-message')
|
||||
if err: #5223
|
||||
raise errors.Invalid('{}: {}'.format(err.text.strip(), self.url))
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -49,10 +60,10 @@ class Downloader_pixiv(Downloader):
|
|||
url = 'https://www.pixiv.net/bookmark_new_illust.php'
|
||||
elif not re.find(r'^https?://', url) and '.' not in url:
|
||||
url = 'https://www.pixiv.net/en/users/{}'.format(url)
|
||||
|
||||
|
||||
#3474
|
||||
url = re.sub(r'(users/[0-9]+)/artworks$', r'\1', url)
|
||||
|
||||
|
||||
url = re.sub(r'[?&]p=[0-9]+$', '', url)
|
||||
return url.strip('/')
|
||||
|
||||
|
@ -64,7 +75,8 @@ class Downloader_pixiv(Downloader):
|
|||
## loop = asyncio.new_event_loop()
|
||||
## asyncio.set_event_loop(loop)
|
||||
try:
|
||||
info = get_info(self.url, self.cw)
|
||||
info = get_info(self.url, self.session, self.cw)
|
||||
self.artist = info.get('artist') #4897
|
||||
for img in info['imgs']:
|
||||
self.urls.append(img.url)
|
||||
self.title = clean_title(info['title'])
|
||||
|
@ -77,10 +89,20 @@ class PixivAPIError(errors.LoginRequired): pass
|
|||
class HTTPError(Exception): pass
|
||||
|
||||
|
||||
class PixivAPI():
|
||||
class PixivAPI:
|
||||
|
||||
def __init__(self):
|
||||
self.session = None#Session()
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
hdr = {
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Accept-Language': 'en-US,en;q=0.9,ko-KR;q=0.8,ko;q=0.7,ja;q=0.6',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Referer': 'https://www.pixiv.net/',
|
||||
'X-User-Id': my_id(session),
|
||||
}
|
||||
self.session.headers.update(hdr)
|
||||
|
||||
def illust_id(self, url):
|
||||
return re.find('/artworks/([0-9]+)', url) or re.find('[?&]illust_id=([0-9]+)', url)
|
||||
|
@ -88,15 +110,18 @@ class PixivAPI():
|
|||
def user_id(self, url):
|
||||
return re.find('/users/([0-9]+)', url) or re.find('[?&]id=([0-9]+)', url)
|
||||
|
||||
@try_n(8)
|
||||
@try_n(8, sleep=5)
|
||||
@sleep_and_retry
|
||||
@limits(30, 1) #3355
|
||||
@limits(2, 3) #3355, #5105
|
||||
def call(self, url):
|
||||
#print('call:', url)
|
||||
url = urljoin('https://www.pixiv.net/ajax/', url)
|
||||
e_ = None
|
||||
try:
|
||||
info = downloader.read_json(url, session=self.session)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
utils.SD['pixiv'] = {}
|
||||
utils.SD['pixiv']['err'] = self.session
|
||||
code = e.response.status_code
|
||||
if code in (403, 404):
|
||||
e_ = HTTPError('{} Client Error'.format(code))
|
||||
|
@ -108,31 +133,29 @@ class PixivAPI():
|
|||
if err:
|
||||
raise PixivAPIError(info.get('message'))
|
||||
return info['body']
|
||||
|
||||
|
||||
def illust(self, id_):
|
||||
return self.call('illust/{}'.format(id_))
|
||||
|
||||
|
||||
|
||||
def pages(self, id_):
|
||||
return self.call('illust/{}/pages'.format(id_))
|
||||
|
||||
|
||||
|
||||
def ugoira_meta(self, id_):
|
||||
return self.call('illust/{}/ugoira_meta'.format(id_))
|
||||
|
||||
def user(self, id_):
|
||||
return self.call('user/{}'.format(id_))
|
||||
|
||||
def profile(self, id_):
|
||||
return self.call('user/{}/profile/all?lang=en'.format(id_))
|
||||
return self.call('user/{}/profile/all'.format(id_))
|
||||
|
||||
def top(self, id_):
|
||||
return self.call('user/{}/profile/top'.format(id_))
|
||||
|
||||
def bookmarks(self, id_, offset=0, limit=None, rest='show'):
|
||||
if limit is None:
|
||||
limit = LIMIT
|
||||
return self.call('user/{}/illusts/bookmarks?tag=&offset={}&limit={}&rest={}&lang=en'.format(id_, offset, limit, rest))
|
||||
return self.call('user/{}/illusts/bookmarks?tag=&offset={}&limit={}&rest={}'.format(id_, offset, limit, rest))
|
||||
|
||||
def search(self, q, order='date_d', mode='all', p=1, s_mode='s_tag_full', type_='all', scd=None, ecd=None, wlt=None, wgt=None, hlt=None, hgt=None, blt=None, bgt=None, ratio=None, tool=None):
|
||||
url = 'search/artworks/{0}?word={0}&order={1}&mode={2}&p={3}&s_mode={4}&type={5}&lang=en'.format(quote(q), order, mode, p, s_mode, type_)
|
||||
url = 'search/artworks/{0}?word={0}&order={1}&mode={2}&p={3}&s_mode={4}&type={5}'.format(quote(q), order, mode, p, s_mode, type_)
|
||||
if scd:
|
||||
url += '&scd={}'.format(scd)
|
||||
if ecd:
|
||||
|
@ -157,10 +180,10 @@ class PixivAPI():
|
|||
|
||||
def following(self, p, r18=False): #4077
|
||||
mode = 'r18' if r18 else 'all'
|
||||
url = f'follow_latest/illust?p={p}&mode={mode}&lang=en'
|
||||
url = f'follow_latest/illust?p={p}&mode={mode}'
|
||||
return self.call(url)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Image():
|
||||
local = False
|
||||
|
@ -191,7 +214,7 @@ class Image():
|
|||
if self.ugoira and self.ugoira['ext']: #3355
|
||||
filename_local = os.path.join(self.cw.dir, self.filename)
|
||||
filename_local = '{}{}'.format(os.path.splitext(filename_local)[0], self.ugoira['ext'])
|
||||
if os.path.exists(filename_local):
|
||||
if os.path.realpath(filename_local) in self.cw.names_old or os.path.exists(filename_local): #4534
|
||||
self.filename = os.path.basename(filename_local)
|
||||
self.local = True
|
||||
return self._url
|
||||
|
@ -251,27 +274,27 @@ def tags_matched(tags_illust, tags_add, cw=None):
|
|||
tags.update((pretty_tag(tag) for tag in tags_add))
|
||||
if init:
|
||||
print_('tags_add: {}'.format(tags_add))
|
||||
|
||||
|
||||
tags_illust = set(pretty_tag(tag) for tag in tags_illust)
|
||||
return (not tags or tags & tags_illust) and tags_ex.isdisjoint(tags_illust)
|
||||
|
||||
|
||||
def get_info(url, cw=None, depth=0, tags_add=None):
|
||||
def get_info(url, session, cw=None, depth=0, tags_add=None):
|
||||
print_ = get_print(cw)
|
||||
api = PixivAPI()
|
||||
api = PixivAPI(session)
|
||||
info = {}
|
||||
imgs = []
|
||||
|
||||
|
||||
ugoira_ext = [None, '.gif', '.webp', '.png'][utils.ui_setting.ugoira_convert.currentIndex()] if utils.ui_setting else None
|
||||
format_ = compatstr(utils.ui_setting.pixivFormat.currentText()) if utils.ui_setting else 'id_ppage'
|
||||
|
||||
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
|
||||
if api.illust_id(url): # Single post
|
||||
id_ = api.illust_id(url)
|
||||
data = api.illust(id_)
|
||||
login = 'noLoginData' not in data
|
||||
if FORCE_LOGIN and not login:#
|
||||
if not login:#
|
||||
raise errors.LoginRequired()
|
||||
if data['xRestrict'] and not login:
|
||||
raise errors.LoginRequired('R-18')
|
||||
|
@ -281,7 +304,7 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
info['title'] = '{} (pixiv_illust_{})'.format(info['raw_title'], id_)
|
||||
info['create_date'] = parse_time(data['createDate'])
|
||||
tags_illust = set(tag['tag'] for tag in data['tags']['tags'])
|
||||
|
||||
|
||||
if tags_matched(tags_illust, tags_add, cw):
|
||||
if data['illustType'] == 2: # ugoira
|
||||
data = api.ugoira_meta(id_)
|
||||
|
@ -301,8 +324,8 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
elif '/bookmarks/' in url or 'bookmark.php' in url: # User bookmarks
|
||||
id_ = api.user_id(url)
|
||||
if id_ is None: #
|
||||
id_ = my_id()
|
||||
if id_ == my_id():
|
||||
id_ = my_id(session)
|
||||
if id_ == my_id(session):
|
||||
rests = ['show', 'hide']
|
||||
else:
|
||||
rests = ['show']
|
||||
|
@ -326,7 +349,7 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
offset += LIMIT
|
||||
if depth == 0:
|
||||
check_alive(cw)
|
||||
process_ids(ids, info, imgs, cw, depth)
|
||||
process_ids(ids, info, imgs, session, cw, depth)
|
||||
elif '/tags/' in url or 'search.php' in url: # Search
|
||||
q = unquote(re.find(r'/tags/([^/]+)', url) or re.find('[?&]word=([^&]*)', url, err='no tags'))
|
||||
info['title'] = '{} (pixiv_search_{})'.format(q, q.replace(' ', '+'))
|
||||
|
@ -373,10 +396,10 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
if not c:
|
||||
break
|
||||
p += 1
|
||||
process_ids(ids, info, imgs, cw, depth)
|
||||
process_ids(ids, info, imgs, session, cw, depth)
|
||||
elif 'bookmark_new_illust.php' in url or 'bookmark_new_illust_r18.php' in url: # Newest works: Following
|
||||
r18 = 'bookmark_new_illust_r18.php' in url
|
||||
id_ = my_id()
|
||||
id_ = my_id(session)
|
||||
process_user(id_, info, api)
|
||||
info['title'] = '{} (pixiv_following_{}{})'.format(info['artist'], 'r18_' if r18 else '', info['artist_id'])
|
||||
ids = []
|
||||
|
@ -394,7 +417,7 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
if not c:
|
||||
break
|
||||
p += 1
|
||||
process_ids(ids, info, imgs, cw, depth)
|
||||
process_ids(ids, info, imgs, session, cw, depth)
|
||||
elif api.user_id(url): # User illusts
|
||||
m = re.search(r'/users/[0-9]+/([\w]+)/?([^\?#/]*)', url)
|
||||
type_ = {'illustrations': 'illusts', 'manga': 'manga'}.get(m and m.groups()[0])
|
||||
|
@ -407,12 +430,12 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
else:
|
||||
tag = None
|
||||
print_('types: {}, tag: {}'.format(types, tag))
|
||||
|
||||
|
||||
id_ = api.user_id(url)
|
||||
process_user(id_, info, api)
|
||||
data = api.profile(id_)
|
||||
info['title'] = '{} (pixiv_{})'.format(info['artist'], info['artist_id'])
|
||||
|
||||
|
||||
ids = []
|
||||
for type_ in types:
|
||||
illusts = data[type_]
|
||||
|
@ -420,9 +443,10 @@ def get_info(url, cw=None, depth=0, tags_add=None):
|
|||
continue
|
||||
ids += list(illusts.keys())
|
||||
ids = sorted(ids, key=int, reverse=True)
|
||||
print_(f'ids: {len(ids)}')
|
||||
if not ids:
|
||||
raise Exception('no imgs')
|
||||
process_ids(ids, info, imgs, cw, depth, tags_add=[tag] if tag else None)
|
||||
process_ids(ids, info, imgs, session, cw, depth, tags_add=[tag] if tag else None)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
info['imgs'] = imgs[:max_pid]
|
||||
|
@ -438,20 +462,23 @@ def parse_time(ds):
|
|||
return time - dt
|
||||
|
||||
|
||||
def my_id():
|
||||
sid = Session().cookies.get('PHPSESSID', domain='.pixiv.net')
|
||||
def my_id(session):
|
||||
sid = session.cookies.get('PHPSESSID', domain='.pixiv.net')
|
||||
if not sid:
|
||||
raise errors.LoginRequired()
|
||||
return re.find(r'^([0-9]+)', sid, err='no userid')
|
||||
userid = re.find(r'^([0-9]+)', sid)
|
||||
if userid is None:
|
||||
raise errors.LoginRequired()
|
||||
return userid
|
||||
|
||||
|
||||
def process_user(id_, info, api):
|
||||
info['artist_id'] = id_
|
||||
data_user = api.user(id_)
|
||||
info['artist'] = data_user['name']
|
||||
data_user = api.top(id_)
|
||||
info['artist'] = data_user['extraData']['meta']['ogp']['title']
|
||||
|
||||
|
||||
def process_ids(ids, info, imgs, cw, depth=0, tags_add=None):
|
||||
def process_ids(ids, info, imgs, session, cw, depth=0, tags_add=None):
|
||||
print_ = get_print(cw)
|
||||
max_pid = get_max_range(cw)
|
||||
class Thread(threading.Thread):
|
||||
|
@ -466,7 +493,7 @@ def process_ids(ids, info, imgs, cw, depth=0, tags_add=None):
|
|||
@lock
|
||||
def add_rem(cls, x):
|
||||
cls.rem += x
|
||||
|
||||
|
||||
def run(self):
|
||||
while self.alive:
|
||||
try:
|
||||
|
@ -475,7 +502,7 @@ def process_ids(ids, info, imgs, cw, depth=0, tags_add=None):
|
|||
sleep(.1)
|
||||
continue
|
||||
try:
|
||||
info_illust = get_info('https://www.pixiv.net/en/artworks/{}'.format(id_), cw, depth=depth+1, tags_add=tags_add)
|
||||
info_illust = get_info('https://www.pixiv.net/en/artworks/{}'.format(id_), session, cw, depth=depth+1, tags_add=tags_add)
|
||||
res[i] = info_illust['imgs']
|
||||
except Exception as e:
|
||||
if depth == 0 and (e.args and e.args[0] == '不明なエラーが発生しました' or type(e) == errors.LoginRequired): # logout during extraction
|
||||
|
@ -497,7 +524,7 @@ def process_ids(ids, info, imgs, cw, depth=0, tags_add=None):
|
|||
queue.append((id_illust, res, j))
|
||||
Thread.add_rem(1)
|
||||
while Thread.rem:
|
||||
sleep(.001, cw)
|
||||
sleep(.01, cw)
|
||||
for imgs_ in res:
|
||||
if isinstance(imgs_, Exception):
|
||||
raise imgs_
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
'''
|
||||
Pornhub Downloader
|
||||
'''
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
from io import BytesIO
|
||||
import os
|
||||
import downloader
|
||||
|
@ -17,37 +16,38 @@ import errors
|
|||
import json
|
||||
import functools
|
||||
import operator
|
||||
from error_printer import print_error
|
||||
|
||||
|
||||
|
||||
class File(object):
|
||||
class File:
|
||||
'''
|
||||
File
|
||||
'''
|
||||
|
||||
def __init__(self, id_, title, url, url_thumb):
|
||||
def __init__(self, id_, title, url, url_thumb, artist=''):
|
||||
self.id_ = id_
|
||||
self.title = clean_title('{}'.format(title))
|
||||
self.url = url
|
||||
|
||||
|
||||
ext = get_ext(self.url)
|
||||
if ext.lower() == '.m3u8':
|
||||
try:
|
||||
self.url = playlist2stream(self.url, n_thread=4)
|
||||
except:
|
||||
self.url = M3u8_stream(self.url, n_thread=4)
|
||||
|
||||
|
||||
self.url_thumb = url_thumb
|
||||
self.thumb = BytesIO()
|
||||
downloader.download(self.url_thumb, buffer=self.thumb)
|
||||
|
||||
|
||||
if ext.lower() == '.m3u8':
|
||||
ext = '.mp4'
|
||||
self.filename = format_filename(self.title, self.id_, ext)
|
||||
self.filename = format_filename(self.title, self.id_, ext, artist=artist)
|
||||
print('filename:', self.filename)
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
'''
|
||||
Video
|
||||
'''
|
||||
|
@ -86,10 +86,11 @@ class Video(object):
|
|||
print_('Locked player')
|
||||
raise Exception('Locked player')
|
||||
url = url_test
|
||||
except: #3511
|
||||
except Exception as e: #3511
|
||||
print_(print_error(e)[0])
|
||||
url = url.replace('pornhub.com', 'pornhubpremium.com')
|
||||
html = downloader.read_html(url, session=session)
|
||||
|
||||
|
||||
soup = Soup(html)
|
||||
soup = fix_soup(soup, url, session, cw)
|
||||
html = soup.html
|
||||
|
@ -103,7 +104,7 @@ class Video(object):
|
|||
print_('GIF')
|
||||
id_ = url.split('/gif/')[1]
|
||||
id_ = re.findall('[0-9a-zA-Z]+', id_)[0]
|
||||
|
||||
|
||||
jss = list(gif.children)
|
||||
for js in jss:
|
||||
if 'data-mp4' in getattr(js, 'attrs', {}):
|
||||
|
@ -139,7 +140,7 @@ class Video(object):
|
|||
return None
|
||||
url = url.strip()
|
||||
return url if re.match(r'^(?:(?:https?|rt(?:m(?:pt?[es]?|fp)|sp[su]?)|mms|ftps?):)?//', url) else None
|
||||
|
||||
|
||||
flashvars = json.loads(re.find(r'var\s+flashvars_\d+\s*=\s*({.+?});', html, err='no flashvars'))
|
||||
url_thumb = flashvars.get('image_url')
|
||||
media_definitions = flashvars.get('mediaDefinitions')
|
||||
|
@ -245,7 +246,7 @@ class Video(object):
|
|||
for video_url, height in video_urls_:
|
||||
if '/video/get_media' in video_url:
|
||||
print_(video_url)
|
||||
medias = downloader.read_json(video_url, session=session)
|
||||
medias = downloader.read_json(video_url, url, session=session)
|
||||
if isinstance(medias, list):
|
||||
for media in medias:
|
||||
if not isinstance(media, dict):
|
||||
|
@ -255,9 +256,9 @@ class Video(object):
|
|||
continue
|
||||
height = int_or_none(media.get('quality'))
|
||||
video_urls.append((video_url, height))
|
||||
continue
|
||||
video_urls.append((video_url, height))
|
||||
|
||||
else:
|
||||
video_urls.append((video_url, height))
|
||||
|
||||
|
||||
videos = []
|
||||
for video_url, height in video_urls:
|
||||
|
@ -286,8 +287,11 @@ class Video(object):
|
|||
video = videos[0]
|
||||
print_('\n[{}p] {} {}'.format(video['height'], video['ext'], video['videoUrl']))
|
||||
|
||||
file = File(id_, title, video['videoUrl'].strip(), url_thumb)
|
||||
|
||||
#4940
|
||||
artist = soup.find('div', class_='userInfo').find('div', class_='usernameWrap').text.strip()
|
||||
|
||||
file = File(id_, title, video['videoUrl'].strip(), url_thumb, artist)
|
||||
|
||||
self._url = file.url
|
||||
self.title = file.title
|
||||
self.filename = file.filename
|
||||
|
@ -313,7 +317,6 @@ def is_login(session, cw=None, n=2):
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_pornhub(Downloader):
|
||||
'''
|
||||
Downloader
|
||||
|
@ -322,12 +325,7 @@ class Downloader_pornhub(Downloader):
|
|||
single = True
|
||||
strip_header = False
|
||||
URLS = ['pornhub.com', 'pornhubpremium.com', 'pornhubthbh7ap3u.onion']
|
||||
|
||||
def init(self):
|
||||
self.session = Session() # 1791
|
||||
if 'pornhubpremium.com' in self.url.lower() and\
|
||||
not is_login(self.session, self.cw):
|
||||
raise errors.LoginRequired()
|
||||
ACCEPT_COOKIES = [r'.*(pornhub|phncdn).*']
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -356,9 +354,14 @@ class Downloader_pornhub(Downloader):
|
|||
raise Exception('no id')
|
||||
return id_.split('#')[0]
|
||||
|
||||
@try_n(2)
|
||||
def read(self):
|
||||
cw = self.cw
|
||||
session = self.session
|
||||
|
||||
session = self.session = Session() # 1791
|
||||
if 'pornhubpremium.com' in self.url.lower() and\
|
||||
not is_login(session, cw):
|
||||
raise errors.LoginRequired()
|
||||
|
||||
videos = []
|
||||
tab = ''.join(self.url.replace('pornhubpremium.com', 'pornhub.com', 1).split('?')[0].split('#')[0].split('pornhub.com/')[-1].split('/')[2:3])
|
||||
|
@ -420,7 +423,7 @@ def fix_soup(soup, url, session=None, cw=None):
|
|||
|
||||
|
||||
|
||||
class Photo(object):
|
||||
class Photo:
|
||||
'''
|
||||
Photo
|
||||
'''
|
||||
|
|
|
@ -28,7 +28,7 @@ def get_tags(url):
|
|||
return id
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_rule34_xxx(Downloader):
|
||||
type = 'rule34_xxx'
|
||||
URLS = ['rule34.xxx']
|
||||
|
@ -68,7 +68,7 @@ class Downloader_rule34_xxx(Downloader):
|
|||
self.title = self.name
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, id_, url):
|
||||
self.url = url
|
||||
ext = os.path.splitext(url)[1]
|
||||
|
|
|
@ -14,20 +14,20 @@ import urllib
|
|||
import sys
|
||||
from timee import sleep
|
||||
import constants
|
||||
from sankaku_login import login
|
||||
from error_printer import print_error
|
||||
from constants import clean_url
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_sankaku(Downloader):
|
||||
type = 'sankaku'
|
||||
URLS = ['chan.sankakucomplex.com', 'idol.sankakucomplex.com', 'www.sankakucomplex.com']
|
||||
MAX_CORE = 4
|
||||
display_name = 'Sankaku Complex'
|
||||
|
||||
ACCEPT_COOKIES = [r'(.*\.)?(sankakucomplex\.com|sankaku\.app)']
|
||||
|
||||
def init(self):
|
||||
type = self.url.split('sankakucomplex.com')[0].split('//')[-1].strip('.').split('.')[-1]
|
||||
if type == '':
|
||||
|
@ -39,9 +39,6 @@ class Downloader_sankaku(Downloader):
|
|||
self.url = clean_url(self.url)
|
||||
self.session = Session()
|
||||
|
||||
if self.type_sankaku != 'www':
|
||||
login(type, self.session, self.cw)
|
||||
|
||||
if self.type_sankaku == 'www':
|
||||
html = downloader.read_html(self.url, session=self.session)
|
||||
self.soup = Soup(html)
|
||||
|
@ -154,7 +151,7 @@ class LazyUrl_sankaku(LazyUrl):
|
|||
return img.url
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
filename = None
|
||||
def __init__(self, type, id, url, referer, local=False, cw=None, d=None, session=None):
|
||||
self.type = type
|
||||
|
@ -174,7 +171,7 @@ class Image(object):
|
|||
cw = self.cw
|
||||
d = self.d
|
||||
print_ = get_print(cw)
|
||||
|
||||
|
||||
for try_ in range(4):
|
||||
wait(cw)
|
||||
html = ''
|
||||
|
@ -197,7 +194,7 @@ class Image(object):
|
|||
t_sleep = 5
|
||||
s = '[Sankaku] failed to read image (id:{}): {}'.format(self.id, e)
|
||||
print_(s)
|
||||
sleep(t_sleep, cw)
|
||||
sleep(t_sleep, cw)
|
||||
else:
|
||||
raise Exception('can not find image (id:{})\n{}'.format(self.id, e_msg))
|
||||
soup = Soup('<p>{}</p>'.format(url))
|
||||
|
@ -216,7 +213,7 @@ def setPage(url, page):
|
|||
url = re.sub(r'page=[0-9]*', 'page={}'.format(page), url)
|
||||
else:
|
||||
url += '&page={}'.format(page)
|
||||
|
||||
|
||||
return url
|
||||
|
||||
|
||||
|
@ -246,7 +243,7 @@ def get_imgs(url, title=None, cw=None, d=None, types=['img', 'gif', 'video'], se
|
|||
id = get_id(url)
|
||||
info['imgs'] = [Image(type, id, url, None, cw=cw, d=d)]
|
||||
return info
|
||||
|
||||
|
||||
# Range
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
|
@ -261,7 +258,7 @@ def get_imgs(url, title=None, cw=None, d=None, types=['img', 'gif', 'video'], se
|
|||
for name in names:
|
||||
id = os.path.splitext(name)[0]
|
||||
local_ids[id] = os.path.join(dir, name)
|
||||
|
||||
|
||||
imgs = []
|
||||
page = 1
|
||||
url_imgs = set()
|
||||
|
@ -287,10 +284,10 @@ def get_imgs(url, title=None, cw=None, d=None, types=['img', 'gif', 'video'], se
|
|||
url_old = url
|
||||
soup = Soup(html)
|
||||
articles = soup.findAll('span', {'class': 'thumb'})
|
||||
|
||||
|
||||
if not articles:
|
||||
break
|
||||
|
||||
|
||||
for article in articles:
|
||||
# 1183
|
||||
tags = article.find('img', class_='preview').attrs['title'].split()
|
||||
|
@ -302,7 +299,7 @@ def get_imgs(url, title=None, cw=None, d=None, types=['img', 'gif', 'video'], se
|
|||
type_ = 'img'
|
||||
if type_ not in types:
|
||||
continue
|
||||
|
||||
|
||||
url_img = article.a.attrs['href']
|
||||
if not url_img.startswith('http'):
|
||||
url_img = urljoin('https://{}.sankakucomplex.com'.format(type), url_img)
|
||||
|
@ -329,15 +326,15 @@ def get_imgs(url, title=None, cw=None, d=None, types=['img', 'gif', 'video'], se
|
|||
# For page > 50
|
||||
pagination = soup.find('div', class_='pagination')
|
||||
url = urljoin('https://{}.sankakucomplex.com'.format(type), pagination.attrs['next-page-url'])
|
||||
#3366
|
||||
p = int(re.find(r'[?&]page=([0-9]+)', url, default=1))
|
||||
if p > 100:
|
||||
url = setPage(url, 100)
|
||||
## #3366
|
||||
## p = int(re.find(r'[?&]page=([0-9]+)', url, default=1))
|
||||
## if p > 100:
|
||||
## break
|
||||
except Exception as e:
|
||||
print_(print_error(e)[-1])
|
||||
#url = setPage(url, page)
|
||||
break
|
||||
|
||||
|
||||
if cw is not None:
|
||||
cw.setTitle('{} {} - {}'.format(tr_('읽는 중...'), title, len(imgs)))
|
||||
else:
|
||||
|
@ -347,10 +344,9 @@ def get_imgs(url, title=None, cw=None, d=None, types=['img', 'gif', 'video'], se
|
|||
raise Exception('no images')
|
||||
|
||||
info['imgs'] = imgs
|
||||
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def get_id(url_img):
|
||||
return re.find('show/([0-9]+)', url_img)
|
||||
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
#coding: utf8
|
||||
import downloader
|
||||
import json
|
||||
from io import BytesIO
|
||||
from utils import Downloader, LazyUrl, get_print, try_n, lock, clean_title
|
||||
from error_printer import print_error
|
||||
import os
|
||||
from timee import sleep
|
||||
from utils import Downloader, LazyUrl, get_print, try_n, lock, clean_title, get_max_range
|
||||
import ffmpeg
|
||||
import ytdl
|
||||
from m3u8_tools import M3u8_stream
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
CLIENT_ID = None
|
||||
|
||||
|
||||
|
@ -24,43 +21,43 @@ def get_cid(force=False):
|
|||
return CLIENT_ID
|
||||
|
||||
|
||||
class Audio(object):
|
||||
class Audio:
|
||||
_url = None
|
||||
|
||||
def __init__(self, info, album_art, cw=None):
|
||||
self.info = info
|
||||
|
||||
def __init__(self, url, album_art, cw=None):
|
||||
self.album_art = album_art
|
||||
self.cw = cw
|
||||
self.url = LazyUrl(info['webpage_url'], self.get, self, pp=self.pp)
|
||||
self.url = LazyUrl(url, self.get, self, pp=self.pp)
|
||||
|
||||
@try_n(2)
|
||||
@sleep_and_retry
|
||||
@limits(1, 1)
|
||||
def get(self, url):
|
||||
print_ = get_print(self.cw)
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
info = self.info
|
||||
|
||||
## ydl = ytdl.YoutubeDL()
|
||||
## info = ydl.extract_info(url)
|
||||
ydl = ytdl.YoutubeDL()
|
||||
self.info = info = ydl.extract_info(url)
|
||||
|
||||
formats = info['formats']
|
||||
print(formats)
|
||||
formats = sorted(formats, key=lambda x: int(x.get('abr', 0)), reverse=True)
|
||||
url_audio = None
|
||||
|
||||
|
||||
for format in formats:
|
||||
protocol = format['protocol']
|
||||
print_(u'【{}】 format【{}】 abr【{}】'.format(protocol, format['format'], format.get('abr', 0)))
|
||||
print_('【{}】 format【{}】 abr【{}】'.format(protocol, format['format'], format.get('abr', 0)))
|
||||
if not url_audio and protocol in ['http', 'https']:
|
||||
url_audio = format['url']
|
||||
|
||||
if not url_audio:
|
||||
url_audio = M3u8_stream(formats[0]['url'])
|
||||
self.album_art = False#
|
||||
|
||||
|
||||
self.username = info['uploader']
|
||||
self.title = u'{} - {}'.format(self.username, info['title'])
|
||||
self.filename = u'{}{}'.format(clean_title(self.title, allow_dot=True, n=-4), '.mp3')
|
||||
self.title = '{} - {}'.format(self.username, info['title'])
|
||||
self.filename = '{}{}'.format(clean_title(self.title, allow_dot=True, n=-4), '.mp3')
|
||||
|
||||
thumb = None
|
||||
for t in info['thumbnails'][::-1]:
|
||||
|
@ -76,7 +73,7 @@ class Audio(object):
|
|||
print(e)
|
||||
thumb = None
|
||||
self.thumb = thumb
|
||||
|
||||
|
||||
self._url = url_audio
|
||||
return self._url
|
||||
|
||||
|
@ -86,7 +83,7 @@ class Audio(object):
|
|||
ffmpeg.add_cover(filename, self.thumb, {'artist':self.username, 'title':self.info['title']}, cw=self.cw)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_soundcloud(Downloader):
|
||||
type = 'soundcloud'
|
||||
single = True
|
||||
|
@ -94,18 +91,22 @@ class Downloader_soundcloud(Downloader):
|
|||
#lock = True
|
||||
audio = None
|
||||
display_name = 'SoundCloud'
|
||||
|
||||
|
||||
def init(self):
|
||||
if 'soundcloud.com' in self.url.lower():
|
||||
self.url = self.url.replace('http://', 'https://')
|
||||
else:
|
||||
self.url = 'https://soundcloud.com/{}'.format(self.url)
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
return url.split('?')[0]
|
||||
|
||||
def read(self):
|
||||
album_art = self.ui_setting.albumArt.isChecked()
|
||||
info = get_audios(self.url, self.cw, album_art)
|
||||
audios = info['audios']
|
||||
|
||||
|
||||
if not audios:
|
||||
raise Exception('no audios')
|
||||
|
||||
|
@ -139,11 +140,12 @@ def get_audios(url, cw, album_art):
|
|||
if url.count('/') == 3:
|
||||
url += '/tracks'
|
||||
|
||||
info = {
|
||||
#'extract_flat': True,
|
||||
options = {
|
||||
'extract_flat': True,
|
||||
'playlistend': get_max_range(cw),
|
||||
}
|
||||
|
||||
ydl = ytdl.YoutubeDL(cw=cw)
|
||||
ydl = ytdl.YoutubeDL(options, cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
if 'entries' in info:
|
||||
entries = info['entries']
|
||||
|
@ -156,20 +158,19 @@ def get_audios(url, cw, album_art):
|
|||
break
|
||||
else:
|
||||
kind = 'Playlist'
|
||||
print_(u'kind: {}'.format(kind))
|
||||
info['title'] = u'[{}] {}'.format(kind.capitalize(), title)
|
||||
print_('kind: {}'.format(kind))
|
||||
info['title'] = '[{}] {}'.format(kind.capitalize(), title)
|
||||
else:
|
||||
entries = [info]
|
||||
|
||||
audios = []
|
||||
for e in entries:
|
||||
if '/sets/' in e['webpage_url']:
|
||||
url = e.get('webpage_url') or e['url']
|
||||
if '/sets/' in url:
|
||||
continue
|
||||
audio = Audio(e, album_art, cw=cw)
|
||||
audio = Audio(url, album_art, cw=cw)
|
||||
audios.append(audio)
|
||||
|
||||
info['audios'] = audios
|
||||
|
||||
|
||||
return info
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import os
|
|||
from translator import tr_
|
||||
|
||||
|
||||
class Text(object):
|
||||
class Text:
|
||||
|
||||
def __init__(self, title, update, url, session, single):
|
||||
if single:
|
||||
|
@ -17,10 +17,10 @@ class Text(object):
|
|||
self.title = title
|
||||
else:
|
||||
self.p = int(re.findall('/([0-9]+)', url)[(-1)])
|
||||
title = (u'[{:04}] {}').format(self.p, title)
|
||||
title = '[{:04}] {}'.format(self.p, title)
|
||||
title = clean_title(title, n=-4)
|
||||
self.title = title
|
||||
self.filename = (u'{}.txt').format(self.title)
|
||||
self.filename = '{}.txt'.format(self.title)
|
||||
|
||||
def f(url):
|
||||
text = get_text(url, self.title, update, session)
|
||||
|
@ -32,16 +32,17 @@ class Text(object):
|
|||
self.url = LazyUrl(url, f, self)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_syosetu(Downloader):
|
||||
type = 'syosetu'
|
||||
URLS = ['syosetu.com']
|
||||
MAX_CORE = 2
|
||||
detect_removed = False
|
||||
display_name = '小説家になろう'
|
||||
ACCEPT_COOKIES = [r'(.*\.)?syosetu\.com']
|
||||
|
||||
def init(self):
|
||||
self.url = (u'https://ncode.syosetu.com/{}/').format(self.id_)
|
||||
self.url = 'https://ncode.syosetu.com/{}/'.format(self.id_)
|
||||
|
||||
@property
|
||||
def id_(self):
|
||||
|
@ -72,7 +73,7 @@ class Downloader_syosetu(Downloader):
|
|||
ncode = re.find(r'syosetu.com/([^/]+)', self.url, err='no ncode') #3938
|
||||
title_dir = clean_title('[{}] {} ({})'.format(self.artist, title, ncode))
|
||||
ex = soup.find('div', id='novel_ex')
|
||||
self.novel_ex = ex.text.strip() if ex else None
|
||||
self.novel_ex = utils.get_text(ex, '') if ex else None
|
||||
texts = []
|
||||
subtitles = soup.findAll('dd', class_='subtitle')
|
||||
if subtitles:
|
||||
|
@ -86,12 +87,12 @@ class Downloader_syosetu(Downloader):
|
|||
|
||||
update = update.text.strip()
|
||||
if update2:
|
||||
update += (u' ({})').format(update2)
|
||||
update += ' ({})'.format(update2)
|
||||
a = subtitle.find('a')
|
||||
subtitle = a.text.strip()
|
||||
href = urljoin(self.url, a.attrs['href'])
|
||||
if not re.search(('ncode.syosetu.com/{}/[0-9]+').format(self.id_), href):
|
||||
self.print_((u'skip: {}').format(href))
|
||||
if not re.search('ncode.syosetu.com/{}/[0-9]+'.format(self.id_), href):
|
||||
self.print_('skip: {}'.format(href))
|
||||
continue
|
||||
text = Text(subtitle, update, href, session, False)
|
||||
texts.append(text)
|
||||
|
@ -100,7 +101,7 @@ class Downloader_syosetu(Downloader):
|
|||
self.single = True
|
||||
text = Text(title_dir, None, self.url, session, True)
|
||||
texts.append(text)
|
||||
self.print_((u'single: {}').format(self.single))
|
||||
self.print_('single: {}'.format(self.single))
|
||||
outdir = get_outdir('syosetu')
|
||||
for text in texts:
|
||||
if self.single:
|
||||
|
@ -118,14 +119,14 @@ class Downloader_syosetu(Downloader):
|
|||
if self.single:
|
||||
return
|
||||
names = self.cw.names
|
||||
filename = os.path.join(self.dir, (u'[merged] {}.txt').format(self.title))
|
||||
filename = os.path.join(self.dir, '[merged] {}.txt'.format(self.title))
|
||||
try:
|
||||
with utils.open(filename, 'wb') as f:
|
||||
f.write(u' {}\n\n \u4f5c\u8005\uff1a{}\n\n\n'.format(self.__title, self.artist).encode('utf8'))
|
||||
f.write(' {}\n\n \u4f5c\u8005\uff1a{}\n\n\n'.format(self.__title, self.artist).encode('utf8'))
|
||||
if self.novel_ex:
|
||||
f.write(self.novel_ex.encode('utf8'))
|
||||
for i, file in enumerate(names):
|
||||
self.cw.pbar.setFormat(u"[%v/%m] {} [{}/{}]".format(tr_(u'\ubcd1\ud569...'), i, len(names)))
|
||||
self.cw.pbar.setFormat(u"[%v/%m] {} [{}/{}]".format(tr_('\ubcd1\ud569...'), i, len(names)))
|
||||
with open(file, 'rb') as f_:
|
||||
text = f_.read()
|
||||
f.write(b'\n\n\n\n')
|
||||
|
@ -135,7 +136,7 @@ class Downloader_syosetu(Downloader):
|
|||
|
||||
|
||||
def get_title_artist(soup):
|
||||
artist = soup.find('div', class_='novel_writername').text.replace(u'\u4f5c\u8005', '').replace(u'\uff1a', '').replace(':', '').replace(u'\u3000', ' ').strip()
|
||||
artist = soup.find('div', class_='novel_writername').text.replace('\u4f5c\u8005', '').replace('\uff1a', '').replace(':', '').replace('\u3000', ' ').strip()
|
||||
rem = len(artist.encode('utf8', 'ignore')) + len('[merged] [] .txt') + len(' (n8273ds)')
|
||||
return clean_title(soup.find('p', class_='novel_title').text.strip(), n=-rem), clean_title(artist)
|
||||
|
||||
|
@ -145,24 +146,24 @@ def get_text(url, subtitle, update, session):
|
|||
html = downloader.read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
if update:
|
||||
update = u' ' + update
|
||||
update = ' ' + update
|
||||
else:
|
||||
update = ''
|
||||
|
||||
story = soup.find('div', id='novel_honbun').text.strip()
|
||||
|
||||
|
||||
story = utils.get_text(soup.find('div', id='novel_honbun'), '')
|
||||
|
||||
p = soup.find('div', id='novel_p')
|
||||
p = '' if p is None else p.text.strip()
|
||||
p = '' if p is None else utils.get_text(p, '')
|
||||
if p:
|
||||
story = '{}\n\n════════════════════════════════\n\n{}'.format(p, story)
|
||||
|
||||
|
||||
#2888
|
||||
a = soup.find('div', id='novel_a')
|
||||
a = '' if a is None else a.text.strip()
|
||||
a = '' if a is None else utils.get_text(a, '')
|
||||
if a:
|
||||
story = '{}\n\n════════════════════════════════\n\n{}'.format(story, a)
|
||||
|
||||
text =u'''────────────────────────────────
|
||||
|
||||
text ='''────────────────────────────────
|
||||
|
||||
◆ {}{}
|
||||
|
||||
|
@ -177,5 +178,3 @@ def get_session():
|
|||
session = Session()
|
||||
session.cookies.set(name='over18', value='yes', path='/', domain='.syosetu.com')
|
||||
return session
|
||||
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import requests
|
|||
from utils import Downloader, Soup
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class DownloaderTalkOPGG(Downloader):
|
||||
type = "talkopgg"
|
||||
URLS = ["talk.op.gg"]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
import ree as re
|
||||
from utils import Soup, LazyUrl, Downloader, try_n, compatstr, get_print, clean_title, Session, get_max_range, format_filename
|
||||
from utils import Soup, LazyUrl, Downloader, try_n, compatstr, get_print, Session, get_max_range, format_filename, json
|
||||
from io import BytesIO
|
||||
import clf2
|
||||
from translator import tr_
|
||||
from timee import sleep
|
||||
from error_printer import print_error
|
||||
import ytdl
|
||||
from urllib.parse import unquote
|
||||
PATTERN_VID = '/(v|video)/(?P<id>[0-9]+)'
|
||||
SHOW = True
|
||||
|
||||
|
@ -15,14 +15,14 @@ SHOW = True
|
|||
def is_captcha(soup):
|
||||
return soup.find('div', class_="verify-wrap") is not None
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
|
||||
class Downloader_tiktok(Downloader):
|
||||
type = 'tiktok'
|
||||
single = True
|
||||
URLS = ['tiktok.com']
|
||||
URLS = ['tiktok.com', 'douyin.com']
|
||||
display_name = 'TikTok'
|
||||
|
||||
|
||||
def init(self):
|
||||
cw = self.cw
|
||||
self.session = Session()
|
||||
|
@ -37,33 +37,50 @@ class Downloader_tiktok(Downloader):
|
|||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
url = url.split('?')[0].split('#')[0].strip('/')
|
||||
if 'tiktok.com' not in url.lower():
|
||||
if '://' not in url:
|
||||
url = 'https://www.tiktok.com/@{}'.format(url)
|
||||
return url
|
||||
|
||||
|
||||
def read(self):
|
||||
format = compatstr(self.ui_setting.youtubeFormat.currentText()).lower().strip()
|
||||
|
||||
if re.search(PATTERN_VID, self.url) is None:
|
||||
|
||||
def parse_video_url(info, item):
|
||||
if 'tiktok.com' in self.url.lower(): # TikTok
|
||||
return 'https://www.tiktok.com/@{}/video/{}'.format(info.get('uid', ''), item['id']) #5235
|
||||
else: # Douyin
|
||||
return 'https://www.douyin.com/video/{}'.format(item['id'])
|
||||
|
||||
if re.search(PATTERN_VID, self.url): # single video
|
||||
video = Video(self.url, self.session, format, self.cw)
|
||||
video.url()
|
||||
self.urls.append(video.url)
|
||||
self.title = video.title
|
||||
elif 'tiktok.com/tag/' in self.url or 'douyin.com/search/' in self.url: # tag search
|
||||
tag = re.find(r'/(tag|search)/([^/#\?]+)', self.url)[1]
|
||||
tag = unquote(tag)
|
||||
title = '#{}'.format(tag)
|
||||
info = read_channel(self.url, self.session, self.cw, title=title)
|
||||
items = info['items']
|
||||
videos = [Video(parse_video_url(info, item), self.session, format, self.cw) for item in items]
|
||||
video = self.process_playlist(title, videos)
|
||||
elif 'tiktok.com/@' in self.url or 'douyin.com/user/' in self.url: # channel
|
||||
info = read_channel(self.url, self.session, self.cw)
|
||||
items = info['items']
|
||||
videos = [Video('https://www.tiktok.com/@{}/video/{}'.format(info['uid'], item['id']), self.session, format) for item in items]
|
||||
videos = [Video(parse_video_url(info, item), self.session, format, self.cw) for item in items]
|
||||
title = '{} (tiktok_{})'.format(info['nickname'], info['uid'])
|
||||
video = self.process_playlist(title, videos)
|
||||
else:
|
||||
video = Video(self.url, self.session, format)
|
||||
video.url()
|
||||
self.urls.append(video.url)
|
||||
self.title = clean_title(video.title)
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
def __init__(self, url, session, format='title (id)'):
|
||||
|
||||
def __init__(self, url, session, format, cw):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.session = session
|
||||
self.format = format
|
||||
self.cw = cw
|
||||
|
||||
@try_n(2)
|
||||
def get(self, url):
|
||||
|
@ -71,19 +88,20 @@ class Video(object):
|
|||
return self._url
|
||||
m = re.search(PATTERN_VID, url)
|
||||
id = m.group('id')
|
||||
ext = '.mp4'
|
||||
self.title = id#
|
||||
self.filename = format_filename(self.title, id, ext)
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
ext = '.mp4'
|
||||
self.title = info['title']
|
||||
self.filename = format_filename(self.title, id, ext)
|
||||
|
||||
self._url = info['url']
|
||||
|
||||
return self._url
|
||||
|
||||
|
||||
def read_channel(url, session, cw=None):
|
||||
def read_channel(url, session, cw=None, title=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
info = {}
|
||||
|
@ -97,7 +115,7 @@ def read_channel(url, session, cw=None):
|
|||
}
|
||||
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
|
||||
def f(html, browser=None):
|
||||
soup = Soup(html)
|
||||
if is_captcha(soup):
|
||||
|
@ -107,25 +125,38 @@ def read_channel(url, session, cw=None):
|
|||
elif sd['shown'] and not SHOW:
|
||||
browser.hide()
|
||||
sd['shown'] = False
|
||||
try:
|
||||
st = soup.find('h2', class_='share-title')
|
||||
if st is None:
|
||||
st = soup.find('h2', class_=lambda c: c and 'ShareTitle' in c)
|
||||
info['uid'] = st.text.strip()
|
||||
st = soup.find('h1', class_='share-sub-title')
|
||||
if st is None:
|
||||
st = soup.find('h1', class_=lambda c: c and 'ShareSubTitle' in c)
|
||||
info['nickname'] = st.text.strip()
|
||||
except Exception as e:
|
||||
print_(print_error(e)[0])
|
||||
if 'tiktok.com' in url.lower(): # TikTok
|
||||
try:
|
||||
st = soup.find('h2', class_='share-title')
|
||||
if st is None:
|
||||
st = soup.find('h2', class_=lambda c: c and 'ShareTitle' in c)
|
||||
info['uid'] = st.text.strip()
|
||||
st = soup.find('h1', class_='share-sub-title')
|
||||
if st is None:
|
||||
st = soup.find('h1', class_=lambda c: c and 'ShareSubTitle' in c)
|
||||
info['nickname'] = st.text.strip()
|
||||
except Exception as e:
|
||||
print_(print_error(e)[0])
|
||||
else: # Douyin
|
||||
try:
|
||||
info['uid'] = re.find(r'''uniqueId%22%3A%22(.+?)%22''', html, err='no uid')
|
||||
info['nickname'] = json.loads(re.find(r'''"name"\s*:\s*(".+?")''', html, err='no nickname'))
|
||||
except Exception as e:
|
||||
print_(print_error(e)[0])
|
||||
c = 0
|
||||
ids_now = set()
|
||||
items = soup.findAll('div', class_='video-feed-item') + soup.findAll('div', class_=lambda c: c and 'DivItemContainer' in c)
|
||||
for div in items:
|
||||
a = div.find('a')
|
||||
if a is None:
|
||||
continue
|
||||
href = a['href']
|
||||
if 'tiktok.com' in url.lower(): # TikTok
|
||||
items = soup.findAll('div', class_='video-feed-item') + soup.findAll('div', class_=lambda c: c and 'DivItemContainer' in c)
|
||||
else: # Douyin
|
||||
items = soup.findAll('a')
|
||||
for item in items:
|
||||
if item.name == 'a':
|
||||
a = item
|
||||
else:
|
||||
a = item.find('a')
|
||||
if a is None:
|
||||
continue
|
||||
href = a.get('href')
|
||||
if not href:
|
||||
continue
|
||||
m = re.search(PATTERN_VID, href)
|
||||
|
@ -143,16 +174,20 @@ def read_channel(url, session, cw=None):
|
|||
if len(info['items']) >= max_pid:
|
||||
info['items'] = info['items'][:max_pid]
|
||||
return True
|
||||
|
||||
|
||||
browser.runJavaScript('window.scrollTo(0, document.body.scrollHeight);')
|
||||
sleep(15, cw)
|
||||
|
||||
|
||||
if c or (ids_now and min(ids_now) > min(ids)):
|
||||
sd['count_empty'] = 0
|
||||
else:
|
||||
print_('empty')
|
||||
sd['count_empty'] += 1
|
||||
msg = '{} {} (tiktok_{}) - {}'.format(tr_('읽는 중...'), info.get('nickname'), info.get('uid'), len(info['items']))
|
||||
if title is None:
|
||||
foo = '{} (tiktok_{})'.format(info.get('nickname'), info.get('uid'))
|
||||
else:
|
||||
foo = title
|
||||
msg = '{} {} - {}'.format(tr_('읽는 중...'), foo, len(info['items']))
|
||||
if cw:
|
||||
if not cw.alive:
|
||||
raise Exception('cw dead')
|
||||
|
@ -166,4 +201,3 @@ def read_channel(url, session, cw=None):
|
|||
raise Exception('no items')
|
||||
|
||||
return info
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@ import ree as re
|
|||
import os
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_tokyomotion(Downloader):
|
||||
type = 'tokyomotion'
|
||||
URLS = ['tokyomotion.net']
|
||||
single = True
|
||||
_type = None
|
||||
display_name = 'TOKYO Motion'
|
||||
|
||||
|
||||
def init(self):
|
||||
html = downloader.read_html(self.url)
|
||||
self.soup = Soup(html)
|
||||
|
@ -45,7 +45,7 @@ class Downloader_tokyomotion(Downloader):
|
|||
self.title = self.name
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
def __init__(self, url, url_thumb, referer, filename):
|
||||
self.url = LazyUrl(referer, lambda x: url, self)
|
||||
self.url_thumb = url_thumb
|
||||
|
@ -67,7 +67,7 @@ def get_video(url, soup=None):
|
|||
if soup is None:
|
||||
html = downloader.read_html(url)
|
||||
soup = Soup(html)
|
||||
|
||||
|
||||
video = soup.find('video', id='vjsplayer').find('source').attrs['src']
|
||||
url_thumb = soup.find('video', id='vjsplayer').attrs['poster']
|
||||
title = get_title(soup)
|
||||
|
@ -76,7 +76,7 @@ def get_video(url, soup=None):
|
|||
return video
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, referer):
|
||||
self.url = LazyUrl(referer, lambda x: url, self)
|
||||
self.filename = os.path.basename(url.split('?')[0])
|
||||
|
|
|
@ -1,24 +1,33 @@
|
|||
from utils import Downloader, clean_title, lock
|
||||
import constants, os, downloader
|
||||
from size import Size
|
||||
try:
|
||||
import torrent
|
||||
except Exception as e:
|
||||
torrent = None
|
||||
from timee import sleep
|
||||
from translator import tr_
|
||||
import utils
|
||||
import filesize as fs
|
||||
from datetime import datetime
|
||||
import errors
|
||||
import ips
|
||||
torrent = None
|
||||
TIMEOUT = 600
|
||||
CACHE_INFO = True
|
||||
TOO_MANY = 1000
|
||||
|
||||
|
||||
def isInfoHash(s):
|
||||
if len(s) != 40:
|
||||
return False
|
||||
try:
|
||||
bytes.fromhex(s)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_torrent(Downloader):
|
||||
type = 'torrent'
|
||||
URLS = [r'regex:^magnet:', r'regex:\.torrent$']
|
||||
URLS = [r'regex:^magnet:', r'regex:\.torrent$', isInfoHash]
|
||||
single = True
|
||||
update_filesize = False
|
||||
_info = None
|
||||
|
@ -29,18 +38,62 @@ class Downloader_torrent(Downloader):
|
|||
_h = None
|
||||
_dn = None
|
||||
MAX_PARALLEL = 16
|
||||
MAX_CORE = 0
|
||||
skip_convert_imgs = True
|
||||
_filesize_init = False
|
||||
_max_speed = None
|
||||
_anon = False
|
||||
_proxy = '', '', 0, '', ''
|
||||
_seeding = False
|
||||
_virgin = True
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
if isInfoHash(url):
|
||||
url = f'magnet:?xt=urn:btih:{url}'
|
||||
return url
|
||||
|
||||
@classmethod
|
||||
def set_max_speed(cls, speed):
|
||||
cls._max_speed = speed
|
||||
cls.updateSettings()
|
||||
|
||||
@classmethod
|
||||
def set_anon(cls, flag):
|
||||
cls._anon = flag
|
||||
cls.updateSettings()
|
||||
|
||||
@classmethod
|
||||
def set_proxy(cls, protocol, host, port, username, password):
|
||||
cls._proxy = protocol, host, port, username, password
|
||||
cls.updateSettings()
|
||||
|
||||
@classmethod
|
||||
@lock
|
||||
def init(self):
|
||||
def updateSettings(cls):
|
||||
if torrent is None:
|
||||
print('torrent is None')
|
||||
return
|
||||
torrent.set_max_speed(cls._max_speed)
|
||||
torrent.set_anon(cls._anon)
|
||||
torrent.set_proxy(*cls._proxy)
|
||||
|
||||
@classmethod
|
||||
def _import_torrent(cls):
|
||||
global torrent
|
||||
if torrent is None:
|
||||
import torrent
|
||||
self.cw.pbar.hide()
|
||||
|
||||
@lock
|
||||
def __init(self):
|
||||
self._import_torrent()
|
||||
Downloader_torrent.updateSettings()
|
||||
|
||||
@classmethod
|
||||
def key_id(cls, url):
|
||||
if torrent is None:
|
||||
print('torrent is None')
|
||||
return url
|
||||
id_, e = torrent.key_id(url)
|
||||
if e:
|
||||
print(e)
|
||||
|
@ -52,13 +105,21 @@ class Downloader_torrent(Downloader):
|
|||
self._name = clean_title(self._info.name())
|
||||
return self._name
|
||||
|
||||
@classmethod
|
||||
def get_dn(cls, url):
|
||||
if url.startswith('magnet:'):
|
||||
qs = utils.query_url(url)
|
||||
if 'dn' in qs:
|
||||
return utils.html_unescape(qs['dn'][0])
|
||||
|
||||
def read(self):
|
||||
cw = self.cw
|
||||
self.cw.pbar.hide()
|
||||
self.__init()
|
||||
if cw:
|
||||
cw._torrent_s = None
|
||||
title = self.url
|
||||
if self.url.startswith('magnet:'):
|
||||
qs = utils.query_url(self.url)
|
||||
if 'dn' in qs:
|
||||
self._dn = qs['dn'][0]
|
||||
self._dn = self.get_dn(self.url)
|
||||
info = getattr(cw, 'info?', None)
|
||||
if info is not None:
|
||||
self.print_('cached info')
|
||||
|
@ -89,14 +150,17 @@ class Downloader_torrent(Downloader):
|
|||
self.print_('Creator: {}'.format(self._info.creator()))
|
||||
self.print_('Comment: {}'.format(self._info.comment()))
|
||||
cw.setTotalFileSize(self._info.total_size())
|
||||
|
||||
|
||||
cw.imgs.clear()
|
||||
cw.dones.clear()
|
||||
|
||||
self.urls = [self.url]
|
||||
self.title = self.name
|
||||
self.update_files()
|
||||
|
||||
|
||||
if not self.single and not os.path.isdir(self.dir): #4698
|
||||
downloader.makedir_event(self.dir, cw)
|
||||
|
||||
cw.pbar.show()
|
||||
|
||||
def update_files(self):
|
||||
|
@ -106,12 +170,15 @@ class Downloader_torrent(Downloader):
|
|||
raise Exception('No files')
|
||||
cw.single = self.single = len(files) <= 1
|
||||
for file in files:
|
||||
filename = os.path.join(self.dir, file)
|
||||
filename = os.path.join(self.dir, file.path)
|
||||
cw.imgs.append(filename)
|
||||
|
||||
def update_pause(self):
|
||||
cw = self.cw
|
||||
if cw.pause_lock:
|
||||
if self._seeding:
|
||||
cw.pause_lock = False
|
||||
return
|
||||
cw.pause_data = {
|
||||
'type': self.type,
|
||||
'url': self.url,
|
||||
|
@ -125,38 +192,47 @@ class Downloader_torrent(Downloader):
|
|||
cw.pbar.setFormat('%p%')
|
||||
cw.setColor('reading')
|
||||
cw.downloader_pausable = True
|
||||
self._seeding = False
|
||||
if cw.paused:
|
||||
data = cw.pause_data
|
||||
cw.paused = False
|
||||
cw.pause_lock = False
|
||||
self.update_tools_buttons()
|
||||
self.read()
|
||||
if self.status == 'stop':
|
||||
self.stop()
|
||||
return True
|
||||
if cw.paused:
|
||||
pass
|
||||
else:
|
||||
cw.dir = self.dir
|
||||
cw.urls[:] = self.urls
|
||||
try:
|
||||
self.read()
|
||||
if self.status == 'stop':
|
||||
self.stop()
|
||||
return True
|
||||
if cw.paused:
|
||||
pass
|
||||
else:
|
||||
cw.dir = self.dir
|
||||
cw.urls[:] = self.urls
|
||||
cw.clearPieces()
|
||||
self.size = Size()
|
||||
self.size_upload = Size()
|
||||
cw.pbar.setMaximum(self._info.total_size())
|
||||
cw.setColor('reading')
|
||||
torrent.download(self._info, save_path=self.dir, callback=self.callback, cw=cw)
|
||||
self.update_progress(self._h, False)
|
||||
cw.setSpeed(0.0)
|
||||
cw.setUploadSpeed(0.0)
|
||||
if not cw.alive:
|
||||
return
|
||||
self.update_pause()
|
||||
if cw.paused:
|
||||
return True
|
||||
self.title = self.name
|
||||
if not self.single:
|
||||
cw.pbar.setMaximum(len(cw.imgs))
|
||||
finally:
|
||||
cw.clearPieces()
|
||||
self.size = Size()
|
||||
self.size_upload = Size()
|
||||
cw.pbar.setMaximum(self._info.total_size())
|
||||
cw.setColor('downloading')
|
||||
torrent.download(self._info, save_path=self.dir, callback=self.callback)
|
||||
cw.setSpeed(0.0)
|
||||
cw.setUploadSpeed(0.0)
|
||||
if not cw.alive:
|
||||
return
|
||||
self.update_pause()
|
||||
if cw.paused:
|
||||
return True
|
||||
self.title = self.name
|
||||
if not self.single:
|
||||
cw.pbar.setMaximum(len(cw.imgs))
|
||||
cw.clearPieces()
|
||||
self._h = None
|
||||
try:
|
||||
cw.set_extra('torrent_progress', torrent.get_file_progress(self._h, self._info, True))
|
||||
except Exception as e:
|
||||
cw.remove_extra('torrent_progress')
|
||||
self.print_error(e)
|
||||
self._h = None
|
||||
|
||||
def _updateIcon(self):
|
||||
cw = self.cw
|
||||
|
@ -166,6 +242,34 @@ class Downloader_torrent(Downloader):
|
|||
break
|
||||
sleep(.5)
|
||||
|
||||
def update_progress(self, h, fast):
|
||||
if self._info is None:
|
||||
return
|
||||
cw = self.cw
|
||||
|
||||
if not cw.imgs: #???
|
||||
self.print_('???')
|
||||
self.update_files()
|
||||
|
||||
sizes = torrent.get_file_progress(h, self._info, fast)
|
||||
for i, (file, size) in enumerate(zip(cw.names, sizes)):
|
||||
if i > 0 and fast:
|
||||
break#
|
||||
file = os.path.realpath(file)
|
||||
if file in cw.dones:
|
||||
continue
|
||||
if size[0] == size[1]:
|
||||
cw.dones.add(file)
|
||||
file = constants.compact(file).replace('\\', '/')
|
||||
files = file.split('/')
|
||||
file = ' / '.join(files[1:])
|
||||
msg = 'Completed: {} | {}'.format(file, fs.size(size[1]))
|
||||
self.print_(msg)
|
||||
if i == 0 and size[0]:
|
||||
self._updateIcon()
|
||||
|
||||
cw.setPieces(torrent.pieces(h, self._info))
|
||||
|
||||
def callback(self, h, s, alerts):
|
||||
try:
|
||||
return self._callback(h, s, alerts)
|
||||
|
@ -176,7 +280,14 @@ class Downloader_torrent(Downloader):
|
|||
def _callback(self, h, s, alerts):
|
||||
self._h = h
|
||||
cw = self.cw
|
||||
|
||||
|
||||
if self._virgin:
|
||||
self._virgin = False
|
||||
try:
|
||||
ips.get('0.0.0.0')
|
||||
except Exception as e:
|
||||
self.print_error(e)
|
||||
|
||||
if self._state != s.state_str:
|
||||
self._state = s.state_str
|
||||
self.print_('state: {}'.format(s.state_str))
|
||||
|
@ -187,37 +298,21 @@ class Downloader_torrent(Downloader):
|
|||
title = (self._dn or self.url) if self._info is None else self.name
|
||||
|
||||
if cw.alive and cw.valid and not cw.pause_lock:
|
||||
if self._info is not None:
|
||||
if not cw.imgs: #???
|
||||
self.print_('???')
|
||||
self.update_files()
|
||||
|
||||
sizes = torrent.get_file_progress(h, self._info)
|
||||
for i, (file, size) in enumerate(zip(cw.names, sizes)):
|
||||
file = os.path.realpath(file.replace('\\\\?\\', ''))
|
||||
if file in cw.dones:
|
||||
continue
|
||||
if size[0] == size[1]:
|
||||
cw.dones.add(file)
|
||||
file = constants.compact(file).replace('\\', '/')
|
||||
files = file.split('/')
|
||||
file = ' / '.join(files[1:])
|
||||
msg = 'Completed: {}'.format(file)
|
||||
self.print_(msg)
|
||||
if i == 0:
|
||||
self._updateIcon()
|
||||
|
||||
cw.setPieces(torrent.pieces(h, self._info))
|
||||
seeding = False
|
||||
cw._torrent_s = s
|
||||
fast = len(cw.imgs) > TOO_MANY
|
||||
self.update_progress(h, fast)
|
||||
|
||||
filesize = s.total_done
|
||||
upload = s.total_upload
|
||||
if s.state_str in ('downloading', ):
|
||||
color = 'downloading'
|
||||
if s.state_str in ('downloading', 'seeding'):
|
||||
# init filesize
|
||||
if not self._filesize_init:
|
||||
self._filesize_prev = filesize
|
||||
self._filesize_init = True
|
||||
self.print_('init filesize: {}'.format(fs.size(filesize)))
|
||||
|
||||
|
||||
# download
|
||||
d_size = filesize - self._filesize_prev
|
||||
self._filesize_prev = filesize
|
||||
|
@ -231,22 +326,63 @@ class Downloader_torrent(Downloader):
|
|||
if self._info is not None:
|
||||
cw.pbar.setValue(s.progress * self._info.total_size())
|
||||
if s.state_str == 'queued':
|
||||
color = 'reading'
|
||||
title_ = 'Waiting... {}'.format(title)
|
||||
elif s.state_str == 'checking files':
|
||||
color = 'reading'
|
||||
title_ = 'Checking files... {}'.format(title)
|
||||
self._filesize_prev = filesize
|
||||
elif s.state_str == 'downloading':
|
||||
title_ = '{} (s: {}, p: {}, a:{:.3f})'.format(title, s.num_seeds, s.num_peers, s.distributed_copies)
|
||||
title_ = '{}'.format(title)
|
||||
cw.setFileSize(filesize)
|
||||
cw.setSpeed(self.size.speed)
|
||||
cw.setUploadSpeed(self.size_upload.speed)
|
||||
elif s.state_str == 'seeding':
|
||||
title_ = '{}'.format(title)
|
||||
cw.setFileSize(filesize)
|
||||
if not cw.seeding:
|
||||
return 'abort'
|
||||
seeding = True
|
||||
title_ = 'Seeding... {}'.format(title)
|
||||
cw.setSpeed(self.size_upload.speed)
|
||||
elif s.state_str == 'reading':
|
||||
color = 'reading'
|
||||
title_ = 'Reading... {}'.format(title)
|
||||
elif s.state_str == 'finished':
|
||||
return 'abort'
|
||||
else:
|
||||
title_ = '{}... {}'.format(s.state_str.capitalize(), title)
|
||||
cw.setTitle(title_, update_filter=False)
|
||||
cw.setColor(color)
|
||||
self._seeding = seeding
|
||||
else:
|
||||
self.print_('abort')
|
||||
if cw:
|
||||
cw._torrent_s = None
|
||||
return 'abort'
|
||||
|
||||
|
||||
@utils.actions('torrent')
|
||||
def actions(cw):
|
||||
if cw.type != 'torrent':
|
||||
return
|
||||
items = [item for item in cw.listWidget().selectedItems() if item.type == 'torrent']
|
||||
seeding = int(all(item._seeding for item in items)) * 2
|
||||
if not seeding:
|
||||
seeding = int(all(item._seeding is False for item in items))
|
||||
if not seeding:
|
||||
seeding = 0 if all(item._seeding is None for item in items) else None
|
||||
if seeding is None:
|
||||
mix_seeding = any(item._seeding for item in items)
|
||||
mix_no_seeding = any(item._seeding is False for item in items)
|
||||
mix_pref = any(item._seeding is None for item in items)
|
||||
else:
|
||||
mix_seeding = mix_no_seeding = mix_pref = False
|
||||
return [
|
||||
{'icon': 'list', 'text': '파일 목록', 'clicked': cw.showFiles},
|
||||
{'icon': 'peer', 'text': 'Peers', 'clicked': cw.showPeers},
|
||||
{'icon': 'tracker', 'text': '트래커 수정', 'clicked': cw.editTrackers},
|
||||
{'text':'-'},
|
||||
{'text': '시딩', 'clicked': lambda:cw.setSeedings(True), 'checkable': True, 'checked': seeding==2, 'group': 'seeding', 'mixed': mix_seeding},
|
||||
{'text': '시딩 하지 않음', 'clicked': lambda:cw.setSeedings(False), 'checkable': True, 'checked': seeding==1, 'group': 'seeding', 'mixed': mix_no_seeding},
|
||||
{'text': '설정을 따름', 'clicked': lambda:cw.setSeedings(None), 'checkable': True, 'checked': seeding==0, 'group': 'seeding', 'mixed': mix_pref},
|
||||
]
|
||||
|
|
|
@ -8,37 +8,40 @@ from ratelimit import limits, sleep_and_retry
|
|||
from error_printer import print_error
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, id, p=0, cw=None):
|
||||
def __init__(self, url, id, referer, p, cw=None):
|
||||
self._url = url
|
||||
self.id_ = id
|
||||
self.p = p
|
||||
self.cw = cw
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.url = LazyUrl(referer, self.get, self)
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(4, 1)
|
||||
def get(self, _):
|
||||
print_ = get_print(self.cw)
|
||||
url = self._url
|
||||
ext = get_ext(url)
|
||||
if ext.lower() == '.gif':
|
||||
if ext.lower()[1:] not in ['jpg', 'png', 'mp4']: #4645
|
||||
print_('get_ext: {}, {}'.format(self.id_, url))
|
||||
try:
|
||||
ext = downloader.get_ext(url)
|
||||
ext = downloader.get_ext(url, referer=_)
|
||||
except Exception as e: #3235
|
||||
print_('Err: {}, {}\n'.format(self.id_, url)+print_error(e)[0])
|
||||
self.filename = '{}_p{}{}'.format(self.id_, self.p, ext)
|
||||
return url
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_tumblr(Downloader):
|
||||
type = 'tumblr'
|
||||
URLS = ['tumblr.com']
|
||||
MAX_CORE = 4
|
||||
|
||||
def init(self):
|
||||
if u'tumblr.com/post/' in self.url:
|
||||
raise errors.Invalid(tr_(u'개별 다운로드는 지원하지 않습니다: {}').format(self.url))
|
||||
raise errors.Invalid(tr_(u'개별 다운로드는 지원하지 않습니다: {}').format(self.url))
|
||||
self.session = Session()
|
||||
|
||||
@classmethod
|
||||
|
@ -49,7 +52,7 @@ class Downloader_tumblr(Downloader):
|
|||
def read(self):
|
||||
username = get_id(self.url)
|
||||
name = get_name(username, self.session)
|
||||
|
||||
|
||||
for img in get_imgs(username, self.session, cw=self.cw):
|
||||
self.urls.append(img.url)
|
||||
|
||||
|
@ -57,7 +60,7 @@ class Downloader_tumblr(Downloader):
|
|||
|
||||
|
||||
|
||||
class TumblrAPI(object):
|
||||
class TumblrAPI:
|
||||
_url_base = 'https://www.tumblr.com/api'
|
||||
_hdr = {
|
||||
'referer': 'https://www.tumblr.com',
|
||||
|
@ -113,13 +116,14 @@ class TumblrAPI(object):
|
|||
if self.cw and not self.cw.alive:
|
||||
break
|
||||
data = self.call(path, qs, default_qs=default_qs)
|
||||
for post in data['posts']:
|
||||
for post in (post for post in data['posts'] if post['object_type'] != 'backfill_ad'):
|
||||
id_ = post['id']
|
||||
if id_ in ids:
|
||||
self.print_('duplicate: {}'.format(id_))
|
||||
continue
|
||||
ids.add(id_)
|
||||
yield Post(post, self.cw)
|
||||
url = 'https://{}.tumblr.com/post/{}'.format(username, id_)
|
||||
yield Post(post, url, self.cw)
|
||||
try:
|
||||
links = data.get('links') or data['_links']
|
||||
path_next = links['next']['href']
|
||||
|
@ -132,16 +136,16 @@ class TumblrAPI(object):
|
|||
break
|
||||
|
||||
|
||||
class Post(object):
|
||||
class Post:
|
||||
|
||||
def __init__(self, data, cw=None):
|
||||
def __init__(self, data, url, cw=None):
|
||||
id_ = data['id']
|
||||
self.imgs = []
|
||||
|
||||
|
||||
cs = data['content']
|
||||
for trail in data['trail']:
|
||||
cs += trail['content']
|
||||
|
||||
|
||||
for c in cs:
|
||||
if c['type'] in ['image', 'video']:
|
||||
media = c.get('media')
|
||||
|
@ -150,12 +154,12 @@ class Post(object):
|
|||
if isinstance(media, list):
|
||||
media = media[0]
|
||||
img = media['url']
|
||||
self.imgs.append(Image(img, id_, len(self.imgs), cw))
|
||||
self.imgs.append(Image(img, id_, url, len(self.imgs), cw))
|
||||
elif c['type'] in ['text', 'link', 'audio']:
|
||||
continue
|
||||
else:
|
||||
raise NotImplementedError(id_, c)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_name(username, session):
|
||||
|
@ -179,7 +183,7 @@ def get_imgs(username, session, cw=None):
|
|||
cw.setTitle(s)
|
||||
else:
|
||||
print(s)
|
||||
|
||||
|
||||
if len(imgs) > max_pid:
|
||||
break
|
||||
|
||||
|
@ -201,4 +205,3 @@ def get_id(url):
|
|||
if url == 'www':
|
||||
raise Exception('no id')
|
||||
return url
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@ import errors
|
|||
import utils
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_twitch(Downloader):
|
||||
type = 'twitch'
|
||||
URLS = ['twitch.tv']
|
||||
single = True
|
||||
ACCEPT_COOKIES = [r'.*(twitch|ttvnw|jtvnw).*']
|
||||
|
||||
def init(self):
|
||||
url = self.url
|
||||
|
@ -25,6 +26,7 @@ class Downloader_twitch(Downloader):
|
|||
else:
|
||||
url = 'https://www.twitch.tv/videos/{}'.format(url)
|
||||
self.url = url
|
||||
self.session = Session()
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -35,7 +37,7 @@ class Downloader_twitch(Downloader):
|
|||
def read(self):
|
||||
if '/directory/' in self.url.lower():
|
||||
raise errors.Invalid('[twitch] Directory is unsupported: {}'.format(self.url))
|
||||
|
||||
|
||||
if self.url.count('/') == 3:
|
||||
if 'www.twitch.tv' in self.url or '//twitch.tv' in self.url:
|
||||
filter = 'live'
|
||||
|
@ -47,7 +49,7 @@ class Downloader_twitch(Downloader):
|
|||
filter = None
|
||||
else:
|
||||
filter = None
|
||||
|
||||
|
||||
if filter is None:
|
||||
video = Video(self.url, self.cw)
|
||||
video.url()
|
||||
|
@ -64,8 +66,10 @@ class Downloader_twitch(Downloader):
|
|||
else:
|
||||
raise NotImplementedError(filter)
|
||||
|
||||
self.artist = video.artist
|
||||
|
||||
self.setIcon(video.thumb)
|
||||
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_videos(url, cw=None):
|
||||
|
@ -102,6 +106,7 @@ def alter(seg):
|
|||
|
||||
|
||||
def extract_info(url, cw=None):
|
||||
print_ = get_print(cw)
|
||||
ydl = ytdl.YoutubeDL(cw=cw)
|
||||
try:
|
||||
info = ydl.extract_info(url)
|
||||
|
@ -116,9 +121,9 @@ def extract_info(url, cw=None):
|
|||
raise errors.LoginRequired()
|
||||
raise
|
||||
return info
|
||||
|
||||
|
||||
class Video(object):
|
||||
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
def __init__(self, url, cw, live=False):
|
||||
|
@ -132,11 +137,12 @@ class Video(object):
|
|||
if self._url:
|
||||
return self._url
|
||||
info = extract_info(url, self.cw)
|
||||
self.artist = info.get('creator') or info.get('uploader') #4953, #5031
|
||||
|
||||
def print_video(video):
|
||||
#print_(video)#
|
||||
print_('{}[{}] [{}] [{}] {}'.format('LIVE ', video['format_id'], video.get('height'), video.get('tbr'), video['url']))
|
||||
|
||||
|
||||
videos = [video for video in info['formats'] if video.get('height')]
|
||||
|
||||
videos = sorted(videos, key=lambda video:(video.get('height', 0), video.get('tbr', 0)), reverse=True)
|
||||
|
@ -151,9 +157,9 @@ class Video(object):
|
|||
else:
|
||||
video_best = videos[-1]
|
||||
print_video(video)
|
||||
|
||||
|
||||
video = video_best['url']
|
||||
|
||||
|
||||
ext = get_ext(video)
|
||||
self.title = info['title']
|
||||
id = info['display_id']
|
||||
|
@ -165,7 +171,7 @@ class Video(object):
|
|||
if ext.lower() == '.m3u8':
|
||||
video = M3u8_stream(video, n_thread=4, alter=alter)
|
||||
ext = '.mp4'
|
||||
self.filename = format_filename(self.title, id, ext)
|
||||
self.filename = format_filename(self.title, id, ext, artist=self.artist)
|
||||
self.url_thumb = info['thumbnail']
|
||||
self.thumb = BytesIO()
|
||||
downloader.download(self.url_thumb, buffer=self.thumb)
|
||||
|
|
|
@ -1,655 +0,0 @@
|
|||
#coding:utf8
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
from utils import Downloader, Session, LazyUrl, get_ext, try_n, Soup, get_print, update_url_query, urljoin, try_n, get_max_range, get_outdir, clean_title, lock, check_alive, check_alive_iter, SkipCounter
|
||||
from timee import time, sleep
|
||||
import json
|
||||
import ree as re
|
||||
from datetime import datetime, timedelta
|
||||
from translator import tr_
|
||||
from error_printer import print_error
|
||||
import os
|
||||
import ytdl
|
||||
import ffmpeg
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
import options
|
||||
AUTH = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
|
||||
UA = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
|
||||
#UA = downloader.hdr['User-Agent']#
|
||||
RETRY_PAGINATION = 21
|
||||
RETRY_MORE = 3
|
||||
RETRY_MORE_IMGS = 16
|
||||
TIMEOUT_GUEST_TOKEN = 3600
|
||||
CACHE_GUEST_TOKEN = None
|
||||
|
||||
|
||||
def change_ua(session):
|
||||
session.headers['User-Agent'] = downloader.ua.random
|
||||
|
||||
|
||||
def get_session():
|
||||
session = Session()
|
||||
session.headers['User-Agent'] = UA
|
||||
session.cookies['app_shell_visited'] = '1'
|
||||
return session
|
||||
|
||||
|
||||
def suitable(url):
|
||||
if 'twitter.com' not in url.lower():
|
||||
return False
|
||||
if '/i/broadcasts/' in url: # Etc
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_twitter(Downloader):
|
||||
type = 'twitter'
|
||||
URLS = [suitable]
|
||||
MAX_CORE = 8
|
||||
#MAX_SPEED = 4.0
|
||||
|
||||
def init(self):
|
||||
self.session = get_session()
|
||||
#self.url = fix_url(self.url)
|
||||
self.artist, self.username = get_artist_username(self.url, self.session, self.cw)
|
||||
if self.username == 'home':
|
||||
raise Exception('No username: home')
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
username = re.find(r'twitter.com/([^/]+)/media', url)
|
||||
if username:
|
||||
url = username
|
||||
if 'twitter.com/' in url and not re.find('^https?://', url): #3165; legacy
|
||||
url = 'https://' + url
|
||||
if not re.find('^https?://', url):
|
||||
url = 'https://twitter.com/{}'.format(url.lstrip('@'))
|
||||
return url.split('?')[0].split('#')[0].strip('/')
|
||||
|
||||
@classmethod
|
||||
def key_id(cls, url):
|
||||
return url.lower()
|
||||
|
||||
def read(self):
|
||||
ui_setting = self.ui_setting
|
||||
|
||||
title = '{} (@{})'.format(clean_title(self.artist), self.username)
|
||||
|
||||
types = {'img', 'video'}
|
||||
if ui_setting.exFile.isChecked():
|
||||
if ui_setting.exFileImg.isChecked():
|
||||
types.remove('img')
|
||||
if ui_setting.exFileVideo.isChecked():
|
||||
types.remove('video')
|
||||
|
||||
if '/status/' in self.url:
|
||||
self.print_('single tweet')
|
||||
imgs = get_imgs_single(self.url, self.session, types, cw=self.cw)
|
||||
else:
|
||||
self.print_('multiple tweets')
|
||||
imgs = get_imgs(self.username, self.session, title, types, cw=self.cw)
|
||||
for img in imgs:
|
||||
if isinstance(img, Image):
|
||||
self.urls.append(img.url)
|
||||
else:
|
||||
self.urls.append(img)
|
||||
|
||||
self.title = title
|
||||
#self.session = None#
|
||||
|
||||
|
||||
@lock
|
||||
def _guest_token(session, headers, cache=True, cw=None):
|
||||
global CACHE_GUEST_TOKEN
|
||||
print_ = get_print(cw)
|
||||
token = None
|
||||
if cache:
|
||||
if CACHE_GUEST_TOKEN and time() - CACHE_GUEST_TOKEN[1] < TIMEOUT_GUEST_TOKEN:
|
||||
token = CACHE_GUEST_TOKEN[0]
|
||||
if token is None:
|
||||
print('!!! get guest_token')
|
||||
name = 'x-guest-token'
|
||||
if name in headers:
|
||||
del headers[name]
|
||||
r = session.post('https://api.twitter.com/1.1/guest/activate.json', headers=headers)
|
||||
data = json.loads(r.text)
|
||||
token = data['guest_token']
|
||||
print_('token type: {}'.format(type(token)))
|
||||
if isinstance(token, int): #3525
|
||||
token = str(token)
|
||||
CACHE_GUEST_TOKEN = token, time()
|
||||
return token
|
||||
|
||||
|
||||
class TwitterAPI(object):
|
||||
def __init__(self, session, cw=None, cache_guest_token=True):
|
||||
self.session = session
|
||||
self.cw = cw
|
||||
hdr = {
|
||||
"authorization": AUTH,
|
||||
"x-twitter-client-language": "en",
|
||||
"x-twitter-active-user": "yes",
|
||||
"Origin": "https://twitter.com",
|
||||
}
|
||||
session.headers.update(hdr)
|
||||
|
||||
auth_token = session.cookies.get("auth_token", domain=".twitter.com")
|
||||
if auth_token:
|
||||
session.headers["x-twitter-auth-type"] = "OAuth2Session"
|
||||
print('auth_token:', auth_token)
|
||||
else:
|
||||
# guest token
|
||||
guest_token = _guest_token(session, session.headers, cache=cache_guest_token, cw=cw)
|
||||
session.headers["x-guest-token"] = guest_token
|
||||
session.cookies.set("gt", guest_token, domain=".twitter.com")
|
||||
print('guest_token:', guest_token)
|
||||
|
||||
self.params = {
|
||||
"include_profile_interstitial_type": "1",
|
||||
"include_blocking": "1",
|
||||
"include_blocked_by": "1",
|
||||
"include_followed_by": "1",
|
||||
"include_want_retweets": "1",
|
||||
"include_mute_edge": "1",
|
||||
"include_can_dm": "1",
|
||||
"include_can_media_tag": "1",
|
||||
"skip_status": "1",
|
||||
"cards_platform": "Web-12",
|
||||
"include_cards": "1",
|
||||
"include_composer_source": "true",
|
||||
"include_ext_alt_text": "true",
|
||||
"include_reply_count": "1",
|
||||
"tweet_mode": "extended",
|
||||
"include_entities": "true",
|
||||
"include_user_entities": "true",
|
||||
"include_ext_media_color": "true",
|
||||
"include_ext_media_availability": "true",
|
||||
"send_error_codes": "true",
|
||||
"simple_quoted_tweet": "true",
|
||||
# "count": "20",
|
||||
"count": "100",
|
||||
#"cursor": None,
|
||||
"ext": "mediaStats%2ChighlightedLabel%2CcameraMoment",
|
||||
"include_quote_count": "true",
|
||||
}
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(1, 2)
|
||||
def _call(self, url_api, referer='https://twitter.com', params=None):
|
||||
url_api = urljoin('https://api.twitter.com', url_api)
|
||||
if params:
|
||||
url_api = update_url_query(url_api, params)
|
||||
#print('call:', url_api)
|
||||
r = self.session.get(url_api, headers={'Referer': referer})
|
||||
csrf = r.cookies.get('ct0')
|
||||
if csrf:
|
||||
self.session.headers['x-csrf-token'] = csrf
|
||||
data = json.loads(r.text)
|
||||
return data
|
||||
|
||||
## @sleep_and_retry
|
||||
## @limits(1, 36)
|
||||
def search(self, query, f='live'):
|
||||
endpoint = "2/search/adaptive.json"
|
||||
params = self.params.copy()
|
||||
params["q"] = query
|
||||
params["tweet_search_mode"] = f
|
||||
params["query_source"] = "typed_query"
|
||||
params["pc"] = "1"
|
||||
params["spelling_corrections"] = "1"
|
||||
return self._pagination(endpoint, params, "sq-I-t-", "sq-cursor-bottom")
|
||||
|
||||
def user_by_screen_name(self, screen_name):
|
||||
url_api = "graphql/-xfUfZsnR_zqjFd-IfrN5A/UserByScreenName"
|
||||
params = {
|
||||
"variables": '{"screen_name":"' + screen_name + '"'
|
||||
',"withHighlightedLabel":true}'
|
||||
}
|
||||
return self._call(url_api, params=params)['data']['user']
|
||||
|
||||
def tweet(self, id, referer):
|
||||
url_api = '/2/timeline/conversation/{}.json'.format(id)
|
||||
url_api += '?tweet_mode=extended'#
|
||||
return self._call(url_api, referer)
|
||||
|
||||
def timeline_media(self, screen_name):
|
||||
user = self.user_by_screen_name(screen_name)
|
||||
url_api = "2/timeline/media/{}.json".format(user["rest_id"])
|
||||
url_api += '?tweet_mode=extended'#
|
||||
return self._pagination(url_api)
|
||||
|
||||
def print_(self, s):
|
||||
get_print(self.cw)(s)
|
||||
|
||||
def _pagination(self, url_api, params=None, entry_tweet="tweet-", entry_cursor="cursor-bottom-"):
|
||||
if params is None:
|
||||
params = self.params.copy()
|
||||
|
||||
while True:
|
||||
cursor = None
|
||||
if params.get("cursor"):
|
||||
self.print_('cursor: {}'.format(params.get("cursor")))
|
||||
|
||||
# 2303
|
||||
n_try = RETRY_PAGINATION
|
||||
for try_ in range(n_try):
|
||||
try:
|
||||
data = self._call(url_api, params=params)
|
||||
if 'globalObjects' not in data:
|
||||
try_ = n_try
|
||||
raise Exception(str(data['errors']))
|
||||
tweets = data["globalObjects"]["tweets"]
|
||||
break
|
||||
except Exception as e:
|
||||
e_ = e
|
||||
e_msg = print_error(e)[0]
|
||||
if try_ < n_try - 1 :
|
||||
self.print_('retry... _pagination ({})\n{}'.format(try_+1, e_msg))
|
||||
sleep(30, self.cw)
|
||||
else:
|
||||
break#raise e_ #3392
|
||||
|
||||
users = data["globalObjects"]["users"]
|
||||
for instr in data["timeline"]["instructions"]:
|
||||
for entry in instr.get("addEntries", {}).get("entries", []):
|
||||
if entry["entryId"].startswith(entry_tweet):
|
||||
tid = entry["content"]["item"]["content"]["tweet"]["id"]
|
||||
if tid not in tweets:
|
||||
self.print_("Skipping unavailable Tweet {}".format(tid))
|
||||
continue
|
||||
#print('tid:', tid)#
|
||||
tweet = tweets[tid]
|
||||
tweet["user"] = users[tweet["user_id_str"]]
|
||||
|
||||
yield tweet
|
||||
|
||||
elif entry["entryId"].startswith(entry_cursor):
|
||||
cursor = entry["content"]["operation"]["cursor"]["value"]
|
||||
|
||||
if not cursor or params.get('cursor') == cursor:
|
||||
print('same cursor')
|
||||
return
|
||||
params["cursor"] = cursor
|
||||
if params.get("cursor") is None: # nothing
|
||||
self.print_('no cursor')
|
||||
break
|
||||
|
||||
|
||||
def get_imgs_single(url, session, types, format='[%y-%m-%d] id_ppage', cw=None):
|
||||
print_ = get_print(cw)
|
||||
id = re.find('/status/([0-9]+)', url)
|
||||
if id is None:
|
||||
raise Exception('no id')
|
||||
|
||||
data = TwitterAPI(session, cw).tweet(id, url)
|
||||
|
||||
tweets = data["globalObjects"]["tweets"]
|
||||
id = tweets[id].get('retweeted_status_id_str') or id
|
||||
tweet = tweets[id]
|
||||
|
||||
time = get_time(tweet)
|
||||
|
||||
img = Image(url, url, id, time, 0, format, cw, True, try_n=1, n_thread=4)
|
||||
try:
|
||||
img.url()
|
||||
return [img]
|
||||
except Exception as e:
|
||||
print(print_error(e)[-1])
|
||||
return get_imgs_from_tweet(tweet, session, types, format, cw)
|
||||
|
||||
|
||||
def get_imgs(username, session, title, types, n=0, format='[%y-%m-%d] id_ppage', cw=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
# Range
|
||||
n = max(n, get_max_range(cw))
|
||||
|
||||
# 2303
|
||||
ids = set()
|
||||
names = dict()
|
||||
dir_ = os.path.join(get_outdir('twitter'), title)
|
||||
if os.path.isdir(dir_) and cw:
|
||||
for name in cw.names_old:
|
||||
name = os.path.basename(name)
|
||||
id_ = re.find('([0-9]+)_p', name)
|
||||
if id_ is None:
|
||||
continue
|
||||
if get_ext(name).lower() == '.mp4':
|
||||
type_ = 'video'
|
||||
else:
|
||||
type_ = 'img'
|
||||
if type_ not in types:
|
||||
continue
|
||||
id_ = int(id_)
|
||||
ids.add(id_)
|
||||
if id_ in names:
|
||||
names[id_].append(name)
|
||||
else:
|
||||
names[id_] = [name]
|
||||
ids_sure = sorted(ids)[:-100]
|
||||
max_id = max(ids_sure) if ids_sure else 0 #3201
|
||||
|
||||
# 2303
|
||||
imgs_old = []
|
||||
for id_ in sorted(ids, reverse=True):
|
||||
for p, file in enumerate(sorted(os.path.join(dir_, name) for name in names[id_])):
|
||||
img = Image(file, '', id_, 0, p, format, cw, False)
|
||||
img.url = LazyUrl_twitter(None, lambda _: file, img)
|
||||
img.filename = os.path.basename(file)
|
||||
imgs_old.append(img)
|
||||
|
||||
imgs_new = []
|
||||
enough = False
|
||||
c_old = 0
|
||||
counter = SkipCounter(1)
|
||||
msg = None
|
||||
for tweet in check_alive_iter(cw, TwitterAPI(session, cw).timeline_media(username)):
|
||||
id_ = int(tweet['id_str'])
|
||||
if id_ < max_id:
|
||||
print_('enough')
|
||||
enough = True
|
||||
break
|
||||
|
||||
if id_ in ids:
|
||||
print_('duplicate: {}'.format(id_))
|
||||
c_old += 1
|
||||
continue
|
||||
ids.add(id_)
|
||||
|
||||
imgs_new += get_imgs_from_tweet(tweet, session, types, format, cw)
|
||||
|
||||
if len(imgs_new) + c_old >= n: #3201
|
||||
break
|
||||
|
||||
if counter.next():
|
||||
msg = '{} {} - {}'.format(tr_('읽는 중...'), title, len(imgs_new))
|
||||
if cw:
|
||||
cw.setTitle(msg)
|
||||
else:
|
||||
print(msg)
|
||||
if msg:
|
||||
if cw:
|
||||
cw.setTitle(msg)
|
||||
else:
|
||||
print(msg)
|
||||
|
||||
if not enough and not imgs_new and c_old == 0:
|
||||
raise Exception('no imgs')
|
||||
|
||||
imgs = sorted(imgs_old + imgs_new, key=lambda img: img.id, reverse=True)
|
||||
|
||||
if len(imgs) < n:
|
||||
imgs = get_imgs_more(username, session, title, types, n, format, cw, imgs=imgs)
|
||||
|
||||
return imgs[:n]
|
||||
|
||||
|
||||
def get_imgs_more(username, session, title, types, n=None, format='[%y-%m-%d] id_ppage', cw=None, mode='media', method='tab', imgs=None):
|
||||
print_ = get_print(cw)
|
||||
imgs = imgs or []
|
||||
print_('imgs: {}, types: {}'.format(len(imgs), ', '.join(types)))
|
||||
|
||||
artist, username = get_artist_username(username, session, cw)#
|
||||
|
||||
# Range
|
||||
n = max(n or 0, get_max_range(cw))
|
||||
|
||||
ids_set = set(img.id for img in imgs)
|
||||
|
||||
count_no_tweets = 0
|
||||
count_no_imgs = 0
|
||||
|
||||
while check_alive(cw) or len(imgs) < n:
|
||||
if options.get('experimental') or count_no_tweets: #2687, #3392
|
||||
filter_ = ''
|
||||
else:
|
||||
filter_ = ' filter:media'
|
||||
cache_guest_token = bool(count_no_tweets)
|
||||
|
||||
if ids_set:
|
||||
max_id = min(ids_set) - 1
|
||||
q = 'from:{} max_id:{} exclude:retweets{} -filter:periscope'.format(username, max_id, filter_)
|
||||
else:
|
||||
q = 'from:{} exclude:retweets{} -filter:periscope'.format(username, filter_)
|
||||
print(q)
|
||||
|
||||
tweets = []
|
||||
for tweet in list(TwitterAPI(session, cw, cache_guest_token).search(q)):
|
||||
id = int(tweet['id'])
|
||||
if id in ids_set:
|
||||
print_('duplicate: {}'.format(id))
|
||||
continue
|
||||
ids_set.add(id)
|
||||
tweets.append(tweet)
|
||||
|
||||
if tweets:
|
||||
exists_more_imgs = False
|
||||
for tweet in tweets:
|
||||
imgs_tweet = get_imgs_from_tweet(tweet, session, types, format, cw)
|
||||
if imgs_tweet:
|
||||
imgs += imgs_tweet
|
||||
exists_more_imgs = True
|
||||
if exists_more_imgs:
|
||||
count_no_imgs = 0
|
||||
else:
|
||||
count_no_imgs += 1
|
||||
if count_no_imgs >= RETRY_MORE_IMGS: #4130
|
||||
break
|
||||
count_no_tweets = 0
|
||||
else:
|
||||
count_no_tweets += 1
|
||||
change_ua(session)
|
||||
if count_no_tweets >= RETRY_MORE:
|
||||
break
|
||||
print_('retry... {}'.format(count_no_tweets))
|
||||
continue
|
||||
|
||||
msg = '{} {} (@{}) - {}'.format(tr_('읽는 중...'), artist, username, len(imgs))
|
||||
if cw:
|
||||
cw.setTitle(msg)
|
||||
else:
|
||||
print(msg)
|
||||
|
||||
return imgs
|
||||
|
||||
|
||||
def get_time(tweet):
|
||||
ds = tweet['created_at']
|
||||
z = re.find(r'[\+\-][0-9]+', ds)
|
||||
ds = re.sub(r'[\+\-][0-9]+', '', ds)
|
||||
time = datetime.strptime(ds.replace(' ', ' '), '%a %b %d %H:%M:%S %Y')
|
||||
time = (time-datetime(1970,1,1)).total_seconds()
|
||||
if z:
|
||||
time -= 3600*int(z)
|
||||
return time
|
||||
|
||||
|
||||
def get_imgs_from_tweet(tweet, session, types, format, cw=None):
|
||||
print_ = get_print(cw)
|
||||
id = tweet['id_str']
|
||||
|
||||
if 'extended_entities' not in tweet:
|
||||
tweet['extended_entities'] = {'media': []}
|
||||
|
||||
media = tweet['extended_entities']['media']
|
||||
|
||||
for url_ in tweet['entities'].get('urls', []):
|
||||
url_ = url_['expanded_url']
|
||||
if '//twitpic.com/' in url_:
|
||||
print_('twitpic: {}'.format(url_))
|
||||
try:
|
||||
url_ = get_twitpic(url_, session)
|
||||
media.append({'type': 'photo', 'media_url': url_, 'expanded_url': 'https://twitter.com'})
|
||||
except Exception as e:
|
||||
print_('Invalid twitpic')
|
||||
print_(print_error(e)[-1])
|
||||
|
||||
time = get_time(tweet)
|
||||
|
||||
imgs = []
|
||||
for m in media:
|
||||
type_ = m['type']
|
||||
if type_ == 'photo':
|
||||
type_ = 'img'
|
||||
elif type_ == 'animated_gif':
|
||||
type_ = 'video'
|
||||
if type_ not in types:
|
||||
continue
|
||||
if type_ == 'video':
|
||||
url_media = sorted(m['video_info']['variants'], key=lambda x: x.get('bitrate', 0))[-1]['url']
|
||||
elif type_ == 'img':
|
||||
url_media = m['media_url']
|
||||
if ':' not in os.path.basename(url_media):
|
||||
url_media += ':orig'
|
||||
else:
|
||||
raise NotImplementedError('unknown type')
|
||||
url = m['expanded_url']
|
||||
img = Image(url_media, url, id, time, len(imgs), format, cw, type_=='video')
|
||||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
|
||||
|
||||
@try_n(4)
|
||||
def get_twitpic(url, session):
|
||||
html = downloader.read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
url = soup.find('img')['src']
|
||||
return url
|
||||
|
||||
|
||||
@LazyUrl.register
|
||||
class LazyUrl_twitter(LazyUrl):
|
||||
type = 'twitter'
|
||||
|
||||
def dump(self):
|
||||
return {
|
||||
'url': self.image._url,
|
||||
'referer': self._url,
|
||||
'id': self.image.id,
|
||||
'time': self.image.time,
|
||||
'p': self.image.p,
|
||||
'format': self.image.format,
|
||||
'cw': LazyUrl.CW,
|
||||
'isVideo': self.image.isVideo,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def load(cls, data):
|
||||
img = Image(data['url'], data['referer'], data['id'], data['time'], data['p'], data['format'], data['cw'], data['isVideo'])
|
||||
return img.url
|
||||
|
||||
|
||||
class Url_alter(object):
|
||||
count = 0
|
||||
|
||||
def __init__(self, url):
|
||||
urls = [url]
|
||||
if ':' in os.path.basename(url):
|
||||
urls.append(':'.join(url.split(':')[:-1]))
|
||||
base, _, fmt = url.rpartition('.')
|
||||
base += '?format=' + fmt.split(':')[0] + '&name='
|
||||
for name in ['orig', 'large']:
|
||||
urls.append(base + name)
|
||||
self.urls = urls
|
||||
|
||||
def __call__(self):
|
||||
self.count += 1
|
||||
return self.urls[self.count%len(self.urls)]
|
||||
|
||||
|
||||
class Image(object):
|
||||
_url_cache = None
|
||||
|
||||
def __init__(self, url, referer, id, time, p, format, cw=None, isVideo=False, try_n=4, n_thread=1):
|
||||
self._url = url
|
||||
self.referer = referer
|
||||
self.id = int(id)
|
||||
self.time = time
|
||||
self.p = p
|
||||
self.n_thread = n_thread
|
||||
if isVideo:
|
||||
url_alter = self.get #4185
|
||||
else:
|
||||
url_alter = Url_alter(url)
|
||||
if isVideo and get_ext(url).lower() not in ['.mp4', '.m3u8']:
|
||||
get = self.get
|
||||
else:
|
||||
get = lambda _: self._url
|
||||
self.url = LazyUrl_twitter(referer, get, self, url_alter)
|
||||
self.format = format
|
||||
self.cw = cw
|
||||
self.isVideo = isVideo
|
||||
self.try_n = try_n
|
||||
## time_ms = (int(id) >> 22) + 1288834974657
|
||||
## time = time_ms / 1000 # GMT+0
|
||||
date = datetime.fromtimestamp(float(time))
|
||||
timeStamp = date.strftime(format).replace(':', '\uff1a') # local time
|
||||
ext = '.mp4' if isVideo else get_ext(url)
|
||||
self.filename = timeStamp.replace('id', str(id)).replace('page', str(p)) + ext
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(1, 5)
|
||||
def get(self, _=None):
|
||||
if self._url_cache:
|
||||
return self._url_cache
|
||||
print_ = get_print(self.cw)
|
||||
for try_ in range(self.try_n):
|
||||
try:
|
||||
d = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = d.extract_info(self.referer)
|
||||
|
||||
fs = info['formats']
|
||||
for f in fs:
|
||||
print_('{} {} - {}'.format(f.get('height'), f['protocol'], f['url']))
|
||||
def key(f):
|
||||
h = f.get('height', 0)
|
||||
if not f['protocol'].startswith('http'):
|
||||
h -= .1
|
||||
return h
|
||||
for f in sorted(fs, key=key, reverse=True):
|
||||
if downloader.ok_url(f['url'], self.referer): #4185
|
||||
break
|
||||
else:
|
||||
print_('invalid video: {}'.format(f['url']))
|
||||
else:
|
||||
raise Exception('no valid videos')
|
||||
url = f['url']
|
||||
ext = get_ext(url)
|
||||
self.ext = ext
|
||||
print_('get_video: {} {}'.format(url, ext))
|
||||
if ext.lower() == '.m3u8':
|
||||
url = ffmpeg.Stream(url)
|
||||
url._live = False
|
||||
self._url_cache = url
|
||||
return url
|
||||
except Exception as e:
|
||||
e_ = e
|
||||
msg = print_error(e)[0]
|
||||
print_('\nTwitter video Error:\n{}'.format(msg))
|
||||
if try_ < self.try_n - 1:
|
||||
sleep(10, self.cw)
|
||||
else:
|
||||
raise e_
|
||||
|
||||
|
||||
@try_n(4)
|
||||
def get_artist_username(url, session, cw=None):
|
||||
if 'twitter.' not in url:
|
||||
username = url.strip('@')
|
||||
else:
|
||||
id = re.find('/status/([0-9]+)', url)
|
||||
if id:
|
||||
tweet = TwitterAPI(session, cw).tweet(id, url)
|
||||
user_id = tweet['globalObjects']['tweets'][id]['user_id_str']
|
||||
username = tweet['globalObjects']['users'][user_id]['screen_name']
|
||||
print('username fixed:', username)
|
||||
else:
|
||||
username = re.find('twitter.[^/]+/([^/?]+)', url)
|
||||
if not username:
|
||||
raise Exception('no username')
|
||||
data = TwitterAPI(session, cw).user_by_screen_name(username)
|
||||
artist = data['legacy']['name']
|
||||
username = data['legacy']['screen_name']
|
||||
return artist, username
|
|
@ -1,11 +1,12 @@
|
|||
#coding:utf8
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
from utils import Soup, get_ext, LazyUrl, Downloader, try_n, clean_title, get_print
|
||||
from utils import get_ext, LazyUrl, Downloader, try_n, clean_title, get_print
|
||||
import ree as re
|
||||
from translator import tr_
|
||||
from timee import sleep
|
||||
import errors
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
import clf2
|
||||
|
||||
|
||||
def setPage(url, p):
|
||||
|
@ -20,28 +21,39 @@ def getPage(url):
|
|||
return int(p or 1)
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, referer, p):
|
||||
self.url = LazyUrl(referer, lambda x: url, self)
|
||||
self._url = url
|
||||
self.url = LazyUrl(referer, self.get, self)
|
||||
ext = get_ext(url)
|
||||
self.filename = '{:04}{}'.format(p, ext)
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(4, 1)
|
||||
def get(self, _):
|
||||
return self._url
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_v2ph(Downloader):
|
||||
type = 'v2ph'
|
||||
URLS = ['v2ph.com/album/']
|
||||
MAX_CORE = 4
|
||||
MAX_PARALLEL = 1
|
||||
display_name = 'V2PH'
|
||||
|
||||
ACCEPT_COOKIES = [r'(.*\.)?v2ph\.com']
|
||||
|
||||
def init(self):
|
||||
self.session = clf2.solve(self.url)['session']
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
return url.split('?')[0]
|
||||
|
||||
def read(self):
|
||||
info = get_info(self.url)
|
||||
|
||||
for img in get_imgs(self.url, info['title'], self.cw):
|
||||
info = get_info(self.url, self.session)
|
||||
|
||||
for img in get_imgs(self.url, self.session, info['title'], self.cw):
|
||||
self.urls.append(img.url)
|
||||
|
||||
self.title = clean_title(info['title'])
|
||||
|
@ -49,31 +61,28 @@ class Downloader_v2ph(Downloader):
|
|||
|
||||
|
||||
@try_n(2)
|
||||
def get_info(url):
|
||||
html = downloader.read_html(url)
|
||||
soup = Soup(html)
|
||||
def get_info(url, session):
|
||||
soup = read_soup(url, session)
|
||||
info = {}
|
||||
info['title'] = soup.find('h1').text.strip()
|
||||
return info
|
||||
|
||||
|
||||
def get_imgs(url, title, cw=None):
|
||||
@try_n(4)
|
||||
@sleep_and_retry
|
||||
@limits(1, 5)
|
||||
def read_soup(url, session):
|
||||
return downloader.read_soup(url, session=session)
|
||||
|
||||
|
||||
def get_imgs(url, session, title, cw=None):
|
||||
print_ = get_print(cw)
|
||||
imgs = []
|
||||
|
||||
for p in range(1, 1001):
|
||||
url = setPage(url, p)
|
||||
print_(url)
|
||||
for try_ in range(4):
|
||||
try:
|
||||
html = downloader.read_html(url, user_agent=downloader.hdr['User-Agent'])
|
||||
#sleep(1)
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
else:
|
||||
raise
|
||||
soup = Soup(html)
|
||||
soup = read_soup(url, session)
|
||||
|
||||
view = soup.find('div', class_='photos-list')
|
||||
if view is None:
|
||||
|
@ -85,7 +94,7 @@ def get_imgs(url, title, cw=None):
|
|||
img = img.attrs['data-src']
|
||||
img = Image(img, url, len(imgs))
|
||||
imgs.append(img)
|
||||
|
||||
|
||||
pgn = soup.find('ul', class_='pagination')
|
||||
ps = [getPage(a.attrs['href']) for a in pgn.findAll('a')] if pgn else []
|
||||
if not ps or p >= max(ps):
|
||||
|
@ -99,5 +108,3 @@ def get_imgs(url, title, cw=None):
|
|||
print(msg)
|
||||
|
||||
return imgs
|
||||
|
||||
|
||||
|
|
|
@ -7,12 +7,11 @@ import ytdl
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_vimeo(Downloader):
|
||||
type = 'vimeo'
|
||||
URLS = ['vimeo.com']
|
||||
single = True
|
||||
|
||||
|
||||
def init(self):
|
||||
if 'vimeo.com' not in self.url.lower():
|
||||
self.url = u'https://vimeo.com/{}'.format(self.url)
|
||||
|
@ -29,9 +28,9 @@ class Downloader_vimeo(Downloader):
|
|||
self.title = video.title
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
@ -40,7 +39,7 @@ class Video(object):
|
|||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
fs = [f for f in info['formats'] if f['protocol'] in ['http', 'https']]
|
||||
|
@ -48,7 +47,7 @@ class Video(object):
|
|||
if not fs:
|
||||
raise Exception('No MP4 videos')
|
||||
f = fs[0]
|
||||
|
||||
|
||||
self.thumb_url = info['thumbnails'][0]['url']
|
||||
self.thumb = IO()
|
||||
downloader.download(self.thumb_url, buffer=self.thumb)
|
||||
|
|
|
@ -7,7 +7,7 @@ from m3u8_tools import M3u8_stream
|
|||
import os
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_vlive(Downloader):
|
||||
type = 'vlive'
|
||||
URLS = ['vlive.tv']
|
||||
|
@ -21,21 +21,21 @@ class Downloader_vlive(Downloader):
|
|||
def read(self):
|
||||
cw = self.cw
|
||||
video = get_video(self.url, cw=cw)
|
||||
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
||||
self.setIcon(video.thumb)
|
||||
self.enableSegment()
|
||||
|
||||
|
||||
self.title = clean_title(video.title)
|
||||
|
||||
|
||||
|
||||
@try_n(4)
|
||||
def get_video(url, cw=None):
|
||||
options = {
|
||||
'noplaylist': True,
|
||||
}
|
||||
|
||||
|
||||
ydl = ytdl.YoutubeDL(options, cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
|
@ -51,25 +51,17 @@ def get_video(url, cw=None):
|
|||
raise Exception('No videos')
|
||||
|
||||
f = sorted(fs, key=lambda f:f['quality'])[-1]
|
||||
|
||||
subs = {}
|
||||
for sub, items in info['subtitles'].items():
|
||||
sub = sub.split('_')[0]
|
||||
for item in items:
|
||||
if item['ext'] != 'vtt':
|
||||
continue
|
||||
subs[sub] = item['url']
|
||||
video = Video(f, info, subs, cw)
|
||||
video = Video(f, info, cw)
|
||||
|
||||
return video
|
||||
|
||||
|
||||
class Video(object):
|
||||
def __init__(self, f, info, subs, cw=None):
|
||||
class Video:
|
||||
def __init__(self, f, info, cw=None):
|
||||
self.title = title = info['title']
|
||||
self.id = info['id']
|
||||
self.url = f['url']
|
||||
self.subs = subs
|
||||
self.subs = ytdl.get_subtitles(info)
|
||||
self.cw = cw
|
||||
|
||||
self.thumb = BytesIO()
|
||||
|
@ -87,5 +79,3 @@ class Video(object):
|
|||
def pp(self, filename):
|
||||
pp_subtitle(self, filename, self.cw)
|
||||
return filename
|
||||
|
||||
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
# coding: utf8
|
||||
# title: Wayback Machine Downloader
|
||||
# author: bog_4t
|
||||
import downloader
|
||||
import json
|
||||
import concurrent.futures
|
||||
|
||||
import downloader, json, os
|
||||
from utils import Downloader, Session, clean_title, get_print, update_url_query, print_error
|
||||
import os
|
||||
import ree as re
|
||||
from hashlib import md5
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
from utils import Downloader, Session, clean_title, get_print, print_error
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_wayback_machine(Downloader):
|
||||
type = 'waybackmachine'
|
||||
URLS = ['archive.org', 'web.archive.org']
|
||||
display_name = 'Wayback Machine'
|
||||
MAX_CORE = 1
|
||||
|
||||
def read(self):
|
||||
filter_ = Filter(self.url, self.cw)
|
||||
|
@ -24,45 +26,44 @@ class Downloader_wayback_machine(Downloader):
|
|||
self.title = filter_.title
|
||||
|
||||
|
||||
class WaybackMachineAPI(object):
|
||||
class WaybackMachineAPI:
|
||||
def __init__(self, session, cw=None):
|
||||
self.session = session
|
||||
self.cw = cw
|
||||
self.params = {
|
||||
'output': 'json',
|
||||
'fl': 'timestamp,original',
|
||||
'filter': 'mimetype:text/html',
|
||||
'statuscode': '200',
|
||||
'filter': 'mimetype:text/html&filter=statuscode:200',
|
||||
'collapse': 'urlkey'
|
||||
}
|
||||
|
||||
@sleep_and_retry
|
||||
@limits(1, 5)
|
||||
def call(self, url):
|
||||
url = update_url_query(url, self.params)
|
||||
for (key, value) in self.params.items():
|
||||
url += f'&{key}={value}'
|
||||
return downloader.read_json(url, session=self.session)
|
||||
|
||||
def snapshots(self, url):
|
||||
data = self.call(url)
|
||||
return data[1:] or None
|
||||
return data[1:]
|
||||
|
||||
|
||||
class Filter(object):
|
||||
class Filter:
|
||||
domains = [
|
||||
'twitter.com'
|
||||
]
|
||||
|
||||
def __init__(self, url, cw=None):
|
||||
self.cw = cw
|
||||
self.url = re.findall(r'archive.[^/]+/(?:cdx/search/cdx\?url=|(?:web/)?(?:[^/]+/))(.+)', url.lower())[0].strip(
|
||||
'/')
|
||||
self.base_url = self.url.split('&')[0].strip('*').strip('/')
|
||||
self.url = re.findall(r'archive.[^/]+/(?:cdx/search/cdx\?url=|(?:web/)?(?:[^/]+/))(.+)', url.lower())[0].strip('/')
|
||||
self.base_url = self.url.split('&')[0].strip('/')
|
||||
self.md5 = md5(self.url.encode('utf8')).hexdigest()[:8]
|
||||
self.mode = self.__get_mode()
|
||||
self.title = self.__get_title()
|
||||
|
||||
def __get_mode(self):
|
||||
for mode in [mode for mode, domain in enumerate(self.domains, start=1) if domain in self.url]:
|
||||
for mode in (mode for mode, domain in enumerate(self.domains, start=1) if domain in self.url):
|
||||
return mode
|
||||
return 0
|
||||
|
||||
|
@ -72,7 +73,7 @@ class Filter(object):
|
|||
return clean_title(os.path.basename(self.base_url), n=-len(tail)) + tail
|
||||
|
||||
def twitter():
|
||||
return '@' + re.findall('twitter.[^/]+/([^/?]+)', self.url)[0]
|
||||
return '@' + re.findall('twitter.[^/]+/([^/*?]+)', self.url)[0]
|
||||
|
||||
return [
|
||||
default,
|
||||
|
@ -80,7 +81,7 @@ class Filter(object):
|
|||
][self.mode]()
|
||||
|
||||
|
||||
class Bitmap(object):
|
||||
class Bitmap:
|
||||
bitmask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
|
||||
|
||||
def __init__(self, size=0, cw=None):
|
||||
|
@ -152,7 +153,7 @@ def get_imgs(url, filter_, directory, session=Session(), cw=None):
|
|||
return [base_url.format(snapshot[0], img['src']) for img in soup.find_all('img', src=True)]
|
||||
|
||||
def twitter():
|
||||
return [base_url.format(snapshot[0], img['src']) for img in soup.find_all('img', {'src': True}) if 'twimg.com/media/' in img['src']]
|
||||
return [base_url.format(snapshot[0], img['src']) for img in soup.find_all('img', src=True) if 'twimg.com/media/' in img['src']]
|
||||
|
||||
return [
|
||||
default,
|
||||
|
@ -178,7 +179,7 @@ def get_imgs(url, filter_, directory, session=Session(), cw=None):
|
|||
with open(urls_path) as file:
|
||||
urls = set()
|
||||
for url in file.readlines():
|
||||
urls.add(re.findall(r'^\S+$', url)[0])
|
||||
urls.update(re.findall(r'^\S+$', url))
|
||||
|
||||
os.remove(urls_path)
|
||||
os.remove(bitmap_path)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import downloader
|
||||
from utils import Soup, LazyUrl, clean_title, get_ext, get_imgs_already, urljoin, try_n, Downloader
|
||||
from utils import Soup, Session, LazyUrl, clean_title, get_ext, get_imgs_already, urljoin, try_n, Downloader
|
||||
import os
|
||||
import page_selector
|
||||
from translator import tr_
|
||||
|
@ -7,17 +7,18 @@ import ree as re
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_webtoon(Downloader):
|
||||
type = 'webtoon'
|
||||
URLS = ['webtoon.com', 'webtoons.com']
|
||||
MAX_CORE = 8
|
||||
MAX_SPEED = 4.0
|
||||
display_name = 'WEBTOON'
|
||||
ACCEPT_COOKIES = [r'(.*\.)?webtoons?\.com']
|
||||
|
||||
def init(self):
|
||||
self.url = get_main(self.url)
|
||||
self.soup = downloader.read_soup(self.url)
|
||||
self.session = Session()
|
||||
self.url = get_main(self.url, self.session)
|
||||
self.soup = downloader.read_soup(self.url, session=self.session)
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
|
@ -26,7 +27,7 @@ class Downloader_webtoon(Downloader):
|
|||
def read(self):
|
||||
title = clean_title(self.soup.find('h1').text.strip())
|
||||
self.title = tr_(u'\uc77d\ub294 \uc911... {}').format(title)
|
||||
imgs = get_imgs_all(self.url, title, cw=self.cw)
|
||||
imgs = get_imgs_all(self.url, self.session, title, cw=self.cw)
|
||||
for img in imgs:
|
||||
if isinstance(img, Image):
|
||||
self.urls.append(img.url)
|
||||
|
@ -34,27 +35,27 @@ class Downloader_webtoon(Downloader):
|
|||
self.urls.append(img)
|
||||
|
||||
self.title = title
|
||||
|
||||
|
||||
class Page(object):
|
||||
|
||||
class Page:
|
||||
|
||||
def __init__(self, url, title):
|
||||
self.url = url
|
||||
self.title = title
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, page, p):
|
||||
ext = get_ext(url) or downloader.get_ext(url, referer=page.url)
|
||||
def __init__(self, url, session, page, p):
|
||||
ext = get_ext(url) or downloader.get_ext(url, referer=page.url, session=session)
|
||||
self.filename = '{}/{:04}{}'.format(clean_title(page.title), p, ext)
|
||||
|
||||
self.url = LazyUrl(page.url, lambda _: url, self)
|
||||
|
||||
|
||||
|
||||
@try_n(2)
|
||||
def get_imgs(page):
|
||||
html = downloader.read_html(page.url)
|
||||
def get_imgs(page, session):
|
||||
html = downloader.read_html(page.url, session=session)
|
||||
if 'window.__motiontoonViewerState__' in html:
|
||||
raise NotImplementedError('motiontoon')
|
||||
soup = Soup(html)
|
||||
|
@ -62,14 +63,14 @@ def get_imgs(page):
|
|||
imgs = []
|
||||
for img in view.findAll('img'):
|
||||
src = img.get('data-url') or img['src']
|
||||
img = Image(urljoin(page.url, src), page, len(imgs))
|
||||
img = Image(urljoin(page.url, src), session, page, len(imgs))
|
||||
imgs.append(img)
|
||||
return imgs
|
||||
|
||||
|
||||
def get_main(url):
|
||||
def get_main(url, session):
|
||||
if 'episode_no=' in url:
|
||||
soup = downloader.read_soup(url)
|
||||
soup = downloader.read_soup(url, session=session)
|
||||
url = urljoin(url, soup.find('div', class_='subj_info').find('a')['href'])
|
||||
return url
|
||||
|
||||
|
@ -84,7 +85,7 @@ def set_page(url, p):
|
|||
return url
|
||||
|
||||
|
||||
def get_pages(url):
|
||||
def get_pages(url, session=None):
|
||||
pages = []
|
||||
urls = set()
|
||||
for p in range(1, 101):
|
||||
|
@ -92,7 +93,7 @@ def get_pages(url):
|
|||
print(url_page)
|
||||
for try_ in range(4):
|
||||
try:
|
||||
soup = downloader.read_soup(url_page)
|
||||
soup = downloader.read_soup(url_page, session=session)
|
||||
view = soup.find('ul', id='_listUl')
|
||||
if view is None:
|
||||
raise Exception('no view')
|
||||
|
@ -122,12 +123,12 @@ def get_pages(url):
|
|||
@page_selector.register('webtoon')
|
||||
@try_n(4)
|
||||
def f(url):
|
||||
url = get_main(url)
|
||||
url = get_main(url, None)
|
||||
return get_pages(url)
|
||||
|
||||
|
||||
def get_imgs_all(url, title, cw=None):
|
||||
pages = get_pages(url)
|
||||
def get_imgs_all(url, session, title, cw=None):
|
||||
pages = get_pages(url, session)
|
||||
pages = page_selector.filter(pages, cw)
|
||||
imgs = []
|
||||
for p, page in enumerate(pages):
|
||||
|
@ -135,7 +136,7 @@ def get_imgs_all(url, title, cw=None):
|
|||
if imgs_already:
|
||||
imgs += imgs_already
|
||||
continue
|
||||
imgs += get_imgs(page)
|
||||
imgs += get_imgs(page, session)
|
||||
msg = tr_(u'\uc77d\ub294 \uc911... {} / {} ({}/{})').format(title, page.title, p + 1, len(pages))
|
||||
if cw is not None:
|
||||
cw.setTitle(msg)
|
||||
|
|
|
@ -21,10 +21,11 @@ def suitable(url):
|
|||
return True
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_weibo(Downloader):
|
||||
type = 'weibo'
|
||||
URLS = [suitable]
|
||||
ACCEPT_COOKIES = [r'(.*\.)?(weibo\.com|sina\.com\.cn)']
|
||||
|
||||
def init(self):
|
||||
self.session = Session()
|
||||
|
@ -48,10 +49,10 @@ class Downloader_weibo(Downloader):
|
|||
|
||||
def read(self):
|
||||
checkLogin(self.session)
|
||||
|
||||
|
||||
uid, oid, name = get_id(self.url, self.cw)
|
||||
title = clean_title('{} (weibo_{})'.format(name, uid))
|
||||
|
||||
|
||||
for img in get_imgs(uid, oid, title, self.session, cw=self.cw, d=self, parent=self.mainWindow):
|
||||
self.urls.append(img.url)
|
||||
self.filenames[img.url] = img.filename
|
||||
|
@ -63,16 +64,16 @@ def checkLogin(session):
|
|||
c = session.cookies._cookies.get('.weibo.com', {}).get('/',{}).get('SUBP')
|
||||
if not c or c.is_expired():
|
||||
raise errors.LoginRequired()
|
||||
|
||||
|
||||
class Album(object):
|
||||
|
||||
class Album:
|
||||
|
||||
def __init__(self, id, type):
|
||||
self.id = id
|
||||
self.type = type
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
|
||||
def __init__(self, url, filename=None, timestamp=0):
|
||||
self.url = url
|
||||
|
@ -117,7 +118,7 @@ def get_id(url, cw=None):
|
|||
def get_imgs(uid, oid, title, session, cw=None, d=None, parent=None):
|
||||
print_ = get_print(cw)
|
||||
print_('uid: {}, oid:{}'.format(uid, oid))
|
||||
|
||||
|
||||
max_pid = get_max_range(cw)
|
||||
|
||||
@try_n(4)
|
||||
|
@ -190,5 +191,3 @@ def get_imgs(uid, oid, title, session, cw=None, d=None, parent=None):
|
|||
|
||||
imgs = sorted(imgs, key=lambda img: img.timestamp, reverse=True)
|
||||
return imgs[:max_pid]
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from translator import tr_
|
|||
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, referer, title, id):
|
||||
self.url = LazyUrl(referer, lambda _: url, self)
|
||||
ext = os.path.splitext(url.split('?')[0])[1]
|
||||
|
@ -17,8 +17,7 @@ class Image(object):
|
|||
self.filename = u'{} - {}{}'.format(id, title, ext)
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_wikiart(Downloader):
|
||||
type = 'wikiart'
|
||||
URLS = ['wikiart.org']
|
||||
|
@ -39,7 +38,7 @@ class Downloader_wikiart(Downloader):
|
|||
|
||||
for img in get_imgs(self.url, artist, cw=self.cw):
|
||||
self.urls.append(img.url)
|
||||
|
||||
|
||||
self.title = clean_title(artist)
|
||||
|
||||
|
||||
|
@ -103,4 +102,3 @@ def get_artist(userid, soup=None):
|
|||
soup = Soup(html)
|
||||
|
||||
return soup.find('h3').text.strip()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import downloader, ree as re
|
||||
from utils import Downloader, get_outdir, Soup, LazyUrl, get_print, cut_pair, get_ext, try_n, format_filename, clean_title
|
||||
from utils import Downloader, get_outdir, Soup, LazyUrl, get_print, cut_pair, get_ext, try_n, format_filename, clean_title, get_resolution
|
||||
from timee import sleep
|
||||
from error_printer import print_error
|
||||
import os
|
||||
|
@ -8,15 +8,11 @@ import shutil, ffmpeg, json
|
|||
from io import BytesIO
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_xhamster(Downloader):
|
||||
type = 'xhamster'
|
||||
__name = r'(xhamster|xhwebsite|xhofficial|xhlocal|xhopen|xhtotal|megaxh)[0-9]*' #3881, #4332
|
||||
URLS = [
|
||||
r'regex:{}\.[a-z0-9]+/videos/'.format(__name),
|
||||
r'regex:{}\.[a-z0-9]+/users/'.format(__name),
|
||||
r'regex:{}\.[a-z0-9]+/photos/gallery/'.format(__name),
|
||||
]
|
||||
__name = r'([^/]*\.)?(xhamster|xhwebsite|xhofficial|xhlocal|xhopen|xhtotal|megaxh|xhwide)[0-9]*' #3881, #4332, #4826, #5029
|
||||
URLS = [r'regex:{}\.[a-z0-9]+/(videos|users|photos/gallery)/'.format(__name)]
|
||||
single = True
|
||||
display_name = 'xHamster'
|
||||
|
||||
|
@ -28,17 +24,19 @@ class Downloader_xhamster(Downloader):
|
|||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
return re.sub(cls.__name, r'\1', url, 1)
|
||||
url = re.sub(cls.__name, r'\2', url, 1)
|
||||
url = re.sub(r'(/users/[^/]+/videos)/[0-9]+', r'\1', url, 1) #5029
|
||||
return url
|
||||
|
||||
@classmethod
|
||||
def key_id(cls, url):
|
||||
return re.sub(cls.__name+r'\.[^/]+', 'domain', url, 1)
|
||||
return re.sub(cls.__name+r'\.[^/]+', 'domain', url, 1).replace('http://', 'https://')
|
||||
|
||||
def read(self):
|
||||
cw = self.cw
|
||||
self.enableSegment(1024*1024//2)
|
||||
thumb = BytesIO()
|
||||
|
||||
|
||||
if '/users/' in self.url:
|
||||
info = read_channel(self.url, cw)
|
||||
urls = info['urls']
|
||||
|
@ -63,7 +61,7 @@ class Downloader_xhamster(Downloader):
|
|||
self.setIcon(thumb)
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
|
@ -77,12 +75,17 @@ class Video(object):
|
|||
|
||||
self.title = self.info['title']
|
||||
id = self.info['id']
|
||||
|
||||
video_best = self.info['formats'][(-1)]
|
||||
|
||||
#4773
|
||||
fs = self.info['formats']
|
||||
res = max(get_resolution(), min(f['height'] for f in fs))
|
||||
fs = [f for f in fs if f['height'] <= res]
|
||||
|
||||
video_best = fs[-1]
|
||||
self._url = video_best['url']
|
||||
ext = get_ext(self._url)
|
||||
self.filename = format_filename(self.title, id, ext)
|
||||
|
||||
|
||||
if isinstance(self._url, str) and 'referer=force' in self._url.lower():
|
||||
self._referer = self._url
|
||||
else:
|
||||
|
@ -105,11 +108,11 @@ def get_info(url):
|
|||
raise Exception(err.text.strip())
|
||||
|
||||
data = get_data(html)
|
||||
|
||||
|
||||
info['title'] = data['videoModel']['title']
|
||||
info['id'] = data['videoModel']['id']
|
||||
info['thumbnail'] = data['videoModel']['thumbURL']
|
||||
|
||||
|
||||
fs = []
|
||||
for res, url_video in data['videoModel']['sources']['mp4'].items():
|
||||
height = int(re.find('(\d+)p', res))
|
||||
|
@ -140,7 +143,7 @@ def read_page(username, p, cw):
|
|||
raise e_
|
||||
return items
|
||||
|
||||
|
||||
|
||||
def read_channel(url, cw=None):
|
||||
print_ = get_print(cw)
|
||||
username = url.split('/users/')[1].split('/')[0]
|
||||
|
@ -149,7 +152,7 @@ def read_channel(url, cw=None):
|
|||
soup = downloader.read_soup(url)
|
||||
title = soup.find('div', class_='user-name').text.strip()
|
||||
info['title'] = '[Channel] {}'.format(title)
|
||||
|
||||
|
||||
urls = []
|
||||
urls_set = set()
|
||||
for p in range(1, 101):
|
||||
|
@ -177,7 +180,7 @@ def read_channel(url, cw=None):
|
|||
return info
|
||||
|
||||
|
||||
class Image(object):
|
||||
class Image:
|
||||
def __init__(self, url, id, referer):
|
||||
self.id = id
|
||||
self._url = url
|
||||
|
@ -199,7 +202,7 @@ def setPage(url, p):
|
|||
url += '/{}'.format(p)
|
||||
return url
|
||||
|
||||
|
||||
|
||||
def read_gallery(url, cw=None):
|
||||
print_ = get_print(cw)
|
||||
|
||||
|
@ -220,14 +223,14 @@ def read_gallery(url, cw=None):
|
|||
print_('p: {}'.format(p))
|
||||
url = setPage(url, p)
|
||||
html = downloader.read_html(url)
|
||||
|
||||
|
||||
data = get_data(html)
|
||||
|
||||
photos = data['photosGalleryModel']['photos']
|
||||
if not photos:
|
||||
print('no photos')
|
||||
break
|
||||
|
||||
|
||||
for photo in photos:
|
||||
img = photo['imageURL']
|
||||
id = photo['id']
|
||||
|
@ -238,7 +241,7 @@ def read_gallery(url, cw=None):
|
|||
ids.add(id)
|
||||
img = Image(img, id, referer)
|
||||
imgs.append(img)
|
||||
|
||||
|
||||
info['imgs'] = imgs
|
||||
|
||||
return info
|
||||
|
|
|
@ -9,7 +9,7 @@ from io import BytesIO as IO
|
|||
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
|
||||
def __init__(self, url, url_page, title, url_thumb):
|
||||
self._url = url
|
||||
|
@ -30,7 +30,7 @@ def get_id(url):
|
|||
return url.split('xnxx.com/')[1].split('/')[0]
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_xnxx(Downloader):
|
||||
type = 'xnxx'
|
||||
URLS = [r'regex:xnxx[0-9]*\.(com|es)']
|
||||
|
@ -46,7 +46,7 @@ class Downloader_xnxx(Downloader):
|
|||
self.urls.append(video.url)
|
||||
self.setIcon(video.thumb)
|
||||
self.title = video.title
|
||||
|
||||
|
||||
|
||||
def get_video(url):
|
||||
html = downloader.read_html(url)
|
||||
|
@ -65,11 +65,10 @@ def get_video(url):
|
|||
title = get_title(soup)
|
||||
|
||||
url_thumb = soup.find('meta', {'property': 'og:image'}).attrs['content'].strip()
|
||||
|
||||
|
||||
video = Video(video, url, title, url_thumb)
|
||||
return video
|
||||
|
||||
|
||||
def get_title(soup):
|
||||
return soup.find('meta', {'property': 'og:title'}).attrs['content'].strip()
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import downloader
|
||||
from utils import Downloader, Soup, LazyUrl, urljoin, format_filename, Session, get_ext, get_print, get_max_range, html_unescape
|
||||
from utils import Downloader, Soup, LazyUrl, urljoin, format_filename, Session, get_ext, get_print, get_max_range, html_unescape, get_resolution
|
||||
from io import BytesIO
|
||||
from constants import try_n
|
||||
import ree as re
|
||||
from m3u8_tools import playlist2stream
|
||||
from translator import tr_
|
||||
CHANNEL_PATTERN = r'/(profiles|[^/]*channels)/([0-9a-zA-Z_]+)'
|
||||
import json
|
||||
from timee import sleep
|
||||
from ratelimit import limits, sleep_and_retry
|
||||
CHANNEL_PATTERN = r'/(profiles|[^/]*channels)/([0-9a-zA-Z_-]+)'
|
||||
|
||||
|
||||
def get_id(url):
|
||||
|
@ -15,33 +18,45 @@ def get_id(url):
|
|||
return re.find(r'xvideos[0-9]*\.[^/]+/video([0-9]+)', url, err='no id')
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
def __init__(self, url_page):
|
||||
url_page = Downloader_xvideo.fix_url(url_page)
|
||||
self.url = LazyUrl(url_page, self.get, self)
|
||||
|
||||
@try_n(4)
|
||||
def get(self, url_page):
|
||||
if not self._url:
|
||||
id = get_id(url_page)
|
||||
html = downloader.read_html(url_page)
|
||||
soup = Soup(html)
|
||||
self.title = html_unescape(soup.find('title').text).replace('- XVIDEOS.COM', '').strip()
|
||||
url = re.find(r'''.setVideoHLS\(['"](.+?)['"]\)''', html)
|
||||
ext = get_ext(url)
|
||||
if ext.lower() == '.m3u8':
|
||||
url = playlist2stream(url, n_thread=5)
|
||||
url_thumb = soup.find('meta', {'property': 'og:image'}).attrs['content']
|
||||
self.thumb = BytesIO()
|
||||
downloader.download(url_thumb, buffer=self.thumb)
|
||||
self.filename = format_filename(self.title, id, '.mp4')
|
||||
self._url= url
|
||||
self._get(url_page)
|
||||
return self._url
|
||||
|
||||
@try_n(4)
|
||||
@sleep_and_retry
|
||||
@limits(1, 2)
|
||||
def _get(self, url_page):
|
||||
id = get_id(url_page)
|
||||
html = downloader.read_html(url_page)
|
||||
soup = Soup(html)
|
||||
self.title = html_unescape(soup.find('title').text).replace('- XVIDEOS.COM', '').strip()
|
||||
url = re.find(r'''.setVideoHLS\(['"](.+?)['"]\)''', html) or re.find(r'''.setVideoUrlHigh\(['"](.+?)['"]\)''', html) or re.find(r'''.setVideoUrlLow\(['"](.+?)['"]\)''', html) #https://www.xvideos.com/video65390539/party_night
|
||||
if not url:
|
||||
raise Exception('no video url')
|
||||
ext = get_ext(url)
|
||||
if ext.lower() == '.m3u8':
|
||||
url = playlist2stream(url, n_thread=5, res=get_resolution()) #4773
|
||||
self.url_thumb = soup.find('meta', {'property': 'og:image'}).attrs['content']
|
||||
self.filename = format_filename(self.title, id, '.mp4')
|
||||
self._url= url
|
||||
|
||||
@property
|
||||
def thumb(self):
|
||||
self.url()
|
||||
f = BytesIO()
|
||||
downloader.download(self.url_thumb, buffer=f)
|
||||
return f
|
||||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_xvideo(Downloader):
|
||||
type = 'xvideo'
|
||||
URLS = [r'regex:[./]xvideos[0-9]*\.(com|in|es)']
|
||||
|
@ -57,6 +72,7 @@ class Downloader_xvideo(Downloader):
|
|||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
url = re.sub(r'[^/]*xvideos[0-9]*\.[^/]+', 'www.xvideos.com', url).replace('http://', 'https://')
|
||||
url = url.replace('/THUMBNUM/', '/')
|
||||
return url
|
||||
|
||||
@classmethod
|
||||
|
@ -77,9 +93,9 @@ class Downloader_xvideo(Downloader):
|
|||
video = Video(self.url)
|
||||
video.url()
|
||||
self.title = video.title
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
||||
self.setIcon(video.thumb)
|
||||
self.urls.append(video.url)
|
||||
|
||||
|
||||
def read_channel(url_page, cw=None):
|
||||
|
@ -95,38 +111,41 @@ def read_channel(url_page, cw=None):
|
|||
info['username'] = username
|
||||
session = Session()
|
||||
urls = []
|
||||
urls_set = set()
|
||||
ids = set()
|
||||
for p in range(100):
|
||||
url_api = urljoin(url_page, '/{}/{}/videos/best/{}'.format(header, username, p))
|
||||
print(url_api)
|
||||
r = session.post(url_api, data='main_cats=false')
|
||||
soup = Soup(r.text)
|
||||
thumbs = soup.findAll('div', class_='thumb-block')
|
||||
if not thumbs:
|
||||
print_(url_api)
|
||||
r = session.post(url_api)
|
||||
data = json.loads(r.text)
|
||||
|
||||
videos = data.get('videos') #4530
|
||||
if not videos:
|
||||
print_('empty')
|
||||
break
|
||||
for thumb in thumbs:
|
||||
info['name'] = thumb.find('span', class_='name').text.strip()
|
||||
href = thumb.find('a')['href']
|
||||
href = urljoin(url_page, href)
|
||||
if href in urls_set:
|
||||
print_('duplicate: {}'.format(href))
|
||||
|
||||
for video in videos:
|
||||
id_ = video['id']
|
||||
if id_ in ids:
|
||||
print_('duplicate: {}'.format(id_))
|
||||
continue
|
||||
urls_set.add(href)
|
||||
urls.append(href)
|
||||
|
||||
ids.add(id_)
|
||||
info['name'] = video['pn']
|
||||
urls.append(urljoin(url_page, video['u']))
|
||||
|
||||
if len(urls) >= max_pid:
|
||||
break
|
||||
|
||||
|
||||
n = data['nb_videos']
|
||||
|
||||
s = '{} {} - {}'.format(tr_('읽는 중...'), info['name'], len(urls))
|
||||
if cw:
|
||||
if not cw.alive:
|
||||
return
|
||||
cw.setTitle(s)
|
||||
else:
|
||||
print(s)
|
||||
if len(ids) >= n:
|
||||
break
|
||||
sleep(1, cw)
|
||||
if not urls:
|
||||
raise Exception('no videos')
|
||||
info['urls'] = urls[:max_pid]
|
||||
return info
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ def read_soup(url):
|
|||
return downloader.read_soup(url)
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_yandere(Downloader):
|
||||
type = 'yande.re'
|
||||
URLS = ['yande.re']
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
import ytdl
|
||||
from m3u8_tools import M3u8_stream
|
||||
|
@ -6,7 +5,7 @@ from utils import LazyUrl, get_ext, Downloader, format_filename, clean_title
|
|||
from io import BytesIO
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
||||
class Downloader_youku(Downloader):
|
||||
type = 'youku'
|
||||
single = True
|
||||
|
@ -21,10 +20,10 @@ class Downloader_youku(Downloader):
|
|||
|
||||
self.title = video.title
|
||||
|
||||
|
||||
class Video(object):
|
||||
|
||||
class Video:
|
||||
_url = None
|
||||
|
||||
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
@ -32,7 +31,7 @@ class Video(object):
|
|||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
|
@ -57,6 +56,5 @@ class Video(object):
|
|||
self.filename = format_filename(self.title, info['id'], '.mp4')
|
||||
|
||||
self._url = url_video
|
||||
|
||||
return self._url
|
||||
|
||||
return self._url
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from __future__ import division, print_function, unicode_literals
|
||||
import downloader
|
||||
import ree as re
|
||||
from io import BytesIO
|
||||
|
@ -9,7 +8,6 @@ import ytdl
|
|||
|
||||
|
||||
|
||||
@Downloader.register
|
||||
class Downloader_youporn(Downloader):
|
||||
type = 'youporn'
|
||||
single = True
|
||||
|
@ -33,7 +31,7 @@ class Downloader_youporn(Downloader):
|
|||
self.title = video.title
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
@try_n(4)
|
||||
def __init__(self, url, cw=None):
|
||||
ydl = ytdl.YoutubeDL(cw=cw)
|
||||
|
@ -42,7 +40,7 @@ class Video(object):
|
|||
f = info['formats'][-1]
|
||||
url_video = f['url']
|
||||
self.url = LazyUrl(url, lambda _: url_video, self)
|
||||
|
||||
|
||||
self.url_thumb = info['thumbnails'][0]['url']
|
||||
self.thumb = BytesIO()
|
||||
downloader.download(self.url_thumb, buffer=self.thumb)
|
||||
|
|
|
@ -7,7 +7,7 @@ from constants import empty_thumbnail, isdeleted
|
|||
from error_printer import print_error
|
||||
from timee import sleep
|
||||
import ree as re
|
||||
from utils import urljoin, Downloader, Soup, try_n, get_print, filter_range, LazyUrl, query_url, compatstr, uuid, get_max_range, format_filename, clean_title, get_resolution, get_abr
|
||||
from utils import urljoin, Downloader, Soup, try_n, get_print, filter_range, LazyUrl, query_url, compatstr, uuid, get_max_range, format_filename, clean_title, get_resolution, get_abr, Session
|
||||
import ffmpeg
|
||||
import sys
|
||||
import constants
|
||||
|
@ -16,23 +16,26 @@ import chardet
|
|||
import os
|
||||
from random import randrange
|
||||
import utils
|
||||
from translator import tr_
|
||||
from translator import tr, tr_
|
||||
from datetime import datetime
|
||||
import threading
|
||||
from putils import DIR
|
||||
|
||||
|
||||
def print_streams(streams, cw):
|
||||
print_ = get_print(cw)
|
||||
|
||||
|
||||
for stream in streams:
|
||||
print_('{}[{}][{}fps][{}{}][{}] {} [{} / {}] ─ {}'.format('LIVE ' if stream.live else '', stream.resolution, stream.fps, stream.abr_str, '(fixed)' if stream.abr_fixed else '', stream.tbr, stream.subtype, stream.video_codec, stream.audio_codec, stream.format))
|
||||
print_('')
|
||||
|
||||
|
||||
class Video(object):
|
||||
class Video:
|
||||
_url = None
|
||||
vcodec = None
|
||||
|
||||
def __init__(self, url, type='video', only_mp4=False, audio_included=False, max_res=None, max_abr=None, cw=None):
|
||||
filename0 = None
|
||||
|
||||
def __init__(self, url, session, type='video', only_mp4=False, audio_included=False, max_res=None, max_abr=None, cw=None):
|
||||
self.type = type
|
||||
self.only_mp4 = only_mp4
|
||||
self.audio_included = audio_included
|
||||
|
@ -40,12 +43,13 @@ class Video(object):
|
|||
self.max_abr = max_abr
|
||||
self.cw = cw
|
||||
self.url = LazyUrl(url, self.get, self, pp=self.pp)
|
||||
self.session = session
|
||||
self.exec_queue = cw.exec_queue if cw else None#
|
||||
|
||||
|
||||
def get(self, url, force=False):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
|
||||
type = self.type
|
||||
only_mp4 = self.only_mp4
|
||||
audio_included = self.audio_included
|
||||
|
@ -72,7 +76,7 @@ class Video(object):
|
|||
|
||||
streams = yt.streams.all()
|
||||
print_streams(streams, cw)
|
||||
|
||||
|
||||
#3528
|
||||
time = datetime.strptime(yt.info['upload_date'], '%Y%m%d')
|
||||
self.utime = (time-datetime(1970,1,1)).total_seconds()
|
||||
|
@ -145,8 +149,8 @@ class Video(object):
|
|||
#print(foo)
|
||||
print_('# stream_final {} {} {} {} {} {}fps'.format(stream, stream.format, stream.resolution, stream.subtype, stream.audio_codec, stream.fps))
|
||||
stream_final = stream
|
||||
|
||||
ok = downloader.ok_url(stream_final.url, referer=url) if isinstance(stream_final.url, str) else True
|
||||
|
||||
ok = downloader.ok_url(stream_final.url, referer=url, session=self.session) if isinstance(stream_final.url, str) else True
|
||||
if ok:
|
||||
break
|
||||
else:
|
||||
|
@ -161,7 +165,7 @@ class Video(object):
|
|||
|
||||
## if stream.video_codec and stream_final.video_codec.lower().startswith('av'):
|
||||
## self.vcodec = 'h264'
|
||||
|
||||
|
||||
self.yt = yt
|
||||
self.id = yt.video_id
|
||||
self.stream = stream
|
||||
|
@ -214,7 +218,7 @@ class Video(object):
|
|||
self.thumb_url = yt.thumbnail_url.replace('default', quality)
|
||||
f = BytesIO()
|
||||
try:
|
||||
downloader.download(self.thumb_url, buffer=f)
|
||||
downloader.download(self.thumb_url, session=self.session, buffer=f)
|
||||
data = f.read()
|
||||
if len(data) == 0:
|
||||
raise AssertionError('Zero thumbnail')
|
||||
|
@ -236,7 +240,11 @@ class Video(object):
|
|||
#title = soup.title.text.replace('- YouTube', '').strip()
|
||||
self.title = title
|
||||
ext = '.' + self.stream.subtype
|
||||
self.filename = format_filename(title, self.id, ext)
|
||||
self.filename = format_filename(title, self.id, ext, artist=self.username) #4953
|
||||
|
||||
if type == 'audio':
|
||||
self.filename0 = self.filename
|
||||
self.filename = f'{uuid()}_audio.tmp' #4776
|
||||
|
||||
print_('Resolution: {}'.format(stream.resolution))
|
||||
print_('Codec: {} / {}'.format(stream.video_codec, stream.audio_codec))
|
||||
|
@ -244,9 +252,24 @@ class Video(object):
|
|||
print_('Subtype: {}'.format(stream.subtype))
|
||||
print_('FPS: {}\n'.format(stream.fps))
|
||||
|
||||
if self.audio is not None: #5015
|
||||
def f(audio):
|
||||
print_('Download audio: {}'.format(audio))
|
||||
path = os.path.join(DIR, f'{uuid()}_a.tmp')
|
||||
if cw is not None:
|
||||
cw.trash_can.append(path)
|
||||
if constants.FAST:
|
||||
downloader_v3.download(audio, session=self.session, chunk=1024*1024, n_threads=2, outdir=os.path.dirname(path), fileName=os.path.basename(path), customWidget=cw, overwrite=True)
|
||||
else:
|
||||
downloader.download(audio, session=self.session, outdir=os.path.dirname(path), fileName=os.path.basename(path), customWidget=cw, overwrite=True)
|
||||
self.audio_path = path
|
||||
print_('audio done')
|
||||
self.thread_audio = threading.Thread(target=f, args=(self.audio,), daemon=True)
|
||||
self.thread_audio.start()
|
||||
|
||||
return self._url
|
||||
|
||||
def pp(self, filename):
|
||||
def pp(self, filename, i=0):
|
||||
cw = self.cw
|
||||
print_ = get_print(cw)
|
||||
ui_setting = utils.ui_setting
|
||||
|
@ -254,20 +277,12 @@ class Video(object):
|
|||
if not os.path.isfile(filename):
|
||||
print('no file: {}'.format(filename))
|
||||
return
|
||||
|
||||
filename_new = None
|
||||
|
||||
filename_new = filename
|
||||
if self.type == 'video' and (self.audio is not None or ext != '.mp4') and not self.stream.live: # UHD or non-mp4
|
||||
if self.audio is not None: # merge
|
||||
print_('Download audio: {}'.format(self.audio))
|
||||
hash = uuid()
|
||||
path = os.path.join(os.path.dirname(filename), '{}_a.tmp'.format(hash))
|
||||
if cw is not None:
|
||||
cw.trash_can.append(path)
|
||||
if constants.FAST:
|
||||
downloader_v3.download(self.audio, chunk=1024*1024, n_threads=2, outdir=os.path.dirname(path), fileName=os.path.basename(path), customWidget=cw, overwrite=True)
|
||||
else:
|
||||
downloader.download(self.audio, outdir=os.path.dirname(path), fileName=os.path.basename(path), customWidget=cw, overwrite=True)
|
||||
ext, out = ffmpeg.merge(filename, path, cw=cw, vcodec=self.vcodec)
|
||||
self.thread_audio.join()
|
||||
ext, out = ffmpeg.merge(filename, self.audio_path, cw=cw, vcodec=self.vcodec)
|
||||
#print(out)
|
||||
name, ext_old = os.path.splitext(filename)
|
||||
if ext_old.lower() != ext.lower():
|
||||
|
@ -286,6 +301,17 @@ class Video(object):
|
|||
filename_new = '{}.mp3'.format(name)
|
||||
ffmpeg.convert(filename, filename_new, '-shortest -preset ultrafast -b:a {}k'.format(get_abr()), cw=cw)
|
||||
|
||||
if self.filename0 and os.path.basename(filename_new) != self.filename0: #4776
|
||||
filename0 = utils.fix_enumerate(self.filename0, i, cw)
|
||||
filename_old = filename_new
|
||||
ext = '.mp4' if self.type == 'video' else '.mp3'
|
||||
filename_new = os.path.join(os.path.dirname(filename_old), os.path.splitext(filename0)[0]+ext)
|
||||
print_(f'rename: {filename_old} -> {filename_new}')
|
||||
if filename_old != filename_new:
|
||||
if os.path.isfile(filename_new):
|
||||
os.remove(filename_new)
|
||||
os.rename(filename_old, filename_new)
|
||||
|
||||
if self.type == 'audio' and ui_setting.albumArt.isChecked():
|
||||
try:
|
||||
self.thumb.seek(0)#
|
||||
|
@ -294,12 +320,18 @@ class Video(object):
|
|||
s = print_error(e)[-1]
|
||||
print_(s)
|
||||
|
||||
utils.pp_subtitle(self, filename, cw)
|
||||
utils.pp_subtitle(self, filename_new, cw)
|
||||
|
||||
return filename_new
|
||||
|
||||
|
||||
@Downloader.register
|
||||
def get_id(url):
|
||||
id_ = re.find(r'youtu.be/([0-9A-Za-z-_]{10,})', url) or re.find(r'[?&]v=([0-9A-Za-z-_]{10,})', url) or re.find(r'/(v|embed)/([0-9A-Za-z-_]{10,})', url) or re.find(r'%3Fv%3D([0-9A-Za-z-_]{10,})', url)
|
||||
if isinstance(id_, tuple):
|
||||
id_ = id_[-1]
|
||||
return id_
|
||||
|
||||
|
||||
class Downloader_youtube(Downloader):
|
||||
type = 'youtube'
|
||||
single = True
|
||||
|
@ -308,23 +340,32 @@ class Downloader_youtube(Downloader):
|
|||
lock = True
|
||||
display_name = 'YouTube'
|
||||
keep_date = True #3528
|
||||
|
||||
__format = {}
|
||||
ACCEPT_COOKIES = [r'.*(youtube|youtu\.be|google).*']
|
||||
|
||||
def init(self):
|
||||
ui_setting = self.ui_setting
|
||||
if self.cw.format:
|
||||
ext_result = self.cw.format
|
||||
format = self.cw.format
|
||||
if format:
|
||||
if isinstance(format, str):
|
||||
ext_result = format
|
||||
elif isinstance(format, dict):
|
||||
ext_result = format['format']
|
||||
self.__format = format
|
||||
else:
|
||||
raise NotImplementedError(format)
|
||||
else:
|
||||
ext_result = compatstr(ui_setting.youtubeCombo_type.currentText()).lower().split()[0]
|
||||
ext_result = default_option()
|
||||
self.cw.format = ext_result
|
||||
|
||||
|
||||
if ext_result in ['mp4', 'mkv', '3gp']:
|
||||
self.yt_type = 'video'
|
||||
else:
|
||||
self.yt_type = 'audio'
|
||||
self.cw.setMusic(True)
|
||||
self.session = Session()
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url): # 2033
|
||||
def fix_url(cls, url): #2033
|
||||
if not re.match('https?://.+', url, re.IGNORECASE):
|
||||
url = 'https://www.youtube.com/watch?v={}'.format(url)
|
||||
qs = query_url(url)
|
||||
|
@ -334,19 +375,18 @@ class Downloader_youtube(Downloader):
|
|||
|
||||
@classmethod
|
||||
def key_id(cls, url):
|
||||
id_ = re.find(r'youtu.be/([0-9A-Za-z-_]{10,})', url) or re.find(r'[?&]v=([0-9A-Za-z-_]{10,})', url)
|
||||
return id_ or url
|
||||
|
||||
return get_id(url) or url
|
||||
|
||||
def read(self):
|
||||
ui_setting = self.ui_setting
|
||||
cw = self.cw
|
||||
print_ = get_print(cw)
|
||||
if self.yt_type == 'video':
|
||||
res = get_resolution()
|
||||
info = get_videos(self.url, type=self.yt_type, max_res=res, only_mp4=False, audio_included=not True, cw=cw)
|
||||
res = self.__format.get('res', get_resolution())
|
||||
info = get_videos(self.url, self.session, type=self.yt_type, max_res=res, only_mp4=False, audio_included=not True, cw=cw)
|
||||
else:
|
||||
abr = get_abr()
|
||||
info = get_videos(self.url, type=self.yt_type, max_abr=abr, cw=cw)
|
||||
abr = self.__format.get('abr', get_abr())
|
||||
info = get_videos(self.url, self.session, type=self.yt_type, max_abr=abr, cw=cw)
|
||||
videos = info['videos']
|
||||
|
||||
if not videos:
|
||||
|
@ -374,7 +414,7 @@ class Downloader_youtube(Downloader):
|
|||
self.title = video.title
|
||||
if video.stream.live:
|
||||
self.lock = False
|
||||
|
||||
|
||||
self.artist = video.username
|
||||
self.setIcon(video.thumb)
|
||||
|
||||
|
@ -385,13 +425,13 @@ def int_(x):
|
|||
except:
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@try_n(2, sleep=1)
|
||||
def get_videos(url, type='video', only_mp4=False, audio_included=False, max_res=None, max_abr=None, cw=None):
|
||||
def get_videos(url, session, type='video', only_mp4=False, audio_included=False, max_res=None, max_abr=None, cw=None):
|
||||
info = {}
|
||||
|
||||
n = get_max_range(cw)
|
||||
|
||||
|
||||
if '/channel/' in url or '/user/' in url or '/c/' in url:
|
||||
info = read_channel(url, n=n, cw=cw)
|
||||
info['type'] = 'channel'
|
||||
|
@ -404,11 +444,13 @@ def get_videos(url, type='video', only_mp4=False, audio_included=False, max_res=
|
|||
info['title'] = '[Playlist] {}'.format(info['title'])
|
||||
if cw:
|
||||
info['urls'] = filter_range(info['urls'], cw.range)
|
||||
else:
|
||||
elif get_id(url):
|
||||
info['type'] = 'single'
|
||||
info['urls'] = [url]
|
||||
else:
|
||||
raise NotImplementedError(url)
|
||||
|
||||
info['videos'] = [Video(url, type, only_mp4, audio_included, max_res, max_abr, cw) for url in info['urls']]
|
||||
info['videos'] = [Video(url, session, type, only_mp4, audio_included, max_res, max_abr, cw) for url in info['urls']]
|
||||
|
||||
return info
|
||||
|
||||
|
@ -425,7 +467,7 @@ def read_playlist(url, n, cw=None):
|
|||
if '/{}/'.format(header) in url.lower():
|
||||
username = re.find(r'/{}/([^/\?]+)'.format(header), url, re.IGNORECASE)
|
||||
url = urljoin(url, '/{}/{}/videos'.format(header, username))
|
||||
|
||||
|
||||
options = {
|
||||
'extract_flat': True,
|
||||
'playlistend': n,
|
||||
|
@ -440,7 +482,7 @@ def read_playlist(url, n, cw=None):
|
|||
urls.append(href)
|
||||
info['urls'] = urls
|
||||
|
||||
if 'uploader' not in info:
|
||||
if not info.get('uploader'):
|
||||
title = info['title']
|
||||
if title.lower().endswith(' - videos'):
|
||||
title = title[:-len(' - videos')]
|
||||
|
@ -453,15 +495,90 @@ def read_playlist(url, n, cw=None):
|
|||
import selector
|
||||
@selector.register('youtube')
|
||||
def select():
|
||||
from Qt import Qt, QDialog, QFormLayout, QLabel, QComboBox, QWidget, QVBoxLayout, QDialogButtonBox
|
||||
if utils.ui_setting.askYoutube.isChecked():
|
||||
value = utils.messageBox(tr_('Youtube format?'), icon=utils.QMessageBox.Question, buttons=[tr_('MP4 (동영상)'), tr_('MP3 (음원)')])
|
||||
format = ['mp4', 'mp3'][value]
|
||||
win = QDialog(constants.mainWindow)
|
||||
win.setWindowTitle('Youtube format')
|
||||
utils.windows.append(win)
|
||||
layout = QFormLayout(win)
|
||||
|
||||
youtubeCombo_type = QComboBox()
|
||||
layout.addRow('파일 형식', youtubeCombo_type)
|
||||
for i in range(utils.ui_setting.youtubeCombo_type.count()):
|
||||
youtubeCombo_type.addItem(utils.ui_setting.youtubeCombo_type.itemText(i))
|
||||
youtubeCombo_type.setCurrentIndex(utils.ui_setting.youtubeCombo_type.currentIndex())
|
||||
|
||||
youtubeLabel_res = QLabel('해상도')
|
||||
youtubeCombo_res = QComboBox()
|
||||
for i in range(utils.ui_setting.youtubeCombo_res.count()):
|
||||
youtubeCombo_res.addItem(utils.ui_setting.youtubeCombo_res.itemText(i))
|
||||
youtubeCombo_res.setCurrentIndex(utils.ui_setting.youtubeCombo_res.currentIndex())
|
||||
|
||||
youtubeLabel_abr = QLabel('음질')
|
||||
youtubeCombo_abr = QComboBox()
|
||||
for i in range(utils.ui_setting.youtubeCombo_abr.count()):
|
||||
youtubeCombo_abr.addItem(utils.ui_setting.youtubeCombo_abr.itemText(i))
|
||||
youtubeCombo_abr.setCurrentIndex(utils.ui_setting.youtubeCombo_abr.currentIndex())
|
||||
|
||||
aa = QWidget()
|
||||
a = QVBoxLayout(aa)
|
||||
a.setContentsMargins(0,0,0,0)
|
||||
a.addWidget(youtubeLabel_res)
|
||||
a.addWidget(youtubeLabel_abr)
|
||||
bb = QWidget()
|
||||
b = QVBoxLayout(bb)
|
||||
b.setContentsMargins(0,0,0,0)
|
||||
b.addWidget(youtubeCombo_res)
|
||||
b.addWidget(youtubeCombo_abr)
|
||||
layout.addRow(aa, bb)
|
||||
|
||||
def currentIndexChanged(index):
|
||||
text_type = compatstr(youtubeCombo_type.currentText())
|
||||
print(text_type)
|
||||
if tr_('동영상') in text_type:
|
||||
youtubeLabel_abr.hide()
|
||||
youtubeCombo_abr.hide()
|
||||
youtubeLabel_res.show()
|
||||
youtubeCombo_res.show()
|
||||
elif tr_('음원') in text_type:
|
||||
youtubeLabel_res.hide()
|
||||
youtubeCombo_res.hide()
|
||||
youtubeLabel_abr.show()
|
||||
youtubeCombo_abr.show()
|
||||
youtubeCombo_type.currentIndexChanged.connect(currentIndexChanged)
|
||||
youtubeCombo_type.currentIndexChanged.emit(youtubeCombo_type.currentIndex())
|
||||
|
||||
buttonBox = QDialogButtonBox()
|
||||
layout.addWidget(buttonBox)
|
||||
buttonBox.setOrientation(Qt.Horizontal)
|
||||
buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
|
||||
buttonBox.accepted.connect(win.accept)
|
||||
buttonBox.rejected.connect(win.reject)
|
||||
|
||||
tr(win)
|
||||
win.setWindowOpacity(constants.opacity_max)
|
||||
try:
|
||||
res = win.exec_()
|
||||
utils.log(f'youtube.select.res: {res}')
|
||||
if not res:
|
||||
return selector.Cancel
|
||||
utils.windows.remove(win)
|
||||
format = {}
|
||||
format['format'] = compatstr(youtubeCombo_type.currentText()).lower().split()[0]
|
||||
format['res'] = get_resolution(compatstr(youtubeCombo_res.currentText()))
|
||||
format['abr'] = get_abr(compatstr(youtubeCombo_abr.currentText()))
|
||||
finally:
|
||||
win.deleteLater()
|
||||
return format
|
||||
|
||||
|
||||
@selector.options('youtube')
|
||||
def options():
|
||||
def options(urls):
|
||||
return [
|
||||
{'text': 'MP4 (동영상)', 'format': 'mp4'},
|
||||
{'text': 'MP3 (음원)', 'format': 'mp3'},
|
||||
]
|
||||
|
||||
@selector.default_option('youtube')
|
||||
def default_option():
|
||||
return compatstr(utils.ui_setting.youtubeCombo_type.currentText()).lower().split()[0]
|
||||
|
|
|
@ -1,50 +1,332 @@
|
|||
3.7i 【】
|
||||
3.7p 【Sep 20, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- 일부 사이트에서 일시 정지 후 다시 시작 안 되는 문제 해결
|
||||
- YouTube shorts 인식 안 되는 문제 해결 (#5260)
|
||||
|
||||
- カクヨム 일부 작품 다운로드 안 되는 문제 해결 (#4262)
|
||||
|
||||
- Facebook 일부 계정 다운로드 안 되는 문제 해결 (#2417)
|
||||
|
||||
- xHamster 새로운 도메인 지원 (#4332)
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7o 【Sep 19, 2022】
|
||||
|
||||
- Hitomi.la 업데이트 대응 (#4351, #4352)
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- KAKAO WEBTOON 업데이트 대응 (#4360)
|
||||
- Twitch 다시보기 다운로드 안 되는 문제 해결 (#5031)
|
||||
|
||||
- #4267
|
||||
- #5033
|
||||
|
||||
- #4288
|
||||
- #5046
|
||||
|
||||
- #4298
|
||||
- #5071
|
||||
|
||||
- #4316
|
||||
- Pixiv 업데이트 대응 (#5105)
|
||||
|
||||
- Weibo 오래된 쿠키 업데이트 메시지 안 나오는 문제 해결
|
||||
- V2ph 업데이트 대응 (#5082)
|
||||
|
||||
- Tiktok 모바일 주소 다운로드 안 되는 문제 해결 (#4324)
|
||||
- nhentai 업데이트 대응 (#5131)
|
||||
|
||||
- Instagram 업데이트 대응 (#4336)
|
||||
- MyReadingManga 업데이트 대응 (#5109, #5148)
|
||||
|
||||
- XVideos 일부 제목 잘못 읽는 문제 해결 (#4359)
|
||||
- Nozomi.la "/" 들어간 태그 인식 못하는 문제 해결 (#5146)
|
||||
|
||||
- "옵션 - 설정 - 일반 - 플레이리스트 파일에 번호 매기기" 적용되지 않는 문제 해결
|
||||
|
||||
- pixivコミック 업데이트 대응 (#5158)
|
||||
|
||||
- KakaoPage 업데이트 대응 (#5176)
|
||||
|
||||
- Manatoki 업데이트 대응 (#5233)
|
||||
|
||||
- WeLoveManga 업데이트 대응 (#5238)
|
||||
|
||||
- Twitch 음소거 복구 작동하지 않는 문제 해결 (#5242)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- Naver Cafe 지원 (#2090)
|
||||
- Live M3U8 stream 지원 (#5110)
|
||||
|
||||
- 폴더 열기 단축키
|
||||
- Pinterest 단일 포스트 다운로드 (#5132)
|
||||
|
||||
- #4287
|
||||
- Nozomi.la 여러 이미지 포함하는 포스트 다운로드 (#4943)
|
||||
|
||||
- ExHentai .onion 도메인 인식
|
||||
|
||||
- TikTok 태그 검색 다운로드 (#5235)
|
||||
|
||||
- 성능 최적화
|
||||
|
||||
- "다시 시작" 한 작업 대기 중 "완료됨으로 표시" 가능
|
||||
|
||||
- #5029
|
||||
|
||||
- Instagram Reels 다운로드 (#5051)
|
||||
|
||||
- 소설 사이트 루비 처리 (#5102)
|
||||
|
||||
- #4980
|
||||
|
||||
- UI 스케일 설정
|
||||
옵션 - 설정 - 고급 - UI 스케일
|
||||
|
||||
- 아이콘 모드에서도 우클릭 - 드래그 동작
|
||||
|
||||
- Pixiv 정지된 계정 처리 (#5223)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7n 【July 22, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- #4857
|
||||
|
||||
- Luscious member 전용 페이지 제목 읽지 못하는 문제 해결 (#4751)
|
||||
|
||||
- Torrent 파일 연결 안 되는 문제 해결 (#4873)
|
||||
|
||||
- --safemode 적용 안 되는 문제 해결 (#4932)
|
||||
|
||||
- #4854
|
||||
|
||||
- Pixiv 작가명 문제 (#4897)
|
||||
|
||||
- Iwara 일부 이미지 다운로드 안 되는 문제 해결 (#4901)
|
||||
|
||||
- Pixiv Comic 업데이트 대응 (#4861)
|
||||
|
||||
- Luscious 업데이트 대응 (#4905)
|
||||
|
||||
- 다크모드에서 일부 위젯 제대로 안 보이는 해결
|
||||
|
||||
- Soundcloud 아주 많은 재생목록 다운로드 안 되는 문제 해결 (#4928)
|
||||
|
||||
- 큰따옴표 들어간 작가명 인식 못하는 문제 해결 (#4936)
|
||||
|
||||
- TikTok 업데이트 대응 (#4941)
|
||||
|
||||
- Hentai Cosplay 업데이트 대응 (#4954, #4961)
|
||||
|
||||
- AsmHentai 업데이트 대응 (#4971)
|
||||
|
||||
- Twitter 특정 쿠키에서 생기는 문제 해결 (#4967)
|
||||
|
||||
- Instagram 특정 쿠키에서 생기는 문제 해결 (#4967)
|
||||
|
||||
- #4982
|
||||
|
||||
- #5006
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- douyin 다운로드
|
||||
|
||||
- Twitter Likes 다운로드 (#726)
|
||||
|
||||
- Twitter Retweets 포함 다운로드 (#3429)
|
||||
|
||||
- Torrent 시딩 설정
|
||||
옵션 - 설정 - Torrent 설정 - 시딩
|
||||
|
||||
- YouTube 해상도 지정 다운로드 (#4851, #4895)
|
||||
옵션 - 설정 - YouTube 설정 - 파일 형식 물어보기
|
||||
|
||||
- Soundcloud 재생목록 더 빠르게 읽음
|
||||
|
||||
- Etc 사이트 자막 지원
|
||||
|
||||
- YouTube 자막 모든 언어 지원
|
||||
|
||||
- 플러그인 기능
|
||||
옵션 - 설정 - 플러그인
|
||||
|
||||
- 커스텀 테마 지원
|
||||
|
||||
- Torrent 작업 메뉴
|
||||
작업 마우스 우클릭 - Torrent
|
||||
|
||||
- 관리자 권한 없이 실행 옵션 (#4934)
|
||||
옵션 - 설정 - 관리자 권한 없이 실행
|
||||
|
||||
- "페이지 지정 다운로드 기본값" → "최대 페이지 제한" 으로 변경 (#4933)
|
||||
|
||||
- TikTok 파일명 제목 포함하도록 변경
|
||||
|
||||
- 작가 추천 현재 언어 설정에 따르도록 변경
|
||||
|
||||
- 내장 웹브라우저 GPU 사용하게 변경 (#4941)
|
||||
|
||||
- 작업표시줄 진행도 현재 다운로드 중인 작업들의 진행도를 표시하도록 변경
|
||||
|
||||
- 성능 / 메모리 최적화
|
||||
|
||||
- YouTube, Twitch 등 파일명 포맷 "artist" 지원 (#4940, #4953)
|
||||
|
||||
- 스크립트 "@Downloader.register" 생략 가능
|
||||
|
||||
- 이미 동일한 작업이 있다면 다시 시작할지 물어봄
|
||||
|
||||
- Torrent 파일 가능하면 헤더 먼저 받음
|
||||
|
||||
- 아주 작은 이미지 & 동영상 파일은 다운로드 실패로 간주 (#4996)
|
||||
|
||||
- 내장 브라우저 쿠키 ↔ 현재 쿠키 설정 동기화
|
||||
|
||||
- Instagram 쿠키가 없다면 로그인 창 띄움
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7m 2022-06-08
|
||||
|
||||
[Bug Resolution / Fix due to site change]
|
||||
|
||||
- Downgrading to V3 module (#4835)
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7l 2022-06-07
|
||||
|
||||
[Bug Resolution / Fix due to site change]
|
||||
|
||||
- Font: font-breaking solved
|
||||
- Video: fixed some sites which didn't follow quality option (in YouTube option) (#4773)
|
||||
- YouTube: false duplicate error solved. You can now download the video and then the audio without the audio being flagged as duplicate (#4776)
|
||||
- luscious: limited files downloaded fixed (#4794)
|
||||
- xHamster: new domain (xhwide.com) support (#4826)
|
||||
- Instagram: update response (#4829)
|
||||
- KissJav: new domain (kissjav.li) support (#4826)
|
||||
- YouTube: fixe proxy not usable (#4850)
|
||||
- Other small things
|
||||
|
||||
[Changed/Added Features]
|
||||
|
||||
- View a list of created folders (ctrl+L) (#4698)
|
||||
- Home/End keybkaord are supported again (#4752)
|
||||
- Hitomi.la / E(x)Hentai: filename new option "Number + Original" (#4762)
|
||||
- Torrent: Fixed reading the remaining magnet link when the Torrent seed file is deleted and restarted
|
||||
- Torrent Tracker: featured modified
|
||||
- When restarting, modify the ones that have already been downloaded to keep the modified date (#4764)
|
||||
- Packer Update (#4648, #4781)
|
||||
- Shortcup: you can now change them (#4805)
|
||||
- Optimizing program start time
|
||||
- Warning if you need to log in to Twitter (#4839)
|
||||
- Other small things
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7k 2022-05-06
|
||||
|
||||
[Bug Resolution / Fix due to site change]
|
||||
|
||||
- Files can now be saved even if config file doesn't exist (#4733)
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7j 2022-05-06
|
||||
|
||||
[Bug Resolution / Fix due to site change]
|
||||
|
||||
- Pawoo: download error resolved (#4381)
|
||||
- Luscious: update response (#4386)
|
||||
- Pixiv Comic: download error resolved (#4389)
|
||||
- XVideos: update response (#4401, #4410, #4530)
|
||||
- Show in browser: resolved WebP file not visible
|
||||
- Twitter: download error resolved (#4438)
|
||||
- Hitomi.la: download error (animated picture) resolved
|
||||
- カクヨム (kakuyomu): download error resolved (#4445)
|
||||
- Iwara: download error resolved (#4450)
|
||||
- Iwara: download error resolved. Only the first picture of channel was downloaded (#4499)
|
||||
- nhentai: update response (#4541)
|
||||
- DeviantArt: update response (#4515)
|
||||
- Pixiv: gif redownload problem (#4534)
|
||||
- Tumblr: update response (#4645)
|
||||
- Manatoki: update response (#4660, #4664)
|
||||
- Various websites: fixed missing files (#4633, #4676)
|
||||
- DeviantArt: update response (#4671)
|
||||
- Novelpia: download error resolved (#4713)
|
||||
- Iwara: download error resolved (#4728)
|
||||
- Other small things
|
||||
|
||||
[Changed/Added Features]
|
||||
- Conversion to PNG is now possible (#4420, #4426)
|
||||
- built-in web brwoser: now accessible via quick access toolbar (#4451)
|
||||
- E(x)Hentai: empty file is not created anymore if quote is exceeded ( #4385)
|
||||
- Group: right-click "move to group" added (#4524)
|
||||
- Twitter: warning if you need to log-in (#4549)
|
||||
- Statistics: total available space added (#4328)
|
||||
- Danbooru Ugoira: now convertible in webm
|
||||
- YouTube: 8k support (#4593)
|
||||
- "Create a new task from a local file" - "Single file" Multiple choices (#4682)
|
||||
- Reconnect DB when hard drive got disconnected (#4712)
|
||||
- Preview video file
|
||||
- Other small things
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7i 2022-01-27
|
||||
|
||||
[Bug Resolution / Fix due to site change]
|
||||
|
||||
- Fixed an issue where some sites should not pause and then resume
|
||||
|
||||
- Kakuyomu (カクヨム): download error resolved (#4262)
|
||||
|
||||
- Facebook: account download error resolved (#2417)
|
||||
|
||||
- xHamster: support (#4332)
|
||||
|
||||
- Hitomi.la: update response (#4351, #4352)
|
||||
|
||||
- KAKAO WEBTOON: update response (#4360)
|
||||
|
||||
- Hitomi.la: fixed some title bug (#4267)
|
||||
|
||||
- Iwara: fixed download error (#4288)
|
||||
|
||||
- welovemanga: new domain support (#4298)
|
||||
|
||||
- Search: multiple bugs corrected (#4316)
|
||||
|
||||
- Weibo: fixed "warning: old cookies" messages
|
||||
|
||||
- Tiktok: fixed download error (#4324)
|
||||
|
||||
- Instagram: update response (#4336)
|
||||
|
||||
- XVideos: fixed title errors (#4359)
|
||||
|
||||
- Twitter: update response
|
||||
|
||||
- Other small things
|
||||
|
||||
|
||||
[Changed/Added Features]
|
||||
|
||||
- Naver Cafe: support (#2090)
|
||||
|
||||
- Open folder shortcut
|
||||
|
||||
- Iwara: same videos title (but different url) are not treated as the same videos anymore (#4287)
|
||||
|
||||
- Other small things
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7f 2022-01-03
|
||||
|
||||
|
@ -244,14 +526,14 @@
|
|||
- Translation update (#3739)
|
||||
- Adding leading zero (#3763)
|
||||
- V LIVE support for caption (#3778)
|
||||
- Numbering playlist files
|
||||
- Numbering playlist files
|
||||
Option - Settings - General Settings - Number the playlist file
|
||||
- Naver blog: duplicate file names corrected (#3788, #3817)
|
||||
- Optimized initial driving speed and storage speed
|
||||
- Optimizing the speed of dark mode change
|
||||
- Theme setting
|
||||
Option - Settings - General Settings - Theme
|
||||
- Task list filtering : search include URL when searching for titles in task list filtering
|
||||
- Task list filtering : search include URL when searching for titles in task list filtering
|
||||
- Display single file work video length
|
||||
- Torrent: maximum download speed support
|
||||
- Torrent: support for proxy settings
|
||||
|
@ -259,7 +541,7 @@
|
|||
- FC2 support for high definition (#3784)
|
||||
- Low specification mode (#3878)
|
||||
Option - Settings - Advanced Settings - Low specification mode
|
||||
- Twitter support for wider dates (previously some years weren't being donwloaded)
|
||||
- Twitter support for wider dates (previously some years weren't being donwloaded)
|
||||
- Hiyobi.io support (#3861, #3862)
|
||||
- LHScan: if there isn't cookies login a warning now appear (#3324, #3815, #3877)
|
||||
- Fiding duplicate images: Specify the similarity range and automatically select it (#3912)
|
||||
|
|
|
@ -1,4 +1,335 @@
|
|||
3.7i 【】
|
||||
3.7p 【Sep 20, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- YouTube shorts 인식 안 되는 문제 해결 (#5260)
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7o 【Sep 19, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- Twitch 다시보기 다운로드 안 되는 문제 해결 (#5031)
|
||||
|
||||
- #5033
|
||||
|
||||
- #5046
|
||||
|
||||
- #5071
|
||||
|
||||
- Pixiv 업데이트 대응 (#5105)
|
||||
|
||||
- V2ph 업데이트 대응 (#5082)
|
||||
|
||||
- nhentai 업데이트 대응 (#5131)
|
||||
|
||||
- MyReadingManga 업데이트 대응 (#5109, #5148)
|
||||
|
||||
- Nozomi.la "/" 들어간 태그 인식 못하는 문제 해결 (#5146)
|
||||
|
||||
- "옵션 - 설정 - 일반 - 플레이리스트 파일에 번호 매기기" 적용되지 않는 문제 해결
|
||||
|
||||
- pixivコミック 업데이트 대응 (#5158)
|
||||
|
||||
- KakaoPage 업데이트 대응 (#5176)
|
||||
|
||||
- Manatoki 업데이트 대응 (#5233)
|
||||
|
||||
- WeLoveManga 업데이트 대응 (#5238)
|
||||
|
||||
- Twitch 음소거 복구 작동하지 않는 문제 해결 (#5242)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- Live M3U8 stream 지원 (#5110)
|
||||
|
||||
- Pinterest 단일 포스트 다운로드 (#5132)
|
||||
|
||||
- Nozomi.la 여러 이미지 포함하는 포스트 다운로드 (#4943)
|
||||
|
||||
- ExHentai .onion 도메인 인식
|
||||
|
||||
- TikTok 태그 검색 다운로드 (#5235)
|
||||
|
||||
- 성능 최적화
|
||||
|
||||
- "다시 시작" 한 작업 대기 중 "완료됨으로 표시" 가능
|
||||
|
||||
- #5029
|
||||
|
||||
- Instagram Reels 다운로드 (#5051)
|
||||
|
||||
- 소설 사이트 루비 처리 (#5102)
|
||||
|
||||
- #4980
|
||||
|
||||
- UI 스케일 설정
|
||||
옵션 - 설정 - 고급 - UI 스케일
|
||||
|
||||
- 아이콘 모드에서도 우클릭 - 드래그 동작
|
||||
|
||||
- Pixiv 정지된 계정 처리 (#5223)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7n 【July 22, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- #4857
|
||||
|
||||
- Luscious member 전용 페이지 제목 읽지 못하는 문제 해결 (#4751)
|
||||
|
||||
- Torrent 파일 연결 안 되는 문제 해결 (#4873)
|
||||
|
||||
- --safemode 적용 안 되는 문제 해결 (#4932)
|
||||
|
||||
- #4854
|
||||
|
||||
- Pixiv 작가명 문제 (#4897)
|
||||
|
||||
- Iwara 일부 이미지 다운로드 안 되는 문제 해결 (#4901)
|
||||
|
||||
- Pixiv Comic 업데이트 대응 (#4861)
|
||||
|
||||
- Luscious 업데이트 대응 (#4905)
|
||||
|
||||
- 다크모드에서 일부 위젯 제대로 안 보이는 해결
|
||||
|
||||
- Soundcloud 아주 많은 재생목록 다운로드 안 되는 문제 해결 (#4928)
|
||||
|
||||
- 큰따옴표 들어간 작가명 인식 못하는 문제 해결 (#4936)
|
||||
|
||||
- TikTok 업데이트 대응 (#4941)
|
||||
|
||||
- Hentai Cosplay 업데이트 대응 (#4954, #4961)
|
||||
|
||||
- AsmHentai 업데이트 대응 (#4971)
|
||||
|
||||
- Twitter 특정 쿠키에서 생기는 문제 해결 (#4967)
|
||||
|
||||
- Instagram 특정 쿠키에서 생기는 문제 해결 (#4967)
|
||||
|
||||
- #4982
|
||||
|
||||
- #5006
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- douyin 다운로드
|
||||
|
||||
- Twitter Likes 다운로드 (#726)
|
||||
|
||||
- Twitter Retweets 포함 다운로드 (#3429)
|
||||
|
||||
- Torrent 시딩 설정
|
||||
옵션 - 설정 - Torrent 설정 - 시딩
|
||||
|
||||
- YouTube 해상도 지정 다운로드 (#4851, #4895)
|
||||
옵션 - 설정 - YouTube 설정 - 파일 형식 물어보기
|
||||
|
||||
- Soundcloud 재생목록 더 빠르게 읽음
|
||||
|
||||
- Etc 사이트 자막 지원
|
||||
|
||||
- YouTube 자막 모든 언어 지원
|
||||
|
||||
- 플러그인 기능
|
||||
옵션 - 설정 - 플러그인
|
||||
|
||||
- 커스텀 테마 지원
|
||||
|
||||
- Torrent 작업 메뉴
|
||||
작업 마우스 우클릭 - Torrent
|
||||
|
||||
- 관리자 권한 없이 실행 옵션 (#4934)
|
||||
옵션 - 설정 - 관리자 권한 없이 실행
|
||||
|
||||
- "페이지 지정 다운로드 기본값" → "최대 페이지 제한" 으로 변경 (#4933)
|
||||
|
||||
- TikTok 파일명 제목 포함하도록 변경
|
||||
|
||||
- 작가 추천 현재 언어 설정에 따르도록 변경
|
||||
|
||||
- 내장 웹브라우저 GPU 사용하게 변경 (#4941)
|
||||
|
||||
- 작업표시줄 진행도 현재 다운로드 중인 작업들의 진행도를 표시하도록 변경
|
||||
|
||||
- 성능 / 메모리 최적화
|
||||
|
||||
- YouTube, Twitch 등 파일명 포맷 "artist" 지원 (#4940, #4953)
|
||||
|
||||
- 스크립트 "@Downloader.register" 생략 가능
|
||||
|
||||
- 이미 동일한 작업이 있다면 다시 시작할지 물어봄
|
||||
|
||||
- Torrent 파일 가능하면 헤더 먼저 받음
|
||||
|
||||
- 아주 작은 이미지 & 동영상 파일은 다운로드 실패로 간주 (#4996)
|
||||
|
||||
- 내장 브라우저 쿠키 ↔ 현재 쿠키 설정 동기화
|
||||
|
||||
- Instagram 쿠키가 없다면 로그인 창 띄움
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7m 【June 08, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- V3 오진으로 모듈 다운그레이드 (#4835)
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7l 【June 07, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- 갑자기 폰트 깨지는 문제 해결
|
||||
|
||||
- 일부 화질 적용 안 되는 사이트 수정 (#4773)
|
||||
|
||||
- YouTube 동영상 다운로드 후 음원 다운로드하면 중복 처리 되는 문제 해결 (#4776)
|
||||
|
||||
- #4794
|
||||
|
||||
- xHamster 새 도메인 지원 (#4826)
|
||||
|
||||
- Instagram 업데이트 대응 (#4829)
|
||||
|
||||
- KissJav 새 도메인 지원 (#4835)
|
||||
|
||||
- YouTube 프록시 적용 안 되는 문제 해결 (#4850)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- 생성한 폴더 목록 보기 (#4698)
|
||||
|
||||
- #4752
|
||||
|
||||
- Hitomi.la / E(x)Hentai 파일명 형식 "숫자 + 원본" 추가 (#4762)
|
||||
|
||||
- Torrent 시드 파일 삭제한 후 재시작하면 남아있는 마그넷 링크로 읽도록 수정
|
||||
|
||||
- Torrent Tracker 수정 기능
|
||||
|
||||
- 다시 시작 시 이미 다운로드한 것들은 수정한 날짜 유지하도록 수정 (#4764)
|
||||
|
||||
- 패커 업데이트 (#4648, #4781)
|
||||
|
||||
- 단축키 수정 (#4805)
|
||||
|
||||
- 프로그램 시작 시간 최적화
|
||||
|
||||
- Twitter 로그인 필요할 경우 경고 (#4839)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7k 【May 06, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- 설정 파일 없는 상태에서 시작할 때 저장 안 되는 문제 해결 (#4733)
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7j 【May 06, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- Pawoo 일부 다운로드 안 되는 문제 해결 (#4381)
|
||||
|
||||
- Luscious 업데이트 대응 (#4386)
|
||||
|
||||
- Pixiv Comic 일부 작품 다운로드 안 되는 문제 해결 (#4389)
|
||||
|
||||
- XVideos 업데이트 대응 (#4401, #4410, #4530)
|
||||
|
||||
- "브라우저로 보기"에서 WebP 파일 안 보이는 문제 해결 (#4424)
|
||||
|
||||
- 일부 단일 트윗 다운로드 안 되는 문제 해결 (#4438)
|
||||
|
||||
- Hitomi.la 애니메이션 다운로드 안 되는 문제 해결
|
||||
|
||||
- カクヨム 일부 다운로드 안 되는 문제 해결 (#4445)
|
||||
|
||||
- Iwara 이미지 다운로드 안 되는 문제 해결 (#4450)
|
||||
|
||||
- Iwara 채널 다운로드 시 첫 이미지만 다운로드 되는 문제 해결 (#4499)
|
||||
|
||||
- nhentai 업데이트 대응 (#4541)
|
||||
|
||||
- DeviantArt 업데이트 대응 (#4515)
|
||||
|
||||
- #4534
|
||||
|
||||
- Tumblr 업데이트 대응(#4645)
|
||||
|
||||
- Manatoki 업데이트 대응(#4660, #4664)
|
||||
|
||||
- 불완전하게 받아진 화 다시 시작 시 완료되는 문제 해결 (#4633, #4676)
|
||||
|
||||
- DeviantArt 업데이트 대응 (#4671)
|
||||
|
||||
- #4713
|
||||
|
||||
- #4728
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- PNG 변환 (#4420)
|
||||
|
||||
- #4451
|
||||
|
||||
- #4385
|
||||
|
||||
- #4524
|
||||
|
||||
- Twitter 로그인 필요할 경우 경고 (#4549)
|
||||
|
||||
- 통계 총 용량 표시 (#4328)
|
||||
|
||||
- Danbooru Ugoira 비디오 형식으로 다운로드 하도록 수정 (#4635)
|
||||
|
||||
- YouTube 8K 다운로드 (#4593)
|
||||
|
||||
- "로컬 파일로 부터 새 작업 만들기" - "단일 파일" 여러 개 선택 가능 (#4682)
|
||||
|
||||
- 도중에 하드드라이브 연결이 끊어졌을 때 다시 DB 연결 (#4712)
|
||||
|
||||
- 동영상 파일 미리보기
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.7i 【Jan 27, 2022】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
|
@ -30,6 +361,8 @@
|
|||
|
||||
- XVideos 일부 제목 잘못 읽는 문제 해결 (#4359)
|
||||
|
||||
- Twitter 업데이트 대응
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
{head}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p align="right"><br></p>
|
||||
<h1 align="center">How to use</h1>
|
||||
<p align="right">{date}</p>
|
||||
<br>
|
||||
|
||||
<h3>Supported sites</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader#supported-sites">https://github.com/KurtBestor/Hitomi-Downloader#supported-sites</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Address bar</h3>
|
||||
<ul>
|
||||
<li>Type some URLs into the address bar and click the Download button to download</li>
|
||||
<li>Examples:<br>
|
||||
https://hitomi.la/galleries/123.html<br>
|
||||
https://hitomi.la/reader/123.html<br>
|
||||
https://e-hentai.org/g/123/356dfa74ce/<br>
|
||||
https://e-hentai.org/s/600e752112/123-4<br>
|
||||
https://hiyobi.me/reader/123<br>
|
||||
123<br>
|
||||
<br>
|
||||
https://www.pixiv.net/users/11<br>
|
||||
https://www.pixiv.net/member_illust.php?id=11<br>
|
||||
https://pixiv.me/pixiv<br>
|
||||
pixiv_11<br>
|
||||
<br>
|
||||
https://twitter.com/TensorFlow<br>
|
||||
@TensorFlow<br>
|
||||
<br>
|
||||
https://www.instagram.com/user_name<br>
|
||||
insta_user_name<br>
|
||||
<br>
|
||||
https://username.deviantart.com<br>
|
||||
deviant_username
|
||||
</li>
|
||||
<li>Examples for multiple URLs:<br>
|
||||
123, 125, 126<br>
|
||||
123 125 126
|
||||
</li>
|
||||
<li>Numbers not in Hitomi.la are automatically converted to E(x)Hentai URL.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Save</h3>
|
||||
<ul>
|
||||
<li>Save the preferences and tasks currently added.</li>
|
||||
<li>File - Save</li>
|
||||
<li>[Ctrl + S] key</li>
|
||||
</ul>
|
||||
|
||||
<h3>Searcher</h3>
|
||||
<ul>
|
||||
<li>Search galleries</li>
|
||||
<li>Menu - Searcher...</li>
|
||||
<li>[Ctrl + F] key</li>
|
||||
</ul>
|
||||
|
||||
<h3>Downloader tasks</h3>
|
||||
<ul>
|
||||
<li>Open the first file:<br>
|
||||
Mouse right-click → [Open the first file]<br>
|
||||
Click thumbnail<br>
|
||||
Double-click<br>
|
||||
[Enter] key
|
||||
</li>
|
||||
<li>Remove multiple tasks:<br>
|
||||
Select multiple tasks → [Del] key
|
||||
</li>
|
||||
<li>Delete multiple files:<br>
|
||||
Select multiple tasks → Mouse right-click → [Delete files]<br>
|
||||
Select multiple tasks → [Shift] + [Del] key
|
||||
</li>
|
||||
<li>Remove all complete tasks:<br>
|
||||
Mouse right-click → [Remove all complete tasks]<br>
|
||||
Remove all complete tasks which are not locked.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Task colors</h3>
|
||||
<ul>
|
||||
<li>Light gray: Wating or Reading</li>
|
||||
<li>Dark gray: Downloading</li>
|
||||
<li>Green: Downloaded completely</li>
|
||||
<li>Orange: Downloaded incompletely</li>
|
||||
<li> Red: Fail or Invalid</li>
|
||||
</ul>
|
||||
|
||||
<h3>Change the order of tasks</h3>
|
||||
<ul>
|
||||
<li>Drag and drop with the mouse wheel button to change the order.</li>
|
||||
<li>Following commands are also working: [Ctrl + ↑], [Ctrl + ↓], [Ctrl + Home], [Ctrl + End].</li>
|
||||
</ul>
|
||||
|
||||
<h3>Filtering the List of tasks</h3>
|
||||
<ul>
|
||||
<li>Click the filter icon at the bottom left of the downloader window and type.</li>
|
||||
<li>Only tasks with a title that contains that word are visible.</li>
|
||||
<li>You can also filter by type, such as "type:youtube".</li>
|
||||
<li>If you type "dup:", you will see only duplicate tasks.</li>
|
||||
<li>Type "rem:" to see only the tasks that have been deleted from the physical storage.</li>
|
||||
<li>You can filter by tags such as "tag:glasses".</li>
|
||||
<li>You can filter by comments, such as "comment:xxxx".</li>
|
||||
<li>Type "bad:" to show only incomplete tasks.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Searcher 검색 목록 필터링</h3>
|
||||
<ul>
|
||||
<li>Searcher 좌측 하단의 필터 모양 아이콘을 클릭하고 내용 입력</li>
|
||||
<li>해당 문자가 포함된 제목, 작가, 그룹, 태그, 언어를 가진 갤러리만 보입니다.</li>
|
||||
<li>"title:A" 를 입력하면 A라는 문자가 포함된 제목을 가진 갤러리만 보입니다.</li>
|
||||
<li>"artist:A", "group:A", "tag:A", "lang:A" 과 같이 필터링 할 수도 있습니다.</li>
|
||||
<li>"p<100" 을 입력하면 100 페이지 미만인 갤러리만 보입니다.</li>
|
||||
<li>"p>100" 을 입력하면 100 페이지 초과인 갤러리만 보입니다.</li>
|
||||
<li>"done:o" 를 입력하면 이미 다운로드한 갤러리만 보입니다.</li>
|
||||
<li>"done:x" 를 입력하면 아직 다운로드하지 않은 갤러리만 보입니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>페이지 지정 다운로드</h3>
|
||||
<ul>
|
||||
<p><img src=":/icons/down_menu" /></p>
|
||||
<li>지정한 페이지만 다운로드합니다.</li>
|
||||
<li>Examples:<br>
|
||||
~ 100 → 앞 100 페이지<br>
|
||||
-100 ~ → 뒤 100 페이지<br>
|
||||
1, 10 ~ 20, -1 → 첫 페이지, 10 ~ 20 페이지, 마지막 페이지</li>
|
||||
</ul>
|
||||
|
||||
<h3>선택 화 다운로드</h3>
|
||||
<ul>
|
||||
<p><img src=":/icons/down_menu_chapter" /></p>
|
||||
<li>선택한 화만 다운로드합니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>환경 설정</h3>
|
||||
<ul>
|
||||
<li>Options → Preferences</li>
|
||||
</ul>
|
||||
|
||||
<h3>스레드</h3>
|
||||
<ul>
|
||||
<li>Parallel downloads per task.</li>
|
||||
<li>컴퓨터 사양이나 인터넷 상태가 좋지 않다면 갯수를 좀 더 내립니다.</li>
|
||||
<li>If you're unsure of this setting, keep the default values.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Simple search - Search keywords</h3>
|
||||
<ul>
|
||||
<li>Search like Google.</li>
|
||||
<li>Examples :<br>
|
||||
maid<br>
|
||||
maid glasses korean<br>
|
||||
maid -schoolgirl<br>
|
||||
maid n/a<br>
|
||||
maid (korean + n/a)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Advanced Search - Title</h3>
|
||||
<ul>
|
||||
<li>Examples :<br>
|
||||
maid<br>
|
||||
maid -schoolgirl<br>
|
||||
maid glasses</li>
|
||||
</ul>
|
||||
|
||||
<h3>Advanced Search - Artists</h3>
|
||||
<ul>
|
||||
<li>Examples :<br>
|
||||
sekiya asami<br>
|
||||
sekiya asami + amezawa koma</li>
|
||||
</ul>
|
||||
|
||||
<h3>Advanced Search - Characters</h3>
|
||||
<ul>
|
||||
<li>Examples :<br>
|
||||
chino kafuu<br>
|
||||
chino kafuu + kokoa hoto<br>
|
||||
hino kafuu, kokoa hoto</li>
|
||||
</ul>
|
||||
|
||||
<h3>Advanced Search - Tags</h3>
|
||||
<ul>
|
||||
<li>Examples :<br>
|
||||
female:maid<br>
|
||||
female:maid, -female:schoolgirl uniform</li>
|
||||
</ul>
|
||||
|
||||
<h3>Searcher 목록</h3>
|
||||
<ul>
|
||||
<li>여러 갤러리 다운로드 :<br>
|
||||
여러 개 선택 → 마우스 우클릭 → [다운로드]<br>
|
||||
여러 개 선택 → [Enter] key</li>
|
||||
|
||||
<li>갤러리 정보 보기 :<br>
|
||||
마우스 우 클릭 → 갤러리 정보...<br>
|
||||
Click thumbnail</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3>검색 중 중단</h3>
|
||||
<ul>
|
||||
<li>검색 버튼 한번 더 클릭</li>
|
||||
</ul>
|
||||
|
||||
<h3>파일이 제대로 다운로드 되지 않는 경우 (체크섬 오프로드 문제)</h3>
|
||||
<ul>
|
||||
<li>권장 (속도 떨어짐) :<br>
|
||||
스레드 갯수를 낮게 조절합니다.</li>
|
||||
<li>비권장 (속도 떨어지지 않음, 보안 문제 발생 가능) :<br>
|
||||
제어판 → 시스템 → 하드웨어 → 장치관리자 → 네트워크 어댑터 → 무슨무슨 컨트롤러 → 우클릭 후 속성 → 고급 탭 → 무슨무슨 오프로드를 모두 '사용 안 함'으로 설정 → 확인<br>
|
||||
조금 기다리면 인터넷이 다시 연결됩니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Searcher 검색 데이터 업데이트</h3>
|
||||
<ul>
|
||||
<li>Searcher - 메뉴 - 데이터 다운로드...</li>
|
||||
<li>데이터를 다시 다운로드할 때마다 서버에서 최신 데이터를 가져옵니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Scripts</h3>
|
||||
<ul>
|
||||
<li>Tools - Import script...</li>
|
||||
<li>Execute python script.</li>
|
||||
<li>다운로드 스크립트를 직접 만들어 추가하는 등 다양한 작업을 할 수 있습니다.</li>
|
||||
<li>스크립트 파일 (*.hds) 은 텍스트에디터(메모장 등)로 수정할 수 있습니다.</li>
|
||||
<li>스크립트 파일들을 프로그램에 드래그 & 드랍해서 실행시킬 수도 있습니다.</li>
|
||||
<li>실행파일 경로에 scripts 폴더 만들고 스크립트 파일 (*.hds) 넣으면 시작할 때 자동으로 실행합니다.</li>
|
||||
<li>Download scripts :<br>
|
||||
<a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/Scripts">https://github.com/KurtBestor/Hitomi-Downloader/wiki/Scripts</a></li>
|
||||
<li>How to write a script :<br>
|
||||
<a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/How-to-write-a-script">https://github.com/KurtBestor/Hitomi-Downloader/wiki/How-to-write-a-script</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Bypass DPI</h3>
|
||||
<ul>
|
||||
<li>Options - Preferences - Advanced Settings - Bypass DPI</li>
|
||||
<li><a href="https://github.com/ValdikSS/GoodbyeDPI">GoodbyeDPI</a>To block/ bypass DPI (Deep Packet Inspection).</li>
|
||||
</ul>
|
||||
|
||||
<h3>Load cookies</h3>
|
||||
<ul>
|
||||
<li>Options - Preferences - Advanced Setting - Cookies - Load...</li>
|
||||
<li>Cookies exported from browser extensions can be loaded. (Netscape HTTP Cookie File)</li>
|
||||
<li>Using these cookies, Hitomi Downloader can access login-required pages.</li>
|
||||
<li>Chrome extension: <a href="https://chrome.google.com/webstore/detail/cookiestxt/njabckikapfpffapmjgojcnbfjonfjfg">cookies.txt</a>, <a href="https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid">Get cookies.txt</a></li>
|
||||
<li>Firefox extension: <a href="https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/">cookies.txt</a></li>
|
||||
<li>Edge extension: <a href="https://microsoftedge.microsoft.com/addons/detail/get-cookiestxt/helleheikohejgehaknifdkcfcmceeip">Get cookies.txt</a></li>
|
||||
<li>Expired cookies are shown in gray.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Built-in Web browser</h3>
|
||||
<ul>
|
||||
<li>Options - Preferences - Advanced Setting - Built-in Web browser - View</li>
|
||||
<li>Used to read pages that require JS rendering.</li>
|
||||
<li>You can use this to update cookies.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Chrome Extension</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/Chrome-Extension">Hitomi Downloader</a></li>
|
||||
<li>Functions:<br>
|
||||
Download sites that require the extension<br>
|
||||
Update cookies
|
||||
</ul>
|
||||
|
||||
<h3>Save</h3>
|
||||
<ul>
|
||||
<li>Save & Quit :<br>
|
||||
Quit with saving settings and currently added lists</li>
|
||||
<li>Quit :<br>
|
||||
Quit without saving</li>
|
||||
<li>When launching the app, it starts with the most recent saved state.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Shortcuts</h3>
|
||||
<ul>
|
||||
<li>Alt + D : Address bar ↔ toggle task list</li>
|
||||
<li>Ctrl + 1 ~ 7 : Set tags in tasks</li>
|
||||
<li>Ctrl + Tab : Show/Hide Toolbar</li>
|
||||
<li>Ctrl + - / + : Thumbnail size adjustment</li>
|
||||
<li>Ctrl + Scroll : Thumbnail size adjustment</li>
|
||||
<li>Space : Expand/Collapse Group(s)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Feedback</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader/issues">https://github.com/KurtBestor/Hitomi-Downloader/issues</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Etc.</h3>
|
||||
<ul>
|
||||
<li>Please use the downloaded files for personal use only.</li>
|
||||
<li>Please look for further updates at Help - About - Bottom button.</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,303 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
{head}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p align="right"><br></p>
|
||||
<h1 align="center">사용법</h1>
|
||||
<p align="right">{date}</p>
|
||||
<br>
|
||||
|
||||
<h3>지원 사이트</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader#supported-sites">https://github.com/KurtBestor/Hitomi-Downloader#supported-sites</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>주소 바</h3>
|
||||
<ul>
|
||||
<li>주소를 입력하고 다운로드 버튼을 눌러 다운로드 합니다.</li>
|
||||
<li>예시:<br>
|
||||
https://hitomi.la/galleries/123.html<br>
|
||||
https://hitomi.la/reader/123.html<br>
|
||||
https://e-hentai.org/g/123/356dfa74ce/<br>
|
||||
https://e-hentai.org/s/600e752112/123-4<br>
|
||||
https://hiyobi.me/reader/123<br>
|
||||
123<br>
|
||||
<br>
|
||||
https://www.pixiv.net/users/11<br>
|
||||
https://www.pixiv.net/member_illust.php?id=11<br>
|
||||
https://pixiv.me/pixiv<br>
|
||||
pixiv_11<br>
|
||||
<br>
|
||||
https://twitter.com/TensorFlow<br>
|
||||
@TensorFlow<br>
|
||||
<br>
|
||||
https://www.instagram.com/user_name<br>
|
||||
insta_user_name<br>
|
||||
<br>
|
||||
https://username.deviantart.com<br>
|
||||
deviant_username
|
||||
</li>
|
||||
<li>여러 주소 추가 예시:<br>
|
||||
123, 125, 126<br>
|
||||
123 125 126
|
||||
</li>
|
||||
<li>히토미에 없는 번호는 자동으로 E(x)Hentai 주소로 변환됩니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>저장</h3>
|
||||
<ul>
|
||||
<li>설정과 현재 추가된 목록을 저장</li>
|
||||
<li>파일 - 저장</li>
|
||||
<li>[Ctrl + S] 키</li>
|
||||
</ul>
|
||||
|
||||
<h3>검색기</h3>
|
||||
<ul>
|
||||
<li>갤러리를 검색</li>
|
||||
<li>메뉴 - 검색기...</li>
|
||||
<li>[Ctrl + F] 키</li>
|
||||
</ul>
|
||||
|
||||
<h3>다운로더 작업</h3>
|
||||
<ul>
|
||||
<li>첫 번째 이미지 열기:<br>
|
||||
마우스 우 클릭 → [첫 번째 파일 열기]<br>
|
||||
섬네일 클릭<br>
|
||||
더블클릭<br>
|
||||
[Enter] 키
|
||||
</li>
|
||||
<li>여러 작업 제거:<br>
|
||||
여러 작업 선택 → [Del] 키
|
||||
</li>
|
||||
<li>여러 파일 삭제:<br>
|
||||
여러 작업 선택 → 마우스 우 클릭 → [파일 삭제]<br>
|
||||
여러 작업 선택 → [Shift] + [Del] 키
|
||||
</li>
|
||||
<li>완료된 작업 모두 제거:<br>
|
||||
마우스 우 클릭 → [완료된 작업 모두 제거]<br>
|
||||
잠기지 않은 모든 완료된 작업을 목록에서 제거합니다.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>다운로더 작업 색</h3>
|
||||
<ul>
|
||||
<li>밝은 회색: 대기 중 or 읽는 중</li>
|
||||
<li>어두운 회색: 다운로드 중</li>
|
||||
<li>초록색: 다운로드를 성공적으로 마침</li>
|
||||
<li>주황색: 다운로드는 완료했지만 완전하지 않음</li>
|
||||
<li> 빨간색: 실패 or 잘못된 입력</li>
|
||||
</ul>
|
||||
|
||||
<h3>다운로더 작업 목록 순서 변경</h3>
|
||||
<ul>
|
||||
<li>마우스 휠버튼으로 드래그&드롭 하면 순서를 바꿀 수 있습니다.</li>
|
||||
<li>[Ctrl + ↑], [Ctrl + ↓], [Ctrl + Home], [Ctrl + End] 키로 옮길 수도 있습니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>다운로더 작업 목록 필터링</h3>
|
||||
<ul>
|
||||
<li>다운로더 창 좌측 하단의 필터 아이콘을 클릭하고 입력</li>
|
||||
<li>해당 문자가 포함된 제목을 가진 작업만 보입니다.</li>
|
||||
<li>"type:youtube" 와 같이 타입으로 필터링 할 수도 있습니다.</li>
|
||||
<li>"dup:" 를 입력하면 중복된 작업만 보이고,</li>
|
||||
<li>"rem:" 를 입력하면 실제 파일이 삭제된 작업만 보입니다.</li>
|
||||
<li>"tag:glasses" 와 같이 태그로 필터링 할 수 있습니다.</li>
|
||||
<li>"comment:xxx" 와 같이 코멘트로 필터링 할 수 있습니다.</li>
|
||||
<li>"bad:" 를 입력하면 불완전한 작업만 보입니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>검색기 검색 목록 필터링</h3>
|
||||
<ul>
|
||||
<li>검색기 좌측 하단의 필터 모양 아이콘을 클릭하고 내용 입력</li>
|
||||
<li>해당 문자가 포함된 제목, 작가, 그룹, 태그, 언어를 가진 갤러리만 보입니다.</li>
|
||||
<li>"title:A" 를 입력하면 A라는 문자가 포함된 제목을 가진 갤러리만 보입니다.</li>
|
||||
<li>"artist:A", "group:A", "tag:A", "lang:A" 과 같이 필터링 할 수도 있습니다.</li>
|
||||
<li>"p<100" 을 입력하면 100 페이지 미만인 갤러리만 보입니다.</li>
|
||||
<li>"p>100" 을 입력하면 100 페이지 초과인 갤러리만 보입니다.</li>
|
||||
<li>"done:o" 를 입력하면 이미 다운로드한 갤러리만 보입니다.</li>
|
||||
<li>"done:x" 를 입력하면 아직 다운로드하지 않은 갤러리만 보입니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>페이지 지정 다운로드</h3>
|
||||
<ul>
|
||||
<p><img src=":/icons/down_menu" /></p>
|
||||
<li>지정한 페이지만 다운로드합니다.</li>
|
||||
<li>예시:<br>
|
||||
~ 100 → 앞 100 페이지<br>
|
||||
-100 ~ → 뒤 100 페이지<br>
|
||||
1, 10 ~ 20, -1 → 첫 페이지, 10 ~ 20 페이지, 마지막 페이지</li>
|
||||
</ul>
|
||||
|
||||
<h3>선택 화 다운로드</h3>
|
||||
<ul>
|
||||
<p><img src=":/icons/down_menu_chapter" /></p>
|
||||
<li>선택한 화만 다운로드합니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>환경 설정</h3>
|
||||
<ul>
|
||||
<li>옵션 → 설정 (Preferences...)</li>
|
||||
</ul>
|
||||
|
||||
<h3>스레드</h3>
|
||||
<ul>
|
||||
<li>한 작업 당 동시 다운로드 수.</li>
|
||||
<li>컴퓨터 사양이나 인터넷 상태가 좋지 않다면 갯수를 좀 더 내립니다.</li>
|
||||
<li>어떻게 설정해야 할지 잘 모르겠으면 그대로 두면 됩니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>간단 검색 - 검색어</h3>
|
||||
<ul>
|
||||
<li>구글 검색하듯이 검색하면 됩니다.</li>
|
||||
<li>예시 :<br>
|
||||
maid<br>
|
||||
maid glasses korean<br>
|
||||
maid -schoolgirl<br>
|
||||
maid n/a<br>
|
||||
maid (korean + n/a)</li>
|
||||
</ul>
|
||||
|
||||
<h3>고급 검색 - 제목 (Title)</h3>
|
||||
<ul>
|
||||
<li>예시 :<br>
|
||||
maid<br>
|
||||
maid -schoolgirl<br>
|
||||
maid glasses</li>
|
||||
</ul>
|
||||
|
||||
<h3>고급 검색 - 작가 (Artists)</h3>
|
||||
<ul>
|
||||
<li>예시 :<br>
|
||||
sekiya asami<br>
|
||||
sekiya asami + amezawa koma</li>
|
||||
</ul>
|
||||
|
||||
<h3>고급 검색 - 캐릭터 (Characters)</h3>
|
||||
<ul>
|
||||
<li>예시 :<br>
|
||||
chino kafuu<br>
|
||||
chino kafuu + kokoa hoto<br>
|
||||
hino kafuu, kokoa hoto</li>
|
||||
</ul>
|
||||
|
||||
<h3>고급 검색 - 태그 (Tags)</h3>
|
||||
<ul>
|
||||
<li>예시 :<br>
|
||||
female:maid<br>
|
||||
female:maid, -female:schoolgirl uniform</li>
|
||||
</ul>
|
||||
|
||||
<h3>검색기 목록</h3>
|
||||
<ul>
|
||||
<li>여러 갤러리 다운로드 :<br>
|
||||
여러 개 선택 → 마우스 우클릭 → [다운로드]<br>
|
||||
여러 개 선택 → [Enter] 키</li>
|
||||
|
||||
<li>갤러리 정보 보기 :<br>
|
||||
마우스 우 클릭 → 갤러리 정보...<br>
|
||||
섬네일 클릭</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3>검색 중 중단</h3>
|
||||
<ul>
|
||||
<li>검색 버튼 한번 더 클릭</li>
|
||||
</ul>
|
||||
|
||||
<h3>파일이 제대로 다운로드 되지 않는 경우 (체크섬 오프로드 문제)</h3>
|
||||
<ul>
|
||||
<li>권장 (속도 떨어짐) :<br>
|
||||
스레드 갯수를 낮게 조절합니다.</li>
|
||||
<li>비권장 (속도 떨어지지 않음, 보안 문제 발생 가능) :<br>
|
||||
제어판 → 시스템 → 하드웨어 → 장치관리자 → 네트워크 어댑터 → 무슨무슨 컨트롤러 → 우클릭 후 속성 → 고급 탭 → 무슨무슨 오프로드를 모두 '사용 안 함'으로 설정 → 확인<br>
|
||||
조금 기다리면 인터넷이 다시 연결됩니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>검색기 검색 데이터 업데이트</h3>
|
||||
<ul>
|
||||
<li>검색기 - 메뉴 - 데이터 다운로드...</li>
|
||||
<li>데이터를 다시 다운로드할 때마다 서버에서 최신 데이터를 가져옵니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>스크립트</h3>
|
||||
<ul>
|
||||
<li>도구 - 스크립트 가져오기...</li>
|
||||
<li>파이썬 스크립트를 실행합니다.</li>
|
||||
<li>다운로드 스크립트를 직접 만들어 추가하는 등 다양한 작업을 할 수 있습니다.</li>
|
||||
<li>스크립트 파일 (*.hds) 은 텍스트에디터(메모장 등)로 수정할 수 있습니다.</li>
|
||||
<li>스크립트 파일들을 프로그램에 드래그 & 드랍해서 실행시킬 수도 있습니다.</li>
|
||||
<li>실행파일 경로에 scripts 폴더 만들고 스크립트 파일 (*.hds) 넣으면 시작할 때 자동으로 실행합니다.</li>
|
||||
<li>스크립트 다운로드 :<br>
|
||||
<a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/Scripts">https://github.com/KurtBestor/Hitomi-Downloader/wiki/Scripts</a></li>
|
||||
<li>스크립트 작성 방법 :<br>
|
||||
<a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/How-to-write-a-script">https://github.com/KurtBestor/Hitomi-Downloader/wiki/How-to-write-a-script</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>DPI 우회</h3>
|
||||
<ul>
|
||||
<li>옵션 - 설정 - 고급 설정 - DPI 우회</li>
|
||||
<li><a href="https://github.com/ValdikSS/GoodbyeDPI">GoodbyeDPI</a>를 이용해서 DPI 차단 / 우회를 합니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>쿠키 불러오기</h3>
|
||||
<ul>
|
||||
<li>옵션 - 설정 - 고급 설정 - 쿠키 - 불러오기...</li>
|
||||
<li>브라우저 확장프로그램에서 추출한 쿠키를 불러올 수 있습니다. (Netscape HTTP Cookie File)</li>
|
||||
<li>이 쿠키를 이용해서 로그인이 필요한 페이지에 접근할 수 있습니다.</li>
|
||||
<li>크롬 확장프로그램: <a href="https://chrome.google.com/webstore/detail/cookiestxt/njabckikapfpffapmjgojcnbfjonfjfg">cookies.txt</a>, <a href="https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid">Get cookies.txt</a></li>
|
||||
<li>파이어폭스 확장프로그램: <a href="https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/">cookies.txt</a></li>
|
||||
<li>엣지 확장프로그램: <a href="https://microsoftedge.microsoft.com/addons/detail/get-cookiestxt/helleheikohejgehaknifdkcfcmceeip">Get cookies.txt</a></li>
|
||||
<li>만료된 쿠키는 회색으로 표시됩니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>내장 웹브라우저</h3>
|
||||
<ul>
|
||||
<li>옵션 - 설정 - 고급 설정 - 내장 웹브라우저 - 보기</li>
|
||||
<li>JS 렌더가 필요한 사이트를 읽는데 주로 사용됩니다.</li>
|
||||
<li>쿠키를 업데이트하는 데 사용할 수 있습니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>크롬 확장 프로그램</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/Chrome-Extension">Hitomi Downloader</a></li>
|
||||
<li>기능:<br>
|
||||
확장프로그램이 필요한 사이트의 다운로드<br>
|
||||
쿠키 업데이트
|
||||
</ul>
|
||||
|
||||
<h3>종료</h3>
|
||||
<ul>
|
||||
<li>저장 & 종료 :<br>
|
||||
설정과 현재 추가된 목록을 저장하고 종료</li>
|
||||
<li>종료 :<br>
|
||||
저장하지 않고 종료</li>
|
||||
<li>다시 켤 때, 가장 최근에 저장한 상태로 시작합니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>기타 단축키</h3>
|
||||
<ul>
|
||||
<li>Alt + D : 주소바 ↔ 작업 목록 전환</li>
|
||||
<li>Ctrl + 1 ~ 7 : 작업에 태그 표시</li>
|
||||
<li>Ctrl + Tab : 도구창 숨기기</li>
|
||||
<li>Ctrl + - / + : 썸네일 크기 조절</li>
|
||||
<li>Ctrl + 스크롤 : 썸네일 크기 조절</li>
|
||||
<li>Space : 그룹 열고 닫기</li>
|
||||
</ul>
|
||||
|
||||
<h3>피드백</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader/issues">https://github.com/KurtBestor/Hitomi-Downloader/issues</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>기타</h3>
|
||||
<ul>
|
||||
<li>다운로드한 파일들은 개인 소장 용도로만 사용해 주세요.</li>
|
||||
<li>자세한 업데이트 사항은 도움말 - 정보 - 우측하단 버튼을 눌러 확인해 주세요.</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,295 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
{head}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p align="right"><br></p>
|
||||
<h1 align="center">Jak urzywać</h1>
|
||||
<p align="right">{date}</p>
|
||||
<br>
|
||||
|
||||
<h3>Obsługiwane witryny</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader#supported-sites">https://github.com/KurtBestor/Hitomi-Downloader#supported-sites</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Pasek adresu</h3>
|
||||
<ul>
|
||||
<li>Wpisz kilka adresów URL w pasku adresu i kliknij przycisk Pobierz, aby pobrać pliki</li>
|
||||
<li>Przykłady:<br>
|
||||
https://hitomi.la/galleries/123.html<br>
|
||||
https://hitomi.la/reader/123.html<br>
|
||||
https://e-hentai.org/g/123/356dfa74ce/<br>
|
||||
https://e-hentai.org/s/600e752112/123-4<br>
|
||||
https://hiyobi.me/reader/123<br>
|
||||
123<br>
|
||||
<br>
|
||||
https://www.pixiv.net/users/11<br>
|
||||
https://www.pixiv.net/member_illust.php?id=11<br>
|
||||
https://pixiv.me/pixiv<br>
|
||||
pixiv_11<br>
|
||||
<br>
|
||||
https://twitter.com/TensorFlow<br>
|
||||
@TensorFlow<br>
|
||||
<br>
|
||||
https://www.instagram.com/user_name<br>
|
||||
insta_user_name<br>
|
||||
<br>
|
||||
https://username.deviantart.com<br>
|
||||
deviant_username
|
||||
</li>
|
||||
<li>Przykłady dla wielu adresów URL:<br>
|
||||
123, 125, 126<br>
|
||||
123 125 126
|
||||
</li>
|
||||
<li>Liczby spoza Hitomi są automatycznie konwertowane na adresy E(x)Hentai.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Zapisz</h3>
|
||||
<ul>
|
||||
<li>Zapisz preferencje i aktualnie dodane zadania.</li>
|
||||
<li>Zadania - Zapisz</li>
|
||||
<li>[Ctrl + S] skrót</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwarka</h3>
|
||||
<ul>
|
||||
<li>Przeszukaj galerie</li>
|
||||
<li>Narzędzia - Wyszukiwarka...</li>
|
||||
<li>[Ctrl + F] skrót</li>
|
||||
</ul>
|
||||
|
||||
<h3>Zadania Downloadera</h3>
|
||||
<ul>
|
||||
<li>Otwórz pierwszy plik:<br>
|
||||
Prawy przycisk myszy → [Otwórz pierwszy plik]<br>
|
||||
Kliknij podgląd<br>
|
||||
Podwójne kliknięcie<br>
|
||||
[Enter] klawisz
|
||||
</li>
|
||||
<li>Usuń wiele zadań:<br>
|
||||
Wybierz wiele zadań → [Del] klawisz
|
||||
</li>
|
||||
<li>Delete multiple files:<br>
|
||||
Wybierz wiele zadań → Prawy przycisk myszy → [Usuń pliki]<br>
|
||||
Wybierz wiele zadań → [Shift] + [Del] klawisz
|
||||
</li>
|
||||
<li>Usuń wszystkie zakończone zadania:<br>
|
||||
Prawy przycisk myszy → [Usuń wszystkie zakończone zadania]<br>
|
||||
Usuń wszystkie kompletne zadania, które nie są zablokowane.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Kolory zadań</h3>
|
||||
<ul>
|
||||
<li>Jasnoszary: Oczekiwanie lub czytanie</li>
|
||||
<li>Ciemnoszary: Pobieranie</li>
|
||||
<li>Zielony: Pobrano w całości</li>
|
||||
<li>Pomarańczowy: Pobrano niekompletnie</li>
|
||||
<li> Czerwony: Nieudane lub nieważne</li>
|
||||
</ul>
|
||||
|
||||
<h3>Zmień kolejność zadań</h3>
|
||||
<ul>
|
||||
<li>Przeciągnij i upuść za pomocą przycisku kółka myszy, aby zmienić kolejność.</li>
|
||||
<li>Działają również następujące polecenia: [Ctrl + ↑], [Ctrl + ↓], [Ctrl + Home], [Ctrl + End].</li>
|
||||
</ul>
|
||||
|
||||
<h3>Filtrowanie Listy zadań</h3>
|
||||
<ul>
|
||||
<li>Kliknij ikonę filtra w lewym dolnym rogu okna pobierania i pisz.</li>
|
||||
<li>Widoczne są tylko zadania, których tytuł zawiera to słowo.</li>
|
||||
<li>Można również filtrować według typu, na przykład "type:youtube".</li>
|
||||
<li>Jeśli wpiszesz "dup:", zobaczysz tylko zduplikowane zadania.</li>
|
||||
<li>Wpisz "rem:" aby zobaczyć tylko te zadania, które zostały usunięte z fizycznego magazynu.</li>
|
||||
<li>Można filtrować według tagów, na przykład "tag:glasses".</li>
|
||||
<li>Można filtrować według komentarzy, na przykład "comment:xxxx".</li>
|
||||
<li>Wpisz "bad:" aby wyświetlać tylko niezakończone zadania.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwarka 검색 목록 필터링</h3>
|
||||
<ul>
|
||||
<li>Wyszukiwarka 좌측 하단의 필터 모양 아이콘을 클릭하고 내용 입력</li>
|
||||
<li>해당 문자가 포함된 제목, 작가, 그룹, 태그, 언어를 가진 갤러리만 보입니다.</li>
|
||||
<li>"title:A" 를 입력하면 A라는 문자가 포함된 제목을 가진 갤러리만 보입니다.</li>
|
||||
<li>"artist:A", "group:A", "tag:A", "lang:A" 과 같이 필터링 할 수도 있습니다.</li>
|
||||
<li>"p<100" 을 입력하면 100 페이지 미만인 갤러리만 보입니다.</li>
|
||||
<li>"p>100" 을 입력하면 100 페이지 초과인 갤러리만 보입니다.</li>
|
||||
<li>"done:o" 를 입력하면 이미 다운로드한 갤러리만 보입니다.</li>
|
||||
<li>"done:x" 를 입력하면 아직 다운로드하지 않은 갤러리만 보입니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>페이지 지정 다운로드</h3>
|
||||
<ul>
|
||||
<p><img src=":/icons/down_menu" /></p>
|
||||
<li>지정한 페이지만 다운로드합니다.</li>
|
||||
<li>Przykłady:<br>
|
||||
~ 100 → 앞 100 페이지<br>
|
||||
-100 ~ → 뒤 100 페이지<br>
|
||||
1, 10 ~ 20, -1 → 첫 페이지, 10 ~ 20 페이지, 마지막 페이지</li>
|
||||
</ul>
|
||||
|
||||
<h3>선택 화 다운로드</h3>
|
||||
<ul>
|
||||
<p><img src=":/icons/down_menu_chapter" /></p>
|
||||
<li>선택한 화만 다운로드합니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>환경 설정</h3>
|
||||
<ul>
|
||||
<li>Opcje → Preferencje</li>
|
||||
</ul>
|
||||
|
||||
<h3>스레드</h3>
|
||||
<ul>
|
||||
<li>Równoległe pobieranie danych dla każdego zadania.</li>
|
||||
<li>컴퓨터 사양이나 인터넷 상태가 좋지 않다면 갯수를 좀 더 내립니다.</li>
|
||||
<li>Jeśli nie masz pewności co do tego ustawienia, zachowaj wartości domyślne.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Proste wyszukiwanie - Wyszukiwanie słów kluczowych</h3>
|
||||
<ul>
|
||||
<li>Szukaj jak Google.</li>
|
||||
<li>Przykłady:<br>
|
||||
maid<br>
|
||||
maid glasses korean<br>
|
||||
maid -schoolgirl<br>
|
||||
maid n/a<br>
|
||||
maid (korean + n/a)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwanie zaawansowane - Tytuł</h3>
|
||||
<ul>
|
||||
<li>Przykłady:<br>
|
||||
maid<br>
|
||||
maid -schoolgirl<br>
|
||||
maid glasses</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwanie zaawansowane - Artyści</h3>
|
||||
<ul>
|
||||
<li>Przykłady:<br>
|
||||
sekiya asami<br>
|
||||
sekiya asami + amezawa koma</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwanie zaawansowane - Postacie</h3>
|
||||
<ul>
|
||||
<li>Przykłady:<br>
|
||||
chino kafuu<br>
|
||||
chino kafuu + kokoa hoto<br>
|
||||
hino kafuu, kokoa hoto</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwanie zaawansowane - Znaczniki</h3>
|
||||
<ul>
|
||||
<li>Przykłady :<br>
|
||||
female:maid<br>
|
||||
female:maid, -female:schoolgirl uniform</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwarka 목록</h3>
|
||||
<ul>
|
||||
<li>여러 갤러리 다운로드 :<br>
|
||||
여러 개 선택 → 마우스 우클릭 → [다운로드]<br>
|
||||
여러 개 선택 → [Enter] key</li>
|
||||
|
||||
<li>갤러리 정보 보기 :<br>
|
||||
마우스 우 클릭 → 갤러리 정보...<br>
|
||||
Kliknij miniaturkę</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3>검색 중 중단</h3>
|
||||
<ul>
|
||||
<li>검색 버튼 한번 더 클릭</li>
|
||||
</ul>
|
||||
|
||||
<h3>파일이 제대로 다운로드 되지 않는 경우 (체크섬 오프로드 문제)</h3>
|
||||
<ul>
|
||||
<li>권장 (속도 떨어짐) :<br>
|
||||
스레드 갯수를 낮게 조절합니다.</li>
|
||||
<li>비권장 (속도 떨어지지 않음, 보안 문제 발생 가능) :<br>
|
||||
제어판 → 시스템 → 하드웨어 → 장치관리자 → 네트워크 어댑터 → 무슨무슨 컨트롤러 → 우클릭 후 속성 → 고급 탭 → 무슨무슨 오프로드를 모두 '사용 안 함'으로 설정 → 확인<br>
|
||||
조금 기다리면 인터넷이 다시 연결됩니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wyszukiwarka 검색 데이터 업데이트</h3>
|
||||
<ul>
|
||||
<li>Wyszukiwarka - 메뉴 - 데이터 다운로드...</li>
|
||||
<li>데이터를 다시 다운로드할 때마다 서버에서 최신 데이터를 가져옵니다.</li>
|
||||
</ul>
|
||||
|
||||
<h3>스크립트</h3>
|
||||
<ul>
|
||||
<li>메뉴 - 스크립트 가져오기...</li>
|
||||
<li>파이썬 스크립트를 실행합니다.</li>
|
||||
<li>다운로드 스크립트를 직접 만들어 추가하는 등 다양한 작업을 할 수 있습니다.</li>
|
||||
<li>스크립트 파일 (*.hds) 은 텍스트에디터(메모장 등)로 수정할 수 있습니다.</li>
|
||||
<li>스크립트 파일들을 프로그램에 드래그 & 드랍해서 실행시킬 수도 있습니다.</li>
|
||||
<li>실행파일 경로에 scripts 폴더 만들고 스크립트 파일 (*.hds) 넣으면 시작할 때 자동으로 실행합니다.</li>
|
||||
<li>스크립트 다운로드 :<br>
|
||||
<a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/Scripts">https://github.com/KurtBestor/Hitomi-Downloader/wiki/Scripts</a></li>
|
||||
<li>스크립트 작성 방법 :<br>
|
||||
<a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/How-to-write-a-script">https://github.com/KurtBestor/Hitomi-Downloader/wiki/How-to-write-a-script</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Pomiń DPI</h3>
|
||||
<ul>
|
||||
<li>Opcje - Preferencje - Ustawienia zaawansowane - Pomiń DPI</li>
|
||||
<li><a href="https://github.com/ValdikSS/GoodbyeDPI">GoodbyeDPI</a>To block/ bypass DPI (Deep Packet Inspection).</li>
|
||||
</ul>
|
||||
|
||||
<h3>Load cookies</h3>
|
||||
<ul>
|
||||
<li>Options - Preferencje - Ustawienia zaawansowane - Pliki Cookie - Wczytaj...</li>
|
||||
<li>Można wczytywać pliki cookie wyeksportowane z rozszerzeń przeglądarki. (Plik cookie Netscape)</li>
|
||||
<li>Dzięki tym plikom cookie program Hitomi Downloader może uzyskać dostęp do stron wymagających zalogowania.</li>
|
||||
<li>Rozszerzenie Chrome: <a href="https://chrome.google.com/webstore/detail/cookiestxt/njabckikapfpffapmjgojcnbfjonfjfg">cookies.txt</a>, <a href="https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid">Get cookies.txt</a></li>
|
||||
<li>Rozszerzenie Firefox: <a href="https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/">cookies.txt</a>
|
||||
<li>Wygasłe pliki cookie są zaznaczone kolorem szarym.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Rozszerzenie Chrome</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader/wiki/Chrome-Extension">Hitomi Downloader</a></li>
|
||||
<li>기능:<br>
|
||||
확장프로그램이 필요한 사이트의 다운로드<br>
|
||||
쿠키 업데이트
|
||||
</ul>
|
||||
|
||||
<h3>Zapisz</h3>
|
||||
<ul>
|
||||
<li>Zapisz && Wyjdź :<br>
|
||||
Zapisz ustawienia i aktualnie dodaną listę i wyjdź</li>
|
||||
<li>Wyjdź :<br>
|
||||
Wyjdź bez zapisywania</li>
|
||||
<li>Podczas uruchamiania aplikacji rozpoczyna się ona od ostatnio zapisanego stanu.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Skróty</h3>
|
||||
<ul>
|
||||
<li>Alt + D : Pasek adresu ↔ przełączanie listy zadań</li>
|
||||
<li>Ctrl + 1 ~ 7 : 작업에 태그 표시</li>
|
||||
<li>Ctrl + Tab : Pokaż/Ukryj pasek narzędzi</li>
|
||||
<li>Ctrl + - / + : Regulacja rozmiaru miniatur</li>
|
||||
<li>Ctrl + Scroll : Regulacja rozmiaru miniatur</li>
|
||||
<li>Space : Rozwiń/Zwiń Grupę(y)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Feedback</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/KurtBestor/Hitomi-Downloader/issues">https://github.com/KurtBestor/Hitomi-Downloader/issues</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Itd.</h3>
|
||||
<ul>
|
||||
<li>Proszę używać pobranych plików wyłącznie do użytku osobistego.</li>
|
||||
<li>Więcej informacji znajdziesz w Pomoc - Informacje - Przycisk dolny.</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "E(x)Hentai login cookies required",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "Your IP is temporarily locked. Please try again in a moment",
|
||||
"PDF 생성": "Create PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "Please type some URLs",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "Press [Download] to download. Press [OK] to exit.",
|
||||
"reCAPTCHA 풀기": "Solve reCAPTCHA",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "Advanced search",
|
||||
"고정": "Pin",
|
||||
"고정 해제": "Unpin",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "Normal",
|
||||
"그룹": "Groups",
|
||||
"그룹 (Groups)": "Groups",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "Group name",
|
||||
"그룹 합쳐서 불러오기": "Load all groups together",
|
||||
"그룹명 복사 (&G)": "Copy group names (&G)",
|
||||
"그룹으로 이동": "Move to group",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "Groups can't contain groups.",
|
||||
"그룹이 없습니다": "No groups",
|
||||
"기본": "Default",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "Deep model",
|
||||
"끝내기": "Quit",
|
||||
"날짜": "Date",
|
||||
"낮음": "Low",
|
||||
"내보내기": "Export",
|
||||
"내보내기 실패": "Failed to export",
|
||||
"내용 보기": "View script",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "Built-in image viewer",
|
||||
"녹화 중...": "Recording...",
|
||||
"녹화 중지": "Stop recording",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "Applies to other video sites also.",
|
||||
"다시 시작 (&S)": "Restart (&S)",
|
||||
"다시 시작 실패; 복구됨": "Failed to retry; Reverted",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "Remove automatically after complete download",
|
||||
"다운로드 일시 정지 / 재개": "Download Pause / Resume",
|
||||
"다운로드가 완료되면 알림": "Show notification when downloading is finished",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "Only downloaded galleries",
|
||||
"다음 검색시 더 빠르게 검색": "Improve performance the next time",
|
||||
"다크 모드": "Dark mode",
|
||||
"단일 파일": "Single file",
|
||||
"단축키": "Shortcuts",
|
||||
"단축키 내보내기 성공": "Shortcuts are successfully exported",
|
||||
"단축키 불러오기 성공": "Shortcuts are successfully imported",
|
||||
"단축키 수정": "Edit shortcuts",
|
||||
"단축키 초기화 성공": "Shortcuts are successfully reseted",
|
||||
"닫기 버튼으로 트레이로 최소화": "Close button minimizes to system tray",
|
||||
"대기 중...": "Waiting...",
|
||||
"데이터 분류 중...": "Classifying data...",
|
||||
|
@ -210,6 +222,7 @@
|
|||
"로컬 파일 감지": "Detect local files",
|
||||
"로컬 파일로부터 새 작업 만들기": "Create a new task from the local files",
|
||||
"를 북마크에서 지웠습니다": "is removed from bookmarks",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "Release note",
|
||||
"링크 열기": "Open link",
|
||||
"링크 주소 복사": "Copy link",
|
||||
|
@ -240,14 +253,15 @@
|
|||
"변경할 최소 크기": "Minimum size",
|
||||
"변환 중...": "Converting...",
|
||||
"보기": "View",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "Start at boot",
|
||||
"북마크": "Bookmarks",
|
||||
"북마크 가져오기": "Import Bookmarks",
|
||||
"북마크 가져오기 실패": "Failed to import bookmarks",
|
||||
"북마크 내보내기": "Export Bookmarks",
|
||||
"북마크 내보내기 실패": "Failed to export bookmarks",
|
||||
"북마크를 가져왔습니다": "Boomark imported",
|
||||
"북마크를 내보냈습니다": "Boomark exported",
|
||||
"북마크를 가져왔습니다": "Bookmarks are imported",
|
||||
"북마크를 내보냈습니다": "Bookmarks are exported",
|
||||
"북마크를 모두 삭제합니다.": "Remove all bookmarks.",
|
||||
"북마크를 모두 삭제했습니다.": "Removed all bookmarks.",
|
||||
"불러오기": "Load",
|
||||
|
@ -255,6 +269,7 @@
|
|||
"불완전한 작업 자동으로 다시 시작": "Restart incomplete tasks automatically",
|
||||
"붙여넣고 다운로드": "Paste and Download",
|
||||
"뷰어... (&V)": "&Viewer...",
|
||||
"브라우저로 보기": "Show in browser",
|
||||
"브라우저로 보기 (&B)": "Show in browser (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "Include similar images (Slow)",
|
||||
"비율 유지": "Aspect ratio",
|
||||
|
@ -288,9 +303,11 @@
|
|||
"설명": "Comment",
|
||||
"설정": "Preferences",
|
||||
"설정 (Preferences)": "Preferences",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "Success: {}\nFailed: {}",
|
||||
"수동": "Manual",
|
||||
"수동 업데이트": "Manual",
|
||||
"수정...": "Edit...",
|
||||
"순서 변경": "Reorder",
|
||||
"숨김 키": "Boss-Key",
|
||||
"숫자": "Number",
|
||||
|
@ -300,8 +317,12 @@
|
|||
"스크립트": "Script",
|
||||
"스크립트 가져오기": "Import script",
|
||||
"스크립트를 실행하시겠습니까?": "Are you sure you want to run this script?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "Reading stories...",
|
||||
"스토리 포함": "Include stories",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "Series",
|
||||
"시리즈 (Series)": "Series",
|
||||
"시리즈가 없습니다": "No series",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "Update",
|
||||
"업데이트 중...": "Updating...",
|
||||
"업데이트 체크 중...": "Checking for updates...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "Choose the program to use to open",
|
||||
"열기": "Open",
|
||||
"예(&Y)": "&Yes",
|
||||
|
@ -339,13 +361,14 @@
|
|||
"올바르지 않은 범위입니다": "Invalid range",
|
||||
"올바르지 않은 주소입니다": "Invalid URL",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "Invalid syntax error with filter",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "Options",
|
||||
"옵션 (Options)": "Options",
|
||||
"완료": "Finished",
|
||||
"완료된 작업 모두 제거": "Remove all complete tasks",
|
||||
"완료됨으로 표시": "Mark as complete",
|
||||
"완전 삭제": "Delete permanently",
|
||||
"우선순위": "priority",
|
||||
"우선순위": "Priority",
|
||||
"움짤": "Ugoira",
|
||||
"움짤 변환...": "Convert Ugoira...",
|
||||
"원본": "Original",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "Rename",
|
||||
"이름을 얻는 도중 실패했습니다": "Failed to read name",
|
||||
"이미 다운로드한 작품 제외": "Exclude already downloaded galleries",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "There is already a group that includes tasks.",
|
||||
"이미지": "Image",
|
||||
"이미지 정보 캐시": "Cache image infos",
|
||||
|
@ -396,6 +422,7 @@
|
|||
"작업": "Tasks",
|
||||
"작업 개수": "Number of tasks",
|
||||
"작업 수정": "Edit task",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "File size of tasks",
|
||||
"작업 정보": "Task info",
|
||||
|
@ -412,6 +439,7 @@
|
|||
"잠금": "Lock",
|
||||
"잠금 해제": "Unlock",
|
||||
"장": "pages",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "Low spec mode",
|
||||
"저장": "Save",
|
||||
"저장 && 종료": "Save && Quit",
|
||||
|
@ -429,6 +457,7 @@
|
|||
"정규식 문법": "Regex syntax",
|
||||
"정규식으로 찾기": "Search by Regex",
|
||||
"정렬 기준": "Sort by",
|
||||
"정말 단축키를 초기화하시겠습니까?": "Are you sure you want to reset shortcuts?",
|
||||
"정말 목록에서 제거하시겠습니까?": "Are you sure you want to remove from the list?",
|
||||
"정말 삭제하시겠습니까?": "Are you sure you want to delete?",
|
||||
"정말 종료하시겠습니까?": "Are you sure you want to quit?",
|
||||
|
@ -446,6 +475,7 @@
|
|||
"종료": "Quit",
|
||||
"종료 중...": "Quit...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "Drag horizontally: ±1\nDrag vertically: ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "URL or gallery id",
|
||||
"주소를 입력해주세요": "Please type some URLs",
|
||||
"중간": "Medium",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "Supported sites:",
|
||||
"지정한 페이지만 다운로드합니다.": "Download selected pages only.",
|
||||
"직접 다운로드": "Direct download",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "Reference: {} galleries",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "Please see: Help - How to use (F1) - Load cookies",
|
||||
"창 보이기 / 숨기기": "Show / Hide windows",
|
||||
"찾기...": "Finder...",
|
||||
"첫 번째 파일 열기": "Open the first file",
|
||||
"첫 번째 파일 열기 (&O)": "Open the first file (&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "First page, 10 ~ 20th pages, Last page",
|
||||
"체인지로그": "Changelog",
|
||||
"초기화": "Reset",
|
||||
"최대 다운로드 속도": "Maximum download speed",
|
||||
"최대 동시 작업": "Maximum parallel tasks",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "Minimize button minimizes to system tray",
|
||||
"추가한 날짜": "Date added",
|
||||
"취소": "Cancel",
|
||||
|
@ -478,10 +511,10 @@
|
|||
"코멘트 수정": "Edit comment",
|
||||
"쿠키": "Cookies",
|
||||
"쿠키 내보내기": "Export cookies",
|
||||
"쿠키 내보내기 성공": "Cookies successfully exported",
|
||||
"쿠키 내보내기 성공": "Cookies are successfully exported",
|
||||
"쿠키 불러오기": "Load cookies",
|
||||
"쿠키 불러오기 성공": "Cookies successfully imported",
|
||||
"쿠키 초기화 성공": "Cookies successfully cleared",
|
||||
"쿠키 불러오기 성공": "Cookies are successfully imported",
|
||||
"쿠키 초기화 성공": "Cookies are successfully cleared",
|
||||
"쿠키가 없습니다": "No cookies",
|
||||
"쿠키를 업데이트하세요": "Update your cookies",
|
||||
"크게": "Large",
|
||||
|
@ -489,6 +522,7 @@
|
|||
"크기 조절": "Resize",
|
||||
"크기 조절...": "Resizing...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Chrome extension link failed.",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "Classic",
|
||||
"클립보드에서 자동 추가": "Clipboard monitor",
|
||||
"타입": "Types",
|
||||
|
@ -499,6 +533,7 @@
|
|||
"태그 (Tags)": "Tags",
|
||||
"태그 :": "Tag :",
|
||||
"태그 설정": "Tag setting",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "No tags",
|
||||
"테마": "Theme",
|
||||
"토렌트 추가": "Add Torrent",
|
||||
|
@ -506,16 +541,18 @@
|
|||
"통계": "Statistics",
|
||||
"통과": "Pass",
|
||||
"투명도": "Opacity",
|
||||
"트래커 수정": "Edit trackers",
|
||||
"트레이로 전환되었습니다": "Minimized to system tray",
|
||||
"트레이로 최소화": "Minimize to system tray",
|
||||
"트레이에서 알림 보이기": "Show notifications in tray",
|
||||
"파일": "File",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "Delete files",
|
||||
"파일 삭제 (&X)": "Delete files (&X)",
|
||||
"파일 수": "Number of files",
|
||||
"파일 스캔 중": "Scanning files",
|
||||
"파일 유형 제외": "Exclude file types",
|
||||
"파일 크기": "File size",
|
||||
"파일 크기": "Filesize",
|
||||
"파일 형식": "Format",
|
||||
"파일 형식 물어보기": "Ask the format",
|
||||
"파일명": "Filename",
|
||||
|
@ -549,6 +586,8 @@
|
|||
"표지 검색...": "Search cover...",
|
||||
"프로그램 패스워드": "Program password",
|
||||
"프록시": "Proxy",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "Numerate files in playlists",
|
||||
"플레이리스트 한 폴더에 다운로드": "Download playlist to one folder",
|
||||
"플로팅 미리보기": "Floating preview",
|
||||
|
@ -558,6 +597,7 @@
|
|||
"하나 이상의 타입을 선택해주세요.": "Choose at least one type.",
|
||||
"하위폴더 포함": "Include subfolders",
|
||||
"학습 중...": "Learning...",
|
||||
"한 줄 당 한 트래커 URL": "One tracker URL per line",
|
||||
"한국어": "Korean",
|
||||
"항상 위": "Always on top",
|
||||
"해당 링크로 이동합니다": "Open link...",
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "E(x)Hentai login cookies required",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "Su dirección IP está bloqueada temporalmente. Inténtelo de nuevo en un rato",
|
||||
"PDF 생성": "Crea un PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "Por favor, introduzca algunas URL",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "Toque [Descargar] para descargar. Presione [OK] para salir.",
|
||||
"reCAPTCHA 풀기": "Resolver reCAPTCHA",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "Búsqueda avanzada",
|
||||
"고정": "Pin",
|
||||
"고정 해제": "Unpin",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "Normal",
|
||||
"그룹": "Grupos",
|
||||
"그룹 (Groups)": "Grupos",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "Nombre del grupo",
|
||||
"그룹 합쳐서 불러오기": "Load all groups together",
|
||||
"그룹명 복사 (&G)": "Copiar nombres de grupos (&G)",
|
||||
"그룹으로 이동": "Move to group",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "Groups can't contain groups.",
|
||||
"그룹이 없습니다": "No grupos",
|
||||
"기본": "Por defecto",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "Deep model",
|
||||
"끝내기": "Salir",
|
||||
"날짜": "Fecha",
|
||||
"낮음": "Low",
|
||||
"내보내기": "Exportación",
|
||||
"내보내기 실패": "Error al exportar",
|
||||
"내용 보기": "Ver el script",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "Visor de imagen integrado",
|
||||
"녹화 중...": "Recording...",
|
||||
"녹화 중지": "Stop recording",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "También se aplica a otros sitios de video.",
|
||||
"다시 시작 (&S)": "Reiniciar (&S)",
|
||||
"다시 시작 실패; 복구됨": "Failed to retry; Reverted",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "Eliminar automáticamente después de completar la descarga",
|
||||
"다운로드 일시 정지 / 재개": "Descargar Pausa / Retomar",
|
||||
"다운로드가 완료되면 알림": "Visualizar notificación cuando se complete la descarga",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "Only downloaded galleries",
|
||||
"다음 검색시 더 빠르게 검색": "Improve performance the next time",
|
||||
"다크 모드": "Dark mode",
|
||||
"단일 파일": "Single file",
|
||||
"단축키": "Shortcuts",
|
||||
"단축키 내보내기 성공": "Shortcuts are successfully exported",
|
||||
"단축키 불러오기 성공": "Shortcuts are successfully imported",
|
||||
"단축키 수정": "Edit shortcuts",
|
||||
"단축키 초기화 성공": "Shortcuts are successfully reseted",
|
||||
"닫기 버튼으로 트레이로 최소화": "El botón Cerrar se minimiza en la barra de tareas",
|
||||
"대기 중...": "Pendiente...",
|
||||
"데이터 분류 중...": "Classifying data...",
|
||||
|
@ -210,6 +222,7 @@
|
|||
"로컬 파일 감지": "Detect local files",
|
||||
"로컬 파일로부터 새 작업 만들기": "Create a new task from the local files",
|
||||
"를 북마크에서 지웠습니다": "se elimina de favoritos",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "Notes de la versió",
|
||||
"링크 열기": "Abrir enlace",
|
||||
"링크 주소 복사": "Copia la URL",
|
||||
|
@ -240,6 +253,7 @@
|
|||
"변경할 최소 크기": "Tamaño mínimo",
|
||||
"변환 중...": "Converting...",
|
||||
"보기": "Visualizar",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "Iniciar automáticamente al inicio",
|
||||
"북마크": "Favoritos",
|
||||
"북마크 가져오기": "Importar favoritos",
|
||||
|
@ -255,6 +269,7 @@
|
|||
"불완전한 작업 자동으로 다시 시작": "Reiniciar automáticamente tareas incompletas",
|
||||
"붙여넣고 다운로드": "Paste and Download",
|
||||
"뷰어... (&V)": "&Viewer...",
|
||||
"브라우저로 보기": "Show in browser",
|
||||
"브라우저로 보기 (&B)": "Ver en el navegador (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "Incluir imágenes similares (lento)",
|
||||
"비율 유지": "Mantener relación de aspecto",
|
||||
|
@ -288,9 +303,11 @@
|
|||
"설명": "Comentario",
|
||||
"설정": "Preferencias",
|
||||
"설정 (Preferences)": "Opciones",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "Éxito:{}\nFracaso:{}",
|
||||
"수동": "Manual",
|
||||
"수동 업데이트": "Manual",
|
||||
"수정...": "Edit...",
|
||||
"순서 변경": "Reorder",
|
||||
"숨김 키": "Boss-Key",
|
||||
"숫자": "Número",
|
||||
|
@ -300,8 +317,12 @@
|
|||
"스크립트": "Guion",
|
||||
"스크립트 가져오기": "Importar script",
|
||||
"스크립트를 실행하시겠습니까?": "¿Seguro que quieres ejecutar este script?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "Reading stories...",
|
||||
"스토리 포함": "Include stories",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "Serie",
|
||||
"시리즈 (Series)": "Serie",
|
||||
"시리즈가 없습니다": "No series",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "Actualizado",
|
||||
"업데이트 중...": "Actualización...",
|
||||
"업데이트 체크 중...": "Buscar actualizaciones...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "Elegir el programa a usar para abrir",
|
||||
"열기": "Open",
|
||||
"예(&Y)": "&Sí",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "Rango inválido",
|
||||
"올바르지 않은 주소입니다": "URL no válida",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "Error de sintaxis no válido con filtro",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "Opciones",
|
||||
"옵션 (Options)": "Opciones",
|
||||
"완료": "Terminado",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "Cambiar nombre",
|
||||
"이름을 얻는 도중 실패했습니다": "No se pudo leer el nombre",
|
||||
"이미 다운로드한 작품 제외": "Exclude already downloaded galleries",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "There is already a group that includes tasks.",
|
||||
"이미지": "Imagen",
|
||||
"이미지 정보 캐시": "Cache image infos",
|
||||
|
@ -396,6 +422,7 @@
|
|||
"작업": "Tareas",
|
||||
"작업 개수": "Número de tareas",
|
||||
"작업 수정": "Edit task",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "Tamaño de archivo de tarea",
|
||||
"작업 정보": "Información del trabajo",
|
||||
|
@ -412,6 +439,7 @@
|
|||
"잠금": "Bloquear",
|
||||
"잠금 해제": "Desbloquear",
|
||||
"장": "páginas",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "Low spec mode",
|
||||
"저장": "Guardar",
|
||||
"저장 && 종료": "Guardar && Salir",
|
||||
|
@ -429,6 +457,7 @@
|
|||
"정규식 문법": "Sintaxis de regex",
|
||||
"정규식으로 찾기": "Buscar por regex",
|
||||
"정렬 기준": "Ordenar por",
|
||||
"정말 단축키를 초기화하시겠습니까?": "Are you sure you want to reset shortcuts?",
|
||||
"정말 목록에서 제거하시겠습니까?": "¿Estás seguro que quieres eliminar la lista?",
|
||||
"정말 삭제하시겠습니까?": "¿Estás seguro de que deseas eliminarlo?",
|
||||
"정말 종료하시겠습니까?": "¿Está seguro que desea salir?",
|
||||
|
@ -446,6 +475,7 @@
|
|||
"종료": "Salir",
|
||||
"종료 중...": "Salir...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "Deslizar horizontalmente: ±1\nDeslizar verticalmente: ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "URL o ID de la galería",
|
||||
"주소를 입력해주세요": "Por favor, introduzca algunas URL",
|
||||
"중간": "Medium",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "Sitios soportados:",
|
||||
"지정한 페이지만 다운로드합니다.": "Descargar solo las páginas seleccionadas.",
|
||||
"직접 다운로드": "Direct download",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "Reference: {} galleries",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "Please see: Help - How to use (F1) - Load cookies",
|
||||
"창 보이기 / 숨기기": "Show / Hide windows",
|
||||
"찾기...": "Buscar...",
|
||||
"첫 번째 파일 열기": "Open the first file",
|
||||
"첫 번째 파일 열기 (&O)": "Abra el primer archivo (&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "Primera página, 10 ~ 20e páginas, última página",
|
||||
"체인지로그": "Changelog",
|
||||
"초기화": "Reset",
|
||||
"최대 다운로드 속도": "Velocidad máxima de descarga",
|
||||
"최대 동시 작업": "Maximum parallel tasks",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "El botón Minimizar minimiza en la barra de tareas",
|
||||
"추가한 날짜": "Fecha de adición",
|
||||
"취소": "Cancelar",
|
||||
|
@ -489,6 +522,7 @@
|
|||
"크기 조절": "Redimensionar",
|
||||
"크기 조절...": "Redimensionando...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Chrome extension link failed.",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "Classic",
|
||||
"클립보드에서 자동 추가": "Portapapeles",
|
||||
"타입": "Tipos",
|
||||
|
@ -499,6 +533,7 @@
|
|||
"태그 (Tags)": "Tags",
|
||||
"태그 :": "Tag :",
|
||||
"태그 설정": "Parámetro Tag",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "No tags",
|
||||
"테마": "Theme",
|
||||
"토렌트 추가": "Add Torrent",
|
||||
|
@ -506,10 +541,12 @@
|
|||
"통계": "Estadísticas",
|
||||
"통과": "Pasar",
|
||||
"투명도": "Opacidad",
|
||||
"트래커 수정": "Edit trackers",
|
||||
"트레이로 전환되었습니다": "Minimizar en la barra de tareas",
|
||||
"트레이로 최소화": "Reducir en la barra de tareas",
|
||||
"트레이에서 알림 보이기": "Visualizar las notificaciones",
|
||||
"파일": "Archivo",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "Eliminar archivos",
|
||||
"파일 삭제 (&X)": "Eliminar archivos (&X)",
|
||||
"파일 수": "Número de archivos",
|
||||
|
@ -549,6 +586,8 @@
|
|||
"표지 검색...": "Busque cubierta...",
|
||||
"프로그램 패스워드": "Program password",
|
||||
"프록시": "Proxy",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "Numerate files in playlists",
|
||||
"플레이리스트 한 폴더에 다운로드": "Download playlist to one folder",
|
||||
"플로팅 미리보기": "Vista previa flotante",
|
||||
|
@ -558,6 +597,7 @@
|
|||
"하나 이상의 타입을 선택해주세요.": "Elige al menos un tipo.",
|
||||
"하위폴더 포함": "Incluir sub-carpetas",
|
||||
"학습 중...": "Learning...",
|
||||
"한 줄 당 한 트래커 URL": "One tracker URL per line",
|
||||
"한국어": "Etnia coreana",
|
||||
"항상 위": "Sempre a sobre",
|
||||
"해당 링크로 이동합니다": "Abrir enlace",
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "Cookie de connexion d'E(x)Hentai requis",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "Votre adresse IP est temporairement bloquée. Veuillez réessayer dans un instant",
|
||||
"PDF 생성": "Créer un PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "Veuillez saisir l'URL",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "Appuyez sur [Télécharger] pour télécharger. Appuyez sur [OK] pour quitter.",
|
||||
"reCAPTCHA 풀기": "Résoudre reCAPTCHA",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "Recherche avancée",
|
||||
"고정": "Épingler",
|
||||
"고정 해제": "Désépingler",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "Normal",
|
||||
"그룹": "Groupes",
|
||||
"그룹 (Groups)": "Groupes",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "Nom du groupe",
|
||||
"그룹 합쳐서 불러오기": "Load all groups together",
|
||||
"그룹명 복사 (&G)": "Copier les noms des groupes (&G)",
|
||||
"그룹으로 이동": "Move to group",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "Groups can't contain groups.",
|
||||
"그룹이 없습니다": "Aucun groupe",
|
||||
"기본": "Défaut",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "Deep model",
|
||||
"끝내기": "Quitter",
|
||||
"날짜": "Date",
|
||||
"낮음": "Low",
|
||||
"내보내기": "Exportation",
|
||||
"내보내기 실패": "Échec de l'exportation",
|
||||
"내용 보기": "Voir le script",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "Visionneuse d'images intégrée",
|
||||
"녹화 중...": "Recording...",
|
||||
"녹화 중지": "Stop recording",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "S'applique également à d'autres sites vidéos.",
|
||||
"다시 시작 (&S)": "Redémarrer (&S)",
|
||||
"다시 시작 실패; 복구됨": "Erreur lors du redémarrage; Retour à l'état précédent",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "Supprimer automatiquement après la complétion du téléchargement",
|
||||
"다운로드 일시 정지 / 재개": "Télécharger Pause / Reprendre",
|
||||
"다운로드가 완료되면 알림": "Afficher la notification lorsque le téléchargement est terminé",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "Télécharger uniquement les galeries",
|
||||
"다음 검색시 더 빠르게 검색": "Améliore la performance la prochaine fois",
|
||||
"다크 모드": "Mode sombre",
|
||||
"단일 파일": "Fichier unique",
|
||||
"단축키": "Shortcuts",
|
||||
"단축키 내보내기 성공": "Shortcuts are successfully exported",
|
||||
"단축키 불러오기 성공": "Shortcuts are successfully imported",
|
||||
"단축키 수정": "Edit shortcuts",
|
||||
"단축키 초기화 성공": "Shortcuts are successfully reseted",
|
||||
"닫기 버튼으로 트레이로 최소화": "Le bouton Fermer minimise dans la barre des tâches",
|
||||
"대기 중...": "En attente...",
|
||||
"데이터 분류 중...": "Classifying data...",
|
||||
|
@ -210,6 +222,7 @@
|
|||
"로컬 파일 감지": "Detect local files",
|
||||
"로컬 파일로부터 새 작업 만들기": "Créer une nouvelle tâche depuis les fichiers locaux",
|
||||
"를 북마크에서 지웠습니다": "est supprimé des favoris",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "Notes de version",
|
||||
"링크 열기": "Ouvrir le lien",
|
||||
"링크 주소 복사": "Copier le lien",
|
||||
|
@ -240,6 +253,7 @@
|
|||
"변경할 최소 크기": "Taille minimum",
|
||||
"변환 중...": "Conversion...",
|
||||
"보기": "Visualiser",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "Démarrer automatiquement au démarrage",
|
||||
"북마크": "Favoris",
|
||||
"북마크 가져오기": "Importer des favoris",
|
||||
|
@ -255,6 +269,7 @@
|
|||
"불완전한 작업 자동으로 다시 시작": "Redémarrer automatiquement les tâches incomplètes",
|
||||
"붙여넣고 다운로드": "Coller et télécharger",
|
||||
"뷰어... (&V)": "&Visualisateur...",
|
||||
"브라우저로 보기": "Show in browser",
|
||||
"브라우저로 보기 (&B)": "Afficher dans le navigateur (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "Inclure les images similaires (lent)",
|
||||
"비율 유지": "Conserver les proportions",
|
||||
|
@ -288,9 +303,11 @@
|
|||
"설명": "Commentaire",
|
||||
"설정": "Paramètres",
|
||||
"설정 (Preferences)": "Paramètres",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "Succès : {}\nÉchec : {}",
|
||||
"수동": "Manuel",
|
||||
"수동 업데이트": "Manuel",
|
||||
"수정...": "Edit...",
|
||||
"순서 변경": "Reorder",
|
||||
"숨김 키": "Boss-Key",
|
||||
"숫자": "Nombre",
|
||||
|
@ -300,8 +317,12 @@
|
|||
"스크립트": "Script",
|
||||
"스크립트 가져오기": "Script d'importation",
|
||||
"스크립트를 실행하시겠습니까?": "Êtes-vous certain de vouloir exécuter ce script ?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "Lecture des stories...",
|
||||
"스토리 포함": "Inclure les stories",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "Séries",
|
||||
"시리즈 (Series)": "Séries",
|
||||
"시리즈가 없습니다": "Aucune séries",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "Mise à jour",
|
||||
"업데이트 중...": "Mise à jour...",
|
||||
"업데이트 체크 중...": "Vérification des mises à jour...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "Choisir le programme à utiliser pour ouvrir",
|
||||
"열기": "Open",
|
||||
"예(&Y)": "&Oui",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "Plage invalide",
|
||||
"올바르지 않은 주소입니다": "URL invalide",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "Erreur de syntaxe non valide avec le filtre",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "Options",
|
||||
"옵션 (Options)": "Options",
|
||||
"완료": "Terminé",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "Renommer",
|
||||
"이름을 얻는 도중 실패했습니다": "Impossible de lire le nom",
|
||||
"이미 다운로드한 작품 제외": "Exclude already downloaded galleries",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "There is already a group that includes tasks.",
|
||||
"이미지": "Image",
|
||||
"이미지 정보 캐시": "Cache image infos",
|
||||
|
@ -396,6 +422,7 @@
|
|||
"작업": "Tâches",
|
||||
"작업 개수": "Nombre de tâches",
|
||||
"작업 수정": "Modifier la tâche",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "Taille des fichiers",
|
||||
"작업 정보": "Informations de la tâche",
|
||||
|
@ -412,6 +439,7 @@
|
|||
"잠금": "Verrouiller",
|
||||
"잠금 해제": "Déverrouiller",
|
||||
"장": "pages",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "Low spec mode",
|
||||
"저장": "Sauvegarder",
|
||||
"저장 && 종료": "Sauvegarder && Quitter",
|
||||
|
@ -429,6 +457,7 @@
|
|||
"정규식 문법": "Syntaxe du regex",
|
||||
"정규식으로 찾기": "Recherche par regex",
|
||||
"정렬 기준": "Trier par",
|
||||
"정말 단축키를 초기화하시겠습니까?": "Are you sure you want to reset shortcuts?",
|
||||
"정말 목록에서 제거하시겠습니까?": "Êtes-vous sûr de vouloir supprimer cela de la liste ?",
|
||||
"정말 삭제하시겠습니까?": "Etes-vous sûr de vouloir supprimer ?",
|
||||
"정말 종료하시겠습니까?": "Êtes-vous sûr de vouloir quitter ?",
|
||||
|
@ -446,6 +475,7 @@
|
|||
"종료": "Quitter",
|
||||
"종료 중...": "Quitter...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "Glissez horizontalement : ±1\nGlissez verticalement : ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "URL ou ID de la galerie",
|
||||
"주소를 입력해주세요": "Veuillez saisir quelques URLs",
|
||||
"중간": "Medium",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "Sites pris en charge :",
|
||||
"지정한 페이지만 다운로드합니다.": "Télécharger les pages sélectionnées uniquement.",
|
||||
"직접 다운로드": "Direct download",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "Reference: {} galleries",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "Merci de lire :Aide - Comment utiliser (F1) - Charger les cookies",
|
||||
"창 보이기 / 숨기기": "Afficher / Cacher la fenêtre",
|
||||
"찾기...": "Rechercher...",
|
||||
"첫 번째 파일 열기": "Open the first file",
|
||||
"첫 번째 파일 열기 (&O)": "Ouvrez le premier fichier (&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "Première page, 10 ~ 20e pages, dernière page",
|
||||
"체인지로그": "Changelog",
|
||||
"초기화": "Reset",
|
||||
"최대 다운로드 속도": "Vitesse maximale de téléchargement",
|
||||
"최대 동시 작업": "Maximum parallel tasks",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "Le bouton Réduire minimise dans la barre des tâches",
|
||||
"추가한 날짜": "Date d'ajout",
|
||||
"취소": "Annuler",
|
||||
|
@ -489,6 +522,7 @@
|
|||
"크기 조절": "Redimensionner",
|
||||
"크기 조절...": "Redimensionnement...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Erreur du lien vers l'extension Chrome.",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "Classic",
|
||||
"클립보드에서 자동 추가": "Presse-papiers",
|
||||
"타입": "Types",
|
||||
|
@ -499,6 +533,7 @@
|
|||
"태그 (Tags)": "Tags",
|
||||
"태그 :": "Tag :",
|
||||
"태그 설정": "Paramètres des tags",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "No tags",
|
||||
"테마": "Theme",
|
||||
"토렌트 추가": "Ajout un torrent",
|
||||
|
@ -506,10 +541,12 @@
|
|||
"통계": "Statistiques",
|
||||
"통과": "Passer",
|
||||
"투명도": "Opacité",
|
||||
"트래커 수정": "Edit trackers",
|
||||
"트레이로 전환되었습니다": "Minimiser dans la barre des tâches",
|
||||
"트레이로 최소화": "Réduire dans la barre des tâches",
|
||||
"트레이에서 알림 보이기": "Afficher les notifications",
|
||||
"파일": "Fichier",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "Supprimer les fichiers",
|
||||
"파일 삭제 (&X)": "Supprimer les fichiers (&X)",
|
||||
"파일 수": "Nombre de fichiers",
|
||||
|
@ -549,6 +586,8 @@
|
|||
"표지 검색...": "Rechercher une couverture...",
|
||||
"프로그램 패스워드": "Mot de passe du programme",
|
||||
"프록시": "Proxy",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "Numerate files in playlists",
|
||||
"플레이리스트 한 폴더에 다운로드": "Télécharger la playlist dans un dossier",
|
||||
"플로팅 미리보기": "Aperçu flottant",
|
||||
|
@ -558,6 +597,7 @@
|
|||
"하나 이상의 타입을 선택해주세요.": "Choisissez au moins un type.",
|
||||
"하위폴더 포함": "Inclure les sous-dossiers",
|
||||
"학습 중...": "Learning...",
|
||||
"한 줄 당 한 트래커 URL": "One tracker URL per line",
|
||||
"한국어": "Coréenne",
|
||||
"항상 위": "Toujours au premier plan",
|
||||
"해당 링크로 이동합니다": "Ouvrir le lien...",
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "E(x)Hentai ログインCookieが必要です",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "IPは一時的にロックされています。 しばらくしてからもう一度お試しください",
|
||||
"PDF 생성": "PDFを作成する",
|
||||
"UI 스케일 ({}%)": "UIスケール ({}%)",
|
||||
"URL을 입력하세요": "URLを入力してください",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "[ダウンロード]を押してダウンロードします。 [OK]を押して終了します。",
|
||||
"reCAPTCHA 풀기": "reCAPTCHAを解決する",
|
||||
|
@ -118,16 +119,16 @@
|
|||
"갤러리 정보": "ギャラリー情報",
|
||||
"갤러리 정보 파일 (info.txt) 생성": "ギャラリー情報ファイルを作成する (info.txt)",
|
||||
"갯수": "カウント",
|
||||
"거꾸로": "反転",
|
||||
"거꾸로": "逆",
|
||||
"검색": "検索",
|
||||
"검색 데이터": "検索データ",
|
||||
"검색 도중에 사용할 수 없는 기능입니다.": "この機能は検索中はロックされています。",
|
||||
"검색 범위": "検索範囲",
|
||||
"검색 순서": "検索順序",
|
||||
"검색 필터": "検索フィルタ",
|
||||
"검색기": "サーチャー",
|
||||
"검색어": "検索キーワード",
|
||||
"검색어 자동 완성": "オートコンプリートキーワード",
|
||||
"검색기": "検索ツール",
|
||||
"검색어": "検索語句",
|
||||
"검색어 자동 완성": "語句の自動補完",
|
||||
"결과": "結果",
|
||||
"결과 저장": "結果を保存する",
|
||||
"경고 없이 삭제": "警告なしに削除",
|
||||
|
@ -139,13 +140,16 @@
|
|||
"고급 검색": "詳細検索",
|
||||
"고정": "固定",
|
||||
"고정 해제": "固定解除",
|
||||
"그대로": "普通",
|
||||
"관리자 권한 없이 실행": "管理者権限なしで実行",
|
||||
"관리자 권한 필요": "管理者権限が必要",
|
||||
"그대로": "通常",
|
||||
"그룹": "グループ",
|
||||
"그룹 (Groups)": "グループ",
|
||||
"그룹 안의 모든 작업 다시 시작": "このグループのすべてのタスクを再開",
|
||||
"그룹 이름": "グループ名",
|
||||
"그룹 합쳐서 불러오기": "全グループをまとめて読み込む",
|
||||
"그룹명 복사 (&G)": "グループ名のコピー (&G)",
|
||||
"그룹으로 이동": "グループに移動",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "グループにグループを含めることはできません。",
|
||||
"그룹이 없습니다": "グループなし",
|
||||
"기본": "デフォルト",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "深層モデル",
|
||||
"끝내기": "終了",
|
||||
"날짜": "日付",
|
||||
"낮음": "Low",
|
||||
"내보내기": "エクスポート",
|
||||
"내보내기 실패": "エクスポートに失敗しました",
|
||||
"내용 보기": "スクリプトの表示",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "内蔵の画像ビューア",
|
||||
"녹화 중...": "レコーディング...",
|
||||
"녹화 중지": "レコーディングを停止する",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "他の動画サイトにも適用されます。",
|
||||
"다시 시작 (&S)": "再起動 (&S)",
|
||||
"다시 시작 실패; 복구됨": "再試行に失敗したため、元に戻しました。",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "ダウンロードが完了時に自動削除",
|
||||
"다운로드 일시 정지 / 재개": "ダウンロード一時停止/再開",
|
||||
"다운로드가 완료되면 알림": "ダウンロードが終了時に通知を表示する",
|
||||
"다운로드하지 않음": "ダウンロードしない",
|
||||
"다운로드한 작품만": "ダウンロード済みのギャラリーのみ",
|
||||
"다음 검색시 더 빠르게 검색": "次回の検索でより早く検索",
|
||||
"다크 모드": "ダークモード",
|
||||
"단일 파일": "ファイル",
|
||||
"단축키": "ショートカット",
|
||||
"단축키 내보내기 성공": "ショートカットが正常にエクスポートされました",
|
||||
"단축키 불러오기 성공": "ショートカットが正常にインポートされました",
|
||||
"단축키 수정": "ショートカットを編集する",
|
||||
"단축키 초기화 성공": "ショートカットが正常にリセットされました",
|
||||
"닫기 버튼으로 트레이로 최소화": "閉じるボタンでシステムトレイに最小化",
|
||||
"대기 중...": "待機中...",
|
||||
"데이터 분류 중...": "データ分類中...",
|
||||
|
@ -208,8 +220,9 @@
|
|||
"로그인 중...": "ログイン中...",
|
||||
"로그인 테스트": "テストログイン",
|
||||
"로컬 파일 감지": "ローカルファイルの検出",
|
||||
"로컬 파일로부터 새 작업 만들기": "ローカルファイルから新しいタスクを作成する",
|
||||
"로컬 파일로부터 새 작업 만들기": "ローカルファイルから新規タスクを作成",
|
||||
"를 북마크에서 지웠습니다": "をブックマークから消去しました",
|
||||
"리트윗 포함": "リツイートを含める",
|
||||
"릴리즈 노트": "リリースノート",
|
||||
"링크 열기": "リンクを開く",
|
||||
"링크 주소 복사": "リンクをコピー",
|
||||
|
@ -233,13 +246,14 @@
|
|||
"목록에서 폴더 제거": "リストからフォルダを削除",
|
||||
"목록을 모두 지울까요?": "本当にリストを消去してよろしいですか?",
|
||||
"미리보기": "プレビュー",
|
||||
"미리보기 크기": "プレビューサイズ",
|
||||
"미리보기 크기": "プレビューのサイズ",
|
||||
"버전 확인": "更新の確認",
|
||||
"번역": "翻訳する",
|
||||
"변경할 최대 크기": "最大サイズ",
|
||||
"변경할 최소 크기": "最小サイズ",
|
||||
"변환 중...": "変換...",
|
||||
"보기": "表示",
|
||||
"보통": "普通",
|
||||
"부팅 시 실행": "起動時に開始",
|
||||
"북마크": "ブックマーク",
|
||||
"북마크 가져오기": "ブックマークのインポート",
|
||||
|
@ -251,10 +265,11 @@
|
|||
"북마크를 모두 삭제합니다.": "すべてのブックマークを削除",
|
||||
"북마크를 모두 삭제했습니다.": "すべてのブックマークを削除しました。",
|
||||
"불러오기": "ロード",
|
||||
"불완전한 작업 모두 다시 시작": "すべての未完了タスクを再起動する",
|
||||
"불완전한 작업 자동으로 다시 시작": "未完了のタスクを自動的に再起動する",
|
||||
"불완전한 작업 모두 다시 시작": "すべての未完了タスクを再起動",
|
||||
"불완전한 작업 자동으로 다시 시작": "未完了のタスクを自動的に再起動",
|
||||
"붙여넣고 다운로드": "貼り付けてダウンロード",
|
||||
"뷰어... (&V)": "&ビューア...",
|
||||
"브라우저로 보기": "ブラウザで表示",
|
||||
"브라우저로 보기 (&B)": "ブラウザで表示 (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "類似画像を含める (遅い)",
|
||||
"비율 유지": "アスペクト比",
|
||||
|
@ -282,15 +297,17 @@
|
|||
"서명": "証明書",
|
||||
"서버": "サーバ",
|
||||
"선택 화 다운로드": "チャプターを選択してダウンロード",
|
||||
"선택된 작업들 내보내기": "選択したタスクをエクスポートする",
|
||||
"선택된 작업들 내보내기": "選択したタスクをエクスポート",
|
||||
"선택된 작업이 없습니다": "タスクが選択されていません",
|
||||
"선택한 항목 중에서 자동 선택": "選択したアイテム内の自動選択",
|
||||
"설명": "コメント",
|
||||
"설정": "環境設定",
|
||||
"설정 (Preferences)": "環境設定",
|
||||
"설정": "設定",
|
||||
"설정 (Preferences)": "設定 (Preferences)",
|
||||
"설정을 따름": "設定に従う",
|
||||
"성공: {}\n실패: {}": "成功: {}\n失敗: {}",
|
||||
"수동": "マニュアル",
|
||||
"수동 업데이트": "マニュアル",
|
||||
"수동": "手動",
|
||||
"수동 업데이트": "手動",
|
||||
"수정...": "編集...",
|
||||
"순서 변경": "順番変更",
|
||||
"숨김 키": "非表示キー",
|
||||
"숫자": "番号",
|
||||
|
@ -298,10 +315,14 @@
|
|||
"스레드 생성 중...": "スレッド作成中...",
|
||||
"스크롤 속도 ({}x)": "スクロール速度 ({}x)",
|
||||
"스크립트": "スクリプト",
|
||||
"스크립트 가져오기": "スクリプトインポート",
|
||||
"스크립트 가져오기": "スクリプトのインポート",
|
||||
"스크립트를 실행하시겠습니까?": "このスクリプトを実行してもよろしいですか?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "スクリプトの読み込み中にエラーが発生しました",
|
||||
"스토리 읽는 중...": "ストーリーを読む...",
|
||||
"스토리 포함": "ストーリーを含む",
|
||||
"스토리 포함": "ストーリーを含める",
|
||||
"시딩": "シード",
|
||||
"시딩 중지": "シードを停止",
|
||||
"시딩 하지 않음": "シードしない",
|
||||
"시리즈": "シリーズ",
|
||||
"시리즈 (Series)": "シリーズ",
|
||||
"시리즈가 없습니다": "シリーズなし",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "更新",
|
||||
"업데이트 중...": "更新中...",
|
||||
"업데이트 체크 중...": "更新を確認しています...",
|
||||
"업로드": "アップロード",
|
||||
"연결 프로그램 변경": "開くために使用するプログラムを選択します",
|
||||
"열기": "開く",
|
||||
"예(&Y)": "&はい",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "無効な範囲",
|
||||
"올바르지 않은 주소입니다": "無効なURL",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "フィルターでの無効な構文エラー",
|
||||
"올바르지 않은 형식의 스크립트입니다": "無効な形式のスクリプト",
|
||||
"옵션": "オプション",
|
||||
"옵션 (Options)": "オプション",
|
||||
"완료": "終了したタスク",
|
||||
|
@ -348,26 +371,28 @@
|
|||
"우선순위": "優先度",
|
||||
"움짤": "うごイラ",
|
||||
"움짤 변환...": "うごイラの変換...",
|
||||
"원본": "オリジナル",
|
||||
"원본 이미지 다운로드": "オリジナル画像をダウンロード",
|
||||
"원본": "そのまま",
|
||||
"원본 이미지 다운로드": "付属の画像をダウンロード",
|
||||
"원본 크기보다 크게 바뀌지는 않습니다": "サイズは元のサイズより大きくなりません",
|
||||
"원본 파일 삭제": "ダウンロードしたファイルを削除する",
|
||||
"원본 파일 삭제": "ダウンロードしたファイルを削除",
|
||||
"원하는 인공신경망 모델을 고르세요:": "必要な人工ニューラルネットワークモデルを選択します。:",
|
||||
"웹툰 제외": "Webtoonを除く",
|
||||
"웹툰 제외": "Webtoonを除外",
|
||||
"유사도": "類似性",
|
||||
"유튜브": "YouTube",
|
||||
"유효하지 않은 계정 / 패스워드입니다": "無効なアカウント/パスワード",
|
||||
"유효한 계정입니다": "有効なアカウント",
|
||||
"음소거": "ミュート",
|
||||
"음악 파일에 앨범아트 삽입": "オーディオファイルにアルバムカバーを追加",
|
||||
"음원": "オーディオ",
|
||||
"음악 파일에 앨범아트 삽입": "音声ファイルにアルバムカバーを追加",
|
||||
"음원": "音声",
|
||||
"음질": "音質",
|
||||
"이동": "移動",
|
||||
"이동할 수 없습니다: {}": "移動できません: {}",
|
||||
"이름": "名前",
|
||||
"이름 변경": "名前を変更",
|
||||
"이름을 얻는 도중 실패했습니다": "名前の読み取りに失敗しました",
|
||||
"이미 다운로드한 작품 제외": "ダウンロード済みのギャラリーを除外する",
|
||||
"이미 다운로드한 작품 제외": "ダウンロード済みのギャラリーを除外",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "すでにリストにあるタスクを再試行するか尋ねる",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "このタスクはすでにリストにあります。 もう一度ダウンロードしますか?",
|
||||
"이미 포함하는 그룹이 있습니다.": "タスクを含むグループはすでに存在します。",
|
||||
"이미지": "画像",
|
||||
"이미지 정보 캐시": "画像情報のキャッシュ",
|
||||
|
@ -396,6 +421,7 @@
|
|||
"작업": "タスク",
|
||||
"작업 개수": "タスクの数",
|
||||
"작업 수정": "タスクの編集",
|
||||
"작업 수정...": "タスクの編集...",
|
||||
"작업 왼쪽에서 순서 변경": "左側で並び替え",
|
||||
"작업 용량": "タスクのファイルサイズ",
|
||||
"작업 정보": "タスク情報",
|
||||
|
@ -412,6 +438,7 @@
|
|||
"잠금": "ロック",
|
||||
"잠금 해제": "ロック解除",
|
||||
"장": "ページ",
|
||||
"재시작 후 적용됩니다": "再起動後に適用",
|
||||
"저사양 모드": "低スペックモード",
|
||||
"저장": "保存",
|
||||
"저장 && 종료": "保存して終了",
|
||||
|
@ -429,23 +456,25 @@
|
|||
"정규식 문법": "正規表現の構文",
|
||||
"정규식으로 찾기": "正規表現で検索",
|
||||
"정렬 기준": "並び替え",
|
||||
"정말 단축키를 초기화하시겠습니까?": "ショートカットをリセットしてもよろしいですか?",
|
||||
"정말 목록에서 제거하시겠습니까?": "リストから削除してよろしいですか?",
|
||||
"정말 삭제하시겠습니까?": "本当に削除してよろしいですか?",
|
||||
"정말 종료하시겠습니까?": "本当に終了してよろしいですか?",
|
||||
"정말 중지하시겠습니까?": "本当に停止してよろしいですか?",
|
||||
"정말 쿠키를 초기화하시겠습니까?": "Cookieを消去してもよろしいですか?",
|
||||
"정보": "About",
|
||||
"정보...": "About...",
|
||||
"정보": "アプリの情報",
|
||||
"정보...": "アプリの情報...",
|
||||
"정확도": "精度",
|
||||
"제거": "削除",
|
||||
"제목": "タイトル",
|
||||
"제목 (Title)": "タイトル",
|
||||
"제목 복사 (&T)": "タイトルのコピー(&T)",
|
||||
"제외 태그": "除外するタグ",
|
||||
"조각 표시": "作品を表示する",
|
||||
"조각 표시": "ピースを表示",
|
||||
"종료": "終了",
|
||||
"종료 중...": "終了中...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "水平方向へのドラッグ: ±1\n垂直方向へのドラッグ: ±100",
|
||||
"주소": "アドレス",
|
||||
"주소 or 갤러리 넘버": "URLまたはギャラリーID",
|
||||
"주소를 입력해주세요": "URLを入力してください。",
|
||||
"중간": "中",
|
||||
|
@ -456,16 +485,19 @@
|
|||
"지원하는 사이트:": "対応サイト:",
|
||||
"지정한 페이지만 다운로드합니다.": "選択したページのみをダウンロード",
|
||||
"직접 다운로드": "直接ダウンロード",
|
||||
"진행도": "進行度",
|
||||
"참고 작품: {} 개": "参照: {} ギャラリー",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "ヘルプ - 使い方 (F1) - Cookieの読み込みをご覧ください。",
|
||||
"창 보이기 / 숨기기": "ウィンドウの表示/非表示",
|
||||
"찾기...": "検索...",
|
||||
"첫 번째 파일 열기": "最初のファイルを開く",
|
||||
"첫 번째 파일 열기 (&O)": "最初のファイルを開く(&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "最初のページ、10〜20ページ、最後のページ",
|
||||
"체인지로그": "変更ログ",
|
||||
"초기화": "リセット",
|
||||
"최대 다운로드 속도": "最大ダウンロード速度",
|
||||
"최대 동시 작업": "最大並列タスク数",
|
||||
"최대 페이지 제한": "最大ページ制限",
|
||||
"최소화 버튼으로 트레이로 최소화": "最小化ボタンでシステムトレイに最小化",
|
||||
"추가한 날짜": "追加された日付",
|
||||
"취소": "キャンセル",
|
||||
|
@ -489,8 +521,9 @@
|
|||
"크기 조절": "サイズ変更",
|
||||
"크기 조절...": "サイズ変更...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Chrome拡張機能のリンクに失敗しました。",
|
||||
"클라이언트": "クライアント",
|
||||
"클래식": "クラシック",
|
||||
"클립보드에서 자동 추가": "クリップボードモニター",
|
||||
"클립보드에서 자동 추가": "クリップボードを監視",
|
||||
"타입": "種類",
|
||||
"타입 (Types)": "種類",
|
||||
"태그": "タグ",
|
||||
|
@ -499,6 +532,7 @@
|
|||
"태그 (Tags)": "タグ",
|
||||
"태그 :": "タグ :",
|
||||
"태그 설정": "タグの設定",
|
||||
"태그 수정": "タグを編集",
|
||||
"태그 없음": "タグなし",
|
||||
"테마": "テーマ",
|
||||
"토렌트 추가": "トレントの追加",
|
||||
|
@ -506,18 +540,20 @@
|
|||
"통계": "統計情報",
|
||||
"통과": "パス",
|
||||
"투명도": "不透明度",
|
||||
"트래커 수정": "トラッカーを編集する",
|
||||
"트레이로 전환되었습니다": "システムトレイに最小化",
|
||||
"트레이로 최소화": "システムトレイに最小化",
|
||||
"트레이에서 알림 보이기": "トレイに通知を表示",
|
||||
"파일": "ファイル",
|
||||
"파일 목록": "ファイル一覧",
|
||||
"파일 삭제": "ファイルの削除",
|
||||
"파일 삭제 (&X)": "ファイルの削除(&X)",
|
||||
"파일 수": "ファイルの数",
|
||||
"파일 스캔 중": "ファイルのスキャン",
|
||||
"파일 유형 제외": "ファイルタイプの除外",
|
||||
"파일 유형 제외": "除外するファイルの種類",
|
||||
"파일 크기": "ファイルサイズ",
|
||||
"파일 형식": "ファイル形式",
|
||||
"파일 형식 물어보기": "ファイル形式の質問",
|
||||
"파일 형식 물어보기": "ファイル形式を問いあわせる",
|
||||
"파일명": "ファイル名",
|
||||
"파일명 형식": "ファイル名の形式",
|
||||
"파일을 선택하세요": "ファイルの選択",
|
||||
|
@ -532,7 +568,7 @@
|
|||
"페이지를 읽는 중에 오류가 발생했습니다": "ページを読み込もうとしたときにエラーが発生しました",
|
||||
"평범하게 찾기": "デフォルトで検索",
|
||||
"포트": "ポート",
|
||||
"포함 태그": "タグを含む",
|
||||
"포함 태그": "タグを含める",
|
||||
"폭 맞춤": "幅に合わせる",
|
||||
"폰트 변경...": "フォントを変更...",
|
||||
"폰트 초기화": "フォントのリセット",
|
||||
|
@ -549,6 +585,8 @@
|
|||
"표지 검색...": "カバーを検索...",
|
||||
"프로그램 패스워드": "プログラムのパスワード",
|
||||
"프록시": "プロキシ",
|
||||
"플러그인": "プラグイン",
|
||||
"플러그인을 추가하시겠습니까?": "プラグインを追加しますか?",
|
||||
"플레이리스트 파일에 번호 매기기": "プレイリストファイルに番号を付ける",
|
||||
"플레이리스트 한 폴더에 다운로드": "プレイリストを1つのフォルダにダウンロード",
|
||||
"플로팅 미리보기": "フローティングプレビュー",
|
||||
|
@ -556,8 +594,9 @@
|
|||
"필터": "フィルター",
|
||||
"필터창": "フィルター",
|
||||
"하나 이상의 타입을 선택해주세요.": "少なくとも1つのタイプを選択してください。",
|
||||
"하위폴더 포함": "サブフォルダーを含む",
|
||||
"하위폴더 포함": "サブフォルダーを含める",
|
||||
"학습 중...": "学習...",
|
||||
"한 줄 당 한 트래커 URL": "1行に1つのトラッカーURL",
|
||||
"한국어": "韓国語",
|
||||
"항상 위": "常に最前面",
|
||||
"해당 링크로 이동합니다": "リンクを開く...",
|
||||
|
@ -570,4 +609,4 @@
|
|||
"후원": "サポート",
|
||||
"휴지통으로 이동": "ごみ箱への移動"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,613 @@
|
|||
{
|
||||
"lang": "pl",
|
||||
"items": {
|
||||
"#Cancel#": "Anuluj",
|
||||
"#EB#": "{} EB",
|
||||
"#GB#": "{} GB",
|
||||
"#GIFs#": "GIF / WebP",
|
||||
"#KB#": "{} KB",
|
||||
"#KB/s#": "{} KB/s",
|
||||
"#MB#": "{} MB",
|
||||
"#MB/s#": "{} MB/s",
|
||||
"#OK#": "OK",
|
||||
"#PB#": "{} PB",
|
||||
"#TB#": "{} TB",
|
||||
"#boss_invalid_pw#": "Nieprawidłowe hasło!",
|
||||
"#boss_pw#": "Hasło:",
|
||||
"#byte#": "{} bajt",
|
||||
"#bytes#": "{} bajty",
|
||||
"#click#": "Kliknij",
|
||||
"#combo_hour#": "{} Godz.",
|
||||
"#combo_hours#": "{} Godziny",
|
||||
"#combo_min#": "{} Min",
|
||||
"#combo_mins#": "{} Minut",
|
||||
"#date01#": "Sty {d}",
|
||||
"#date01y#": "Sty {d}, {y}",
|
||||
"#date02#": "Lut {d}",
|
||||
"#date02y#": "Lut {d}, {y}",
|
||||
"#date03#": "Mar {d}",
|
||||
"#date03y#": "Mar {d}, {y}",
|
||||
"#date04#": "Kwi {d}",
|
||||
"#date04y#": "Kwi {d}, {y}",
|
||||
"#date05#": "Maj {d}",
|
||||
"#date05y#": "Maj {d}, {y}",
|
||||
"#date06#": "Cze {d}",
|
||||
"#date06y#": "Cze {d}, {y}",
|
||||
"#date07#": "Lip {d}",
|
||||
"#date07y#": "Lip {d}, {y}",
|
||||
"#date08#": "Sie {d}",
|
||||
"#date08y#": "Sie {d}, {y}",
|
||||
"#date09#": "Wrz {d}",
|
||||
"#date09y#": "Wrz {d}, {y}",
|
||||
"#date10#": "Paź {d}",
|
||||
"#date10y#": "Paź {d}, {y}",
|
||||
"#date11#": "Lis {d}",
|
||||
"#date11y#": "Lis {d}, {y}",
|
||||
"#date12#": "Gru {d}",
|
||||
"#date12y#": "Gru {d}, {y}",
|
||||
"#eta#": "{h:02}:{m:02}:{s:02}",
|
||||
"#filter_cookie#": "Pliki cookie Netscape HTTP (*.txt)",
|
||||
"#invalid_browserRequired#": "Przeglądarka wymagana; Użyj --safemode",
|
||||
"#invalid_loginRequired#": "Wymagane zalogowanie; Zaktualizuj swoje ciasteczka",
|
||||
"#invalid_outdatedExtension#": "Rozszerzenie jest nieaktualne; Zaktualizuj rozszerzenie",
|
||||
"#invalid_unknownSite#": "Nieznana witryna",
|
||||
"#loading_lib#": "Ładowanie: {}",
|
||||
"#new_item#": "Nowy przedmiot",
|
||||
"#p#": "{}p",
|
||||
"#recomm_all_langs#": "Wszystkie języki",
|
||||
"#recomm_artist#": "Artysta",
|
||||
"#recomm_main#": "저장 폴더에 있는 작품들을 분석해서 작가를 추천합니다.\n\n결과에 나오는 정확도는 주어진 작품 내에서의 정확도입니다.\n작품은 많으면 많을수록 좋습니다. (100 개 이상 권장)\n\n{item} 개의 작품이 있습니다:",
|
||||
"#recomm_score#": "Wynik",
|
||||
"#setting_MB/s#": "MB/s",
|
||||
"#setting_autosaveL#": "Co",
|
||||
"#setting_autosaveR#": "",
|
||||
"#setting_incompleteL#": "Po",
|
||||
"#setting_incompleteR#": "",
|
||||
"#task_artist#": "Artysta",
|
||||
"#task_date#": "Data",
|
||||
"#task_done#": "Gotowe",
|
||||
"#task_folder#": "Folder",
|
||||
"#task_incomplete#": "Nieukończone",
|
||||
"#task_input#": "Wejście",
|
||||
"#task_invalid#": "Niewłaściwy",
|
||||
"#task_multiple#": "Wielokrotny",
|
||||
"#task_single#": "Pojedynczy",
|
||||
"#task_site#": "Strona",
|
||||
"#task_status#": "Status",
|
||||
"#task_title#": "Tytuł",
|
||||
"#task_type#": "Typ",
|
||||
"#task_url#": "URL",
|
||||
"#task_zipfile#": "Plik zip",
|
||||
"50개씩 끊어서 검색": "Wyświetl tylko 50 wyników wyszukiwania na raz",
|
||||
"DPI 우회": "Pomiń DPI",
|
||||
"DPI 우회가 꺼져있습니다. 일부 사이트에서 작동하지 않을 수 있습니다.": "\"Pomiń DPI\" jest wyłączone. To morze nie działać w niektórych stronach internetowych.",
|
||||
"E(x)Hentai 로그인 쿠키 필요": "Ciasteczka zalogowania E(x)Hentai są wymagane.",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "Twoje IP jest tymczasowo zablokowane. Proszę spróbuj później.",
|
||||
"PDF 생성": "Utwórz PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "Proszę wpisz jakieś linki",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "Wciśnij [Download] aby pobrać. Wciśnij [OK] aby wyjść.",
|
||||
"reCAPTCHA 풀기": "Rozwiąż reCAPTCHA",
|
||||
"{hour}시간 {min}분 {sec}초": "{hour}h {min}m {sec}s",
|
||||
"{min}분 {sec}초 뒤 다시 시작: {id}": "Restart za {min}m {sec}s: {id}",
|
||||
"{} 개 선택": "{} wybrane przedmioty",
|
||||
"{} 개 찾음": "{} znaleziony",
|
||||
"{} 개의 작업들이 있습니다": "Są {} zadania",
|
||||
"{} 개의 진행 중인 작업이 있습니다.": "Trwają {} zadania.",
|
||||
"{} 설정": "{} Preferencje",
|
||||
"{} 시간 전 데이터": "Data; {} godzin temu",
|
||||
"{} 일 전 데이터": "Data; {} dni temu",
|
||||
"{}개의 불완전한 작업을 다시 시작합니다.": "Zrestartuj {} nieukończonych zadań.",
|
||||
"{}개의 완료된 작업을 목록에서 제거합니다.": "Usuń {} zakonczonych zadań z listy.",
|
||||
"{}개의 완료된 작업을 제거합니다.": "Usuń {} wykonanych zadań.",
|
||||
"『 {} 』는 이미 북마크되어 있습니다": "『 {} 』jest już dodany do zakładek",
|
||||
"『 {} 』를 복사했습니다": "『 {} 』Skopiowane",
|
||||
"『 {} 』를 북마크로부터 불러왔습니다": "『 {} 』jest załadowane z zakładek",
|
||||
"『 {} 』를 북마크에서 불러왔습니다": "『 {} 』jest załadowane z zakładek",
|
||||
"『 {} 』를 북마크했습니다": "『 {} 』jest dodany do zakładek",
|
||||
"『 {} 』를 저장 폴더에서 불러왔습니다": "『 {} 』jest załadowany z folderu pobierania",
|
||||
"가능하면 일본어 제목 사용": "Użyj japońskiego tytułu. jeśli jest dostępne",
|
||||
"가져오기": "Zaimportuj",
|
||||
"가져오기 실패": "Nie udało się zaimportować",
|
||||
"간단 검색": "Proste wyszukiwanie",
|
||||
"개": "przedmioty",
|
||||
"개별 다운로드는 지원하지 않습니다": "Pobieranie pojedynczego posta nie jest obsługiwane",
|
||||
"갤러리 넘버": "ID galerii",
|
||||
"갤러리 넘버 복사": "Skopiuj ID galerii",
|
||||
"갤러리 넘버를 복사하는 중에 오류가 발생했습니다.": "Wystąpił błąd podczas kopiowania ID galerii.",
|
||||
"갤러리 작업 정보... (&I)": "Informacja o galerii... (&I)",
|
||||
"갤러리 정보": "Informacja galerii",
|
||||
"갤러리 정보 파일 (info.txt) 생성": "Utwórz plik o informacjach galerii (info.txt)",
|
||||
"갯수": "Count",
|
||||
"거꾸로": "Odwrócony",
|
||||
"검색": "Szukaj",
|
||||
"검색 데이터": "Szukaj dane",
|
||||
"검색 도중에 사용할 수 없는 기능입니다.": "Ta funkcja jest zablokowana podczas szukania.",
|
||||
"검색 범위": "Szukaj Zakres",
|
||||
"검색 순서": "Szukaj kolejność",
|
||||
"검색 필터": "Szukaj filtry",
|
||||
"검색기": "Wyszukiwarka",
|
||||
"검색어": "Szukaj słów kluczowych",
|
||||
"검색어 자동 완성": "Auto uzupełniaj słowa kluczowa",
|
||||
"결과": "Wynik",
|
||||
"결과 저장": "Zapisz wynik",
|
||||
"경고 없이 삭제": "Usuń bez ostrzeżenia",
|
||||
"경로를 선택하세요": "Wybierz ścieżkę",
|
||||
"계속 검색": "Szukaj następny",
|
||||
"계정": "Nazwa użytkownika",
|
||||
"계정 / 패스워드": "Nazwa użytkownika / Hasło",
|
||||
"고급": "Zaawansowane",
|
||||
"고급 검색": "Zaawansowane wyszukiwanie",
|
||||
"고정": "Przypnij",
|
||||
"고정 해제": "Odepnij",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "Normalny",
|
||||
"그룹": "Grupy",
|
||||
"그룹 (Groups)": "Grupy",
|
||||
"그룹 안의 모든 작업 다시 시작": "Zrestartuj wszystkie zadania w tej grupie",
|
||||
"그룹 이름": "Nazwa Grupy",
|
||||
"그룹 합쳐서 불러오기": "Wczytaj wszystkie grupy razem",
|
||||
"그룹명 복사 (&G)": "Skopiuj nazwy grup (&G)",
|
||||
"그룹으로 이동": "Przenieś do grupy",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "Grupy nie mogą zawierać grup.",
|
||||
"그룹이 없습니다": "Brak grup",
|
||||
"기본": "Domyślne",
|
||||
"기본 데이터": "Dane domyślne",
|
||||
"기본 데이터 다운로드": "Pobierz dane domyślne",
|
||||
"기본 정렬 상태에서만 그룹을 만들 수 있습니다.": "Grupę można utworzyć tylko w domyślnym trybie sortowania.",
|
||||
"기본값": "Wartość domyślna",
|
||||
"기타": "Itp.",
|
||||
"깊은 모델": "Deep model",
|
||||
"끝내기": "Wyjście",
|
||||
"날짜": "Data",
|
||||
"낮음": "Low",
|
||||
"내보내기": "Eksport",
|
||||
"내보내기 실패": "Nie udało się wyeksportować",
|
||||
"내용 보기": "Zobacz skrypt",
|
||||
"내장 웹브라우저": "Wbudowana przeglądarka internetowa",
|
||||
"내장 이미지 뷰어": "Wbudowana przeglądarka obrazów",
|
||||
"녹화 중...": "Nagrywanie...",
|
||||
"녹화 중지": "Zatrzymaj nagrywanie",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "Dotyczy również innych serwisów wideo.",
|
||||
"다시 시작 (&S)": "Uruchom ponownie (&S)",
|
||||
"다시 시작 실패; 복구됨": "Nie udało się ponowić próby; Cofnięto",
|
||||
"다운로드": "Pobierz",
|
||||
"다운로드 & 제거": "Pobierz & Usuń",
|
||||
"다운로드 && 제거": "Pobierz && Usuń",
|
||||
"다운로드 (&D)": "Pobierz (&D)",
|
||||
"다운로드 날짜 표시": "Pokaż pobrane daty",
|
||||
"다운로드 속도": "Prędkość pobierania",
|
||||
"다운로드 완료": "Pobieranie zakończone",
|
||||
"다운로드 완료 후 자동 제거": "Usuń automatycznie po zakończeniu pobierania",
|
||||
"다운로드 일시 정지 / 재개": "Wstrzymaj pobieranie / Wznów",
|
||||
"다운로드가 완료되면 알림": "Pokaż powiadomienie po zakończeniu pobierania",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "Tylko pobrane galerie",
|
||||
"다음 검색시 더 빠르게 검색": "Popraw wydajność następnym razem",
|
||||
"다크 모드": "Tryb ciemny",
|
||||
"단일 파일": "Pojedynczy plik",
|
||||
"단축키": "Skróty",
|
||||
"단축키 내보내기 성공": "Skróty zostały pomyślnie wyeksportowane",
|
||||
"단축키 불러오기 성공": "Skróty zostały pomyślnie zaimportowane",
|
||||
"단축키 수정": "Edytuj skróty",
|
||||
"단축키 초기화 성공": "Skróty zostały pomyślnie zresetowane",
|
||||
"닫기 버튼으로 트레이로 최소화": "Przycisk Zamknij minimalizuje do zasobnika systemowego",
|
||||
"대기 중...": "Czekanie...",
|
||||
"데이터 분류 중...": "Klasyfikowanie danych...",
|
||||
"데이터 분석 중...": "Analizowanie danych...",
|
||||
"데이터 읽는 중...": "Odczytywanie danych...",
|
||||
"데이터가 없습니다.\n메뉴에서 데이터를 다운로드해주세요.": "Brak danych.\nProszę pobrać dane z menu.",
|
||||
"데이터가 없습니다. 검색기에서 데이터를 다운로드 해주세요": "Nie ma danych. Proszę pobrać dane z wyszukiwarki.",
|
||||
"데이터가 일주일 이상 경과했습니다.\n데이터를 업데이트 해주세요.": "Twoje dane są nieaktualne od ponad tygodnia.\nProszę zaktualizować dane.",
|
||||
"데이터를 다운로드하는 도중에 실패했습니다.": "Nie udało się pobrać danych",
|
||||
"데이터를 모두 받았습니다!": "Wszystkie dane zostały pobrane!",
|
||||
"도구": "Narzędzia",
|
||||
"도움말": "Pomoc",
|
||||
"동영상": "Wideo",
|
||||
"동영상 변환...": "Konwertuj wideo...",
|
||||
"뒤 100 페이지": "Ostatnie 100 stron",
|
||||
"드래그 & 드랍해서 바꾸세요:": "Przeciągnij i upusć, aby zmienić:",
|
||||
"디더링": "Dither",
|
||||
"디버그": "Debug",
|
||||
"디스코드": "Discord",
|
||||
"라이선스": "Licencja",
|
||||
"랜덤으로 하나 선택": "Wybierz losowo jedną z nich",
|
||||
"로그...": "Log...",
|
||||
"로그인": "Zaloguj się",
|
||||
"로그인 실패: {}{}\n[옵션 - 설정 - Pixiv 설정 - 로그인] 에서 설정해주세요.": "Nie udało się zalogować: {}{}\nProszę zobaczyć [Opcje - Preferencje - Ustawienia Pixiv - Zaloguj się].",
|
||||
"로그인 중...": "Logowanie...",
|
||||
"로그인 테스트": "Test Login",
|
||||
"로컬 파일 감지": "Wykryj lokalne pliki",
|
||||
"로컬 파일로부터 새 작업 만들기": "Utwórz nowe zadanie z lokalnych plików",
|
||||
"를 북마크에서 지웠습니다": "jest usunięty z zakładek",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "Informacja o wydaniu",
|
||||
"링크 열기": "Otwórz link",
|
||||
"링크 주소 복사": "Skopiuj link",
|
||||
"링크 주소 복사 (&C)": "Skopiuj link (&C)",
|
||||
"마무리 중...": "Koniec...",
|
||||
"메뉴": "Menu",
|
||||
"메모리 사용량": "Wykorzystanie pamięci",
|
||||
"메모리 사용량 표시": "Pokaż użycie pamięci",
|
||||
"모델": "Model",
|
||||
"모델 생성 중...": "Tworzenie modelu...",
|
||||
"모델: {}": "Model: {}",
|
||||
"모두": "Wszystkie",
|
||||
"모두 삭제": "Usuń wszystko",
|
||||
"모두 선택": "Zaznacz wszystko",
|
||||
"모든 갤러리 넘버를 복사했습니다.": "Skopiowano wszystkie ID galerii.",
|
||||
"모든 언어": "Wszystkie języki",
|
||||
"목록": "Lista",
|
||||
"목록 주소를 입력해주세요": "Proszę podaj adres listy",
|
||||
"목록 지우기": "Wyczyść listę",
|
||||
"목록에서 제거": "Usuń z listy",
|
||||
"목록에서 폴더 제거": "Usuń folder z listy",
|
||||
"목록을 모두 지울까요?": "Czy na pewno chcesz wyczyścić listę?",
|
||||
"미리보기": "Podgląd",
|
||||
"미리보기 크기": "Rozmiar podglądu",
|
||||
"버전 확인": "Sprawdź aktualizacje",
|
||||
"번역": "Tłumaczenie",
|
||||
"변경할 최대 크기": "Maksymalny rozmiar",
|
||||
"변경할 최소 크기": "Minimalny rozmiar",
|
||||
"변환 중...": "Konwersja...",
|
||||
"보기": "Zobacz",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "Uruchom od startu systemu",
|
||||
"북마크": "Zakładki",
|
||||
"북마크 가져오기": "Importuj zakładki",
|
||||
"북마크 가져오기 실패": "Nie udało się zaimportować zakładek",
|
||||
"북마크 내보내기": "Eksportuj zakładki",
|
||||
"북마크 내보내기 실패": "Nie udało się wyeksportować zakładek",
|
||||
"북마크를 가져왔습니다": "Zakładki są importowane",
|
||||
"북마크를 내보냈습니다": "Zakładki są eksportowane",
|
||||
"북마크를 모두 삭제합니다.": "Usuń wszystkie zakładki.",
|
||||
"북마크를 모두 삭제했습니다.": "Usunięto wszystkie zakładki.",
|
||||
"불러오기": "Wczytaj",
|
||||
"불완전한 작업 모두 다시 시작": "Zrestartuj wszystkie niekompletne zadania",
|
||||
"불완전한 작업 자동으로 다시 시작": "Automatycznie uruchamiaj ponownie niekompletne zadania",
|
||||
"붙여넣고 다운로드": "Wklej i pobierz",
|
||||
"뷰어... (&V)": "&Przeglądarka...",
|
||||
"브라우저로 보기": "Wyświetl w przeglądarce",
|
||||
"브라우저로 보기 (&B)": "Wyświetl w przeglądarce (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "Uwzględnij podobne obrazy (Wolne)",
|
||||
"비율 유지": "Proporcja obrazu",
|
||||
"비정상적인 로그인 시도": "Nieprawidłowa próba logowania",
|
||||
"빠른 시작을 위한 작업 레이지 로딩": "Leniwe ładowanie zadań w celu szybkiego uruchomienia",
|
||||
"빠른 실행 도구 모음 사용자 지정": "Dostosuj pasek narzędzi szybkiego dostępu",
|
||||
"뽑기": "Losowe",
|
||||
"뽑을 갯수": "Liczba galerii",
|
||||
"사용법": "Jak używać",
|
||||
"사용법...": "Jak używać...",
|
||||
"사용자 지정 테마 색": "Niestandardowy kolor motywu",
|
||||
"사이트": "Strona",
|
||||
"사이트 추가": "Obsługa witryny",
|
||||
"사이트를 추가했습니다": "Dodano obsługę witryny",
|
||||
"삭제된 데이터": "Usunięte dane",
|
||||
"삭제된 데이터 다운로드": "Pobierz usunięte dane",
|
||||
"삭제된 데이터가 없습니다.\n메뉴에서 데이터를 다운로드해주세요.": "Nie ma Usuniętych danych.\nProszę pobrać dane z Menu.",
|
||||
"삭제된 데이터를 다운로드하는 도중에 실패했습니다.": "Nie udało się pobrać usuniętych danych",
|
||||
"삭제된 데이터를 모두 받았습니다!": "Pobieranie zakończone: Usunięte dane",
|
||||
"삭제시 휴지통으로 보냄": "Użyj kosza",
|
||||
"상태": "Status",
|
||||
"새 그룹 만들기": "Utwórz nową grupę",
|
||||
"새 버전이 있습니다. 업데이트 하실래요?": "Pojawiła się nowa wersja. Czy chcesz ją zaktualizować?",
|
||||
"새로고침": "Odśwież",
|
||||
"서명": "Certyfikat",
|
||||
"서버": "Serwer",
|
||||
"선택 화 다운로드": "Wybierz rozdziały && Pobierz",
|
||||
"선택된 작업들 내보내기": "Eksportuj wybrane zadania",
|
||||
"선택된 작업이 없습니다": "Brak wybranych zadań",
|
||||
"선택한 항목 중에서 자동 선택": "Automatyczny wybór w obrębie wybranych elementów",
|
||||
"설명": "Komentarz",
|
||||
"설정": "Preferencje",
|
||||
"설정 (Preferences)": "Preferencje",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "Sukces: {}\nNiepowodzenie: {}",
|
||||
"수동": "Manualne",
|
||||
"수동 업데이트": "Manualne",
|
||||
"수정...": "Edytuj...",
|
||||
"순서 변경": "Zmień kolejność",
|
||||
"숨김 키": "Boss-Key",
|
||||
"숫자": "Liczba",
|
||||
"스레드": "Wątki",
|
||||
"스레드 생성 중...": "Rozpoczynanie wątków...",
|
||||
"스크롤 속도 ({}x)": "Prędkość przewijania ({}x)",
|
||||
"스크립트": "Skrypt",
|
||||
"스크립트 가져오기": "Importuj skrypt",
|
||||
"스크립트를 실행하시겠습니까?": "Czy na pewno chcesz uruchomić ten skrypt?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "Czytanie historii...",
|
||||
"스토리 포함": "Dołącz relacje",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "Seria",
|
||||
"시리즈 (Series)": "Seria",
|
||||
"시리즈가 없습니다": "Brak serii",
|
||||
"시리즈명 복사 (&S)": "Kopiuj nazwy serii (&S)",
|
||||
"시스템": "System",
|
||||
"시스템 트레이": "Zasobnik systemowy",
|
||||
"시작 후 다운로드": "Pobrano od włączenia aplikacji",
|
||||
"시작 후 지난 시간": "Czas, który upłynął od włączenia aplikacji",
|
||||
"실패": "Porażka",
|
||||
"실험 기능": "Eksperymentalny",
|
||||
"썸네일": "Miniaturka",
|
||||
"썸네일 고정": "Napraw miniaturę",
|
||||
"썸네일 숨기기": "Ukryj miniatury",
|
||||
"썸네일 크기": "Wielkość miniaturki",
|
||||
"썸네일을 선택하세요": "Wybierz miniaturę",
|
||||
"아니오(&N)": "&Nie",
|
||||
"아이콘": "Ikona",
|
||||
"알 수 없는 오류": "Błąd",
|
||||
"압축": "Kompresja",
|
||||
"압축 형식": "Format",
|
||||
"압축파일 연결 프로그램": "Otwórz skompresowane pliki używając",
|
||||
"앞 100 페이지": "Pierwsze 100 stron",
|
||||
"얕은 모델": "Shallow model",
|
||||
"언어": "Język",
|
||||
"언어 (Language)": "Język",
|
||||
"언어 (Languages)": "Języki",
|
||||
"업데이트": "Aktualizacja",
|
||||
"업데이트 중...": "Aktualizacja...",
|
||||
"업데이트 체크 중...": "Sprawdzanie dostępności aktualizacji...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "Wybierz program, którego chcesz użyć do otwarcia",
|
||||
"열기": "Otwórz",
|
||||
"예(&Y)": "&Tak",
|
||||
"예상치 못한 오류로 다운로드에 실패했습니다.": "Nie udało się pobrać z powodu nieoczekiwanego błędu.",
|
||||
"예시": "Przykład",
|
||||
"올바르지 않은 범위입니다": "Nieprawidłowy zakres",
|
||||
"올바르지 않은 주소입니다": "Nieprawidłowy link",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "Nieprawidłowo sformatowany filtr wyszukiwania",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "Opcje",
|
||||
"옵션 (Options)": "Opcje",
|
||||
"완료": "Gotowe",
|
||||
"완료된 작업 모두 제거": "Usuń wszystkie zakończone zadania",
|
||||
"완료됨으로 표시": "Oznacz jako zakończone",
|
||||
"완전 삭제": "Usuń na stałe",
|
||||
"우선순위": "priorytet",
|
||||
"움짤": "Ugoira",
|
||||
"움짤 변환...": "Konwertuj Ugoira...",
|
||||
"원본": "Oryginał",
|
||||
"원본 이미지 다운로드": "Pobierz oryginalne obrazy",
|
||||
"원본 크기보다 크게 바뀌지는 않습니다": "Rozmiar nie będzie większy niż rozmiar oryginalny",
|
||||
"원본 파일 삭제": "Usuń pobrane pliki",
|
||||
"원하는 인공신경망 모델을 고르세요:": "Wybierz żądany model sztucznej sieci neuronowej:",
|
||||
"웹툰 제외": "Wyklucz Webtoons",
|
||||
"유사도": "Podobieństwo",
|
||||
"유튜브": "YouTube",
|
||||
"유효하지 않은 계정 / 패스워드입니다": "Nieprawidłowe konto / hasło",
|
||||
"유효한 계정입니다": "Ważne konto",
|
||||
"음소거": "Wycisz",
|
||||
"음악 파일에 앨범아트 삽입": "Dodaj okładkę albumu do plików audio",
|
||||
"음원": "Dźwięk",
|
||||
"음질": "Jakość dźwięku",
|
||||
"이동": "Przesuń",
|
||||
"이동할 수 없습니다: {}": "Nie można przesunąć: {}",
|
||||
"이름": "Nazwa",
|
||||
"이름 변경": "Zmień nazwę",
|
||||
"이름을 얻는 도중 실패했습니다": "Nie udało się odczytać nazwy",
|
||||
"이미 다운로드한 작품 제외": "Wyklucz już pobrane galerie",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "Istnieje już grupa, która zawiera zadania.",
|
||||
"이미지": "Obraz",
|
||||
"이미지 정보 캐시": "Informacje o pamięci podręcznej obrazu",
|
||||
"이미지 포맷 변환": "Zmień format obrazu",
|
||||
"이미지를 읽는 중 실패": "Nie udało się odczytać obrazów",
|
||||
"익명 모드": "Tryb anonimowy",
|
||||
"인코딩": "Kodowanie",
|
||||
"일반": "Ogólne",
|
||||
"읽는 중...": "Czytanie...",
|
||||
"자동": "Automatyczny",
|
||||
"자동 새로고침 & 스크롤": "Automatyczne odświeżanie i przewijanie",
|
||||
"자동 선택": "Wybór automatyczny",
|
||||
"자동 업데이트": "Automatyczny",
|
||||
"자동 저장": "Autozapis",
|
||||
"자막": "Podtytuł",
|
||||
"자세한 정보 보기": "Pokaż szczegóły",
|
||||
"작가": "Artyści",
|
||||
"작가 (Artists)": "Artyści",
|
||||
"작가 이름": "Imię artysty",
|
||||
"작가 추천": "Poleć artystów",
|
||||
"작가 합쳐서 불러오기": "Wczytaj wszystkich artystów razem",
|
||||
"작가가 없습니다": "Brak artystów",
|
||||
"작가명 복사 (&A)": "Skopiuj nazwy artystów (&A)",
|
||||
"작게": "Mała",
|
||||
"작성자": "Autor",
|
||||
"작업": "Zadania",
|
||||
"작업 개수": "Ilość zadań",
|
||||
"작업 수정": "Edytuj zadanie",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "Zmień kolejność po lewej stronie",
|
||||
"작업 용량": "Rozmiar pliku zadań",
|
||||
"작업 정보": "Informacje o zadaniu",
|
||||
"작업 정보... (&I)": "Informacje... (&I)",
|
||||
"작업 타입?": "Typ zadania?",
|
||||
"작업들 가져오기": "Importuj zadania",
|
||||
"작업들 내보내기": "Eksportuj zadania",
|
||||
"작업들을 가져올까요?": "Czy chcesz zaimportować zadania?",
|
||||
"작업들을 가져왔습니다": "Zadania zaimportowane",
|
||||
"작업들을 내보냈습니다": "Zadania wyeksportowane",
|
||||
"작업이 아직 완료되지 않았습니다": "Zadanie nie zostało jeszcze zakończone",
|
||||
"작품 목록 가져오기": "Importuj listę galerii",
|
||||
"작품 목록 내보내기": "Eksportuj listę galerii",
|
||||
"잠금": "Zablokuj",
|
||||
"잠금 해제": "Odblokuj",
|
||||
"장": "strony",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "Tryb niskiej wydajności",
|
||||
"저장": "Zapisz",
|
||||
"저장 && 종료": "Zapisz && Wyjdź",
|
||||
"저장 실패": "Nie udało się zapisać",
|
||||
"저장 완료": "Zapisane",
|
||||
"저장 중...": "Zapisywanie...",
|
||||
"저장 폴더": "Folder pobierania",
|
||||
"저장 폴더 변경...": "Zmień folder pobierania...",
|
||||
"저장 폴더에 있는 모든 갤러리 넘버 복사": "Skopiuj wszystkie ID galerii w folderze pobierania",
|
||||
"저장 폴더에 있는 모든 작가 불러오기": "Wczytaj wszystkich artystów z folderu pobierania",
|
||||
"저장... (&S)": "&Zapisz...",
|
||||
"저장된 설정 파일이 없습니다.\n[F1] 을 눌러서 사용법을 보세요.": "Brak pliku konfiguracyjnego; Naciśnij [F1], aby przeczytać \"Jak używać\"",
|
||||
"적어도 {} 개의 작품이 필요합니다.": "Wymagane są co najmniej {} galerie.",
|
||||
"점수": "Wynik",
|
||||
"정규식 문법": "Składnia Regex",
|
||||
"정규식으로 찾기": "Szukaj używając Regex",
|
||||
"정렬 기준": "Sortuj według",
|
||||
"정말 단축키를 초기화하시겠습니까?": "Czy na pewno chcesz zresetować skróty?",
|
||||
"정말 목록에서 제거하시겠습니까?": "Czy na pewno chcesz usunąć z listy?",
|
||||
"정말 삭제하시겠습니까?": "Czy na pewno chcesz usunąć?",
|
||||
"정말 종료하시겠습니까?": "Czy na pewno chcesz wyjść?",
|
||||
"정말 중지하시겠습니까?": "Czy na pewno chcesz przestać działanie?",
|
||||
"정말 쿠키를 초기화하시겠습니까?": "Czy na pewno chcesz wyczyścić pliki cookie?",
|
||||
"정보": "Informacje",
|
||||
"정보...": "Informacje...",
|
||||
"정확도": "Dokładność",
|
||||
"제거": "Usuń",
|
||||
"제목": "Tytuł",
|
||||
"제목 (Title)": "Tytuł",
|
||||
"제목 복사 (&T)": "Kopiuj tytuł (&T)",
|
||||
"제외 태그": "Znaczniki do wykluczenia",
|
||||
"조각 표시": "Pokaż części",
|
||||
"종료": "Wyjście",
|
||||
"종료 중...": "Wychodzenie...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "Przeciągnij poziomo: ±1\nPrzeciągnij pionowo: ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "Adres URL albo ID galerii",
|
||||
"주소를 입력해주세요": "Proszę wpisz jakieś adresy URL",
|
||||
"중간": "Średnia",
|
||||
"중단": "Przerwij działanie",
|
||||
"중복 이미지 찾기": "Wyszukiwarka zduplikowanych zdjęć",
|
||||
"중복 이미지 찾는 중": "Znajdowanie zduplikowanych zdjęć",
|
||||
"중복 제거": "Wytnij duplikaty",
|
||||
"지원하는 사이트:": "Obsługiwane witryny:",
|
||||
"지정한 페이지만 다운로드합니다.": "Pobieraj tylko wybrane strony.",
|
||||
"직접 다운로드": "Bezpośrednie pobieranie",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "Odniesienie: {} galerie",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "Proszę zobaczyć: Pomoc - Jak używać (F1) - Załaduj pliki cookie",
|
||||
"창 보이기 / 숨기기": "Pokaż / Ukryj okna",
|
||||
"찾기...": "Szukaj...",
|
||||
"첫 번째 파일 열기": "Otwórz pierwszy plik",
|
||||
"첫 번째 파일 열기 (&O)": "Otwórz pierwszy plik (&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "Pierwsza strona, 10 ~ 20 stron, Ostatnia strona",
|
||||
"체인지로그": "Lista zmian",
|
||||
"초기화": "Resetuj",
|
||||
"최대 다운로드 속도": "Maksymalna prędkość pobierania",
|
||||
"최대 동시 작업": "Maksymalna liczba zadań równoległych",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "Przycisk minimalizuj minimalizuje do zasobnika systemowego",
|
||||
"추가한 날짜": "Data dodania",
|
||||
"취소": "Anuluj",
|
||||
"캐릭터": "Postacie",
|
||||
"캐릭터 (Characters)": "Postacie",
|
||||
"캐릭터 이름": "Imię postaci",
|
||||
"캐릭터가 없습니다": "Brak postaci",
|
||||
"캐릭터명 복사 (&C)": "Kopiuj imię postaci (&C)",
|
||||
"코멘트": "Komentarz",
|
||||
"코멘트 수정": "Edytuj komentarz",
|
||||
"쿠키": "Pliki Cookie",
|
||||
"쿠키 내보내기": "Eksportuj pliki cookie",
|
||||
"쿠키 내보내기 성공": "Pliki cookie zostały pomyślnie wyeksportowane",
|
||||
"쿠키 불러오기": "Załaduj pliki cookie",
|
||||
"쿠키 불러오기 성공": "Pliki cookie zostały pomyślnie zaimportowane",
|
||||
"쿠키 초기화 성공": "Pliki cookie zostały pomyślnie wyczyszczone",
|
||||
"쿠키가 없습니다": "Brak plików cookie",
|
||||
"쿠키를 업데이트하세요": "Zaktualizuj swoje pliki cookie",
|
||||
"크게": "Duża",
|
||||
"크기": "Rozmiar",
|
||||
"크기 조절": "Zmień rozmiar",
|
||||
"크기 조절...": "Zmiana rozmiaru...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Połączenie z rozszerzeniem Chrome nie powiodło się.",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "Klasyczna",
|
||||
"클립보드에서 자동 추가": "Monitor schowka",
|
||||
"타입": "Typy",
|
||||
"타입 (Types)": "Typy",
|
||||
"태그": "Znaczniki",
|
||||
"태그 \"{}\" 제거": "Usuń Znacznik \"{}\"",
|
||||
"태그 \"{}\" 추가": "Dodaj Znacznik \"{}\"",
|
||||
"태그 (Tags)": "Znaczniki",
|
||||
"태그 :": "Znacznik :",
|
||||
"태그 설정": "Ustawienia znacznika",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "Brak znaczników",
|
||||
"테마": "Motyw",
|
||||
"토렌트 추가": "Dodaj torrent",
|
||||
"토렌트 파일 연결": "Powiąż z plikami torrent",
|
||||
"통계": "Statystyka",
|
||||
"통과": "Pomiń",
|
||||
"투명도": "Przezroczystość",
|
||||
"트래커 수정": "Edytuj trackery",
|
||||
"트레이로 전환되었습니다": "Zminimalizowany do zasobnika systemowego",
|
||||
"트레이로 최소화": "Minimalizuj do zasobnika systemowego",
|
||||
"트레이에서 알림 보이기": "Wyświetl powiadomienia w zasobniku",
|
||||
"파일": "Plik",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "Usuń pliki",
|
||||
"파일 삭제 (&X)": "Usuń pliki (&X)",
|
||||
"파일 수": "Liczba plików",
|
||||
"파일 스캔 중": "Skanowanie plików",
|
||||
"파일 유형 제외": "Wyklucz typy plików",
|
||||
"파일 크기": "Rozmiar pliku",
|
||||
"파일 형식": "Format",
|
||||
"파일 형식 물어보기": "Zapytaj o typ pliku",
|
||||
"파일명": "Nazwa pliku",
|
||||
"파일명 형식": "Format nazwy pliku",
|
||||
"파일을 선택하세요": "Wybierz plik",
|
||||
"파일이 없습니다": "Brak plików",
|
||||
"패스워드": "Hasło",
|
||||
"퍼지로 찾기": "Szukaj używając Fuzzy",
|
||||
"페이지 읽는 중...": "Czytanie stron...",
|
||||
"페이지 지정": "Wybierz strony",
|
||||
"페이지 지정 다운로드": "Wybierz strony && Pobierz",
|
||||
"페이지 지정 다운로드 기본값": "Wartość domyślna dla opcji \"Wybierz strony && Pobierz\"",
|
||||
"페이지를 선택해주세요": "Proszę wybrać strony",
|
||||
"페이지를 읽는 중에 오류가 발생했습니다": "Wystąpił błąd podczas próby odczytania stron",
|
||||
"평범하게 찾기": "Domyślny tryb wyszukiwania",
|
||||
"포트": "Port",
|
||||
"포함 태그": "Znaczniki do uwzględnienia",
|
||||
"폭 맞춤": "Dopasuj do szerokości",
|
||||
"폰트 변경...": "Zmień czcionkę...",
|
||||
"폰트 초기화": "Resetuj czcionkę",
|
||||
"폴더": "Folder",
|
||||
"폴더 && 압축파일": "Folder && Pliki zip",
|
||||
"폴더 열기": "Otwórz folder",
|
||||
"폴더 이동": "Przenieś folder",
|
||||
"폴더 추가": "Dodaj folder",
|
||||
"폴더들": "Katalogi",
|
||||
"폴더를 선택하세요": "Wybierz folder",
|
||||
"폴더를 추가해주세요": "Proszę dodać kilka folderów",
|
||||
"폴더명 형식": "Format folderów",
|
||||
"표지 검색 실패": "Nie udało się wyszukać okładki",
|
||||
"표지 검색...": "Szukanie okładki...",
|
||||
"프로그램 패스워드": "Hasło programu",
|
||||
"프록시": "Proxy",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "Numeruj pliki w playlistach",
|
||||
"플레이리스트 한 폴더에 다운로드": "Pobierz playlistę do jednego folderu",
|
||||
"플로팅 미리보기": "Pływający podgląd",
|
||||
"피드백": "Feedback",
|
||||
"필터": "Filtr",
|
||||
"필터창": "Filtr",
|
||||
"하나 이상의 타입을 선택해주세요.": "Wybierz co najmniej jeden typ.",
|
||||
"하위폴더 포함": "Uwzględnij podfoldery",
|
||||
"학습 중...": "Uczenie się...",
|
||||
"한 줄 당 한 트래커 URL": "Jeden adres URL trackera w każdym wierszu",
|
||||
"한국어": "Koreański",
|
||||
"항상 위": "Zawsze na wierzchu",
|
||||
"해당 링크로 이동합니다": "Otwórz link...",
|
||||
"해상도": "Rozdzielczość",
|
||||
"현재 버전: v{}\n최신 버전: v{}": "Bieżąca wersja: v{}\nNajnowsza wersja: v{}",
|
||||
"호스트": "Host",
|
||||
"화 선택": "Wybierz odcinki",
|
||||
"화질": "Jakość",
|
||||
"확인": "OK",
|
||||
"후원": "Wspomóż",
|
||||
"휴지통으로 이동": "Przenieś do Kosza"
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "Cookies de login E(x)Hentai necessários",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "Seu IP está bloqueado temporariamente. Por favor tente novamente mais tarde",
|
||||
"PDF 생성": "Criar um PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "Por favor digite algumas URLs",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "Aperte [Download] para baixar. Aperte [OK] para sair.",
|
||||
"reCAPTCHA 풀기": "Conclua o reCAPTCHA",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "Busca avançada",
|
||||
"고정": "Fixar",
|
||||
"고정 해제": "Desfixar",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "Normal",
|
||||
"그룹": "Grupos",
|
||||
"그룹 (Groups)": "Grupos",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "Nome do grupo",
|
||||
"그룹 합쳐서 불러오기": "Load all groups together",
|
||||
"그룹명 복사 (&G)": "Copiar nome do grupo (&G)",
|
||||
"그룹으로 이동": "Move to group",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "Você não pode criar grupos que contenham grupos.",
|
||||
"그룹이 없습니다": "Nenhum grupo",
|
||||
"기본": "Padrão",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "Deep model",
|
||||
"끝내기": "Sair",
|
||||
"날짜": "Data",
|
||||
"낮음": "Low",
|
||||
"내보내기": "Exportar",
|
||||
"내보내기 실패": "Falha na exportação",
|
||||
"내용 보기": "Ver script",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "Visualizador de imagem acoplado",
|
||||
"녹화 중...": "Recording...",
|
||||
"녹화 중지": "Parar gravação",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "Isso também se aplica para outros sites de vídeo.",
|
||||
"다시 시작 (&S)": "Reinicialização em (&S)",
|
||||
"다시 시작 실패; 복구됨": "Falha ao tentar novamente; Revertido",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "Remover automaticamente após completar o download",
|
||||
"다운로드 일시 정지 / 재개": "Pausar / Resumir Download",
|
||||
"다운로드가 완료되면 알림": "Exibir notificação ao terminar o download",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "Apenas galerias baixadas",
|
||||
"다음 검색시 더 빠르게 검색": "Melhora desempenho da próxima vez",
|
||||
"다크 모드": "Modo noturno",
|
||||
"단일 파일": "Arquivo único",
|
||||
"단축키": "Shortcuts",
|
||||
"단축키 내보내기 성공": "Shortcuts are successfully exported",
|
||||
"단축키 불러오기 성공": "Shortcuts are successfully imported",
|
||||
"단축키 수정": "Edit shortcuts",
|
||||
"단축키 초기화 성공": "Shortcuts are successfully reseted",
|
||||
"닫기 버튼으로 트레이로 최소화": "O botão de fechar minimiza para a bandeja",
|
||||
"대기 중...": "Aguardando...",
|
||||
"데이터 분류 중...": "Classificando dados...",
|
||||
|
@ -210,6 +222,7 @@
|
|||
"로컬 파일 감지": "Detect local files",
|
||||
"로컬 파일로부터 새 작업 만들기": "Criar uma nova tarefa a partir de arquivo local",
|
||||
"를 북마크에서 지웠습니다": "removido dos favoritos",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "Nota de atualização",
|
||||
"링크 열기": "Abrir link",
|
||||
"링크 주소 복사": "Copiar link",
|
||||
|
@ -240,6 +253,7 @@
|
|||
"변경할 최소 크기": "Tamanho mínimo",
|
||||
"변환 중...": "Convertendo...",
|
||||
"보기": "Visualizar",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "Iniciar ao ligar a máquina",
|
||||
"북마크": "Favoritos",
|
||||
"북마크 가져오기": "Importar favoritos",
|
||||
|
@ -255,6 +269,7 @@
|
|||
"불완전한 작업 자동으로 다시 시작": "Reiniciar todas as tarefas incompletas automaticamente",
|
||||
"붙여넣고 다운로드": "Colar e baixar",
|
||||
"뷰어... (&V)": "&Visualizador...",
|
||||
"브라우저로 보기": "Show in browser",
|
||||
"브라우저로 보기 (&B)": "Mostrar no navegador (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "Incluir imagens similares (Lento)",
|
||||
"비율 유지": "Proporção de tela",
|
||||
|
@ -288,9 +303,11 @@
|
|||
"설명": "Comentário",
|
||||
"설정": "Preferências",
|
||||
"설정 (Preferences)": "Preferências",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "Sucesso: {}\nFalho: {}",
|
||||
"수동": "Manual",
|
||||
"수동 업데이트": "Atualização Manual",
|
||||
"수정...": "Edit...",
|
||||
"순서 변경": "Alterar a ordem",
|
||||
"숨김 키": "Boss-Key",
|
||||
"숫자": "Número",
|
||||
|
@ -300,8 +317,12 @@
|
|||
"스크립트": "Script",
|
||||
"스크립트 가져오기": "Importar script",
|
||||
"스크립트를 실행하시겠습니까?": "Você tem certeza que você quer executar esse script?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "Lendo stories...",
|
||||
"스토리 포함": "Incluir stories",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "Série",
|
||||
"시리즈 (Series)": "Série",
|
||||
"시리즈가 없습니다": "Nenhuma série",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "Atualizar",
|
||||
"업데이트 중...": "Atualizando...",
|
||||
"업데이트 체크 중...": "Buscando Atualizações...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "Escolha o programa para usar para abrir",
|
||||
"열기": "Abrir",
|
||||
"예(&Y)": "&Sim",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "Intervalo inválido",
|
||||
"올바르지 않은 주소입니다": "URL Inválida",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "Filtro de pesquisa formatado de forma inválida",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "Opções",
|
||||
"옵션 (Options)": "Opções",
|
||||
"완료": "Finalizado",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "Renomear",
|
||||
"이름을 얻는 도중 실패했습니다": "Falha ao obter o nome",
|
||||
"이미 다운로드한 작품 제외": "Ignorando os trabalhos que já foram baixados",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "Já existe um grupo que contém estas tarefas.",
|
||||
"이미지": "Imagem",
|
||||
"이미지 정보 캐시": "Cache de informações de imagem",
|
||||
|
@ -396,6 +422,7 @@
|
|||
"작업": "Tarefas",
|
||||
"작업 개수": "Número de tarefas",
|
||||
"작업 수정": "Editar tarefa",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "Reorganizar à esquerda",
|
||||
"작업 용량": "Tamanho de arquivo das tarefas",
|
||||
"작업 정보": "Informações da Tarefa",
|
||||
|
@ -412,6 +439,7 @@
|
|||
"잠금": "Bloquear",
|
||||
"잠금 해제": "Desbloquear",
|
||||
"장": "páginas",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "Modo de baixo desempenho",
|
||||
"저장": "Salvar",
|
||||
"저장 && 종료": "Salvar && Sair",
|
||||
|
@ -429,6 +457,7 @@
|
|||
"정규식 문법": "Síntaxe do Regex",
|
||||
"정규식으로 찾기": "Busca por Regex",
|
||||
"정렬 기준": "Ordenar por",
|
||||
"정말 단축키를 초기화하시겠습니까?": "Are you sure you want to reset shortcuts?",
|
||||
"정말 목록에서 제거하시겠습니까?": "Você tem certeza que deseja remover da lista?",
|
||||
"정말 삭제하시겠습니까?": "Você tem certeza que deseja deletar?",
|
||||
"정말 종료하시겠습니까?": "Você tem certeza que deseja sair?",
|
||||
|
@ -446,6 +475,7 @@
|
|||
"종료": "Sair",
|
||||
"종료 중...": "Sair...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "Arraste para a esquerda para diminuir ou para a direita para aumentar em 1\nArraste para cima para aumentar ou para baixo para diminuir em 100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "URL ou ID de Galeria",
|
||||
"주소를 입력해주세요": "Por favor insira algumas URLs",
|
||||
"중간": "Meio",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "Sites suportados:",
|
||||
"지정한 페이지만 다운로드합니다.": "Baixar apenas págians selecionadas.",
|
||||
"직접 다운로드": "Download Direto",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "Referenciar: {} galerias",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "Por favor veja: Ajuda - Como Usar (F1) - Carregar Cookies",
|
||||
"창 보이기 / 숨기기": "Exibir / Ocultar Janelas",
|
||||
"찾기...": "Buscador...",
|
||||
"첫 번째 파일 열기": "Open the first file",
|
||||
"첫 번째 파일 열기 (&O)": "Abrir o primeiro arquivo (&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "Primeira página, 10 ~ 20 páginas, Última página",
|
||||
"체인지로그": "Alterações",
|
||||
"초기화": "Resetar",
|
||||
"최대 다운로드 속도": "Velocidade de download máxima",
|
||||
"최대 동시 작업": "QUantidade máxima de tarefas paralelas",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "Botão de minimizar envia para a bandeja do sistema",
|
||||
"추가한 날짜": "Data adicionado",
|
||||
"취소": "Cancelar",
|
||||
|
@ -489,6 +522,7 @@
|
|||
"크기 조절": "Redimensionar",
|
||||
"크기 조절...": "Redimensionando...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Vínculo com a extensão do Chrome falhou.",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "Clássico",
|
||||
"클립보드에서 자동 추가": "Adicionar automaticamente da área de transferência",
|
||||
"타입": "Tipos",
|
||||
|
@ -499,6 +533,7 @@
|
|||
"태그 (Tags)": "Tags",
|
||||
"태그 :": "Tag :",
|
||||
"태그 설정": "Configurações de Tag",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "Nenhuma tag",
|
||||
"테마": "Tema",
|
||||
"토렌트 추가": "Adicionar Torrent",
|
||||
|
@ -506,10 +541,12 @@
|
|||
"통계": "Estatísticas",
|
||||
"통과": "Ignorar",
|
||||
"투명도": "Opacidade",
|
||||
"트래커 수정": "Edit trackers",
|
||||
"트레이로 전환되었습니다": "Minimizado para a bandeja do sistema",
|
||||
"트레이로 최소화": "Minimizar para a bandeja do sistema",
|
||||
"트레이에서 알림 보이기": "Mostrar notificações na bandeja",
|
||||
"파일": "Arquivo",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "Deletar arquivos",
|
||||
"파일 삭제 (&X)": "Deletar arquivos (&X)",
|
||||
"파일 수": "Númeror de arquivos",
|
||||
|
@ -549,6 +586,8 @@
|
|||
"표지 검색...": "Buscar capa...",
|
||||
"프로그램 패스워드": "Senha do programa",
|
||||
"프록시": "Proxy",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "Enumerar arquivos na playlist",
|
||||
"플레이리스트 한 폴더에 다운로드": "Baixar a playlist para uma pasta",
|
||||
"플로팅 미리보기": "Prévia flutuante",
|
||||
|
@ -558,6 +597,7 @@
|
|||
"하나 이상의 타입을 선택해주세요.": "Escolha pelo menos um tipo.",
|
||||
"하위폴더 포함": "Incluir subpastas",
|
||||
"학습 중...": "Aprendendo...",
|
||||
"한 줄 당 한 트래커 URL": "One tracker URL per line",
|
||||
"한국어": "Coreano",
|
||||
"항상 위": "Sempre visível",
|
||||
"해당 링크로 이동합니다": "Abrir o link...",
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "Cần có cookies đăng nhập của E(x)Hentai",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "Địa chỉ IP hiện tại của bạn tạm thời bị khóa. Hãy thử lại sau",
|
||||
"PDF 생성": "Tạo PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "Hãy nhập vào một vài đường dẫn",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "Nhấn [Tải xuống] để tải. Nhấn [OK] để thoát.",
|
||||
"reCAPTCHA 풀기": "giải reCAPTCHA",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "Tìm kiếm nâng cao",
|
||||
"고정": "Ghim",
|
||||
"고정 해제": "Bỏ ghim",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "Bình thường",
|
||||
"그룹": "Nhóm",
|
||||
"그룹 (Groups)": "Nhóm",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "Tên nhóm",
|
||||
"그룹 합쳐서 불러오기": "Load all groups together",
|
||||
"그룹명 복사 (&G)": "Sao chép tên nhóm (&G)",
|
||||
"그룹으로 이동": "Move to group",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "Không thể chứa nhóm trong nhóm.",
|
||||
"그룹이 없습니다": "Không có nhóm",
|
||||
"기본": "Mặc định",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "Deep model",
|
||||
"끝내기": "Thoát",
|
||||
"날짜": "Ngày",
|
||||
"낮음": "Low",
|
||||
"내보내기": "Xuất",
|
||||
"내보내기 실패": "Xuất thất bại",
|
||||
"내용 보기": "Xem tập lệnh",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "Trình xem ảnh đính kèm",
|
||||
"녹화 중...": "Recording...",
|
||||
"녹화 중지": "Dừng ghi",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "Áp dụng với các trang video khác.",
|
||||
"다시 시작 (&S)": "Khởi động lại (&S)",
|
||||
"다시 시작 실패; 복구됨": "Thử lại thất bại; Hoàn trả hiện trạng",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "Tự động gỡ bỏ sau khi hoàn tất tải xuống",
|
||||
"다운로드 일시 정지 / 재개": "Tạm dừng tại xuống / Tiếp tục",
|
||||
"다운로드가 완료되면 알림": "Thông báo khi tải xuống thành công",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "Chỉ tải xuống thư viện",
|
||||
"다음 검색시 더 빠르게 검색": "Cải thiện hiệu năng trong lần tới",
|
||||
"다크 모드": "Chế độ tối",
|
||||
"단일 파일": "Tệp đơn",
|
||||
"단축키": "Shortcuts",
|
||||
"단축키 내보내기 성공": "Shortcuts are successfully exported",
|
||||
"단축키 불러오기 성공": "Shortcuts are successfully imported",
|
||||
"단축키 수정": "Edit shortcuts",
|
||||
"단축키 초기화 성공": "Shortcuts are successfully reseted",
|
||||
"닫기 버튼으로 트레이로 최소화": "Nút đóng sẽ thu nhỏ ứng dụng xuống khay hệ thống",
|
||||
"대기 중...": "Xin đợi...",
|
||||
"데이터 분류 중...": "Phân loại dữ liệu...",
|
||||
|
@ -210,6 +222,7 @@
|
|||
"로컬 파일 감지": "Detect local files",
|
||||
"로컬 파일로부터 새 작업 만들기": "Tạo nhiệm vụ mới từ tệp cục bộ",
|
||||
"를 북마크에서 지웠습니다": "Được gỡ bỏ khỏi danh sách yêu thích",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "Ghi chú khi phát hành",
|
||||
"링크 열기": "Mở link",
|
||||
"링크 주소 복사": "Sao chép link",
|
||||
|
@ -240,6 +253,7 @@
|
|||
"변경할 최소 크기": "Kích thước tối thiểu",
|
||||
"변환 중...": "Đang chuyển đổi...",
|
||||
"보기": "Xem",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "Bắt đầu khi máy khởi động",
|
||||
"북마크": "Danh sách yêu thích",
|
||||
"북마크 가져오기": "Nhập danh sách yêu thích",
|
||||
|
@ -255,6 +269,7 @@
|
|||
"불완전한 작업 자동으로 다시 시작": "Khởi động lại các nhiệm vụ chưa hoàn thành tự động",
|
||||
"붙여넣고 다운로드": "Dán vào và Tải xuống",
|
||||
"뷰어... (&V)": "&Trình xem...",
|
||||
"브라우저로 보기": "Show in browser",
|
||||
"브라우저로 보기 (&B)": "Xem trong trình duyệt (&B)",
|
||||
"비슷한 이미지 포함 (느림)": "Bao gồm các bức ảnh tương tự (Chậm)",
|
||||
"비율 유지": "Tỉ lệ",
|
||||
|
@ -288,9 +303,11 @@
|
|||
"설명": "Bình luận",
|
||||
"설정": "Tùy chỉnh",
|
||||
"설정 (Preferences)": "Tùy chỉnh",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "Thành công: {}\nThất bại: {}",
|
||||
"수동": "Thủ công",
|
||||
"수동 업데이트": "Thủ công",
|
||||
"수정...": "Edit...",
|
||||
"순서 변경": "Sắp xếp lại",
|
||||
"숨김 키": "Boss-Key",
|
||||
"숫자": "Số",
|
||||
|
@ -300,8 +317,12 @@
|
|||
"스크립트": "Tập lệnh",
|
||||
"스크립트 가져오기": "Nạp tập lệnh",
|
||||
"스크립트를 실행하시겠습니까?": "Bạn có chắc bạn muốn chạy tập lệnh này?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "Xem stories...",
|
||||
"스토리 포함": "Bao gồm cả stories",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "Series",
|
||||
"시리즈 (Series)": "Series",
|
||||
"시리즈가 없습니다": "Không có series",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "Cập nhập",
|
||||
"업데이트 중...": "Đang cập nhập...",
|
||||
"업데이트 체크 중...": "Kiểm tra bản cập nhập...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "Chọn một phần mềm sử dụng đê mở",
|
||||
"열기": "Mở",
|
||||
"예(&Y)": "&Đúng",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "Phạm vi không hợp lệ",
|
||||
"올바르지 않은 주소입니다": "Đường dẫn không hợp lệ",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "Lỗi cú pháp với bộ lọc",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "Lựa chọn",
|
||||
"옵션 (Options)": "Lựa chọn",
|
||||
"완료": "Hoàn tất",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "Đổi tên",
|
||||
"이름을 얻는 도중 실패했습니다": "Đọc tên thất bại",
|
||||
"이미 다운로드한 작품 제외": "Loại trừ đã tải xuống thư viện",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "Có một nhóm đã bao gồm nhiệm vụ.",
|
||||
"이미지": "Ảnh",
|
||||
"이미지 정보 캐시": "Bộ nhớ đệm thông tin ảnh",
|
||||
|
@ -396,6 +422,7 @@
|
|||
"작업": "Nhiệm vụ",
|
||||
"작업 개수": "Số lượng nhiệm vụ",
|
||||
"작업 수정": "Chỉnh sửa nhiệm vụ",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "Sắp xếp lại phía bên trái",
|
||||
"작업 용량": "Kích thước tệp nhiệm vụ",
|
||||
"작업 정보": "Thông tin nhiệm vụ",
|
||||
|
@ -412,6 +439,7 @@
|
|||
"잠금": "Khóa",
|
||||
"잠금 해제": "Mở khóa",
|
||||
"장": "Trang",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "Chế độ hiệu năng thấp",
|
||||
"저장": "Lưu",
|
||||
"저장 && 종료": "Lưu && Thoát",
|
||||
|
@ -429,6 +457,7 @@
|
|||
"정규식 문법": "Cú pháp Regex",
|
||||
"정규식으로 찾기": "Tìm kiếm bằng Regex",
|
||||
"정렬 기준": "Sắp xếp theo",
|
||||
"정말 단축키를 초기화하시겠습니까?": "Are you sure you want to reset shortcuts?",
|
||||
"정말 목록에서 제거하시겠습니까?": "Bạn có chắc bạn muốn xóa khỏi danh sách?",
|
||||
"정말 삭제하시겠습니까?": "Bạn có chắc bạn muốn xóa?",
|
||||
"정말 종료하시겠습니까?": "Bạn có chắc bạn muốn thoát?",
|
||||
|
@ -446,6 +475,7 @@
|
|||
"종료": "Thoát",
|
||||
"종료 중...": "Thoát...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "Kéo theo chiều ngang: ±1\nKéo theo chiều dọc: ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "Đường dẫn hoặc ID thư viện",
|
||||
"주소를 입력해주세요": "Hãy nhập vào vài đường dẫn",
|
||||
"중간": "Ở giữa",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "Các trang hỗ trợ:",
|
||||
"지정한 페이지만 다운로드합니다.": "Chỉ tải xuống những trang được chọn.",
|
||||
"직접 다운로드": "Tải xuống trực tiếp",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "Tham khảo: {} thư viện",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "Hãy xem qua: Hỗ trợ - Hướng dẫn sử dụng (F1) - Tải cookies",
|
||||
"창 보이기 / 숨기기": "Xem / Ẩn cửa sổ",
|
||||
"찾기...": "Công cụ tìm kiếm...",
|
||||
"첫 번째 파일 열기": "Open the first file",
|
||||
"첫 번째 파일 열기 (&O)": "Mở tệp đầu tiên (&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "Trang đầu tiên, Trang thứ 10 ~ 20, Trang cuối cùng",
|
||||
"체인지로그": "Changelog",
|
||||
"초기화": "Khởi động lại",
|
||||
"최대 다운로드 속도": "Tốc độ tải xuống tối đa",
|
||||
"최대 동시 작업": "Tối đa các nhiệm vụ chạy song song",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "Nút ẩn sẽ ẩn xuống khay hệ thống",
|
||||
"추가한 날짜": "Ngày đã được thêm vào",
|
||||
"취소": "Hủy",
|
||||
|
@ -489,6 +522,7 @@
|
|||
"크기 조절": "Sửa kích thước",
|
||||
"크기 조절...": "Sửa kích thước...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Kết nối tới tiện ích Chrome thất bại.",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "Cổ điển",
|
||||
"클립보드에서 자동 추가": "Giám sát bản tạm",
|
||||
"타입": "Loại",
|
||||
|
@ -499,6 +533,7 @@
|
|||
"태그 (Tags)": "Thẻ",
|
||||
"태그 :": "Thẻ :",
|
||||
"태그 설정": "Cài đặt thẻ",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "Thẻ không tồn tại",
|
||||
"테마": "Chủ đề",
|
||||
"토렌트 추가": "Thêm Torrrent",
|
||||
|
@ -506,10 +541,12 @@
|
|||
"통계": "Số liệu",
|
||||
"통과": "Qua",
|
||||
"투명도": "Độ trong suốt",
|
||||
"트래커 수정": "Edit trackers",
|
||||
"트레이로 전환되었습니다": "Đã ẩn xuống khay hệ thống",
|
||||
"트레이로 최소화": "Ẩn xuống khay hệ thống",
|
||||
"트레이에서 알림 보이기": "Xem thông báo trong khay",
|
||||
"파일": "Tệp",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "Xóa tệp",
|
||||
"파일 삭제 (&X)": "Xóa các tệp (&X)",
|
||||
"파일 수": "Số lượng tệp",
|
||||
|
@ -549,6 +586,8 @@
|
|||
"표지 검색...": "Tìm ảnh bìa...",
|
||||
"프로그램 패스워드": "Mật khẩu phần mềm",
|
||||
"프록시": "Proxy",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "Đánh số tệp trong danh sách phát ",
|
||||
"플레이리스트 한 폴더에 다운로드": "Tải xuống danh sách phát vào một tệp",
|
||||
"플로팅 미리보기": "Xem trước nổi",
|
||||
|
@ -558,6 +597,7 @@
|
|||
"하나 이상의 타입을 선택해주세요.": "Chọn ít nhất một loại.",
|
||||
"하위폴더 포함": "Bao gồm thư mục con",
|
||||
"학습 중...": "Đang học...",
|
||||
"한 줄 당 한 트래커 URL": "One tracker URL per line",
|
||||
"한국어": "Hàn Quốc",
|
||||
"항상 위": "Luôn ở trên cùng",
|
||||
"해당 링크로 이동합니다": "Mở đường dẫn...",
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "需要 E Hentai 登入 cookie",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "您的 IP 被暫時鎖定,請稍後再試。",
|
||||
"PDF 생성": "建立 PDF",
|
||||
"UI 스케일 ({}%)": "UI scale ({}%)",
|
||||
"URL을 입력하세요": "請輸入網址",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "按[下載]進行下載。按[確定]退出.",
|
||||
"reCAPTCHA 풀기": "解決驗證碼",
|
||||
|
@ -114,7 +115,7 @@
|
|||
"갤러리 넘버": "相簿 ID",
|
||||
"갤러리 넘버 복사": "複製相簿編號",
|
||||
"갤러리 넘버를 복사하는 중에 오류가 발생했습니다.": "複製相簿 ID 時出錯.",
|
||||
"갤러리 작업 정보... (&I)": "相簿資訊(&I)...",
|
||||
"갤러리 작업 정보... (&I)": "相簿資訊(&I)……",
|
||||
"갤러리 정보": "相簿資訊",
|
||||
"갤러리 정보 파일 (info.txt) 생성": "建立相簿資訊檔案(info.txt)",
|
||||
"갯수": "計數",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "高級搜尋",
|
||||
"고정": "固定",
|
||||
"고정 해제": "取消固定",
|
||||
"관리자 권한 없이 실행": "Run without administrator privileges",
|
||||
"관리자 권한 필요": "Administrator privileges required",
|
||||
"그대로": "正序",
|
||||
"그룹": "分組",
|
||||
"그룹 (Groups)": "分組",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "組名",
|
||||
"그룹 합쳐서 불러오기": "一起載入所有組",
|
||||
"그룹명 복사 (&G)": "複製組名(&G)",
|
||||
"그룹으로 이동": "移動至群組",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "組不能包含組。",
|
||||
"그룹이 없습니다": "沒有分組",
|
||||
"기본": "預設",
|
||||
|
@ -157,13 +161,15 @@
|
|||
"깊은 모델": "深度模型",
|
||||
"끝내기": "關閉",
|
||||
"날짜": "日期",
|
||||
"낮음": "Low",
|
||||
"내보내기": "匯出",
|
||||
"내보내기 실패": "匯出失敗",
|
||||
"내용 보기": "檢視指令碼",
|
||||
"내장 웹브라우저": "內建 Web 瀏覽器",
|
||||
"내장 이미지 뷰어": "內建影像檢視器",
|
||||
"녹화 중...": "正在錄制中...",
|
||||
"녹화 중...": "正在錄制中……",
|
||||
"녹화 중지": "停止錄制",
|
||||
"높음": "High",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "也適用於其他影片網站。",
|
||||
"다시 시작 (&S)": "重新開始(&S)",
|
||||
"다시 시작 실패; 복구됨": "重試失敗; 已恢復",
|
||||
|
@ -177,15 +183,21 @@
|
|||
"다운로드 완료 후 자동 제거": "完成下載後自動刪除",
|
||||
"다운로드 일시 정지 / 재개": "下載暫停/恢復",
|
||||
"다운로드가 완료되면 알림": "下載完成後顯示通知",
|
||||
"다운로드하지 않음": "Don't download",
|
||||
"다운로드한 작품만": "僅下載畫廊",
|
||||
"다음 검색시 더 빠르게 검색": "下一次搜尋時更快搜尋",
|
||||
"다크 모드": "深色模式",
|
||||
"단일 파일": "單個檔案",
|
||||
"단축키": "捷徑",
|
||||
"단축키 내보내기 성공": "捷徑已成功匯出",
|
||||
"단축키 불러오기 성공": "捷徑已成功匯入",
|
||||
"단축키 수정": "編輯捷徑",
|
||||
"단축키 초기화 성공": "捷徑已經重設成功",
|
||||
"닫기 버튼으로 트레이로 최소화": "點選關閉時最小化到系統托盤",
|
||||
"대기 중...": "等待...",
|
||||
"데이터 분류 중...": "分類資料...",
|
||||
"데이터 분석 중...": "分析資料...",
|
||||
"데이터 읽는 중...": "讀取資料...",
|
||||
"대기 중...": "等待……",
|
||||
"데이터 분류 중...": "分類資料……",
|
||||
"데이터 분석 중...": "分析資料……",
|
||||
"데이터 읽는 중...": "讀取資料……",
|
||||
"데이터가 없습니다.\n메뉴에서 데이터를 다운로드해주세요.": "沒有資料。\n請從選單下載資料。",
|
||||
"데이터가 없습니다. 검색기에서 데이터를 다운로드 해주세요": "沒有資料。\n請從搜尋器下載資料。",
|
||||
"데이터가 일주일 이상 경과했습니다.\n데이터를 업데이트 해주세요.": "您的資料已過期一週。\n請更新資料。",
|
||||
|
@ -194,7 +206,7 @@
|
|||
"도구": "工具",
|
||||
"도움말": "幫助",
|
||||
"동영상": "影片",
|
||||
"동영상 변환...": "轉換影片...",
|
||||
"동영상 변환...": "轉換影片……",
|
||||
"뒤 100 페이지": "最後 100 頁",
|
||||
"드래그 & 드랍해서 바꾸세요:": "拖拽排序伺服器:",
|
||||
"디더링": "調節",
|
||||
|
@ -202,24 +214,25 @@
|
|||
"디스코드": "Discord",
|
||||
"라이선스": "許可證",
|
||||
"랜덤으로 하나 선택": "隨機選擇一個",
|
||||
"로그...": "日誌...",
|
||||
"로그...": "日誌……",
|
||||
"로그인": "登入",
|
||||
"로그인 실패: {}{}\n[옵션 - 설정 - Pixiv 설정 - 로그인] 에서 설정해주세요.": "登入失敗: {}{}\n請檢視 [選項 - 偏好 - Pixiv 設定 - 登入]。",
|
||||
"로그인 중...": "登入...",
|
||||
"로그인 중...": "登入……",
|
||||
"로그인 테스트": "登入測試",
|
||||
"로컬 파일 감지": "檢測本地檔案",
|
||||
"로컬 파일로부터 새 작업 만들기": "從本地檔案建立新任務",
|
||||
"를 북마크에서 지웠습니다": "已從書簽中刪除",
|
||||
"리트윗 포함": "Include retweets",
|
||||
"릴리즈 노트": "版本說明",
|
||||
"링크 열기": "開啟連結",
|
||||
"링크 주소 복사": "複製連結位址",
|
||||
"링크 주소 복사 (&C)": "複製連結位址(&C)",
|
||||
"마무리 중...": "完成...",
|
||||
"마무리 중...": "完成……",
|
||||
"메뉴": "選單",
|
||||
"메모리 사용량": "記憶體使用情況",
|
||||
"메모리 사용량 표시": "顯示記憶體使用情況",
|
||||
"모델": "模型",
|
||||
"모델 생성 중...": "建立模型...",
|
||||
"모델 생성 중...": "建立模型……",
|
||||
"모델: {}": "模型: {}",
|
||||
"모두": "All",
|
||||
"모두 삭제": "全部刪除",
|
||||
|
@ -238,8 +251,9 @@
|
|||
"번역": "翻譯",
|
||||
"변경할 최대 크기": "最大尺寸",
|
||||
"변경할 최소 크기": "最小尺寸",
|
||||
"변환 중...": "轉換中...",
|
||||
"변환 중...": "轉換中……",
|
||||
"보기": "檢視",
|
||||
"보통": "Normal",
|
||||
"부팅 시 실행": "開機自動執行",
|
||||
"북마크": "書簽",
|
||||
"북마크 가져오기": "匯入書簽",
|
||||
|
@ -254,7 +268,8 @@
|
|||
"불완전한 작업 모두 다시 시작": "重新開始所有未完成的任務",
|
||||
"불완전한 작업 자동으로 다시 시작": "啟動後自動開始未完成任務",
|
||||
"붙여넣고 다운로드": "貼上並下載",
|
||||
"뷰어... (&V)": "檢視器(&V)...",
|
||||
"뷰어... (&V)": "檢視器(&V)……",
|
||||
"브라우저로 보기": "在瀏覽器中顯示",
|
||||
"브라우저로 보기 (&B)": "在瀏覽器中檢視(&B)",
|
||||
"비슷한 이미지 포함 (느림)": "包括相似的影像(慢)",
|
||||
"비율 유지": "寬高比",
|
||||
|
@ -264,7 +279,7 @@
|
|||
"뽑기": "隨機",
|
||||
"뽑을 갯수": "畫廊數量",
|
||||
"사용법": "如何使用",
|
||||
"사용법...": "使用方法...",
|
||||
"사용법...": "使用方法……",
|
||||
"사용자 지정 테마 색": "自定義主題顏色",
|
||||
"사이트": "網站",
|
||||
"사이트 추가": "網站支援",
|
||||
|
@ -288,20 +303,26 @@
|
|||
"설명": "注釋",
|
||||
"설정": "首選項",
|
||||
"설정 (Preferences)": "設定",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "成功: {}\n失敗: {}",
|
||||
"수동": "手動",
|
||||
"수동 업데이트": "手動更新",
|
||||
"수정...": "編輯……",
|
||||
"순서 변경": "重新排序",
|
||||
"숨김 키": "老闆鍵",
|
||||
"숫자": "重新編號",
|
||||
"스레드": "最大連線",
|
||||
"스레드 생성 중...": "開始執行緒...",
|
||||
"스레드 생성 중...": "開始執行緒……",
|
||||
"스크롤 속도 ({}x)": "滾動速度 ({}x)",
|
||||
"스크립트": "指令碼",
|
||||
"스크립트 가져오기": "匯入指令碼",
|
||||
"스크립트를 실행하시겠습니까?": "您確定要執行此指令碼嗎?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "Error occurred while reading the script",
|
||||
"스토리 읽는 중...": "閱讀故事……",
|
||||
"스토리 포함": "包括故事",
|
||||
"시딩": "Seeding",
|
||||
"시딩 중지": "Stop seeding",
|
||||
"시딩 하지 않음": "No seeding",
|
||||
"시리즈": "系列",
|
||||
"시리즈 (Series)": "系列",
|
||||
"시리즈가 없습니다": "沒有系列",
|
||||
|
@ -329,8 +350,9 @@
|
|||
"언어 (Language)": "語言 (Language)",
|
||||
"언어 (Languages)": "語言 (Languages)",
|
||||
"업데이트": "更新",
|
||||
"업데이트 중...": "更新...",
|
||||
"업데이트 체크 중...": "檢查更新...",
|
||||
"업데이트 중...": "更新……",
|
||||
"업데이트 체크 중...": "檢查更新……",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "選擇用於解壓壓縮包的工具",
|
||||
"열기": "開啟",
|
||||
"예(&Y)": "是(&Y)",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "無效的范圍",
|
||||
"올바르지 않은 주소입니다": "無效的網址",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "搜尋過濾器格式不正確",
|
||||
"올바르지 않은 형식의 스크립트입니다": "Invalid format script",
|
||||
"옵션": "選項",
|
||||
"옵션 (Options)": "選項",
|
||||
"완료": "完成",
|
||||
|
@ -347,7 +370,7 @@
|
|||
"완전 삭제": "永久刪除",
|
||||
"우선순위": "排序",
|
||||
"움짤": "其他設定",
|
||||
"움짤 변환...": "轉換 Ugoira...",
|
||||
"움짤 변환...": "轉換 Ugoira……",
|
||||
"원본": "原始",
|
||||
"원본 이미지 다운로드": "下載原始圖片",
|
||||
"원본 크기보다 크게 바뀌지는 않습니다": "它的變化不會超過原始尺寸",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "重新命名分組",
|
||||
"이름을 얻는 도중 실패했습니다": "獲取名稱時失敗",
|
||||
"이미 다운로드한 작품 제외": "排除已經下載的相簿",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "Ask to retry tasks already in the list",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "This task already in the list. Do you want to download it again?",
|
||||
"이미 추가한 플러그인입니다": "This plugin already in the list",
|
||||
"이미 포함하는 그룹이 있습니다.": "已經有一個包含任務的組。",
|
||||
"이미지": "影象",
|
||||
"이미지 정보 캐시": "快取影像資訊",
|
||||
|
@ -376,7 +402,7 @@
|
|||
"익명 모드": "匿名模式",
|
||||
"인코딩": "編碼",
|
||||
"일반": "一般",
|
||||
"읽는 중...": "讀取中...",
|
||||
"읽는 중...": "讀取中……",
|
||||
"자동": "自動",
|
||||
"자동 새로고침 & 스크롤": "自動重新整理和滾動",
|
||||
"자동 선택": "自動檢查",
|
||||
|
@ -396,10 +422,11 @@
|
|||
"작업": "任務",
|
||||
"작업 개수": "完成任務數量",
|
||||
"작업 수정": "編輯任務",
|
||||
"작업 수정...": "Edit task...",
|
||||
"작업 왼쪽에서 순서 변경": "在左側重新排序",
|
||||
"작업 용량": "完成檔案大小",
|
||||
"작업 정보": "任務資訊",
|
||||
"작업 정보... (&I)": "資訊(&I)...",
|
||||
"작업 정보... (&I)": "資訊(&I)……",
|
||||
"작업 타입?": "任務型別?",
|
||||
"작업들 가져오기": "匯入任務",
|
||||
"작업들 내보내기": "匯出任務",
|
||||
|
@ -412,30 +439,32 @@
|
|||
"잠금": "鎖定",
|
||||
"잠금 해제": "解鎖",
|
||||
"장": "頁面",
|
||||
"재시작 후 적용됩니다": "Applies after restart",
|
||||
"저사양 모드": "低規格模式",
|
||||
"저장": "儲存",
|
||||
"저장 && 종료": "儲存 && 關閉",
|
||||
"저장 실패": "儲存失敗",
|
||||
"저장 완료": "儲存完畢",
|
||||
"저장 중...": "儲存...",
|
||||
"저장 중...": "儲存……",
|
||||
"저장 폴더": "儲存資料夾",
|
||||
"저장 폴더 변경...": "更改儲存路徑...",
|
||||
"저장 폴더 변경...": "更改儲存路徑……",
|
||||
"저장 폴더에 있는 모든 갤러리 넘버 복사": "複製下載資料夾中的所有所有相簿編號",
|
||||
"저장 폴더에 있는 모든 작가 불러오기": "從下載資料夾中載入所有藝術家",
|
||||
"저장... (&S)": "儲存(&S)...",
|
||||
"저장... (&S)": "儲存(&S)……",
|
||||
"저장된 설정 파일이 없습니다.\n[F1] 을 눌러서 사용법을 보세요.": "沒有配置檔案 [F1] 鍵閱讀 \"如何使用\"",
|
||||
"적어도 {} 개의 작품이 필요합니다.": "至少需要 {} 個庫。",
|
||||
"점수": "評分",
|
||||
"정규식 문법": "正規表示式",
|
||||
"정규식으로 찾기": "用正則查詢",
|
||||
"정렬 기준": "排序方式",
|
||||
"정말 단축키를 초기화하시겠습니까?": "你確定要重設捷徑嗎?",
|
||||
"정말 목록에서 제거하시겠습니까?": "確定要從列表中刪除嗎?",
|
||||
"정말 삭제하시겠습니까?": "確實要刪除嗎?",
|
||||
"정말 종료하시겠습니까?": "你確定你要關閉嗎?",
|
||||
"정말 중지하시겠습니까?": "你確定要停止嗎?",
|
||||
"정말 쿠키를 초기화하시겠습니까?": "您確定要清除 cookie 嗎?",
|
||||
"정보": "資訊",
|
||||
"정보...": "關於...",
|
||||
"정보...": "關於……",
|
||||
"정확도": "準確",
|
||||
"제거": "移除",
|
||||
"제목": "標題",
|
||||
|
@ -444,8 +473,9 @@
|
|||
"제외 태그": "要排除的標簽",
|
||||
"조각 표시": "顯示示作品",
|
||||
"종료": "關閉",
|
||||
"종료 중...": "Quit...",
|
||||
"종료 중...": "Quit……",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "向左或向右拖動增加或減少: ±1\n向上或向下拖動增加或減少: ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "網址或相簿 ID",
|
||||
"주소를 입력해주세요": "請輸入一些網址",
|
||||
"중간": "中等",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "支援的網站:",
|
||||
"지정한 페이지만 다운로드합니다.": "僅下載所選頁面。",
|
||||
"직접 다운로드": "直接下載",
|
||||
"진행도": "Progress",
|
||||
"참고 작품: {} 개": "引用:{} 個畫廊",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "請參閱:幫助-手冊 (F1) - 載入 cookie",
|
||||
"창 보이기 / 숨기기": "顯示/隱藏視窗",
|
||||
"찾기...": "搜尋...",
|
||||
"찾기...": "搜尋……",
|
||||
"첫 번째 파일 열기": "開啟第一個檔案",
|
||||
"첫 번째 파일 열기 (&O)": "開啟第一個檔案(&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "第一頁, 10 ~ 20th 頁, 最後一頁",
|
||||
"체인지로그": "更改日誌",
|
||||
"초기화": "重置",
|
||||
"최대 다운로드 속도": "下載限速",
|
||||
"최대 동시 작업": "同時下載的最大任務數",
|
||||
"최대 페이지 제한": "Maximum page limit",
|
||||
"최소화 버튼으로 트레이로 최소화": "點選最小化時最小化到托盤",
|
||||
"추가한 날짜": "新增日期",
|
||||
"취소": "取消",
|
||||
|
@ -487,8 +520,9 @@
|
|||
"크게": "大",
|
||||
"크기": "大小",
|
||||
"크기 조절": "縮放",
|
||||
"크기 조절...": "調整大小...",
|
||||
"크기 조절...": "調整大小……",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Chrome 擴充套件連結失敗。",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "經典",
|
||||
"클립보드에서 자동 추가": "監視剪貼簿",
|
||||
"타입": "型別",
|
||||
|
@ -499,6 +533,7 @@
|
|||
"태그 (Tags)": "標簽",
|
||||
"태그 :": "標簽:",
|
||||
"태그 설정": "標簽設定",
|
||||
"태그 수정": "Edit tags",
|
||||
"태그 없음": "無標簽",
|
||||
"테마": "主題",
|
||||
"토렌트 추가": "開啟種子檔案",
|
||||
|
@ -506,10 +541,12 @@
|
|||
"통계": "統計",
|
||||
"통과": "通過",
|
||||
"투명도": "透明度",
|
||||
"트래커 수정": "編輯 trackers",
|
||||
"트레이로 전환되었습니다": "最小化到系統托盤",
|
||||
"트레이로 최소화": "最小化到系統托盤",
|
||||
"트레이에서 알림 보이기": "在托盤中顯示通知",
|
||||
"파일": "檔案",
|
||||
"파일 목록": "File list",
|
||||
"파일 삭제": "刪除檔案",
|
||||
"파일 삭제 (&X)": "刪除檔案(&X)",
|
||||
"파일 수": "檔案數",
|
||||
|
@ -524,7 +561,7 @@
|
|||
"파일이 없습니다": "無檔案",
|
||||
"패스워드": "密碼",
|
||||
"퍼지로 찾기": "模糊搜尋",
|
||||
"페이지 읽는 중...": "讀取頁面...",
|
||||
"페이지 읽는 중...": "讀取頁面……",
|
||||
"페이지 지정": "選擇頁面",
|
||||
"페이지 지정 다운로드": "選擇頁面 && 下載",
|
||||
"페이지 지정 다운로드 기본값": "預設值 \"選擇頁面 && 下載\"",
|
||||
|
@ -534,7 +571,7 @@
|
|||
"포트": "埠",
|
||||
"포함 태그": "要包含的標簽",
|
||||
"폭 맞춤": "適合寬度",
|
||||
"폰트 변경...": "更改字型...",
|
||||
"폰트 변경...": "更改字型……",
|
||||
"폰트 초기화": "重置字型",
|
||||
"폴더": "資料夾",
|
||||
"폴더 && 압축파일": "資料夾 && 壓縮檔案",
|
||||
|
@ -546,9 +583,11 @@
|
|||
"폴더를 추가해주세요": "請輸入一個資料夾",
|
||||
"폴더명 형식": "資料夾名格式",
|
||||
"표지 검색 실패": "封面搜尋失敗",
|
||||
"표지 검색...": "搜尋封面...",
|
||||
"표지 검색...": "搜尋封面……",
|
||||
"프로그램 패스워드": "設定啟動密碼",
|
||||
"프록시": "代理",
|
||||
"플러그인": "Plugins",
|
||||
"플러그인을 추가하시겠습니까?": "Are you sure you want to add this plugin?",
|
||||
"플레이리스트 파일에 번호 매기기": "計算播放列表中的檔案",
|
||||
"플레이리스트 한 폴더에 다운로드": "全部下載到一個資料夾",
|
||||
"플로팅 미리보기": "浮動預覽",
|
||||
|
@ -557,10 +596,11 @@
|
|||
"필터창": "過濾器",
|
||||
"하나 이상의 타입을 선택해주세요.": "至少選擇一種型別。",
|
||||
"하위폴더 포함": "包含子資料夾",
|
||||
"학습 중...": "學習...",
|
||||
"학습 중...": "學習……",
|
||||
"한 줄 당 한 트래커 URL": "每一行一個 tracker",
|
||||
"한국어": "韓語",
|
||||
"항상 위": "總是可見",
|
||||
"해당 링크로 이동합니다": "轉到該連結...",
|
||||
"해당 링크로 이동합니다": "轉到該連結……",
|
||||
"해상도": "解析度",
|
||||
"현재 버전: v{}\n최신 버전: v{}": "當前版本:v{}\n最新版本:v{}",
|
||||
"호스트": "主機",
|
||||
|
@ -570,4 +610,4 @@
|
|||
"후원": "捐贈",
|
||||
"휴지통으로 이동": "移動到回收站"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@
|
|||
"E(x)Hentai 로그인 쿠키 필요": "需要 E Hentai 登录 cookie",
|
||||
"IP가 일시적으로 잠겼습니다. 잠시 후에 다시 시도해주세요": "您的 IP 被暂时锁定,请稍后再试。",
|
||||
"PDF 생성": "创建 PDF",
|
||||
"UI 스케일 ({}%)": "界面缩放 ({}%)",
|
||||
"URL을 입력하세요": "请输入网址",
|
||||
"[다운로드]를 눌러 다운받아주세요. [OK]를 누르면 종료됩니다": "按[下载]进行下载。按[确定]退出.",
|
||||
"reCAPTCHA 풀기": "解决验证码",
|
||||
|
@ -139,6 +140,8 @@
|
|||
"고급 검색": "高级搜索",
|
||||
"고정": "固定",
|
||||
"고정 해제": "取消固定",
|
||||
"관리자 권한 없이 실행": "在没有管理员权限的情况下运行",
|
||||
"관리자 권한 필요": "需要管理员权限",
|
||||
"그대로": "正序",
|
||||
"그룹": "分组",
|
||||
"그룹 (Groups)": "分组",
|
||||
|
@ -146,6 +149,7 @@
|
|||
"그룹 이름": "组名",
|
||||
"그룹 합쳐서 불러오기": "一起加载所有组",
|
||||
"그룹명 복사 (&G)": "复制组名(&G)",
|
||||
"그룹으로 이동": "移动到组",
|
||||
"그룹을 포함하는 그룹은 만들 수 없습니다.": "组不能包含组。",
|
||||
"그룹이 없습니다": "没有分组",
|
||||
"기본": "默认",
|
||||
|
@ -157,6 +161,7 @@
|
|||
"깊은 모델": "深度模型",
|
||||
"끝내기": "关闭",
|
||||
"날짜": "日期",
|
||||
"낮음": "低",
|
||||
"내보내기": "导出",
|
||||
"내보내기 실패": "导出失败",
|
||||
"내용 보기": "查看脚本",
|
||||
|
@ -164,6 +169,7 @@
|
|||
"내장 이미지 뷰어": "内置图像查看器",
|
||||
"녹화 중...": "正在录制中...",
|
||||
"녹화 중지": "停止录制",
|
||||
"높음": "高",
|
||||
"다른 동영상 사이트에도 적용됩니다.": "也适用于其他视频网站。",
|
||||
"다시 시작 (&S)": "重新开始(&S)",
|
||||
"다시 시작 실패; 복구됨": "重试失败; 已恢复",
|
||||
|
@ -177,10 +183,16 @@
|
|||
"다운로드 완료 후 자동 제거": "完成下载后自动删除",
|
||||
"다운로드 일시 정지 / 재개": "下载暂停/恢复",
|
||||
"다운로드가 완료되면 알림": "下载完成后显示通知",
|
||||
"다운로드하지 않음": "不要下载",
|
||||
"다운로드한 작품만": "仅下载画廊",
|
||||
"다음 검색시 더 빠르게 검색": "下一次搜索时更快搜索",
|
||||
"다크 모드": "深色模式",
|
||||
"단일 파일": "单个文件",
|
||||
"단축키": "快捷键",
|
||||
"단축키 내보내기 성공": "已导出快捷键设置",
|
||||
"단축키 불러오기 성공": "已导入快捷键设置",
|
||||
"단축키 수정": "编辑快捷键",
|
||||
"단축키 초기화 성공": "重置快捷键",
|
||||
"닫기 버튼으로 트레이로 최소화": "点击关闭时最小化到系统托盘",
|
||||
"대기 중...": "等待...",
|
||||
"데이터 분류 중...": "分类数据...",
|
||||
|
@ -210,6 +222,7 @@
|
|||
"로컬 파일 감지": "检测本地文件",
|
||||
"로컬 파일로부터 새 작업 만들기": "从本地文件创建新任务",
|
||||
"를 북마크에서 지웠습니다": "已从书签中删除",
|
||||
"리트윗 포함": "包含转发推文",
|
||||
"릴리즈 노트": "版本说明",
|
||||
"링크 열기": "打开链接",
|
||||
"링크 주소 복사": "复制链接地址",
|
||||
|
@ -240,7 +253,8 @@
|
|||
"변경할 최소 크기": "最小尺寸",
|
||||
"변환 중...": "转换中...",
|
||||
"보기": "查看",
|
||||
"부팅 시 실행": "开机自动运行",
|
||||
"보통": "正常",
|
||||
"부팅 시 실행": "随Windows启动运行",
|
||||
"북마크": "书签",
|
||||
"북마크 가져오기": "导入书签",
|
||||
"북마크 가져오기 실패": "无法导入书签",
|
||||
|
@ -255,6 +269,7 @@
|
|||
"불완전한 작업 자동으로 다시 시작": "启动后自动开始未完成任务",
|
||||
"붙여넣고 다운로드": "粘贴并下载",
|
||||
"뷰어... (&V)": "查看器(&V)...",
|
||||
"브라우저로 보기": "在浏览器中显示",
|
||||
"브라우저로 보기 (&B)": "在浏览器中查看(&B)",
|
||||
"비슷한 이미지 포함 (느림)": "包括相似的图像(慢)",
|
||||
"비율 유지": "宽高比",
|
||||
|
@ -288,9 +303,11 @@
|
|||
"설명": "注释",
|
||||
"설정": "首选项",
|
||||
"설정 (Preferences)": "设置",
|
||||
"설정을 따름": "Follow preferences",
|
||||
"성공: {}\n실패: {}": "成功: {}\n失败: {}",
|
||||
"수동": "手动",
|
||||
"수동 업데이트": "手动更新",
|
||||
"수정...": "编辑...",
|
||||
"순서 변경": "重新排序",
|
||||
"숨김 키": "老板键",
|
||||
"숫자": "重新编号",
|
||||
|
@ -300,8 +317,12 @@
|
|||
"스크립트": "脚本",
|
||||
"스크립트 가져오기": "导入脚本",
|
||||
"스크립트를 실행하시겠습니까?": "您确定要运行此脚本吗?",
|
||||
"스크립트를 읽는 중 오류가 발생했습니다": "读取脚本时出错",
|
||||
"스토리 읽는 중...": "阅读故事...",
|
||||
"스토리 포함": "包括故事",
|
||||
"시딩": "做种中",
|
||||
"시딩 중지": "停止做种",
|
||||
"시딩 하지 않음": "无种子",
|
||||
"시리즈": "系列",
|
||||
"시리즈 (Series)": "系列",
|
||||
"시리즈가 없습니다": "没有系列",
|
||||
|
@ -331,6 +352,7 @@
|
|||
"업데이트": "更新",
|
||||
"업데이트 중...": "更新...",
|
||||
"업데이트 체크 중...": "检查更新...",
|
||||
"업로드": "Upload",
|
||||
"연결 프로그램 변경": "选择用于解压压缩包的工具",
|
||||
"열기": "打开",
|
||||
"예(&Y)": "是(&Y)",
|
||||
|
@ -339,6 +361,7 @@
|
|||
"올바르지 않은 범위입니다": "无效的范围",
|
||||
"올바르지 않은 주소입니다": "无效的网址",
|
||||
"올바르지 않은 형식의 검색 필터입니다": "搜索过滤器格式不正确",
|
||||
"올바르지 않은 형식의 스크립트입니다": "格式脚本无效",
|
||||
"옵션": "选项",
|
||||
"옵션 (Options)": "选项",
|
||||
"완료": "完成",
|
||||
|
@ -368,6 +391,9 @@
|
|||
"이름 변경": "重命名分组",
|
||||
"이름을 얻는 도중 실패했습니다": "获取名称时失败",
|
||||
"이미 다운로드한 작품 제외": "排除已经下载的图库",
|
||||
"이미 추가한 작업 다시 시작할지 물어봄": "请求重试列表中的任务",
|
||||
"이미 추가한 작업입니다. 다시 다운로드하시겠습니까?": "任务已在列表中,是否重新下载?",
|
||||
"이미 추가한 플러그인입니다": "插件已在列表中",
|
||||
"이미 포함하는 그룹이 있습니다.": "已经有一个包含任务的组。",
|
||||
"이미지": "图像",
|
||||
"이미지 정보 캐시": "缓存图像信息",
|
||||
|
@ -396,6 +422,7 @@
|
|||
"작업": "任务",
|
||||
"작업 개수": "完成任务数量",
|
||||
"작업 수정": "编辑任务",
|
||||
"작업 수정...": "编辑任务...",
|
||||
"작업 왼쪽에서 순서 변경": "在左侧重新排序",
|
||||
"작업 용량": "完成文件大小",
|
||||
"작업 정보": "任务信息",
|
||||
|
@ -412,7 +439,8 @@
|
|||
"잠금": "锁定",
|
||||
"잠금 해제": "解锁",
|
||||
"장": "页面",
|
||||
"저사양 모드": "低规格模式",
|
||||
"재시작 후 적용됩니다": "重启后应用",
|
||||
"저사양 모드": "低性能模式",
|
||||
"저장": "保存",
|
||||
"저장 && 종료": "保存 && 关闭",
|
||||
"저장 실패": "保存失败",
|
||||
|
@ -429,6 +457,7 @@
|
|||
"정규식 문법": "正则表达式",
|
||||
"정규식으로 찾기": "用正则查找",
|
||||
"정렬 기준": "排序方式",
|
||||
"정말 단축키를 초기화하시겠습니까?": "您确定要重置设置的快捷键吗?",
|
||||
"정말 목록에서 제거하시겠습니까?": "确定要从列表中删除吗?",
|
||||
"정말 삭제하시겠습니까?": "确实要删除吗?",
|
||||
"정말 종료하시겠습니까?": "你确定你要关闭吗?",
|
||||
|
@ -444,8 +473,9 @@
|
|||
"제외 태그": "要排除的标签",
|
||||
"조각 표시": "显示示作品",
|
||||
"종료": "关闭",
|
||||
"종료 중...": "Quit...",
|
||||
"종료 중...": "关闭...",
|
||||
"좌우로 끌어서 1 씩 증감\n상하로 끌어서 100 씩 증감": "向左或向右拖动增加或减少: ±1\n向上或向下拖动增加或减少: ±100",
|
||||
"주소": "Address",
|
||||
"주소 or 갤러리 넘버": "网址或图库 ID",
|
||||
"주소를 입력해주세요": "请输入一些网址",
|
||||
"중간": "中等",
|
||||
|
@ -456,16 +486,19 @@
|
|||
"지원하는 사이트:": "支持的网站:",
|
||||
"지정한 페이지만 다운로드합니다.": "仅下载所选页面。",
|
||||
"직접 다운로드": "直接下载",
|
||||
"진행도": "进程",
|
||||
"참고 작품: {} 개": "引用:{} 个画廊",
|
||||
"참고해주세요: 도움말 - 사용법 (F1) - 쿠키 불러오기": "请参阅:帮助-手册 (F1) - 加载 cookie",
|
||||
"창 보이기 / 숨기기": "显示/隐藏窗口",
|
||||
"찾기...": "搜索...",
|
||||
"첫 번째 파일 열기": "打开第一个文件",
|
||||
"첫 번째 파일 열기 (&O)": "打开第一个文件(&O)",
|
||||
"첫 페이지, 10 ~ 20 페이지, 마지막 페이지": "第一页, 10 ~ 20th 页, 最后一页",
|
||||
"체인지로그": "更改日志",
|
||||
"초기화": "重置",
|
||||
"최대 다운로드 속도": "下载限速",
|
||||
"최대 동시 작업": "同时下载的最大任务数",
|
||||
"최대 페이지 제한": "最大页面限制",
|
||||
"최소화 버튼으로 트레이로 최소화": "点击最小化时最小化到托盘",
|
||||
"추가한 날짜": "添加日期",
|
||||
"취소": "取消",
|
||||
|
@ -489,6 +522,7 @@
|
|||
"크기 조절": "缩放",
|
||||
"크기 조절...": "调整大小...",
|
||||
"크롬 확장프로그램 연동에 실패했습니다.": "Chrome 扩展链接失败。",
|
||||
"클라이언트": "Clilent",
|
||||
"클래식": "经典",
|
||||
"클립보드에서 자동 추가": "监视剪贴板",
|
||||
"타입": "类型",
|
||||
|
@ -499,17 +533,20 @@
|
|||
"태그 (Tags)": "标签",
|
||||
"태그 :": "标签:",
|
||||
"태그 설정": "标签设置",
|
||||
"태그 수정": "编辑标签",
|
||||
"태그 없음": "无标签",
|
||||
"테마": "主题",
|
||||
"토렌트 추가": "打开种子文件",
|
||||
"토렌트 파일 연결": "与 torrent 文件关联",
|
||||
"통계": "统计",
|
||||
"통과": "通过",
|
||||
"통과": "不压缩打包",
|
||||
"투명도": "透明度",
|
||||
"트래커 수정": "编辑 trackers",
|
||||
"트레이로 전환되었습니다": "最小化到系统托盘",
|
||||
"트레이로 최소화": "最小化到系统托盘",
|
||||
"트레이에서 알림 보이기": "在托盘中显示通知",
|
||||
"파일": "文件",
|
||||
"파일 목록": "文件列表",
|
||||
"파일 삭제": "删除文件",
|
||||
"파일 삭제 (&X)": "删除文件(&X)",
|
||||
"파일 수": "文件数",
|
||||
|
@ -549,6 +586,8 @@
|
|||
"표지 검색...": "搜索封面...",
|
||||
"프로그램 패스워드": "设置启动密码",
|
||||
"프록시": "代理",
|
||||
"플러그인": "插件",
|
||||
"플러그인을 추가하시겠습니까?": "确定增加此插件?",
|
||||
"플레이리스트 파일에 번호 매기기": "计算播放列表中的文件",
|
||||
"플레이리스트 한 폴더에 다운로드": "全部下载到一个文件夹",
|
||||
"플로팅 미리보기": "浮动预览",
|
||||
|
@ -558,6 +597,7 @@
|
|||
"하나 이상의 타입을 선택해주세요.": "至少选择一种类型。",
|
||||
"하위폴더 포함": "包含子文件夹",
|
||||
"학습 중...": "学习...",
|
||||
"한 줄 당 한 트래커 URL": "每行一个 tracker URL ",
|
||||
"한국어": "韩语",
|
||||
"항상 위": "总是可见",
|
||||
"해당 링크로 이동합니다": "转到该链接...",
|
||||
|
|
Loading…
Reference in New Issue