Compare commits

...

79 Commits

Author SHA1 Message Date
Kurt Bestor 018cf8405c
Merge pull request #5397 from maboroshin/master
Update tr_ja.hdl
2022-11-09 13:13:57 +09:00
maboroshin 1b39bb7700
Update tr_ja.hdl
more natural expression, Reducing long menu strings. (より自然な表現、長いメニューの文字列を減らす)
2022-11-07 14:02:13 +09:00
Kurt Bestor 8b97a60861 ^q^ 2022-09-20 09:25:17 +09:00
Kurt Bestor 5e53ab1691 ^q^ 2022-09-19 16:08:59 +09:00
Kurt Bestor b6f12454df
Merge pull request #5254 from lukemin/patch-1
Patch 1
2022-09-19 15:32:20 +09:00
Kurt Bestor 92d769f0a4
Merge pull request #5253 from Blank-1973/master
Update tr_ja.hdl
2022-09-19 15:32:07 +09:00
lukemin 8b602b6bfb
Update tr_zh.hdl 2022-09-19 13:28:06 +08:00
lukemin e34d1814cc
Update tr_zh.hdl 2022-09-19 13:17:10 +08:00
Blank-1973 cb5538d2bc
Update tr_ja.hdl 2022-09-19 10:59:29 +09:00
Kurt Bestor 00bc7e0d60 ^q^ 2022-09-18 15:45:32 +09:00
Kurt Bestor 468fb0210f ^q^ 2022-09-18 15:40:54 +09:00
Kurt Bestor 9473eb6680 ^q^
fix #5041

Co-Authored-By: lhj5426 <30548000+lhj5426@users.noreply.github.com>
2022-07-27 12:12:45 +09:00
Kurt Bestor b5effb248b
Merge pull request #5026 from Blank-1973/master
Update tr_ja.hdl
2022-07-27 12:09:03 +09:00
Blank-1973 532692ab60 Update tr_ja.hdl 2022-07-22 23:15:27 +09:00
Kurt Bestor e79e6c0bcb ^q^
fix #5018

