^q^
This commit is contained in:
parent
c1ec9bae97
commit
ef3030f75c
|
@ -26,7 +26,7 @@ class Downloader_coub(Downloader):
|
|||
return get_id(url)
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, cw=self.cw)
|
||||
video.url()#
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
@ -41,15 +41,16 @@ class Downloader_coub(Downloader):
|
|||
class Video(object):
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self, pp=self.pp)
|
||||
self.cw = cw
|
||||
|
||||
@try_n(2)
|
||||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
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: int(f.get('filesize', 0)))[-1]
|
||||
|
|
|
@ -28,7 +28,7 @@ class Downloader_etc(Downloader):
|
|||
|
||||
if video.artist:
|
||||
self.artist = video.artist
|
||||
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
||||
self.print_('url_thumb: {}'.format(video.url_thumb))
|
||||
|
@ -37,7 +37,7 @@ class Downloader_etc(Downloader):
|
|||
self.enableSegment()#
|
||||
if isinstance(video.url(), M3u8_stream):
|
||||
self.disableSegment()
|
||||
|
||||
|
||||
self.title = '[{}] {}'.format(video.header, video.title)
|
||||
|
||||
|
||||
|
@ -54,16 +54,29 @@ def format_(f):
|
|||
return '{} - {} - {} - {}'.format(f['format'], f['_resolution'], f['_audio'], f['url'])
|
||||
|
||||
|
||||
@try_n(4)
|
||||
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.url(), M3u8_stream):
|
||||
c = video.url().segs[0].download(cw)
|
||||
if not c:
|
||||
raise Exception('invalid m3u8')
|
||||
return video
|
||||
except Exception as e:
|
||||
print_(e)
|
||||
return _get_video(url, session, cw, ie_key, allow_m3u8=False)
|
||||
|
||||
@try_n(4)
|
||||
def _get_video(url, session, cw, ie_key=None, allow_m3u8=True):
|
||||
print_ = get_print(cw)
|
||||
print_('get_video: {}, {}'.format(allow_m3u8, url))
|
||||
options = {
|
||||
'noplaylist': True,
|
||||
#'extract_flat': True,
|
||||
'playlistend': 1,
|
||||
}
|
||||
|
||||
ydl = ytdl.YoutubeDL(options)
|
||||
ydl = ytdl.YoutubeDL(options, cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
if not ie_key:
|
||||
ie_key = ytdl.get_extractor_name(url)
|
||||
|
@ -79,7 +92,7 @@ def get_video(url, session, cw, ie_key=None):
|
|||
url_new = entry.get('url') or entry['webpage_url']
|
||||
if url_new != url:
|
||||
return get_video(url_new, session, cw, ie_key=get_ie_key(info))
|
||||
|
||||
|
||||
session.headers.update(info.get('http_headers', {}))
|
||||
#session.cookies.update(ydl.cookiejar)
|
||||
|
||||
|
@ -100,8 +113,20 @@ def get_video(url, session, cw, ie_key=None):
|
|||
if not fs:
|
||||
raise Exception('No videos')
|
||||
|
||||
f = sorted(fs, key=lambda f:(f['_resolution'], f['_index']))[-1]
|
||||
if f['_audio']:
|
||||
def filter_f(fs):
|
||||
for f in fs:
|
||||
if allow_m3u8:
|
||||
return f
|
||||
ext = get_ext_(f['url'], session, url)
|
||||
if ext.lower() != '.m3u8':
|
||||
return f
|
||||
print_('invalid url: {}'.format(f['url']))
|
||||
return list(fs)[0]#
|
||||
|
||||
f_video = filter_f(reversed(sorted(fs, key=lambda f:(f['_resolution'], 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']))
|
||||
|
@ -109,13 +134,14 @@ def get_video(url, session, cw, ie_key=None):
|
|||
f_audio = fs_audio[-1]
|
||||
else:
|
||||
try:
|
||||
f = sorted([f for f in fs if f['_audio']], key=lambda f:(f['_resolution'], f['_index']))[-1]
|
||||
except IndexError:
|
||||
pass
|
||||
print_('trying to get f_video with audio')
|
||||
f_video = filter_f(reversed(sorted([f for f in fs if f['_audio']], key=lambda f:(f['_resolution'], f['_index']))))
|
||||
except Exception as e:
|
||||
print_('failed to get f_video with audio: {}'.format(e))
|
||||
f_audio = None
|
||||
print_('video: {}'.format(format_(f)))
|
||||
print_('video: {}'.format(format_(f_video)))
|
||||
print_('audio: {}'.format(format_(f_audio)))
|
||||
video = Video(f, f_audio, info, session, url, cw=cw)
|
||||
video = Video(f_video, f_audio, info, session, url, cw=cw)
|
||||
|
||||
return video
|
||||
|
||||
|
@ -128,6 +154,15 @@ def get_ie_key(info):
|
|||
return ie_key
|
||||
|
||||
|
||||
def get_ext_(url, session, referer):
|
||||
try:
|
||||
ext = downloader.get_ext(url, session, referer)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
ext = get_ext(url)
|
||||
return ext
|
||||
|
||||
|
||||
class Video(object):
|
||||
def __init__(self, f, f_audio, info, session, referer, cw=None):
|
||||
self.f_audio = f_audio
|
||||
|
@ -145,11 +180,7 @@ class Video(object):
|
|||
if self.url_thumb:
|
||||
downloader.download(self.url_thumb, referer=referer, buffer=self.thumb, session=session)
|
||||
|
||||
try:
|
||||
ext = downloader.get_ext(self.url, session, referer)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
ext = get_ext(self.url)
|
||||
ext = get_ext_(self.url, session, referer)
|
||||
|
||||
if not ext:
|
||||
print('empty ext')
|
||||
|
@ -157,7 +188,7 @@ class Video(object):
|
|||
ext = '.mp4'
|
||||
else:
|
||||
ext = '.mp3'
|
||||
|
||||
|
||||
if ext.lower() == '.m3u8':
|
||||
try:
|
||||
url = playlist2stream(self.url, referer, session=session, n_thread=4)
|
||||
|
@ -182,5 +213,3 @@ class Video(object):
|
|||
downloader.download(self.f_audio['url'], buffer=f, referer=self.referer, session=self.session)
|
||||
ffmpeg.merge(filename, f, cw=self.cw)
|
||||
return filename
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
# 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: file_downloader.pyo
|
||||
# Compiled at: 2019-10-02 14:06:58
|
||||
import downloader, json, os
|
||||
from constants import try_n
|
||||
from utils import Downloader, query_url, clean_title, get_ext
|
||||
from timee import sleep
|
||||
from hashlib import md5
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
@ -32,10 +28,20 @@ class Downloader_file(Downloader):
|
|||
for esc in ['?', '#']:
|
||||
name = name.split(esc)[0]
|
||||
|
||||
if not get_ext(name):
|
||||
name += downloader.get_ext(self.url)
|
||||
ext = get_ext(name)
|
||||
if not ext:
|
||||
try:
|
||||
ext = downloader.get_ext(self.url)
|
||||
except:
|
||||
ext = ''
|
||||
name = os.path.splitext(name)[0]
|
||||
|
||||
self.urls.append(self.url)
|
||||
self.filenames[self.url] = clean_title(name)
|
||||
|
||||
self.title = name
|
||||
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
|
||||
|
|
|
@ -69,7 +69,7 @@ def get_pages(url, session):
|
|||
|
||||
pages = []
|
||||
ids = set()
|
||||
for p in range(100):
|
||||
for p in range(500): #2966
|
||||
url_api = 'https://api2-page.kakao.com/api/v5/store/singles'
|
||||
data = {
|
||||
'seriesid': id_,
|
||||
|
|
|
@ -17,7 +17,7 @@ class Downloader_vlive(Downloader):
|
|||
return url.split('?')[0].strip('/')
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, cw=self.cw)
|
||||
video.url()#
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
@ -32,15 +32,16 @@ class Downloader_vlive(Downloader):
|
|||
class Video(object):
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
||||
@try_n(2)
|
||||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
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]
|
||||
|
|
|
@ -37,19 +37,26 @@ class Page(object):
|
|||
@Downloader.register
|
||||
class Downloader_lhscan(Downloader):
|
||||
type = 'lhscan'
|
||||
URLS = ['lhscan.net', 'loveheaven.net', 'lovehug.net']
|
||||
URLS = [
|
||||
#'lhscan.net', 'loveheaven.net',
|
||||
'lovehug.net', 'welovemanga.net', 'weloma.net',
|
||||
]
|
||||
MAX_CORE = 16
|
||||
display_name = 'LHScan'
|
||||
_soup = None
|
||||
|
||||
def init(self):
|
||||
self.url = self.url.replace('lhscan.net', 'loveheaven.net')
|
||||
self.session = Session()
|
||||
#clf2.solve(self.url, session=self.session, cw=self.cw)
|
||||
soup = self.soup
|
||||
if not soup.find('ul', class_='manga-info'):
|
||||
self.Invalid(u'{}: {}'.format(tr_(u'목록 주소를 입력해주세요'), self.url))
|
||||
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
url = url.replace('lovehug.net', 'welovemanga.net')
|
||||
return url
|
||||
|
||||
@property
|
||||
def soup(self):
|
||||
if self._soup is None:
|
||||
|
@ -58,9 +65,10 @@ class Downloader_lhscan(Downloader):
|
|||
html = downloader.read_html(self.url, session=self.session)
|
||||
break
|
||||
except Exception as e:
|
||||
e_ = e
|
||||
print(e)
|
||||
else:
|
||||
raise
|
||||
raise e_
|
||||
self._soup = Soup(html)
|
||||
return self._soup
|
||||
|
||||
|
@ -99,14 +107,20 @@ def get_imgs_page(page, session, cw=None):
|
|||
src = base64.b64decode(src).strip().decode('utf8')
|
||||
except:
|
||||
pass
|
||||
src = urljoin(page.url, src)
|
||||
src0 = src
|
||||
src = src.replace('welovemanga.net', '1')#
|
||||
src = urljoin(page.url, src).strip()
|
||||
if 'Credit_LHScan_' in src or '5e1ad960d67b2_5e1ad962338c7' in src:
|
||||
continue
|
||||
if 'fe132b3d32acc39f5adcea9075bedad4LoveHeaven' in src:
|
||||
continue
|
||||
if 'LoveHug_600cfd96e98ff.jpg' in src:
|
||||
continue
|
||||
img = Image(src.strip(), page, len(imgs))
|
||||
if 'image_5f0ecf23aed2e.png' in src:
|
||||
continue
|
||||
if not imgs:
|
||||
print_(src0)
|
||||
img = Image(src, page, len(imgs))
|
||||
imgs.append(img)
|
||||
|
||||
return imgs
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from utils import Downloader, LazyUrl, clean_title
|
||||
from m3u8_tools import playlist2stream, M3u8_stream
|
||||
import os
|
||||
from hashlib import md5
|
||||
from translator import tr_
|
||||
DEFAULT_N_THREAD = 1
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
@ -10,24 +13,42 @@ class Downloader_m3u8(Downloader):
|
|||
single = True
|
||||
display_name = 'M3U8'
|
||||
|
||||
def init(self):
|
||||
if '://' not in self.url:
|
||||
self.url = 'http://' + self.url
|
||||
@classmethod
|
||||
def fix_url(cls, url):
|
||||
if '://' not in url:
|
||||
url = 'http://' + url
|
||||
return url
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
|
||||
n_thread = self.cw.format or DEFAULT_N_THREAD
|
||||
self.print_('n_thread: {}'.format(n_thread))
|
||||
video = Video(self.url, n_thread)
|
||||
self.urls.append(video.url)
|
||||
|
||||
self.title = video.title
|
||||
self.title = '{} ({})'.format(video.title, video.id_)
|
||||
|
||||
|
||||
class Video(object):
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, n_thread):
|
||||
try:
|
||||
m = playlist2stream(url)
|
||||
m = playlist2stream(url, n_thread=n_thread)
|
||||
except:
|
||||
m = M3u8_stream(url)
|
||||
m = M3u8_stream(url, n_thread=n_thread)
|
||||
self.url = LazyUrl(url, lambda _: m, self)
|
||||
self.title = os.path.splitext(os.path.basename(url))[0]
|
||||
self.filename = clean_title(self.title, n=-4) + '.mp4'
|
||||
self.id_ = md5(url.encode('utf8')).hexdigest()[:8]
|
||||
tail = ' ({}).mp4'.format(self.id_)
|
||||
self.filename = clean_title(self.title, n=-len(tail)) + tail
|
||||
|
||||
|
||||
import selector
|
||||
@selector.options('m3u8')
|
||||
def options():
|
||||
def f(urls):
|
||||
from Qt import QInputDialog
|
||||
n_thread, ok = 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:
|
||||
return
|
||||
return n_thread
|
||||
return [
|
||||
{'text': 'Set number of threads...', 'format': f},
|
||||
]
|
||||
|
|
|
@ -48,14 +48,15 @@ class Downloader_manatoki(Downloader):
|
|||
else:
|
||||
raise Exception('no selected option')
|
||||
self.session, self.soup, url = get_soup(url)
|
||||
self.url = self.fix_url(url)
|
||||
url_page = self.fix_url(url)
|
||||
|
||||
for i, page in enumerate(get_pages(self.url, self.soup)):
|
||||
for i, page in enumerate(get_pages(url_page, self.soup)):
|
||||
if page.id == int(op['value']):
|
||||
break
|
||||
else:
|
||||
raise Exception('can not find page')
|
||||
self.cw.range_p = [i]
|
||||
self.url = url_page
|
||||
|
||||
self.name
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class Downloader_navertv(Downloader):
|
|||
self.url = 'https://tv.naver.com/v/{}'.format(self.url)
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, cw=self.cw)
|
||||
video.url()#
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
@ -36,15 +36,16 @@ class Downloader_navertv(Downloader):
|
|||
class Video(object):
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
||||
@try_n(4)
|
||||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
fs = [f for f in info['formats'] if f['protocol'] in ['http', 'https']]
|
||||
fs = sorted(fs, key=lambda f: int(f.get('width', 0)), reverse=True)
|
||||
|
|
|
@ -179,6 +179,7 @@ class Image():
|
|||
self.p = p
|
||||
self.format_ = format_
|
||||
self.artist = info['artist']
|
||||
self.artistid = info['artist_id'] #3636
|
||||
self.title = info['raw_title']
|
||||
self.utime = info['create_date']
|
||||
self.cw = cw
|
||||
|
@ -187,8 +188,14 @@ class Image():
|
|||
|
||||
def get(self, referer):
|
||||
ext = get_ext(self._url)
|
||||
name = self.format_.replace('id', '###id*').replace('page', '###page*').replace('artist', '###artist*').replace('title', '###title*')
|
||||
name = name.replace('###id*', str(self.id_)).replace('###page*', str(self.p)).replace('###artist*', self.artist).replace('###title*', self.title)
|
||||
d ={
|
||||
'id': self.id_,
|
||||
'page': self.p,
|
||||
'artist': self.artist,
|
||||
'artistid': self.artistid,
|
||||
'title': self.title,
|
||||
}
|
||||
name = utils.format(self.format_, d)
|
||||
self.filename = clean_title(name.strip(), allow_dot=True, n=-len(ext)) + ext
|
||||
if self.ugoira and self.ugoira['ext']: #3355
|
||||
filename_local = os.path.join(self.cw.dir, self.filename)
|
||||
|
|
|
@ -122,7 +122,7 @@ class Video(object):
|
|||
#title = j['video_title']
|
||||
title = soup.find('h1', class_='title').text.strip()
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
url_thumb = info['thumbnail']
|
||||
videos = []
|
||||
|
|
|
@ -148,7 +148,7 @@ def get_audios(url, cw, album_art):
|
|||
#'extract_flat': True,
|
||||
}
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
ydl = ytdl.YoutubeDL(cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
if 'entries' in info:
|
||||
entries = info['entries']
|
||||
|
|
|
@ -142,15 +142,24 @@ def get_title_artist(soup):
|
|||
def get_text(url, subtitle, update, session):
|
||||
html = downloader.read_html(url, session=session)
|
||||
soup = Soup(html)
|
||||
p = soup.find('div', id='novel_p')
|
||||
p = '' if p is None else p.text.strip()
|
||||
story = soup.find('div', id='novel_honbun').text.strip()
|
||||
if update:
|
||||
update = u' ' + update
|
||||
else:
|
||||
update = ''
|
||||
|
||||
story = soup.find('div', id='novel_honbun').text.strip()
|
||||
|
||||
p = soup.find('div', id='novel_p')
|
||||
p = '' if p is None else p.text.strip()
|
||||
if p:
|
||||
story = (u'{}\n\n{}').format(p, story)
|
||||
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()
|
||||
if a:
|
||||
story = '{}\n\n════════════════════════════════\n\n{}'.format(story, a)
|
||||
|
||||
text =u'''────────────────────────────────
|
||||
|
||||
◆ {}{}
|
||||
|
|
|
@ -6,6 +6,7 @@ from io import BytesIO
|
|||
from m3u8_tools import M3u8_stream
|
||||
import ree as re
|
||||
from translator import tr_
|
||||
import errors
|
||||
|
||||
|
||||
@Downloader.register
|
||||
|
@ -47,7 +48,7 @@ class Downloader_twitch(Downloader):
|
|||
filter = None
|
||||
|
||||
if filter is None:
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, self.cw)
|
||||
video.url()
|
||||
self.urls.append(video.url)
|
||||
self.title = video.title
|
||||
|
@ -94,7 +95,7 @@ def get_videos(url, cw=None):
|
|||
for edge in data[0]['data']['user']['clips']['edges']:
|
||||
url_video = edge['node']['url']
|
||||
info['name'] = edge['node']['broadcaster']['displayName']
|
||||
video = Video(url_video)
|
||||
video = Video(url_video, cw)
|
||||
video.id = int(edge['node']['id'])
|
||||
videos.append(video)
|
||||
cursor_new = edge['cursor']
|
||||
|
@ -124,15 +125,26 @@ def alter(seg):
|
|||
class Video(object):
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, cw):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
||||
@try_n(4)
|
||||
def get(self, url):
|
||||
print_ = get_print(self.cw)
|
||||
if self._url:
|
||||
return self._url
|
||||
ydl = ytdl.YoutubeDL()
|
||||
info = ydl.extract_info(url)
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
try:
|
||||
info = ydl.extract_info(url)
|
||||
except Exception as e:
|
||||
ex = type(ytdl.get_extractor(url))(ydl)
|
||||
_download_info = getattr(ex, '_download_info', None)
|
||||
if _download_info is not None:
|
||||
vod_id = ex._match_id(url)
|
||||
info = _download_info(vod_id)
|
||||
print_(info)
|
||||
raise
|
||||
video_best = info['formats'][-1]
|
||||
video = video_best['url']
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ def _guest_token(session, headers, cache=True, cw=None):
|
|||
data = json.loads(r.text)
|
||||
token = data['guest_token']
|
||||
print_('token type: {}'.format(type(token)))
|
||||
if isinstance(token ,int): #3525
|
||||
if isinstance(token, int): #3525
|
||||
token = str(token)
|
||||
CACHE_GUEST_TOKEN = token, time()
|
||||
return token
|
||||
|
@ -410,19 +410,23 @@ def get_imgs_more(username, session, title, types, n=None, format='[%y-%m-%d] id
|
|||
continue
|
||||
ids_set.add(id)
|
||||
tweets.append(tweet)
|
||||
|
||||
imgs_ = []
|
||||
for tweet in tweets:
|
||||
imgs_ += get_imgs_from_tweet(tweet, session, types, format, cw)
|
||||
|
||||
if tweets:
|
||||
if imgs_:
|
||||
if count_no_imgs:
|
||||
print_('reset count_no_imgs: {}'.format(len(imgs_)))
|
||||
imgs += imgs_
|
||||
count_no_imgs = 0
|
||||
else:
|
||||
count_no_imgs += 1
|
||||
change_ua(session)
|
||||
if count_no_imgs >= RETRY_MORE:
|
||||
break
|
||||
print_('retry...')
|
||||
print_('retry... {}'.format(count_no_imgs))
|
||||
continue
|
||||
|
||||
for tweet in tweets:
|
||||
imgs += get_imgs_from_tweet(tweet, session, types, format, cw)
|
||||
|
||||
msg = '{} {} (@{}) - {}'.format(tr_('읽는 중...'), artist, username, len(imgs))
|
||||
if cw:
|
||||
|
@ -576,7 +580,7 @@ class Image(object):
|
|||
print_ = get_print(self.cw)
|
||||
for try_ in range(self.try_n):
|
||||
try:
|
||||
d = ytdl.YoutubeDL()
|
||||
d = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = d.extract_info(self._url)
|
||||
|
||||
url = info['url']
|
||||
|
|
|
@ -18,7 +18,7 @@ class Downloader_vimeo(Downloader):
|
|||
self.url = u'https://vimeo.com/{}'.format(self.url)
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, cw=self.cw)
|
||||
video.url()#
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
@ -32,15 +32,16 @@ class Downloader_vimeo(Downloader):
|
|||
class Video(object):
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
||||
@try_n(4)
|
||||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
fs = [f for f in info['formats'] if f['protocol'] in ['http', 'https']]
|
||||
fs = sorted(fs, key=lambda f: int(f.get('width', 0)), reverse=True)
|
||||
|
|
|
@ -19,7 +19,7 @@ class Downloader_vlive(Downloader):
|
|||
raise NotImplementedError('channel')
|
||||
|
||||
def read(self):
|
||||
video = get_video(self.url)
|
||||
video = get_video(self.url, cw=self.cw)
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
||||
|
@ -30,12 +30,12 @@ class Downloader_vlive(Downloader):
|
|||
|
||||
|
||||
@try_n(4)
|
||||
def get_video(url):
|
||||
def get_video(url, cw=None):
|
||||
options = {
|
||||
'noplaylist': True,
|
||||
}
|
||||
|
||||
ydl = ytdl.YoutubeDL(options)
|
||||
ydl = ytdl.YoutubeDL(options, cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
fs = []
|
||||
|
|
|
@ -13,7 +13,7 @@ class Downloader_youku(Downloader):
|
|||
URLS = ['v.youku.com']
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, cw=self.cw)
|
||||
video.url()# get thumb
|
||||
|
||||
self.urls.append(video.url)
|
||||
|
@ -25,14 +25,15 @@ class Downloader_youku(Downloader):
|
|||
class Video(object):
|
||||
_url = None
|
||||
|
||||
def __init__(self, url):
|
||||
def __init__(self, url, cw=None):
|
||||
self.url = LazyUrl(url, self.get, self)
|
||||
self.cw = cw
|
||||
|
||||
def get(self, url):
|
||||
if self._url:
|
||||
return self._url
|
||||
|
||||
ydl = ytdl.YoutubeDL()
|
||||
ydl = ytdl.YoutubeDL(cw=self.cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
# get best video
|
||||
|
|
|
@ -23,7 +23,7 @@ class Downloader_youporn(Downloader):
|
|||
return url
|
||||
|
||||
def read(self):
|
||||
video = Video(self.url)
|
||||
video = Video(self.url, cw=self.cw)
|
||||
|
||||
self.urls.append(video.url)
|
||||
self.setIcon(video.thumb)
|
||||
|
@ -35,8 +35,8 @@ class Downloader_youporn(Downloader):
|
|||
|
||||
class Video(object):
|
||||
@try_n(4)
|
||||
def __init__(self, url):
|
||||
ydl = ytdl.YoutubeDL()
|
||||
def __init__(self, url, cw=None):
|
||||
ydl = ytdl.YoutubeDL(cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
f = info['formats'][-1]
|
||||
|
|
|
@ -27,7 +27,7 @@ def print_streams(streams, cw):
|
|||
print_ = get_print(cw)
|
||||
|
||||
for stream in streams:
|
||||
print_(u'[{}][{}fps][{}{}][{}] {} [{} / {}] ─ {}'.format(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_('[{}][{}fps][{}{}][{}] {} [{} / {}] ─ {}'.format(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_('')
|
||||
|
||||
|
||||
|
@ -63,7 +63,7 @@ class Video(object):
|
|||
print('max_res: {}'.format(max_res))
|
||||
for try_ in range(8):
|
||||
try:
|
||||
yt = ytdl.YouTube(url)
|
||||
yt = ytdl.YouTube(url, cw=self.cw)
|
||||
break
|
||||
except Exception as e:
|
||||
e_ = e
|
||||
|
@ -124,7 +124,7 @@ class Video(object):
|
|||
streams.append(stream)
|
||||
#'''
|
||||
else:
|
||||
raise Exception(u'type "{}" is not supported'.format(type))
|
||||
raise Exception('type "{}" is not supported'.format(type))
|
||||
|
||||
# Pick the best
|
||||
while streams:
|
||||
|
@ -146,7 +146,7 @@ class Video(object):
|
|||
foo = False
|
||||
if stream_final is None or (stream_final.fps <= stream.fps and (foo or (stream_final.subtype.lower()!=prefer_format and stream.subtype.lower()==prefer_format) or stream_final.fps < stream.fps)):
|
||||
#print(foo)
|
||||
print_(u'# stream_final {} {} {} {} {} {}fps'.format(stream, stream.format, stream.resolution, stream.subtype, stream.audio_codec, stream.fps))
|
||||
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
|
||||
|
@ -183,7 +183,7 @@ class Video(object):
|
|||
print('audio required')
|
||||
streams = [stream for stream in yt.streams.all() if stream.abr]
|
||||
print_streams(streams, cw)
|
||||
# only mp4; https://github.com/KurtBestor/Hitomi-Downloader-issues/issues/480
|
||||
# only mp4; https://github.com/KurtBestor/Hitomi-Downloader/issues/480
|
||||
def isGood(stream):
|
||||
return stream.audio_codec.lower().startswith('mp4')
|
||||
streams_good = [stream for stream in streams if isGood(stream)]
|
||||
|
@ -238,14 +238,14 @@ class Video(object):
|
|||
#soup = Soup(yt.watch_html)
|
||||
#title = soup.title.text.replace('- YouTube', '').strip()
|
||||
self.title = title
|
||||
ext = u'.' + self.stream.subtype
|
||||
ext = '.' + self.stream.subtype
|
||||
self.filename = format_filename(title, self.id, ext)
|
||||
|
||||
print_(u'Resolution: {}'.format(stream.resolution))
|
||||
print_(u'Codec: {} / {}'.format(stream.video_codec, stream.audio_codec))
|
||||
print_(u'Abr: {}'.format(stream.abr))
|
||||
print_(u'Subtype: {}'.format(stream.subtype))
|
||||
print_(u'FPS: {}\n'.format(stream.fps))
|
||||
print_('Resolution: {}'.format(stream.resolution))
|
||||
print_('Codec: {} / {}'.format(stream.video_codec, stream.audio_codec))
|
||||
print_('Abr: {}'.format(stream.abr))
|
||||
print_('Subtype: {}'.format(stream.subtype))
|
||||
print_('FPS: {}\n'.format(stream.fps))
|
||||
|
||||
return self._url
|
||||
|
||||
|
@ -263,13 +263,13 @@ class Video(object):
|
|||
ui_setting = utils.ui_setting
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
if not os.path.isfile(filename):
|
||||
print(u'no file: {}'.format(filename))
|
||||
print('no file: {}'.format(filename))
|
||||
return
|
||||
|
||||
filename_new = None
|
||||
if self.type == 'video' and (self.audio is not None or ext != '.mp4'): # UHD or non-mp4
|
||||
if self.audio is not None: # merge
|
||||
print_(u'Download audio: {}'.format(self.audio))
|
||||
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:
|
||||
|
@ -282,19 +282,19 @@ class Video(object):
|
|||
#print(out)
|
||||
name, ext_old = os.path.splitext(filename)
|
||||
if ext_old.lower() != ext.lower():
|
||||
print_(u'rename ext {} --> {}'.format(ext_old, ext))
|
||||
filename_new = u'{}{}'.format(name, ext)
|
||||
print_('rename ext {} --> {}'.format(ext_old, ext))
|
||||
filename_new = '{}{}'.format(name, ext)
|
||||
if os.path.isfile(filename_new):
|
||||
os.remove(filename_new)
|
||||
os.rename(filename, filename_new)
|
||||
else: # convert non-mp4 video -> mp4
|
||||
name, ext_old = os.path.splitext(filename)
|
||||
filename_new = u'{}.mp4'.format(name)
|
||||
print_(u'Convert video: {} -> {}'.format(filename, filename_new))
|
||||
filename_new = '{}.mp4'.format(name)
|
||||
print_('Convert video: {} -> {}'.format(filename, filename_new))
|
||||
ffmpeg.convert(filename, filename_new, cw=cw)
|
||||
elif self.type == 'audio' and ext != '.mp3': # convert non-mp3 audio -> mp3
|
||||
name, ext_old = os.path.splitext(filename)
|
||||
filename_new = u'{}.mp3'.format(name)
|
||||
filename_new = '{}.mp3'.format(name)
|
||||
ffmpeg.convert(filename, filename_new, '-shortest -preset ultrafast -b:a {}k'.format(get_abr()), cw=cw)
|
||||
|
||||
if self.type == 'audio' and ui_setting.albumArt.isChecked():
|
||||
|
@ -310,9 +310,9 @@ class Video(object):
|
|||
if lang in self.subtitles:
|
||||
try:
|
||||
subtitle = self.subtitles[lang]
|
||||
filename_sub = u'{}.vtt'.format(os.path.splitext(filename)[0])
|
||||
filename_sub = '{}.vtt'.format(os.path.splitext(filename)[0])
|
||||
downloader.download(subtitle, os.path.dirname(filename_sub), fileName=os.path.basename(filename_sub), overwrite=True)
|
||||
filename_sub_new = u'{}.srt'.format(os.path.splitext(filename_sub)[0])
|
||||
filename_sub_new = '{}.srt'.format(os.path.splitext(filename_sub)[0])
|
||||
cw.imgs.append(filename_sub_new)
|
||||
cw.dones.add(os.path.realpath(filename_sub_new).replace('\\\\?\\', ''))
|
||||
srt_converter.convert(filename_sub, filename_sub_new)
|
||||
|
@ -420,13 +420,13 @@ def get_videos(url, type='video', only_mp4=False, audio_included=False, max_res=
|
|||
if '/channel/' in url or '/user/' in url or '/c/' in url:
|
||||
info = read_channel(url, n=n, cw=cw)
|
||||
info['type'] = 'channel'
|
||||
info['title'] = u'[Channel] {}'.format(info['uploader'])
|
||||
info['title'] = '[Channel] {}'.format(info['uploader'])
|
||||
if cw:
|
||||
info['urls'] = filter_range(info['urls'], cw.range)
|
||||
elif '/playlist' in url:
|
||||
info = read_playlist(url, n=n, cw=cw)
|
||||
info['type'] = 'playlist'
|
||||
info['title'] = u'[Playlist] {}'.format(info['title'])
|
||||
info['title'] = '[Playlist] {}'.format(info['title'])
|
||||
if cw:
|
||||
info['urls'] = filter_range(info['urls'], cw.range)
|
||||
else:
|
||||
|
@ -455,7 +455,7 @@ def read_playlist(url, n, cw=None):
|
|||
'extract_flat': True,
|
||||
'playlistend': n,
|
||||
}
|
||||
ydl = ytdl.YoutubeDL(options)
|
||||
ydl = ytdl.YoutubeDL(options, cw=cw)
|
||||
info = ydl.extract_info(url)
|
||||
|
||||
es = info['entries']
|
||||
|
@ -479,7 +479,7 @@ import selector
|
|||
@selector.register('youtube')
|
||||
def select():
|
||||
if utils.ui_setting.askYoutube.isChecked():
|
||||
value = utils.messageBox(tr_(u'Youtube format?'), icon=QtGui.QMessageBox.Question, buttons=[tr_(u'MP4 (동영상)'), tr_(u'MP3 (음원)')])
|
||||
value = utils.messageBox(tr_('Youtube format?'), icon=QtGui.QMessageBox.Question, buttons=[tr_('MP4 (동영상)'), tr_('MP3 (음원)')])
|
||||
format = ['mp4', 'mp3'][value]
|
||||
return format
|
||||
|
||||
|
|
|
@ -1,3 +1,49 @@
|
|||
3.6 【】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- #2966
|
||||
|
||||
- Twitter 일부 계정 무한 로딩 문제 해결
|
||||
|
||||
- LHScan 새 도메인 지원 (#3603)
|
||||
|
||||
- Hitomi.la 업데이트 대응 (#3638)
|
||||
|
||||
- #3641
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- 초기 로딩 시간 최적화
|
||||
|
||||
- 썸네일 로딩 시간 최적화
|
||||
|
||||
- 설정 파일 (*.ini) 크기 최적화
|
||||
|
||||
- 설정 파일 (*.ini) 구조 변경 (신버전에서 작성한 설정 파일 구버전에서 읽을 수 없음)
|
||||
|
||||
- 고급 설정에서 스크롤할 때 콤보박스나 슬라이더 반응하지 않도록 수정
|
||||
|
||||
- 미리보기 창 크기 조절 가능하게 수정
|
||||
|
||||
- 다운로더 작업 목록 필터링했을 때 그룹에 해당 갯수 표시
|
||||
|
||||
- 소설가가 되자 꼬릿말 추가 (#2888, #3610)
|
||||
|
||||
- 일시정지 후 완료시킬 수 있게 수정 (#3482, #3609)
|
||||
|
||||
- 작업 레이지 로딩 끄는 옵션 (#3613)
|
||||
|
||||
- Pixiv 파일명 형식 "artistid" 추가 (#3636)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.5 【July 11, 2021】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
|
|
@ -1,3 +1,49 @@
|
|||
3.6 【】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
||||
- #2966
|
||||
|
||||
- Twitter 일부 계정 무한 로딩 문제 해결
|
||||
|
||||
- LHScan 새 도메인 지원 (#3603)
|
||||
|
||||
- Hitomi.la 업데이트 대응 (#3638)
|
||||
|
||||
- #3641
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
[변경/추가된 기능]
|
||||
|
||||
- 초기 로딩 시간 최적화
|
||||
|
||||
- 썸네일 로딩 시간 최적화
|
||||
|
||||
- 설정 파일 (*.ini) 크기 최적화
|
||||
|
||||
- 설정 파일 (*.ini) 구조 변경 (신버전에서 작성한 설정 파일 구버전에서 읽을 수 없음)
|
||||
|
||||
- 고급 설정에서 스크롤할 때 콤보박스나 슬라이더 반응하지 않도록 수정
|
||||
|
||||
- 미리보기 창 크기 조절 가능하게 수정
|
||||
|
||||
- 다운로더 작업 목록 필터링했을 때 그룹에 해당 갯수 표시
|
||||
|
||||
- 소설가가 되자 꼬릿말 추가 (#2888, #3610)
|
||||
|
||||
- 일시정지 후 완료시킬 수 있게 수정 (#3482, #3609)
|
||||
|
||||
- 작업 레이지 로딩 끄는 옵션 (#3613)
|
||||
|
||||
- Pixiv 파일명 형식 "artistid" 추가 (#3636)
|
||||
|
||||
- 기타 자잘한 것들
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
3.5 【July 11, 2021】
|
||||
|
||||
[버그 해결 / 사이트 변경에 의한 수정]
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
"비슷한 이미지 포함 (느림)": "Include similar images (Slow)",
|
||||
"비율 유지": "Aspect ratio",
|
||||
"비정상적인 로그인 시도": "Abnormal login attempt",
|
||||
"빠른 시작을 위한 작업 레이지 로딩": "Lazy load tasks for quick start",
|
||||
"빠른 실행 도구 모음 사용자 지정": "Customize Quick Access Toolbar",
|
||||
"뽑기": "Random",
|
||||
"뽑을 갯수": "Number of galleries",
|
||||
|
@ -182,6 +183,7 @@
|
|||
"상태": "Status",
|
||||
"새 그룹 만들기": "Create a new group",
|
||||
"새 버전이 있습니다. 업데이트 하실래요?": "There is a new version. Would you like to update?",
|
||||
"서명": "Certificate",
|
||||
"서버": "Server",
|
||||
"선택 화 다운로드": "Select chapters && Download",
|
||||
"선택된 작업들 내보내기": "Export selected tasks",
|
||||
|
@ -291,6 +293,7 @@
|
|||
"작업": "Tasks",
|
||||
"작업 개수": "Number of tasks",
|
||||
"작업 수정": "Edit task",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "File size of tasks",
|
||||
"작업 정보": "Task info",
|
||||
"작업 정보... (&I)": "Info... (&I)",
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
"비슷한 이미지 포함 (느림)": "Incluir imágenes similares (lento)",
|
||||
"비율 유지": "Mantener relación de aspecto",
|
||||
"비정상적인 로그인 시도": "Intento de conexión anormal",
|
||||
"빠른 시작을 위한 작업 레이지 로딩": "Lazy load tasks for quick start",
|
||||
"빠른 실행 도구 모음 사용자 지정": "Customize Quick Access Toolbar",
|
||||
"뽑기": "Aleatorio",
|
||||
"뽑을 갯수": "Numero de galerias",
|
||||
|
@ -182,6 +183,7 @@
|
|||
"상태": "Estado",
|
||||
"새 그룹 만들기": "Crear un grupo nuevo",
|
||||
"새 버전이 있습니다. 업데이트 하실래요?": "Hay una nueva versión ¿Quiere actualizar?",
|
||||
"서명": "Certificate",
|
||||
"서버": "Servidor",
|
||||
"선택 화 다운로드": "Seleccionar capítulos && Descargar",
|
||||
"선택된 작업들 내보내기": "Exportar tareas seleccionadas",
|
||||
|
@ -291,6 +293,7 @@
|
|||
"작업": "Tareas",
|
||||
"작업 개수": "Número de tareas",
|
||||
"작업 수정": "Edit task",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "Tamaño de archivo de tarea",
|
||||
"작업 정보": "Información del trabajo",
|
||||
"작업 정보... (&I)": "Info... (&I)",
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
"비슷한 이미지 포함 (느림)": "Inclure les images similaires (lent)",
|
||||
"비율 유지": "Conserver les proportions",
|
||||
"비정상적인 로그인 시도": "Tentative de connexion anormale",
|
||||
"빠른 시작을 위한 작업 레이지 로딩": "Lazy load tasks for quick start",
|
||||
"빠른 실행 도구 모음 사용자 지정": "Personnaliser la barre d'accès rapide",
|
||||
"뽑기": "Au hasard",
|
||||
"뽑을 갯수": "Nombre de galeries",
|
||||
|
@ -182,6 +183,7 @@
|
|||
"상태": "Statut",
|
||||
"새 그룹 만들기": "Créer un nouveau groupe",
|
||||
"새 버전이 있습니다. 업데이트 하실래요?": "Il y a une nouvelle version. Voulez-vous mettre à jour ?",
|
||||
"서명": "Certificate",
|
||||
"서버": "Serveur",
|
||||
"선택 화 다운로드": "Sélectionnez les chapitres && Télécharger",
|
||||
"선택된 작업들 내보내기": "Exporter les tâches sélectionnées",
|
||||
|
@ -291,6 +293,7 @@
|
|||
"작업": "Tâches",
|
||||
"작업 개수": "Nombre de tâches",
|
||||
"작업 수정": "Modifier la tâche",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "Taille des fichiers",
|
||||
"작업 정보": "Informations de la tâche",
|
||||
"작업 정보... (&I)": "Info... (&I)",
|
||||
|
@ -440,4 +443,4 @@
|
|||
"후원": "Sponsor",
|
||||
"휴지통으로 이동": "Déplacer dans la corbeille"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -164,6 +164,7 @@
|
|||
"비슷한 이미지 포함 (느림)": "包括相似的图像 (慢)",
|
||||
"비율 유지": "宽高比",
|
||||
"비정상적인 로그인 시도": "登录异常",
|
||||
"빠른 시작을 위한 작업 레이지 로딩": "Lazy load tasks for quick start",
|
||||
"빠른 실행 도구 모음 사용자 지정": "自定义快速访问工具栏",
|
||||
"뽑기": "随机",
|
||||
"뽑을 갯수": "画廊数",
|
||||
|
@ -182,6 +183,7 @@
|
|||
"상태": "状态",
|
||||
"새 그룹 만들기": "创建新组",
|
||||
"새 버전이 있습니다. 업데이트 하실래요?": "发现新版本现在更新么?",
|
||||
"서명": "Certificate",
|
||||
"서버": "服务器",
|
||||
"선택 화 다운로드": "选择章节 && 下载",
|
||||
"선택된 작업들 내보내기": "导出所选任务",
|
||||
|
@ -291,6 +293,7 @@
|
|||
"작업": "任务",
|
||||
"작업 개수": "完成任务数量",
|
||||
"작업 수정": "Edit task",
|
||||
"작업 왼쪽에서 순서 변경": "Reorder on the left side",
|
||||
"작업 용량": "完成文件大小",
|
||||
"작업 정보": "任务信息",
|
||||
"작업 정보... (&I)": "信息... (&I)",
|
||||
|
|
Loading…
Reference in New Issue