Co-Authored-By: lhj5426 <30548000+lhj5426@users.noreply.github.com>
2022-07-22 15:32:44 +09:00
KurtBestor 34f23f18c8 ^q^ 2022-07-21 15:30:56 +09:00
KurtBestor 7952f9173e ^q^ 2022-07-21 14:14:09 +09:00
Kurt Bestor ff397445a9
^q^
Updated polish translation and added translation for how to use the program
2022-06-26 15:20:04 +09:00
Oliwer Stryjewski 947f617624 ^q^ 2022-06-23 13:46:27 +02:00
Oliwer Stryjewski 8593fd422d Added polish translation for how to use the program 2022-06-23 13:41:08 +02:00
Oliwer Stryjewski 468e3fd1f7 Fixed polish translation 2022-06-23 13:29:31 +02:00
Kurt Bestor a3cec35bc4
Merge pull request #4907 from olsko/translating-hitomi-downloader
Add Polish translation
2022-06-20 23:28:38 +09:00
Oliwer 2af331ab67
Merge branch 'KurtBestor:master' into translating-hitomi-downloader 2022-06-16 23:27:06 +02:00
Oliwer Stryjewski cbfab862af Translated Hitomi-Downloader into Polish 2022-06-16 23:22:04 +02:00
Kurt Bestor a8b7a0fa46
Merge pull request #4883 from ZorroGuevara/patch-10
Update changelog_en.txt
2022-06-16 19:03:54 +09:00
ZG 2c23b2d3b8
Update changelog_en.txt 2022-06-12 18:21:38 +02:00
Kurt Bestor 1fb3f9bfa6
Merge pull request #4862 from abc0922001/patch-2
Update Traditional Chinese translation
2022-06-08 18:36:22 +09:00
abc0922001 7f7f39c408
Update Traditional Chinese translation 2022-06-08 14:45:16 +08:00
KurtBestor 27ae4180e9 ^q^ 2022-06-08 14:38:51 +09:00
Kurt Bestor fd40ac2d1c
Merge pull request #4852 from Blank-1973/master
Update tr_ja.hdl
2022-06-08 05:08:36 +09:00
Blank-1973 2d800d4de1 Update tr_ja.hdl 2022-06-07 21:53:16 +09:00
KurtBestor f68a837ddf ^q^ 2022-06-07 14:59:29 +09:00
KurtBestor f0a8e5914d ^q^ 2022-06-06 15:21:37 +09:00
Kurt Bestor 71e08d25e7
Merge pull request #4797 from bog4t/master
wayback_machine_downloader: Minor fixes
2022-05-29 23:22:14 +09:00
bog_4t 8c48305382 wayback_machine_downloader: Minor fixes 2022-05-22 19:40:45 -04:00
KurtBestor de7728691e ^q^ 2022-05-17 18:32:44 +09:00
Kurt Bestor 116ef29afe
Merge pull request #4763 from SaidBySolo/patch-1
fix(novelpia): handle plus membership
2022-05-17 18:02:20 +09:00
Ryu juheon 07b0b9416e
style(novelpia): indent 2022-05-13 12:45:16 +09:00
Ryu juheon af5dae6f11
fix(novelpia): handle plus membership 2022-05-13 12:44:09 +09:00
Kurt Bestor aeff19c32a
Merge pull request #4744 from ZorroGuevara/patch-9
Update changelog_en.txt
2022-05-09 12:30:17 +09:00
ZG df8a01a59b
Update changelog_en.txt 2022-05-08 22:28:52 +02:00
KurtBestor 65ef86e414 ^q^ 2022-05-06 19:08:09 +09:00
KurtBestor c2247b082b ^q^ 2022-05-06 15:45:38 +09:00
Kurt Bestor 27f397fcff
Merge pull request #4730 from Blank-1973/patch-1
Update tr_ja.hdl
2022-05-06 09:34:58 +09:00
Blank-1973 08faa47ddc
Update tr_ja.hdl 2022-05-05 19:03:37 +09:00
KurtBestor 4c4a8fbd0c ^q^ 2022-05-05 15:23:47 +09:00
Kurt Bestor adf33e1150
Merge pull request #4717 from SaidBySolo/fix/novelpia
fix(novelpia): close #4713
2022-05-05 14:52:55 +09:00
Ryu juheon c29857243e
fix(novelpia): use utils.Session 2022-05-03 22:30:12 +09:00
Ryu juheon ad5f99e040
fix(novelpia): handle rate limit and add user agent 2022-05-03 13:15:46 +00:00
Ryu juheon 2a21584e28
style(novelpia): use extend 2022-05-03 10:52:37 +09:00
Ryu juheon d9663229d9
fix(novelpia): remove test prefix 2022-05-03 00:20:55 +09:00
Ryu juheon 1c89435723
feat(novelpia): handle rate limit 2022-05-03 00:18:33 +09:00
Ryu juheon 9b6fd24f88
fix(novelpia): close #4713 2022-05-03 00:12:37 +09:00
Kurt Bestor 3a5f80353d
Merge pull request #4629 from SaidBySolo/fix/novelpia
feat(novelpia): support download all episodes
2022-04-12 10:45:03 +09:00
Ryu juheon 7fb542942f
style(novelpia): fix typo 2022-04-09 02:25:34 +09:00
Ryu juheon 5bbd6030b0
fix(novelpia): move format func 2022-04-09 02:24:41 +09:00
Ryu juheon 0675e8fe64
fix(novelpia): remove test_ prefix 2022-04-09 01:31:31 +09:00
Ryu juheon ace7f3d07c
fix(novelpia): some bugs and logic 2022-04-09 01:28:05 +09:00
Ryu juheon f3c3c6053d
fix(novelpia): fix regex 2022-04-08 15:30:38 +09:00
Ryu juheon 6b32261de8
fix(novelpia): page_no -> page 2022-04-08 15:18:38 +09:00
Ryu juheon 356af051ca
feat(novelpia): all implement
need test
2022-04-08 11:51:53 +09:00
Ryu juheon 4268150ff4
fix(novelpia): page_no -> page 2022-04-08 11:12:31 +09:00
Ryu juheon 649a2bf28b
feat(novelpia): add get all viewer numbers func 2022-04-08 11:09:43 +09:00
Ryu juheon fa499b7785
fix(novelpia): remove test_ prefix 2022-04-06 22:54:57 +09:00
Ryu juheon f9cf582a69
fix(novelpia): attribute error 2022-04-06 22:52:47 +09:00
Kurt Bestor b9eccc70c4
Merge pull request #4497 from cclauss/patch-2
Define print_() for line 115
2022-03-05 14:47:41 +09:00
Kurt Bestor c71c96cf9c
Merge pull request #4496 from cclauss/patch-1
comicwalker_downloader: Fix typo in variable name
2022-03-05 14:47:27 +09:00
Christian Clauss af933eba71
Define print_() for line 115 2022-02-21 22:28:12 +01:00
Christian Clauss 7ce6fb6314
comicwalker_downloader: Fix typo in variable name 2022-02-21 22:16:15 +01:00
Kurt Bestor d58897fe9f
Merge pull request #4487 from bachig26/master
Update help_en.html
2022-02-20 10:04:53 +09:00
Bharathi 3070fc49b6
Update help_en.html 2022-02-17 17:59:21 +01:00
KurtBestor 01e41d0f17 ^q^ 2022-02-18 00:03:37 +09:00
Kurt Bestor eb76f6814a
Merge pull request #4437 from bog4t/master
some fixes regarding issue #4379 and other minor changes
2022-02-08 12:44:31 +09:00
bog_4t 0c2a1248d7 luscious_downloader: temporary fix for retrieving html
downloader.read_html() fails to get html for luscious link in issue #4379
2022-02-05 21:47:30 -05:00
bog_4t ff96316bef tumblr_downloader: ignore backfill ads for post queries
addresses issue #4379
2022-02-05 21:47:30 -05:00
bog_4t ec1581926f wayback_machine_downloader: minor changes 2022-02-05 21:47:30 -05:00
Kurt Bestor ba8b763654
Merge pull request #4393 from ZorroGuevara/patch-8
Update changelog_en.txt
2022-01-30 11:08:29 +09:00
ZG 442e1ab88b
Update changelog_en.txt 2022-01-30 01:12:48 +01:00
KurtBestor d53aa1e4b4 ^q^ 2022-01-27 11:47:07 +09:00
94 changed files with 4138 additions and 2568 deletions

View File

@ -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) |

View File

@ -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']

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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'NA'
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

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -30,7 +30,7 @@ import requests
import errors
@Downloader.register
class DownloaderDiscordEmoji(Downloader):
type = "discord"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 = []

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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"]

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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'},

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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_

View File

@ -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
'''

View File

@ -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]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -31,7 +31,7 @@ import requests
from utils import Downloader, Soup
@Downloader.register
class DownloaderTalkOPGG(Downloader):
type = "talkopgg"
URLS = ["talk.op.gg"]

View File

@ -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

View File

@ -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])

View File

@ -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},
]

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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]

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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)

View File

@ -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 업데이트 대응
- 기타 자잘한 것들

303
translation/help_en.html Normal file
View File

@ -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> &nbsp; &nbsp;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>&quot;title:A&quot; 를 입력하면 A라는 문자가 포함된 제목을 가진 갤러리만 보입니다.</li>
<li>&quot;artist:A&quot;, &quot;group:A&quot;, &quot;tag:A&quot;, &quot;lang:A&quot; 과 같이 필터링 할 수도 있습니다.</li>
<li>&quot;p&lt;100&quot; 을 입력하면 100 페이지 미만인 갤러리만 보입니다.</li>
<li>&quot;p&gt;100&quot; 을 입력하면 100 페이지 초과인 갤러리만 보입니다.</li>
<li>&quot;done:o&quot; 를 입력하면 이미 다운로드한 갤러리만 보입니다.</li>
<li>&quot;done:x&quot; 를 입력하면 아직 다운로드하지 않은 갤러리만 보입니다.</li>
</ul>
<h3>페이지 지정 다운로드</h3>
<ul>
<p><img src=":/icons/down_menu" /></p>
<li>지정한 페이지만 다운로드합니다.</li>
<li>Examples:<br>
~ 100 &nbsp; &nbsp;&nbsp;앞 100 페이지<br>
-100 ~ &nbsp; &nbsp;&nbsp;뒤 100 페이지<br>
1, 10 ~ 20, -1 &nbsp; &nbsp;&nbsp;첫 페이지, 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>스크립트 파일들을 프로그램에 드래그 &amp; 드랍해서 실행시킬 수도 있습니다.</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>

303
translation/help_ko.html Normal file
View File

@ -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> &nbsp; &nbsp;빨간색: 실패 or 잘못된 입력</li>
</ul>
<h3>다운로더 작업 목록 순서 변경</h3>
<ul>
<li>마우스 휠버튼으로 드래그&amp;드롭 하면 순서를 바꿀 수 있습니다.</li>
<li>[Ctrl + ↑], [Ctrl + ↓], [Ctrl + Home], [Ctrl + End] 키로 옮길 수도 있습니다.</li>
</ul>
<h3>다운로더 작업 목록 필터링</h3>
<ul>
<li>다운로더 창 좌측 하단의 필터 아이콘을 클릭하고 입력</li>
<li>해당 문자가 포함된 제목을 가진 작업만 보입니다.</li>
<li>&quot;type:youtube&quot; 와 같이 타입으로 필터링 할 수도 있습니다.</li>
<li>&quot;dup:&quot; 를 입력하면 중복된 작업만 보이고,</li>
<li>&quot;rem:&quot; 를 입력하면 실제 파일이 삭제된 작업만 보입니다.</li>
<li>&quot;tag:glasses&quot; 와 같이 태그로 필터링 할 수 있습니다.</li>
<li>&quot;comment:xxx&quot; 와 같이 코멘트로 필터링 할 수 있습니다.</li>
<li>&quot;bad:&quot; 를 입력하면 불완전한 작업만 보입니다.</li>
</ul>
<h3>검색기 검색 목록 필터링</h3>
<ul>
<li>검색기 좌측 하단의 필터 모양 아이콘을 클릭하고 내용 입력</li>
<li>해당 문자가 포함된 제목, 작가, 그룹, 태그, 언어를 가진 갤러리만 보입니다.</li>
<li>&quot;title:A&quot; 를 입력하면 A라는 문자가 포함된 제목을 가진 갤러리만 보입니다.</li>
<li>&quot;artist:A&quot;, &quot;group:A&quot;, &quot;tag:A&quot;, &quot;lang:A&quot; 과 같이 필터링 할 수도 있습니다.</li>
<li>&quot;p&lt;100&quot; 을 입력하면 100 페이지 미만인 갤러리만 보입니다.</li>
<li>&quot;p&gt;100&quot; 을 입력하면 100 페이지 초과인 갤러리만 보입니다.</li>
<li>&quot;done:o&quot; 를 입력하면 이미 다운로드한 갤러리만 보입니다.</li>
<li>&quot;done:x&quot; 를 입력하면 아직 다운로드하지 않은 갤러리만 보입니다.</li>
</ul>
<h3>페이지 지정 다운로드</h3>
<ul>
<p><img src=":/icons/down_menu" /></p>
<li>지정한 페이지만 다운로드합니다.</li>
<li>예시:<br>
~ 100 &nbsp; &nbsp;&nbsp;앞 100 페이지<br>
-100 ~ &nbsp; &nbsp;&nbsp;뒤 100 페이지<br>
1, 10 ~ 20, -1 &nbsp; &nbsp;&nbsp;첫 페이지, 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>스크립트 파일들을 프로그램에 드래그 &amp; 드랍해서 실행시킬 수도 있습니다.</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>저장 &amp; 종료 :<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>

295
translation/help_pl.html Normal file
View File

@ -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> &nbsp; &nbsp;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>&quot;title:A&quot; 를 입력하면 A라는 문자가 포함된 제목을 가진 갤러리만 보입니다.</li>
<li>&quot;artist:A&quot;, &quot;group:A&quot;, &quot;tag:A&quot;, &quot;lang:A&quot; 과 같이 필터링 할 수도 있습니다.</li>
<li>&quot;p&lt;100&quot; 을 입력하면 100 페이지 미만인 갤러리만 보입니다.</li>
<li>&quot;p&gt;100&quot; 을 입력하면 100 페이지 초과인 갤러리만 보입니다.</li>
<li>&quot;done:o&quot; 를 입력하면 이미 다운로드한 갤러리만 보입니다.</li>
<li>&quot;done:x&quot; 를 입력하면 아직 다운로드하지 않은 갤러리만 보입니다.</li>
</ul>
<h3>페이지 지정 다운로드</h3>
<ul>
<p><img src=":/icons/down_menu" /></p>
<li>지정한 페이지만 다운로드합니다.</li>
<li>Przykłady:<br>
~ 100 &nbsp; &nbsp;&nbsp;앞 100 페이지<br>
-100 ~ &nbsp; &nbsp;&nbsp;뒤 100 페이지<br>
1, 10 ~ 20, -1 &nbsp; &nbsp;&nbsp;첫 페이지, 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>스크립트 파일들을 프로그램에 드래그 &amp; 드랍해서 실행시킬 수도 있습니다.</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>

View File

@ -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...",

View File

@ -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",

View File

@ -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...",

View File

@ -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 @@
"후원": "サポート",
"휴지통으로 이동": "ごみ箱への移動"
}
}
}

613
translation/tr_pl.hdl Normal file
View File

@ -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"
}
}

View File

@ -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...",

View File

@ -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...",

View File

@ -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 @@
"후원": "捐贈",
"휴지통으로 이동": "移動到回收站"
}
}
}

View File

@ -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 ",
"한국어": "韩语",
"항상 위": "总是可见",
"해당 링크로 이동합니다": "转到该链接...",