diff -Nru youtube-dlg-0.3.5/debian/changelog youtube-dlg-0.3.7/debian/changelog --- youtube-dlg-0.3.5/debian/changelog 2014-04-07 13:13:28.000000000 +0000 +++ youtube-dlg-0.3.7/debian/changelog 2015-04-02 09:38:17.000000000 +0000 @@ -1,3 +1,22 @@ +youtube-dlg (0.3.7-1~webupd8~trusty1) trusty; urgency=medium + + * New upstream release + * Disabled 00_remove_icon.patch, applied upstream + + -- Alin Andrei Thu, 02 Apr 2015 12:13:48 +0200 + +youtube-dlg (0.3.5-1~webupd8~vivid0) vivid; urgency=medium + + * Upload for Vivid + + -- Alin Andrei Wed, 11 Mar 2015 14:18:39 +0200 + +youtube-dlg (0.3.5-1~webupd8~utopic1) utopic; urgency=medium + + * Upload for Utopic + + -- Alin Andrei Thu, 11 Sep 2014 11:27:49 +0200 + youtube-dlg (0.3.5-1~webupd8~trusty1) trusty; urgency=medium * new upstream release diff -Nru youtube-dlg-0.3.5/debian/control youtube-dlg-0.3.7/debian/control --- youtube-dlg-0.3.5/debian/control 2014-03-24 14:34:46.000000000 +0000 +++ youtube-dlg-0.3.7/debian/control 2015-04-02 09:17:29.000000000 +0000 @@ -8,7 +8,7 @@ Package: youtube-dlg Architecture: all -Depends: ${python:Depends}, python-wxgtk2.8 +Depends: ${python:Depends}, python-wxgtk2.8, python-distutils-extra Recommends: libav-tools | ffmpeg Description: youtube-dl gui A cross platform front-end GUI of the popular youtube-dl written in wxPython. diff -Nru youtube-dlg-0.3.5/debian/patches/series youtube-dlg-0.3.7/debian/patches/series --- youtube-dlg-0.3.5/debian/patches/series 2014-04-07 13:10:28.000000000 +0000 +++ youtube-dlg-0.3.7/debian/patches/series 2015-04-02 09:13:33.000000000 +0000 @@ -1,3 +1,3 @@ -00_remove_icon.patch +#00_remove_icon.patch #01_use_cache_dir_for_youtube-dl.patch #02_support_dash_audio_and_video.patch diff -Nru youtube-dlg-0.3.5/debian/rules youtube-dlg-0.3.7/debian/rules --- youtube-dlg-0.3.5/debian/rules 2014-03-24 13:34:19.000000000 +0000 +++ youtube-dlg-0.3.7/debian/rules 2015-04-02 09:29:32.000000000 +0000 @@ -8,8 +8,14 @@ include /usr/share/cdbs/1/class/python-distutils.mk binary-install/youtube-dlg:: - install -D --mode=0644 debian/youtube-dlg.png debian/youtube-dlg/usr/share/pixmaps/youtube-dlg.png - install -D --mode=0644 debian/youtube-dlg.desktop debian/youtube-dlg/usr/share/applications/youtube-dlg.desktop + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_16x16.png $(CURDIR)/debian/youtube-dlg/usr/share/icons/hicolor/16x16/apps/youtube-dlg.png + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_32x32.png $(CURDIR)/debian/youtube-dlg/usr/share/icons/hicolor/32x32/apps/youtube-dlg.png + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_48x48.png $(CURDIR)/debian/youtube-dlg/usr/share/icons/hicolor/48x48/apps/youtube-dlg.png + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_64x64.png $(CURDIR)/debian/youtube-dlg/usr/share/icons/hicolor/64x64/apps/youtube-dlg.png + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_128x128.png $(CURDIR)/debian/youtube-dlg/usr/share/icons/hicolor/128x128/apps/youtube-dlg.png + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_256x256.png $(CURDIR)/debian/youtube-dlg/usr/share/icons/hicolor/256x256/apps/youtube-dlg.png + install -Dm 644 $(CURDIR)/youtube_dl_gui/icons/youtube-dl-gui_512x512.png $(CURDIR)/debian/youtube-dlg/usr/share/pixmaps/youtube-dlg.png + install -Dm 644 $(CURDIR)/debian/youtube-dlg.desktop $(CURDIR)/debian/youtube-dlg/usr/share/applications/youtube-dlg.desktop clean:: rm -rf build Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/debian/youtube-dlg.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/debian/youtube-dlg.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/icons/ytube.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/icons/ytube.png differ diff -Nru youtube-dlg-0.3.5/locale_build/build_locale.bat youtube-dlg-0.3.7/locale_build/build_locale.bat --- youtube-dlg-0.3.5/locale_build/build_locale.bat 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/locale_build/build_locale.bat 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,35 @@ +@echo off + +REM Author: Sotiris Papadopoulos +REM Last-Edited: 25/02/2015 +REM Script to auto-create a locale file. + +REM Usage: build_locale.bat +REM Example: build_locale.bat gr_GR gr.po + +REM To create a new locale file copy youtube_dl_gui.po +REM to a new locale file (e.g. gr.po) edit it with your +REM favorite editor and then run this script. + +REM You also need to install gettext for Windows + +set FILENAME=youtube_dl_gui + +set /a args=0 +for %%A in (%*) do set /a args+=1 + +if not %args% == 2 ( + echo Usage: %~n0.bat "locale" "locale_file" + echo Example: %~n0.bat gr_GR gr.po +) else ( + mkdir ..\%FILENAME%\locale\%1\LC_MESSAGES + + msgfmt.exe --output-file %FILENAME%.mo %2 + + move %2 ..\%FILENAME%\locale\%1\LC_MESSAGES\%FILENAME%.po 1>NUL + move %FILENAME%.mo ..\%FILENAME%\locale\%1\LC_MESSAGES\ 1>NUL + + tree /F ..\%FILENAME%\locale\%1 + + echo Done +) diff -Nru youtube-dlg-0.3.5/locale_build/build_locale.sh youtube-dlg-0.3.7/locale_build/build_locale.sh --- youtube-dlg-0.3.5/locale_build/build_locale.sh 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/locale_build/build_locale.sh 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,32 @@ +#!/bin/bash + +# Author: Sotiris Papadopoulos +# Last-Edited: 25/02/2015 +# Script to auto-create a locale file. + +# Usage: ./build_locale.sh +# Example: ./build_locale.sh gr_GR gr.po + +# To create a new locale file copy youtube_dl_gui.po +# to a new locale file (e.g. gr.po) edit it with your +# favorite editor and then run this script. + + +FILENAME="youtube_dl_gui" + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 gr_GR gr.po" + exit 1 +fi + +mkdir -p "../$FILENAME/locale/$1/LC_MESSAGES/" + +msgfmt --output-file "$FILENAME.mo" "$2" + +mv "$2" "../$FILENAME/locale/$1/LC_MESSAGES/$FILENAME.po" +mv "$FILENAME.mo" "../$FILENAME/locale/$1/LC_MESSAGES/" + +tree "../$FILENAME/locale/$1" + +echo "Done" diff -Nru youtube-dlg-0.3.5/locale_build/HOWTO.md youtube-dlg-0.3.7/locale_build/HOWTO.md --- youtube-dlg-0.3.5/locale_build/HOWTO.md 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/locale_build/HOWTO.md 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,54 @@ +## ADD SUPPORT FOR NEW LANGUAGE + +**Requires**: [GNU GetText](https://www.gnu.org/software/gettext/) (Only if you want to build the MO files on your own) + +1. Clone or Fork the repository +2. Copy **youtube-dl-gui/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po** to **youtube-dl-gui/locale_build/** +3. Go to **youtube-dl-gui/locale_build** directory +4. Edit the PO file with your favorite text editor (See *EDIT* section) +5. After you have finished the file editing save the file +6. Now you have two options + 1. Send me the translated PO file to this email address: ytubedlg@gmail.com + 2. Build the binary translation file (MO) on your own using the build scripts (See *BUILD* section) + +### EDIT +PO file headers informations: +https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html + +To translate the PO file just edit the **msgstr** fields + +**Example** +``` pot +#: mainframe.py:78 +msgid "Download" +msgstr "Add the translation here" +``` + +### BUILD +1. To build the MO file you need to run the corresponding build script for your OS + + **Windows**: build_locale.**bat** + + **Linux**: build_locale.**sh** + + **Example** + + Usage: *build_locale.sh* <*language code*> <*translated PO file*> + + $ ./build_locale.sh *gr_GR* *gr.po* + +2. Now you also need to add the corresponding language option under the options frame localization tab + 1. Open optionsframe.py + 2. Locate the LocalizationTab class + 3. Find the LOCALE_NAMES attribute + 4. Add your language to the LOCALE_NAMES + + **Example** + ``` python + LOCALE_NAMES = twodict([ + ('en_US', 'English'), + ('gr_GR', 'Greek') + ]) + ``` + +3. Save the file and now you can make a new pull request after you push your changes to your remote diff -Nru youtube-dlg-0.3.5/MANIFEST.in youtube-dlg-0.3.7/MANIFEST.in --- youtube-dlg-0.3.5/MANIFEST.in 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/MANIFEST.in 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,8 @@ +include LICENSE +include README.md +include ToDo + +include locale_build/* +include youtube_dl_gui/icons/* + +recursive-include youtube_dl_gui/locale *.mo *.po diff -Nru youtube-dlg-0.3.5/README.md youtube-dlg-0.3.7/README.md --- youtube-dlg-0.3.5/README.md 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/README.md 2015-04-01 15:26:52.000000000 +0000 @@ -10,9 +10,25 @@ ![Options window](http://i.imgur.com/eShdoLD.png) +## INSTALLATION +**Linux**: sudo python setup.py install + +Then you can call youtube-dlg from the command line +using "youtube-dl-gui" command or by creating a desktop launcher + +**Windows**: Check WINDOWS_SETUP + ## WINDOWS_SETUP http://code.google.com/p/youtube-dlg/ +## LINUX_PACKAGES +**Ubuntu**: http://ppa.launchpad.net/nilarimogard/webupd8/ubuntu/pool/main/y/youtube-dlg/ + +**Arch**: https://aur.archlinux.org/packages/youtube-dl-gui-git/ + +## ADD SUPPORT FOR NEW LANGUAGE +See https://github.com/MrS0m30n3/youtube-dl-gui/blob/master/locale_build/HOWTO.md + ## REQUIREMENTS [Python (version 2.7+)](http://www.python.org) @@ -23,7 +39,7 @@ ## PROJECT HOMEPAGE -**Youtube-dlG**: https://github.com/MrS0m30n3/youtube-dl-gui +**Youtube-dlG**: http://mrs0m30n3.github.io/youtube-dl-gui/ **Youtube-dl**: http://rg3.github.io/youtube-dl/ diff -Nru youtube-dlg-0.3.5/setup.py youtube-dlg-0.3.7/setup.py --- youtube-dlg-0.3.5/setup.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/setup.py 2015-04-01 15:26:52.000000000 +0000 @@ -1,12 +1,169 @@ -#! /usr/bin/env python +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtube-dlg setup file. + +Note: + If you get 'TypeError: decoding Unicode is not supported' when you run + py2exe then apply the following patch: + + http://sourceforge.net/p/py2exe/patches/28/ + +""" + +import os +import sys +import shutil + +PY2EXE = len(sys.argv) >= 2 and sys.argv[1] == 'py2exe' + +if PY2EXE: + try: + import py2exe + except ImportError as error: + print error + sys.exit(1) from distutils.core import setup -from youtube_dl_gui import version +from distutils.sysconfig import get_python_lib + +from youtube_dl_gui import ( + __author__, + __appname__, + __contact__, + __version__, + __license__, + __projecturl__, + __description__, + __descriptionfull__ +) + + +ICONS_SIZES = ('16x16', '32x32', '48x48', '64x64', '128x128', '256x256') +ICONS_TEMPLATE = 'youtube_dl_gui/icons/youtube-dl-gui_{size}.png' + +ICONS_LIST = [ICONS_TEMPLATE.format(size=size) for size in ICONS_SIZES] + + +# Set icons path +PY2EXE_ICONS = 'icons' +WINDOWS_ICONS = os.path.join(get_python_lib(), 'youtube_dl_gui', 'icons') +LINUX_ICONS = '/usr/share/icons/hicolor/' + +LINUX_FALLBACK_ICONS = '/usr/share/pixmaps/' + + +# Set localization files path +LOCALE_PATH = os.path.join('youtube_dl_gui', 'locale') + +PY2EXE_LOCALE_DIR = 'locale' +WIN_LOCALE_DIR = os.path.join(get_python_lib(), 'youtube_dl_gui', 'locale') +LINUX_LOCALE_DIR = '/usr/share/{app_name}/locale/'.format(app_name=__appname__.lower()) + + +def create_scripts(): + if not os.path.exists('build/_scripts/'): + os.makedirs('build/_scripts') + + shutil.copyfile('youtube_dl_gui/__main__.py', 'build/_scripts/youtube-dl-gui') + + +def set_locale_files(data_files): + for directory in os.listdir(LOCALE_PATH): + locale_lang = os.path.join(directory, 'LC_MESSAGES') + + src = os.path.join(LOCALE_PATH, locale_lang, 'youtube_dl_gui.mo') + + if PY2EXE: + dst = os.path.join(PY2EXE_LOCALE_DIR, locale_lang) + elif os.name == 'nt': + dst = os.path.join(WIN_LOCALE_DIR, locale_lang) + else: + dst = os.path.join(LINUX_LOCALE_DIR, locale_lang) + + data_files.append((dst, [src])) + + +def py2exe_setup(): + py2exe_dependencies = [ + 'C:\\Windows\\System32\\ffmpeg.exe', + 'C:\\Windows\\System32\\ffprobe.exe', + 'C:\\python27\\DLLs\\MSVCP90.dll' + ] + + py2exe_data_files = [ + ('', py2exe_dependencies), + (PY2EXE_ICONS, ICONS_LIST) + ] + + set_locale_files(py2exe_data_files) + + py2exe_options = { + 'includes': ['wx.lib.pubsub.*', + 'wx.lib.pubsub.core.*', + 'wx.lib.pubsub.core.arg1.*'] + } + + py2exe_windows = { + 'script': 'youtube_dl_gui\\__main__.py', + 'icon_resources': [(0, 'youtube_dl_gui\\icons\\youtube-dl-gui.ico')] + } + + params = { + 'data_files': py2exe_data_files, + 'windows': [py2exe_windows], + 'options': {'py2exe': py2exe_options} + } + + return params + + +def normal_setup(): + data_files = list() + + if os.name == 'nt': + icons_dir = (WINDOWS_ICONS, ICONS_LIST) + data_files.append(icons_dir) + + set_locale_files(data_files) + + params = {'data_files': data_files} + else: + # Create all the hicolor icons + for index, size in enumerate(ICONS_SIZES): + icons_dest = os.path.join(LINUX_ICONS, size, 'apps') + icons_dir = (icons_dest, [ICONS_LIST[index]]) + + data_files.append(icons_dir) + + # Add the 48x48 icon as fallback + fallback_icon = (LINUX_FALLBACK_ICONS, [ICONS_LIST[2]]) + data_files.append(fallback_icon) + + set_locale_files(data_files) + + create_scripts() + params = {'data_files': data_files, 'scripts': ['build/_scripts/youtube-dl-gui']} + + return params + + +if PY2EXE: + params = py2exe_setup() +else: + params = normal_setup() + + +setup( + author = __author__, + name = __appname__, + version = __version__, + license = __license__, + author_email = __contact__, + url = __projecturl__, + description = __description__, + long_description = __descriptionfull__, + packages = ['youtube_dl_gui'], -setup(name='Youtube-DLG', - version=version.__version__, - description='Youtube-dl GUI', - author='Sotiris Papadopoulos', - author_email='ytubedlg@gmail.com', - url='https://github.com/MrS0m30n3/youtube-dl-gui', - packages=['youtube_dl_gui']) + **params +) diff -Nru youtube-dlg-0.3.5/SIGNALS.txt youtube-dlg-0.3.7/SIGNALS.txt --- youtube-dlg-0.3.5/SIGNALS.txt 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/SIGNALS.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ - -Signals list that DownloadManager, ProcessWrapper -threads send (DownloadThread.py) - -HANDLER -======= - -SignalHandler.py - - class DownloadHandler() - def handle(self, msg): - . - . - . - -HEADER -====== - -[ -'finish', Download thread finished -'close', Download thread stopped by the user -'error', Error occured while downloading -'playlist', Playlist current download no/from -'youtube', Pre-Processing -'download', Download stuff [size, percent, eta, speed] -'ffmpeg', Post-Processing -'ignore', Ignore this header -'remove', Removing extra DASH files -'filename', Extract filenames -] - - -INDEX -===== - -index == -1: GLOBAL -index != -1: URL IN COLUMN - -DATA -==== - -e.g. -['57.3%', '20.63MiB', '542.44KiB/s', '00:16'] - -DATA-PACK -========= - -DataPack.header = header -DataPack.index = index -DataPack.data = data - diff -Nru youtube-dlg-0.3.5/ToDo youtube-dlg-0.3.7/ToDo --- youtube-dlg-0.3.5/ToDo 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/ToDo 2015-04-01 15:26:52.000000000 +0000 @@ -1,2 +1,7 @@ -* Use import instead of subprocess +* Build 0.3.7 setup for Windows (Remove the old settings file) +* Build 0.3.7 portable for Windows + * New layout for MainFrame +* Redesign options Window +* Add context menu (issue #16) +* Use import instead of subprocess diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/downloaders.py youtube-dlg-0.3.7/youtube_dl_gui/downloaders.py --- youtube-dlg-0.3.5/youtube_dl_gui/downloaders.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/downloaders.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,352 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Python module to download videos. + +This module contains the actual downloaders responsible +for downloading the video files. + +Note: + downloaders.py is part of the youtubedlg package but it can be used + as a stand alone module for downloading videos. + +""" + +from __future__ import unicode_literals + +import os +import sys +import locale +import subprocess + + +class YoutubeDLDownloader(object): + + """Python class for downloading videos using youtube-dl & subprocess. + + Attributes: + OK, ERROR, STOPPED, ALREADY, FILESIZE_ABORT, WARNING (int): 'Random' + integers that describe the return code from the download() method. + + Args: + youtubedl_path (string): Absolute path to youtube-dl binary. + + data_hook (function): Optional callback function to retrieve download + process data. + + log_manager (logmanager.LogManager): Object responsible for writing + errors to the log. + + Note: + For available data keys check self._data under __init__(). + + Example: + How to use YoutubeDLDownloader from a python script. + + from downloaders import YoutubeDLDownloader + + def data_hook(data): + print data + + downloader = YoutubeDLDownloader('/usr/bin/youtube-dl', data_hook) + + downloader.download(, ['-f', 'flv']) + + """ + + OK = 0 + ERROR = 1 + STOPPED = 2 + ALREADY = 3 + FILESIZE_ABORT = 4 + WARNING = 5 + + def __init__(self, youtubedl_path, data_hook=None, log_manager=None): + self.youtubedl_path = youtubedl_path + self.log_manager = log_manager + self.data_hook = data_hook + + self._return_code = 0 + self._proc = None + self._data = { + 'playlist_index': None, + 'playlist_size': None, + 'filesize': None, + 'filename': None, + 'percent': None, + 'status': None, + 'speed': None, + 'eta': None + } + + def download(self, url, options): + """Download url using given options. + + Args: + url (string): URL string to download. + options (list): Python list that contains youtube-dl options. + + Returns: + An integer that shows the status of the download process. + Right now we support 5 different return codes. + + OK (0): The download process completed successfully. + ERROR (1): An error occured during the download process. + STOPPED (2): The download process was stopped from the user. + ALREADY (3): The given url is already downloaded. + FILESIZE_ABORT (4): The corresponding url video file was larger or + smaller from the given options filesize limit. + WARNING (5): A warning occured during the download process. + + """ + self._reset() + + cmd = self._get_cmd(url, options) + self._create_process(cmd) + + while self._proc_is_alive(): + stdout, stderr = self._read() + + if stderr: + + if self._is_warning(stderr): + self._return_code = self.WARNING + else: + self._return_code = self.ERROR + + self._log(stderr) + + if stdout: + self._sync_data(extract_data(stdout)) + self._hook_data() + + self._last_data_hook() + + return self._return_code + + def stop(self): + """Stop the download process and set return code to STOPPED. """ + if self._proc_is_alive(): + self._proc.kill() + self._return_code = self.STOPPED + + def _is_warning(self, stderr): + return stderr.split(':')[0] == 'WARNING' + + def _last_data_hook(self): + """Set the last data information based on the return code. """ + if self._return_code == self.OK: + self._data['status'] = 'Finished' + elif self._return_code == self.ERROR: + self._data['status'] = 'Error' + self._data['speed'] = '' + self._data['eta'] = '' + elif self._return_code == self.WARNING: + self._data['status'] = 'Warning' + self._data['speed'] = '' + self._data['eta'] = '' + elif self._return_code == self.STOPPED: + self._data['status'] = 'Stopped' + self._data['speed'] = '' + self._data['eta'] = '' + elif self._return_code == self.ALREADY: + self._data['status'] = 'Already Downloaded' + else: + self._data['status'] = 'Filesize Abort' + + self._hook_data() + + def _reset(self): + """Reset the data. """ + self._return_code = 0 + self._data = { + 'playlist_index': None, + 'playlist_size': None, + 'filesize': None, + 'filename': None, + 'percent': None, + 'status': None, + 'speed': None, + 'eta': None + } + + def _sync_data(self, data): + """Synchronise self._data with data. It also filters some keys. + + Args: + data (dictionary): Python dictionary that contains different + keys. The keys are not standar the dictionary can also be + empty when there are no data to extract. See extract_data(). + + """ + for key in data: + if key == 'filename': + # Keep only the filename on data['filename'] + data['filename'] = os.path.basename(data['filename']) + + if key == 'status': + if data['status'] == 'Already Downloaded': + # Set self._return_code to already downloaded + # and trash that key + self._return_code = self.ALREADY + data['status'] = None + + if data['status'] == 'Filesize Abort': + # Set self._return_code to filesize abort + # and trash that key + self._return_code = self.FILESIZE_ABORT + data['status'] = None + + self._data[key] = data[key] + + def _log(self, data): + """Log data using log_manager. """ + if self.log_manager is not None: + self.log_manager.log(data) + + def _hook_data(self): + """Pass self._data back to the data_hook. """ + if self.data_hook is not None: + self.data_hook(self._data) + + def _proc_is_alive(self): + """Returns True if self._proc is alive else False. """ + if self._proc is None: + return False + + return self._proc.poll() is None + + def _read(self): + """Read subprocess stdout, stderr. + + Returns: + Python tuple that contains the STDOUT and STDERR + strings. + + """ + stdout = stderr = '' + + if self._proc is not None: + stdout = self._proc.stdout.readline().rstrip() + + if not stdout: + stderr = self._proc.stderr.readline().rstrip() + + encoding = self._get_encoding() + + return stdout.decode(encoding, 'ignore'), stderr.decode(encoding, 'ignore') + + def _get_cmd(self, url, options): + """Build the subprocess command. + + Args: + url (string): URL string to download. + options (list): Python list that contains youtube-dl options. + + Returns: + Python list that contains the command to execute. + + """ + if os.name == 'nt': + cmd = [self.youtubedl_path] + options + [url] + else: + cmd = ['python', self.youtubedl_path] + options + [url] + + return cmd + + def _get_encoding(self): + """Return system encoding. """ + try: + encoding = locale.getpreferredencoding() + 'TEST'.encode(encoding) + except: + encoding = 'UTF-8' + + return encoding + + def _create_process(self, cmd): + """Create new subprocess. + + Args: + cmd (list): Python list that contains the command to execute. + + """ + encoding = info = None + + # Hide subprocess window on Windows + if os.name == 'nt': + info = subprocess.STARTUPINFO() + info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + # Encode command for subprocess + # Refer to http://stackoverflow.com/a/9951851/35070 + if sys.version_info < (3, 0): + encoding = self._get_encoding() + + if encoding is not None: + cmd = [item.encode(encoding, 'ignore') for item in cmd] + + self._proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=info) + + +def extract_data(stdout): + """Extract data from youtube-dl stdout. + + Args: + stdout (string): String that contains the youtube-dl stdout. + + Returns: + Python dictionary. For available keys check self._data under + YoutubeDLDownloader.__init__(). + + """ + data_dictionary = dict() + + if not stdout: + return data_dictionary + + stdout = [string for string in stdout.split(' ') if string != ''] + + stdout[0] = stdout[0].lstrip('\r') + + if stdout[0] == '[download]': + data_dictionary['status'] = 'Downloading' + + # Get filename + if stdout[1] == 'Destination:': + data_dictionary['filename'] = ' '.join(stdout[2:]) + + # Get progress info + if '%' in stdout[1]: + if stdout[1] == '100%': + data_dictionary['speed'] = '' + data_dictionary['eta'] = '' + else: + data_dictionary['percent'] = stdout[1] + data_dictionary['filesize'] = stdout[3] + data_dictionary['speed'] = stdout[5] + data_dictionary['eta'] = stdout[7] + + # Get playlist info + if stdout[1] == 'Downloading' and stdout[2] == 'video': + data_dictionary['playlist_index'] = stdout[3] + data_dictionary['playlist_size'] = stdout[5] + + # Get file already downloaded status + if stdout[-1] == 'downloaded': + data_dictionary['status'] = 'Already Downloaded' + + # Get filesize abort status + if stdout[-1] == 'Aborting.': + data_dictionary['status'] = 'Filesize Abort' + + elif stdout[0] == '[ffmpeg]': + data_dictionary['status'] = 'Post Processing' + + else: + data_dictionary['status'] = 'Pre Processing' + + return data_dictionary diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/downloadmanager.py youtube-dlg-0.3.7/youtube_dl_gui/downloadmanager.py --- youtube-dlg-0.3.5/youtube_dl_gui/downloadmanager.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/downloadmanager.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,325 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module for managing the download process. + +This module is responsible for managing the download process +and update the GUI interface. + +Attributes: + MANAGER_PUB_TOPIC (string): wxPublisher subscription topic of the + DownloadManager thread. + + WORKER_PUB_TOPIC (string): wxPublisher subscription topic of the + Worker thread. + +Note: + It's not the actual module that downloads the urls + thats the job of the 'downloaders' module. + +""" + +from __future__ import unicode_literals + +import time +import os.path +from threading import Thread + +from wx import CallAfter +from wx.lib.pubsub import setuparg1 +from wx.lib.pubsub import pub as Publisher + +from .parsers import OptionsParser +from .updatemanager import UpdateThread +from .downloaders import YoutubeDLDownloader + +from .utils import YOUTUBEDL_BIN + + +MANAGER_PUB_TOPIC = 'dlmanager' +WORKER_PUB_TOPIC = 'dlworker' + + +class DownloadManager(Thread): + + """Manages the download process. + + Attributes: + WAIT_TIME (float): Time in seconds to sleep. + + Args: + urls_list (list): Python list that contains multiple dictionaries + with the url to download and the corresponding row(index) in + which the worker should send the download process information. + + opt_manager (optionsmanager.OptionsManager): Object responsible for + managing the youtubedlg options. + + log_manager (logmanager.LogManager): Object responsible for writing + errors to the log. + + """ + + WAIT_TIME = 0.1 + + def __init__(self, urls_list, opt_manager, log_manager=None): + super(DownloadManager, self).__init__() + self.opt_manager = opt_manager + self.log_manager = log_manager + self.urls_list = urls_list + + self._time_it_took = 0 + self._successful = 0 + self._running = True + + self._workers = self._init_workers(opt_manager.options['workers_number']) + self.start() + + @property + def successful(self): + """Returns number of successful downloads. """ + return self._successful + + @property + def time_it_took(self): + """Returns time(seconds) it took for the download process + to complete. """ + return self._time_it_took + + def increase_succ(self): + """Increase number of successful downloads. """ + self._successful += 1 + + def run(self): + self._check_youtubedl() + self._time_it_took = time.time() + + while self._running: + for worker in self._workers: + if worker.available() and self.urls_list: + worker.download(self.urls_list.pop(0)) + + time.sleep(self.WAIT_TIME) + + if not self.urls_list and self._jobs_done(): + break + + # Close all the workers + for worker in self._workers: + worker.close() + worker.join() + + self._time_it_took = time.time() - self._time_it_took + + if not self._running: + self._talk_to_gui('closed') + else: + self._talk_to_gui('finished') + + def active(self): + """Returns number of active items. + + Note: + active_items = (workers that work) + (items waiting in the url_list). + + """ + counter = 0 + for worker in self._workers: + if not worker.available(): + counter += 1 + + counter += len(self.urls_list) + + return counter + + def stop_downloads(self): + """Stop the download process. Also send 'closing' + signal back to the GUI. + + Note: + It does NOT kill the workers thats the job of the + clean up task in the run() method. + + """ + self._talk_to_gui('closing') + self._running = False + for worker in self._workers: + worker.stop_download() + + def add_url(self, url): + """Add given url to the urls_list. + + Args: + url (dictionary): Python dictionary that contains two keys. + The url and the index of the corresponding row in which + the worker should send back the information about the + download process. + + """ + self.urls_list.append(url) + + def _talk_to_gui(self, data): + """Send data back to the GUI using wxCallAfter and wxPublisher. + + Args: + data (string): Unique signal string that informs the GUI for the + download process. + + Note: + DownloadManager supports 3 signals. + 1) closing: The download process is closing. + 2) closed: The download process has closed. + 3) finished: The download process was completed normally. + + """ + CallAfter(Publisher.sendMessage, MANAGER_PUB_TOPIC, data) + + def _check_youtubedl(self): + """Check if youtube-dl binary exists. If not try to download it. """ + if not os.path.exists(self._youtubedl_path()): + UpdateThread(self.opt_manager.options['youtubedl_path'], True).join() + + def _jobs_done(self): + """Returns True if the workers have finished their jobs else False. """ + for worker in self._workers: + if not worker.available(): + return False + + return True + + def _youtubedl_path(self): + """Returns the path to youtube-dl binary. """ + path = self.opt_manager.options['youtubedl_path'] + path = os.path.join(path, YOUTUBEDL_BIN) + return path + + def _init_workers(self, workers_number): + """Initialize the custom thread pool. + + Returns: + Python list that contains the workers. + + """ + youtubedl = self._youtubedl_path() + return [Worker(self.opt_manager, youtubedl, self.increase_succ, self.log_manager) for i in xrange(workers_number)] + + +class Worker(Thread): + + """Simple worker which downloads the given url using a downloader + from the 'downloaders' module. + + Attributes: + WAIT_TIME (float): Time in seconds to sleep. + + Args: + opt_manager (optionsmanager.OptionsManager): Check DownloadManager + description. + + youtubedl (string): Absolute path to youtube-dl binary. + + increase_succ (DownloadManager.increase_succ() method): Callback to + increase the number of successful downloads. + + log_manager (logmanager.LogManager): Check DownloadManager + description. + + """ + + WAIT_TIME = 0.1 + + def __init__(self, opt_manager, youtubedl, increase_succ, log_manager=None): + super(Worker, self).__init__() + self.increase_succ = increase_succ + self.opt_manager = opt_manager + + self._downloader = YoutubeDLDownloader(youtubedl, self._data_hook, log_manager) + self._options_parser = OptionsParser() + self._running = True + self._url = None + self._index = -1 + + self.start() + + def run(self): + while self._running: + if self._url is not None: + options = self._options_parser.parse(self.opt_manager.options) + ret_code = self._downloader.download(self._url, options) + + if (ret_code == YoutubeDLDownloader.OK or + ret_code == YoutubeDLDownloader.ALREADY): + self.increase_succ() + + # Reset url value + self._url = None + + time.sleep(self.WAIT_TIME) + + def download(self, item): + """Download given item. + + Args: + item (dictionary): Python dictionary that contains two keys. + The url and the index of the corresponding row in which + the worker should send back the information about the + download process. + + """ + self._url = item['url'] + self._index = item['index'] + + def stop_download(self): + """Stop the download process of the worker. """ + self._downloader.stop() + + def close(self): + """Kill the worker after stopping the download process. """ + self._running = False + self._downloader.stop() + + def available(self): + """Return True if the worker has no job else False. """ + return self._url is None + + def _data_hook(self, data): + """Callback method to be used with the YoutubeDLDownloader object. + + This method takes the data from the downloader, merges the + playlist_info with the current status(if any) and sends the + data back to the GUI using the self._talk_to_gui method. + + Args: + data (dictionary): Python dictionary which contains information + about the download process. (See YoutubeDLDownloader class). + + """ + if data['status'] is not None and data['playlist_index'] is not None: + playlist_info = ' ' + playlist_info += data['playlist_index'] + playlist_info += '/' + playlist_info += data['playlist_size'] + + data['status'] += playlist_info + + self._talk_to_gui(data) + + def _talk_to_gui(self, data): + """Send data back to the GUI after inserting the index. """ + data['index'] = self._index + CallAfter(Publisher.sendMessage, WORKER_PUB_TOPIC, data) + + +if __name__ == '__main__': + """Direct call of the module for testing. + + Raises: + ValueError: Attempted relative import in non-package + + Note: + Before you run the tests change relative imports else an exceptions + will be raised. You need to change relative imports on all the modules + you are gonna use. + + """ + print "No tests available" diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/DownloadThread.py youtube-dlg-0.3.7/youtube_dl_gui/DownloadThread.py --- youtube-dlg-0.3.5/youtube_dl_gui/DownloadThread.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/DownloadThread.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,195 +0,0 @@ -#! /usr/bin/env python - -import subprocess -from time import sleep -from threading import Thread - -from wx import CallAfter -from wx.lib.pubsub import setuparg1 -from wx.lib.pubsub import pub as Publisher - -from .OutputHandler import ( - DataPack, - OutputFormatter -) - -from .Utils import ( - get_encoding, - encode_list, - remove_file, - get_os_type, - file_exist -) - -MAX_DOWNLOAD_THREADS = 3 -PUBLISHER_TOPIC = 'download' - -class DownloadManager(Thread): - - def __init__(self, options, downloadlist, clear_dash_files, logmanager=None): - super(DownloadManager, self).__init__() - self.clear_dash_files = clear_dash_files - self.downloadlist = downloadlist - self.logmanager = logmanager - self.options = options - self.running = True - self.procList = [] - self.procNo = 0 - self.start() - - def run(self): - while self.running: - if self.downloadlist: - # Extract url, index from data - url, index = self.extract_data() - # Wait for your turn if there are not more positions in 'queue' - while self.procNo >= MAX_DOWNLOAD_THREADS: - proc = self.check_queue() - if proc != None: - self.procList.remove(proc) - self.procNo -= 1 - sleep(1) - # If we still running create new ProcessWrapper thread - if self.running: - self.procList.append( - ProcessWrapper( - self.options, - url, - index, - self.clear_dash_files, - self.logmanager - ) - ) - self.procNo += 1 - else: - # Return True if at least one process is alive else return False - if not self.downloading(): - self.running = False - else: - sleep(0.1) - # If we reach here close down all child threads - self.terminate_all() - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('finish')) - - def downloading(self): - for proc in self.procList: - if proc.isAlive(): - return True - return False - - def _add_download_item(self, downloadItem): - self.downloadlist.append(downloadItem) - - def extract_data(self): - data = self.downloadlist.pop(0) - url = data['url'] - index = data['index'] - return url, index - - def terminate_all(self): - for proc in self.procList: - if proc.isAlive(): - proc.close() - proc.join() - - def check_queue(self): - for proc in self.procList: - if not self.running: break - if not proc.isAlive(): - return proc - return None - - def close(self): - self.procNo = 0 - self.running = False - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('close')) - -class ProcessWrapper(Thread): - - def __init__(self, options, url, index, clear_dash_files, log=None): - super(ProcessWrapper, self).__init__() - self.clear_dash_files = clear_dash_files - self.options = options - self.index = index - self.log = log - self.url = url - self.filenames = [] - self.stopped = False - self.error = False - self.proc = None - self.start() - - def run(self): - self.proc = self.create_process(self.get_cmd(), self.get_process_info()) - # while subprocess is alive and NOT the current thread - while self.proc_is_alive(): - # read stdout, stderr from proc - stdout, stderr = self.read() - if stdout != '': - # pass stdout to output formatter - data = OutputFormatter(stdout).get_data() - if self.clear_dash_files: self.add_file(data) - # add index to data pack - data.index = self.index - # send data back to caller - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, data) - if stderr != '': - self.error = True - self.write_to_log(stderr) - if not self.stopped: - if self.clear_dash_files: - self.clear_dash() - if not self.error: - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('finish', self.index)) - else: - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('error', self.index)) - - def add_file(self, dataPack): - if dataPack.header == 'filename': - self.filenames.append(dataPack.data) - - def write_to_log(self, data): - if self.log != None: - self.log.write(data) - - def clear_dash(self): - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('remove', self.index)) - for f in self.filenames: - if file_exist(f): - remove_file(f) - - def close(self): - self.proc.kill() - self.stopped = True - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, DataPack('close', self.index)) - - def proc_is_alive(self): - return self.proc.poll() == None - - def read(self): - stdout = '' - stderr = '' - stdout = self.proc.stdout.readline() - if stdout == '': - stderr = self.proc.stderr.readline() - return stdout.rstrip(), stderr.rstrip() - - def create_process(self, cmd, info): - return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=info) - - def get_cmd(self): - enc = get_encoding() - if enc != None: - cmd = encode_list(self.options + [self.url], enc) - else: - cmd = self.options + [self.url] - return cmd - - def get_process_info(self): - if get_os_type() == 'nt': - info = subprocess.STARTUPINFO() - info.dwFlags |= subprocess.STARTF_USESHOWWINDOW - return info - else: - return None - Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_128x128.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_128x128.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_16x16.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_16x16.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_256x256.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_256x256.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_32x32.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_32x32.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_48x48.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_48x48.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_512x512.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_512x512.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui_64x64.png and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui_64x64.png differ Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/icons/youtube-dl-gui.ico and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/icons/youtube-dl-gui.ico differ diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/info.py youtube-dlg-0.3.7/youtube_dl_gui/info.py --- youtube-dlg-0.3.5/youtube_dl_gui/info.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/info.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +"""Youtubedlg module that holds package information. + +Note: + All those info could be stored in the __init__ file + but we keep them here to keep the code clean. + +""" + +from __future__ import unicode_literals + +__author__ = 'Sotiris Papadopoulos' +__contact__ = 'ytubedlg@gmail.com' +__projecturl__ = 'http://mrs0m30n3.github.io/youtube-dl-gui/' + +__appname__ = 'Youtube-DLG' +__license__ = 'UNLICENSE' + +__description__ = 'Youtube-dl GUI' + +__descriptionfull__ = '''A cross platform front-end GUI of the popular +youtube-dl written in wxPython''' + +__licensefull__ = ''' +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to ''' diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/__init__.py youtube-dlg-0.3.7/youtube_dl_gui/__init__.py --- youtube-dlg-0.3.5/youtube_dl_gui/__init__.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/__init__.py 2015-04-01 15:26:52.000000000 +0000 @@ -1,20 +1,80 @@ -#! /usr/bin/env python +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- -from sys import exit +"""Youtubedlg __init__ file. + +Responsible on how the package looks from the outside. + +Example: + In order to load the GUI from a python script. + + import youtube_dl_gui + + youtube_dl_gui.main() + +""" + +from __future__ import unicode_literals + +import sys +import os.path +import gettext try: - import wx -except ImportError, e: - print '[ERROR]', e - print 'Please install latest wx.Python' - exit(1) + import wx +except ImportError as error: + print error + sys.exit(1) + +# For package use +from .version import __version__ +from .info import ( + __author__, + __appname__, + __contact__, + __license__, + __projecturl__, + __licensefull__, + __description__, + __descriptionfull__, +) + +from .logmanager import LogManager +from .optionsmanager import OptionsManager + +from .utils import ( + get_config_path, + get_locale_file +) + + +# Set config path and create options and log managers +config_path = get_config_path() + +opt_manager = OptionsManager(config_path) +log_manager = None + +if opt_manager.options['enable_log']: + log_manager = LogManager(config_path, opt_manager.options['log_time']) + +# Set gettext before MainFrame import +# because the GUI strings are class level attributes +locale_dir = get_locale_file() + +try: + gettext.translation('youtube_dl_gui', locale_dir, [opt_manager.options['locale_name']]).install(unicode=True) +except IOError: + opt_manager.options['locale_name'] = 'en_US' + gettext.install('youtube_dl_gui') + + +from .mainframe import MainFrame -from .YoutubeDLGUI import MainFrame def main(): - app = wx.App() - frame = MainFrame() - frame.Centre() - frame.Show() - app.MainLoop() - + """The real main. Creates and calls the main app windows. """ + app = wx.App() + frame = MainFrame(opt_manager, log_manager) + frame.Centre() + frame.Show() + app.MainLoop() Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/locale/ar_AR/LC_MESSAGES/youtube_dl_gui.mo and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/locale/ar_AR/LC_MESSAGES/youtube_dl_gui.mo differ diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/locale/ar_AR/LC_MESSAGES/youtube_dl_gui.po youtube-dlg-0.3.7/youtube_dl_gui/locale/ar_AR/LC_MESSAGES/youtube_dl_gui.po --- youtube-dlg-0.3.5/youtube_dl_gui/locale/ar_AR/LC_MESSAGES/youtube_dl_gui.po 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/locale/ar_AR/LC_MESSAGES/youtube_dl_gui.po 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,443 @@ +# Youtube-dlG localization file. +# FIRST AUTHOR: Sotiris Papadopoulos , 2015. +# Snosi , 2015. +msgid "" +msgstr "" +"Project-Id-Version: 0.3.6\n" +"POT-Creation-Date: 2015-03-09 18:21+EET\n" +"PO-Revision-Date: 2015-03-19 17:39+0200\n" +"Last-Translator: Snosi \n" +"Language-Team: العربية <>\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" +"X-Generator: Virtaal 0.7.1\n" +"Generated-By: pygettext.py 1.5\n" + +#: mainframe.py:80 +msgid "URLs" +msgstr "الرابط" + +#: mainframe.py:81 +msgid "Download" +msgstr "بدأ التحميل" + +#: mainframe.py:82 +msgid "Update" +msgstr "تحديث" + +#: mainframe.py:83 optionsframe.py:43 +msgid "Options" +msgstr "الخيارات" + +#: mainframe.py:84 +msgid "Error" +msgstr "خطأ" + +#: mainframe.py:85 +msgid "Stop" +msgstr "إيقاف" + +#: mainframe.py:86 +msgid "Info" +msgstr "معلومات" + +#: mainframe.py:87 +msgid "Welcome" +msgstr "مرحباً" + +#: mainframe.py:88 +msgid "Successfully downloaded {0} url(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" +msgstr "إنتهاء التحميلات {0} رابط {1} يوم {2} ساعة {3} دقيقة {4} ثانية" + +#: mainframe.py:90 +msgid "Download completed" +msgstr "إكتمال التحميل" + +#: mainframe.py:91 +msgid "Downloading {0} url(s)" +msgstr "تحميل {0} رابط" + +#: mainframe.py:92 +msgid "Stopping downloads" +msgstr "إيقاف التحميل" + +#: mainframe.py:93 +msgid "Downloads stopped" +msgstr "تم إيقاف التحميل" + +#: mainframe.py:94 +msgid "You need to provide at least one url" +msgstr "أدخل رابطا على الأقل" + +#: mainframe.py:95 +msgid "Download started" +msgstr "بدأ التحميل" + +#: mainframe.py:97 +msgid "Downloading latest youtube-dl. Please wait..." +msgstr "تحديث محمل فيديوهات يوتيوب. يرجى الإنتظار..." + +#: mainframe.py:98 +msgid "Youtube-dl download failed [{0}]" +msgstr "فشل تحديث محمل فيديوهات يوتيوب [{0}]" + +#: mainframe.py:99 +msgid "Youtube-dl downloaded correctly" +msgstr "محمل فيديوهات يوتيوب محدث" + +#: mainframe.py:101 +msgid "Unable to open directory: '{dir}'. The specified path does not exist" +msgstr "فعل لفتح الدليل : '{dir}'. المسار خاطئ" + +#: mainframe.py:103 +msgid "Error while shutting down. Make sure you typed the correct password" +msgstr "تعذر إيقاف الجهاز . تأكد من كلمة المرور" + +#: mainframe.py:105 +msgid "Shutting down system" +msgstr "إيقاف تشغيل النظام" + +#: mainframe.py:107 +msgid "Title" +msgstr "العنوان" + +#: mainframe.py:108 +msgid "Size" +msgstr "الحجم" + +#: mainframe.py:109 +msgid "Percent" +msgstr "النِسبة" + +#: mainframe.py:110 +msgid "ETA" +msgstr "الوقت المتبقي" + +#: mainframe.py:111 +msgid "Speed" +msgstr "السرعة" + +#: mainframe.py:112 +msgid "Status" +msgstr "الحالة" + +#: optionsframe.py:45 +msgid "General" +msgstr "عام" + +#: optionsframe.py:46 +msgid "Video" +msgstr "الفيديو" + +#: optionsframe.py:47 +msgid "Audio" +msgstr "الصوت" + +#: optionsframe.py:48 +msgid "Playlist" +msgstr "قائمة التشغيل" + +#: optionsframe.py:49 +msgid "Output" +msgstr "المخرجات" + +#: optionsframe.py:50 +msgid "Subtitles" +msgstr "الترجمة" + +#: optionsframe.py:51 +msgid "Filesystem" +msgstr "ملفات النظام" + +#: optionsframe.py:52 +msgid "Shutdown" +msgstr "إيقاف التشغيل" + +#: optionsframe.py:53 +msgid "Authentication" +msgstr "إستيثاق" + +#: optionsframe.py:54 +msgid "Connection" +msgstr "الإتصال" + +#: optionsframe.py:55 +msgid "Log" +msgstr "السِجِل" + +#: optionsframe.py:56 +msgid "Commands" +msgstr "أوامر" + +#: optionsframe.py:57 +msgid "Localization" +msgstr "اللغة" + +#: optionsframe.py:375 +msgid "Enable Log" +msgstr "فعّل السِجِل" + +#: optionsframe.py:376 +msgid "Write Time" +msgstr "كتابة الوقت" + +#: optionsframe.py:377 +msgid "Clear Log" +msgstr "إمسح السِجِل" + +#: optionsframe.py:378 +msgid "View Log" +msgstr "شاهد السِجِل" + +#: optionsframe.py:379 +msgid "Path: {0}" +msgstr "المسار: {0}" + +#: optionsframe.py:380 +msgid "Log Size: {0} Bytes" +msgstr "حجم السِجِل: {0} Octets" + +#: optionsframe.py:381 optionsframe.py:1394 +msgid "Restart" +msgstr "إعادة التشغيل" + +#: optionsframe.py:382 +msgid "Please restart {0}" +msgstr "أعد التشغيل {0}" + +#: optionsframe.py:490 +msgid "Shutdown when finished" +msgstr "أوقف تشغيل النظام عند الإنتهاء" + +#: optionsframe.py:491 +msgid "SUDO password" +msgstr "كلمة مرور المسؤول" + +#: optionsframe.py:543 +msgid "Playlist Start" +msgstr "بدأ قائمة التشغيل" + +#: optionsframe.py:544 +msgid "Playlist Stop" +msgstr "إيقاف قائمة التشغيل" + +#: optionsframe.py:545 +msgid "Max Downloads" +msgstr "أقصى حد للتحميلات" + +#: optionsframe.py:603 +msgid "Retries" +msgstr "فحص" + +#: optionsframe.py:604 +msgid "User Agent" +msgstr "اسم العميل" + +#: optionsframe.py:605 +msgid "Referer" +msgstr "يدل" + +#: optionsframe.py:606 +msgid "Proxy" +msgstr "الخادم الوكيل" + +#: optionsframe.py:680 +msgid "Username" +msgstr "اسم المستخدم" + +#: optionsframe.py:681 +msgid "Password" +msgstr "كلمة المرور" + +#: optionsframe.py:682 +msgid "Video Password (vimeo, smotri)" +msgstr "كلمة المرور (vimeo, smotri)" + +#: optionsframe.py:740 +msgid "high" +msgstr "عالي" + +#: optionsframe.py:740 +msgid "low" +msgstr "منخفض" + +#: optionsframe.py:740 +msgid "mid" +msgstr "متوسط" + +#: optionsframe.py:743 +msgid "Convert to Audio" +msgstr "حول الى صوت" + +#: optionsframe.py:744 +msgid "Keep Video" +msgstr "فيديو" + +#: optionsframe.py:745 +msgid "Audio Format" +msgstr "صيغة الصوت" + +#: optionsframe.py:746 +msgid "Audio Quality" +msgstr "جودة الصوت" + +#: optionsframe.py:863 +msgid "default" +msgstr "مبدئي" + +#: optionsframe.py:864 +msgid "none" +msgstr "بدون" + +#: optionsframe.py:868 +msgid "Video Format" +msgstr "صيغة الفيديو" + +#: optionsframe.py:869 +msgid "Mix Format" +msgstr "خلط الصيغة" + +#: optionsframe.py:928 +msgid "Restrict filenames (ASCII)" +msgstr "حول الاسماء الى (ASCII)" + +#: optionsframe.py:929 +msgid "ID as Name" +msgstr "الإسم = الهوية" + +#: optionsframe.py:930 +msgid "Title as Name" +msgstr "الإسم = العنوان" + +#: optionsframe.py:931 +msgid "Custom Template (youtube-dl)" +msgstr "قالب مخصص" + +#: optionsframe.py:1025 +msgid "Ignore Errors" +msgstr "تجاهل الأخطاء" + +#: optionsframe.py:1026 +msgid "Open destination folder" +msgstr "افتح وجهة المجلد" + +#: optionsframe.py:1027 +msgid "Write info to (.json) file" +msgstr "اكتب المعلومات في ملف (.json)" + +#: optionsframe.py:1028 +msgid "Write description to file" +msgstr "اكتب الوصف" + +#: optionsframe.py:1029 +msgid "Write thumbnail to disk" +msgstr "إحفظ المصغرات" + +#: optionsframe.py:1030 +msgid "Filesize" +msgstr "حجم الملف" + +#: optionsframe.py:1031 +msgid "Min" +msgstr "اقل" + +#: optionsframe.py:1032 +msgid "Max" +msgstr "اقصى" + +#: optionsframe.py:1145 +msgid "English" +msgstr "الانجليزية" + +#: optionsframe.py:1146 +msgid "Greek" +msgstr "اليونانية" + +#: optionsframe.py:1147 +msgid "Portuguese" +msgstr "البرتغالية" + +#: optionsframe.py:1148 +msgid "French" +msgstr "الفرنسية" + +#: optionsframe.py:1149 +msgid "Italian" +msgstr "الايطالية" + +#: optionsframe.py:1150 +msgid "Russian" +msgstr "الروسية" + +#: optionsframe.py:1151 +msgid "Spanish" +msgstr "الاسبانية" + +#: optionsframe.py:1152 +msgid "German" +msgstr "الالمانية" + +#: optionsframe.py:1155 +msgid "Download subtitle file by language" +msgstr "تحميل الترجمة حسب المرشح" + +#: optionsframe.py:1156 +msgid "Download all available subtitles" +msgstr "تحميل كل الترجمات المتوفرة" + +#: optionsframe.py:1157 +msgid "Download automatic subtitle file (YOUTUBE ONLY)" +msgstr "تحميل تلقائي للترجمة (يوتيوب فقط)" + +#: optionsframe.py:1158 +msgid "Embed subtitles in the video (only for mp4 videos)" +msgstr "اضف الترجمة للفيديو (mp4 فقط)" + +#: optionsframe.py:1159 +msgid "Subtitles Language" +msgstr "لغة الترجمة" + +#: optionsframe.py:1253 +msgid "About" +msgstr "عن" + +#: optionsframe.py:1254 +msgid "Open" +msgstr "فتح" + +#: optionsframe.py:1255 +msgid "Reset Options" +msgstr "إعادة تعين" + +#: optionsframe.py:1256 +msgid "Save Path" +msgstr "حفظ المسار" + +#: optionsframe.py:1257 +msgid "Settings File: {0}" +msgstr "خيارات الملف : {0}" + +#: optionsframe.py:1258 +msgid "Choose Directory" +msgstr "اختيار الوجهة" + +#: optionsframe.py:1343 +msgid "Command line arguments (e.g. --help)" +msgstr "معطيات سطر الاوامر (e.g. --help)" + +#: optionsframe.py:1395 +msgid "Localization Language" +msgstr "اللغة" + +#: optionsframe.py:1396 +msgid "In order for the changes to take effect please restart {0}" +msgstr "لتصبح التغييرات سارية المفعول أعد تشغيل {0}" + +#: optionsframe.py:1443 +msgid "Log Viewer" +msgstr "عارض السجل" + Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/locale/de_DE/LC_MESSAGES/youtube_dl_gui.mo and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/locale/de_DE/LC_MESSAGES/youtube_dl_gui.mo differ diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/locale/de_DE/LC_MESSAGES/youtube_dl_gui.po youtube-dlg-0.3.7/youtube_dl_gui/locale/de_DE/LC_MESSAGES/youtube_dl_gui.po --- youtube-dlg-0.3.5/youtube_dl_gui/locale/de_DE/LC_MESSAGES/youtube_dl_gui.po 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/locale/de_DE/LC_MESSAGES/youtube_dl_gui.po 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,440 @@ +# Youtube-dlG localization file. +# FIRST AUTHOR: Sotiris Papadopoulos , 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.3.6\n" +"POT-Creation-Date: 2015-03-09 18:21+EET\n" +"PO-Revision-Date: 2015-03-09 18:21+EET\n" +"Last-Translator: Max Bruckner \n" +"Language-Team: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: mainframe.py:80 +msgid "URLs" +msgstr "URLs" + +#: mainframe.py:81 +msgid "Download" +msgstr "Herunterladen" + +#: mainframe.py:82 +msgid "Update" +msgstr "Updaten" + +#: mainframe.py:83 optionsframe.py:43 +msgid "Options" +msgstr "Optionen" + +#: mainframe.py:84 +msgid "Error" +msgstr "Fehler" + +#: mainframe.py:85 +msgid "Stop" +msgstr "Anhalten" + +#: mainframe.py:86 +msgid "Info" +msgstr "Info" + +#: mainframe.py:87 +msgid "Welcome" +msgstr "Willkommen" + +#: mainframe.py:88 +msgid "Successfully downloaded {0} url(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" +msgstr "{0} url(s) wurden innerhalb von {1} Tage(n) {2} Stunde(n) {3} Minute(n) {4} Sekunde(n) erfolgreich heruntergeladen" + +#: mainframe.py:90 +msgid "Download completed" +msgstr "Download abgeschlossen" + +#: mainframe.py:91 +msgid "Downloading {0} url(s)" +msgstr "Lade {0} URL(s) herunter" + +#: mainframe.py:92 +msgid "Stopping downloads" +msgstr "Halte Downloads an" + +#: mainframe.py:93 +msgid "Downloads stopped" +msgstr "Downloads angehalten" + +#: mainframe.py:94 +msgid "You need to provide at least one url" +msgstr "Sie müssen mindestens eine URL angeben" + +#: mainframe.py:95 +msgid "Download started" +msgstr "Download gestartet" + +#: mainframe.py:97 +msgid "Downloading latest youtube-dl. Please wait..." +msgstr "Lade neuestes youtube-dl herunter. Bitte warten..." + +#: mainframe.py:98 +msgid "Youtube-dl download failed [{0}]" +msgstr "Herunterladen von youtube-dl fehlgeschlagen [{0}]" + +#: mainframe.py:99 +msgid "Youtube-dl downloaded correctly" +msgstr "Youtube-dl erfolgreich heruntergeladen" + +#: mainframe.py:101 +msgid "Unable to open directory: '{dir}'. The specified path does not exist" +msgstr "Konnte '{dir}' nicht öffnen. Der angegebene Pfad existiert nicht" + +#: mainframe.py:103 +msgid "Error while shutting down. Make sure you typed the correct password" +msgstr "Fehler beim Herunterfahren. Stellen Sie sicher dass sie das Passwort korrekt eingegeben haben" + +#: mainframe.py:105 +msgid "Shutting down system" +msgstr "System wird heruntergefahren" + +#: mainframe.py:107 +msgid "Title" +msgstr "Titel" + +#: mainframe.py:108 +msgid "Size" +msgstr "Größe" + +#: mainframe.py:109 +msgid "Percent" +msgstr "Prozent" + +#: mainframe.py:110 +msgid "ETA" +msgstr "Verbleibend" + +#: mainframe.py:111 +msgid "Speed" +msgstr "Geschwindigkeit" + +#: mainframe.py:112 +msgid "Status" +msgstr "Status" + +#: optionsframe.py:45 +msgid "General" +msgstr "Allgemein" + +#: optionsframe.py:46 +msgid "Video" +msgstr "Video" + +#: optionsframe.py:47 +msgid "Audio" +msgstr "Audio" + +#: optionsframe.py:48 +msgid "Playlist" +msgstr "Wiedergabeliste" + +#: optionsframe.py:49 +msgid "Output" +msgstr "Ausgabe" + +#: optionsframe.py:50 +msgid "Subtitles" +msgstr "Untertitel" + +#: optionsframe.py:51 +msgid "Filesystem" +msgstr "Dateisystem" + +#: optionsframe.py:52 +msgid "Shutdown" +msgstr "Herunterfahren" + +#: optionsframe.py:53 +msgid "Authentication" +msgstr "Authentifizierung" + +#: optionsframe.py:54 +msgid "Connection" +msgstr "Verbindung" + +#: optionsframe.py:55 +msgid "Log" +msgstr "Protokoll" + +#: optionsframe.py:56 +msgid "Commands" +msgstr "Befehle" + +#: optionsframe.py:57 +msgid "Localization" +msgstr "Sprache" + +#: optionsframe.py:375 +msgid "Enable Log" +msgstr "Protokollierung aktivieren" + +#: optionsframe.py:376 +msgid "Write Time" +msgstr "Zeit protokollieren" + +#: optionsframe.py:377 +msgid "Clear Log" +msgstr "Protokoll leeren" + +#: optionsframe.py:378 +msgid "View Log" +msgstr "Protokoll anzeigen" + +#: optionsframe.py:379 +msgid "Path: {0}" +msgstr "Pfad: {0}" + +#: optionsframe.py:380 +msgid "Log Size: {0} Bytes" +msgstr "Protokollgröße: {0} Bytes" + +#: optionsframe.py:381 optionsframe.py:1394 +msgid "Restart" +msgstr "Neustarten" + +#: optionsframe.py:382 +msgid "Please restart {0}" +msgstr "Bitte starten Sie {0} neu" + +#: optionsframe.py:490 +msgid "Shutdown when finished" +msgstr "Herunterfahren sobald abgeschlossen" + +#: optionsframe.py:491 +msgid "SUDO password" +msgstr "SUDO Passwort" + +#: optionsframe.py:543 +msgid "Playlist Start" +msgstr "Starten mit" + +#: optionsframe.py:544 +msgid "Playlist Stop" +msgstr "Aufhören mit" + +#: optionsframe.py:545 +msgid "Max Downloads" +msgstr "Maximale Downloads" + +#: optionsframe.py:603 +msgid "Retries" +msgstr "Verbindungsversuche" + +#: optionsframe.py:604 +msgid "User Agent" +msgstr "User Agent" + +#: optionsframe.py:605 +msgid "Referer" +msgstr "Referer" + +#: optionsframe.py:606 +msgid "Proxy" +msgstr "Proxy" + +#: optionsframe.py:680 +msgid "Username" +msgstr "Nutzername" + +#: optionsframe.py:681 +msgid "Password" +msgstr "Passwort" + +#: optionsframe.py:682 +msgid "Video Password (vimeo, smotri)" +msgstr "Video-Passwort (vimeo, smotri)" + +#: optionsframe.py:740 +msgid "high" +msgstr "gut" + +#: optionsframe.py:740 +msgid "low" +msgstr "schlecht" + +#: optionsframe.py:740 +msgid "mid" +msgstr "mittel" + +#: optionsframe.py:743 +msgid "Convert to Audio" +msgstr "Zu Audio konvertieren" + +#: optionsframe.py:744 +msgid "Keep Video" +msgstr "Video behalten" + +#: optionsframe.py:745 +msgid "Audio Format" +msgstr "Audio-Format" + +#: optionsframe.py:746 +msgid "Audio Quality" +msgstr "Audio-Qualität" + +#: optionsframe.py:863 +msgid "default" +msgstr "standard" + +#: optionsframe.py:864 +msgid "none" +msgstr "keines" + +#: optionsframe.py:868 +msgid "Video Format" +msgstr "Video-Format" + +#: optionsframe.py:869 +msgid "Mix Format" +msgstr "Mix-Format" + +#: optionsframe.py:928 +msgid "Restrict filenames (ASCII)" +msgstr "Dateinamen einschränken (ASCII)" + +#: optionsframe.py:929 +msgid "ID as Name" +msgstr "ID als Name" + +#: optionsframe.py:930 +msgid "Title as Name" +msgstr "Titel als Name" + +#: optionsframe.py:931 +msgid "Custom Template (youtube-dl)" +msgstr "Benutzerdefiniertes Template (youtube-dl)" + +#: optionsframe.py:1025 +msgid "Ignore Errors" +msgstr "Fehler ignorieren" + +#: optionsframe.py:1026 +msgid "Open destination folder" +msgstr "Zielordner öffnen" + +#: optionsframe.py:1027 +msgid "Write info to (.json) file" +msgstr "In JSON-Datei schreiben" + +#: optionsframe.py:1028 +msgid "Write description to file" +msgstr "Beschreibung in Datei speichern" + +#: optionsframe.py:1029 +msgid "Write thumbnail to disk" +msgstr "Vorschaubild speichern" + +#: optionsframe.py:1030 +msgid "Filesize" +msgstr "Dateigröße" + +#: optionsframe.py:1031 +msgid "Min" +msgstr "Min" + +#: optionsframe.py:1032 +msgid "Max" +msgstr "Max" + +#: optionsframe.py:1145 +msgid "English" +msgstr "Englisch" + +#: optionsframe.py:1146 +msgid "Greek" +msgstr "Griechisch" + +#: optionsframe.py:1147 +msgid "Portuguese" +msgstr "Portugiesisch" + +#: optionsframe.py:1148 +msgid "French" +msgstr "Französisch" + +#: optionsframe.py:1149 +msgid "Italian" +msgstr "Italienisch" + +#: optionsframe.py:1150 +msgid "Russian" +msgstr "Russisch" + +#: optionsframe.py:1151 +msgid "Spanish" +msgstr "Spanisch" + +#: optionsframe.py:1152 +msgid "German" +msgstr "Deutsch" + +#: optionsframe.py:1155 +msgid "Download subtitle file by language" +msgstr "Untertitel nach Sprache herunterladen" + +#: optionsframe.py:1156 +msgid "Download all available subtitles" +msgstr "Alle verfügbaren Untertitel herunterladen" + +#: optionsframe.py:1157 +msgid "Download automatic subtitle file (YOUTUBE ONLY)" +msgstr "Automatische Untertitel-Datei herunterladen (NUR YOUTUBE)" + +#: optionsframe.py:1158 +msgid "Embed subtitles in the video (only for mp4 videos)" +msgstr "Untertitel in Video einbetten (nur MP4-Videos)" + +#: optionsframe.py:1159 +msgid "Subtitles Language" +msgstr "Untertitel-Sprache" + +#: optionsframe.py:1253 +msgid "About" +msgstr "Über" + +#: optionsframe.py:1254 +msgid "Open" +msgstr "Öffnen" + +#: optionsframe.py:1255 +msgid "Reset Options" +msgstr "Zurücksetzen" + +#: optionsframe.py:1256 +msgid "Save Path" +msgstr "Speicherort" + +#: optionsframe.py:1257 +msgid "Settings File: {0}" +msgstr "Konfigurationsdatei: {0}" + +#: optionsframe.py:1258 +msgid "Choose Directory" +msgstr "Ordner auswählen" + +#: optionsframe.py:1343 +msgid "Command line arguments (e.g. --help)" +msgstr "Kommandozeilen-Parameter (z.B. --help)" + +#: optionsframe.py:1395 +msgid "Localization Language" +msgstr "Sprache" + +#: optionsframe.py:1396 +msgid "In order for the changes to take effect please restart {0}" +msgstr "{0} neustarten, um Änderungen anzuwenden" + +#: optionsframe.py:1443 +msgid "Log Viewer" +msgstr "Protokoll" + Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.mo and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.mo differ diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po youtube-dlg-0.3.7/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po --- youtube-dlg-0.3.5/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,440 @@ +# Youtube-dlG localization file. +# FIRST AUTHOR: Sotiris Papadopoulos , 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.3.6\n" +"POT-Creation-Date: 2015-03-09 18:21+EET\n" +"PO-Revision-Date: 2015-03-09 18:21+EET\n" +"Last-Translator: Sotiris Papadopoulos \n" +"Language-Team: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: mainframe.py:80 +msgid "URLs" +msgstr "" + +#: mainframe.py:81 +msgid "Download" +msgstr "" + +#: mainframe.py:82 +msgid "Update" +msgstr "" + +#: mainframe.py:83 optionsframe.py:43 +msgid "Options" +msgstr "" + +#: mainframe.py:84 +msgid "Error" +msgstr "" + +#: mainframe.py:85 +msgid "Stop" +msgstr "" + +#: mainframe.py:86 +msgid "Info" +msgstr "" + +#: mainframe.py:87 +msgid "Welcome" +msgstr "" + +#: mainframe.py:88 +msgid "Successfully downloaded {0} url(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" +msgstr "" + +#: mainframe.py:90 +msgid "Download completed" +msgstr "" + +#: mainframe.py:91 +msgid "Downloading {0} url(s)" +msgstr "" + +#: mainframe.py:92 +msgid "Stopping downloads" +msgstr "" + +#: mainframe.py:93 +msgid "Downloads stopped" +msgstr "" + +#: mainframe.py:94 +msgid "You need to provide at least one url" +msgstr "" + +#: mainframe.py:95 +msgid "Download started" +msgstr "" + +#: mainframe.py:97 +msgid "Downloading latest youtube-dl. Please wait..." +msgstr "" + +#: mainframe.py:98 +msgid "Youtube-dl download failed [{0}]" +msgstr "" + +#: mainframe.py:99 +msgid "Youtube-dl downloaded correctly" +msgstr "" + +#: mainframe.py:101 +msgid "Unable to open directory: '{dir}'. The specified path does not exist" +msgstr "" + +#: mainframe.py:103 +msgid "Error while shutting down. Make sure you typed the correct password" +msgstr "" + +#: mainframe.py:105 +msgid "Shutting down system" +msgstr "" + +#: mainframe.py:107 +msgid "Title" +msgstr "" + +#: mainframe.py:108 +msgid "Size" +msgstr "" + +#: mainframe.py:109 +msgid "Percent" +msgstr "" + +#: mainframe.py:110 +msgid "ETA" +msgstr "" + +#: mainframe.py:111 +msgid "Speed" +msgstr "" + +#: mainframe.py:112 +msgid "Status" +msgstr "" + +#: optionsframe.py:45 +msgid "General" +msgstr "" + +#: optionsframe.py:46 +msgid "Video" +msgstr "" + +#: optionsframe.py:47 +msgid "Audio" +msgstr "" + +#: optionsframe.py:48 +msgid "Playlist" +msgstr "" + +#: optionsframe.py:49 +msgid "Output" +msgstr "" + +#: optionsframe.py:50 +msgid "Subtitles" +msgstr "" + +#: optionsframe.py:51 +msgid "Filesystem" +msgstr "" + +#: optionsframe.py:52 +msgid "Shutdown" +msgstr "" + +#: optionsframe.py:53 +msgid "Authentication" +msgstr "" + +#: optionsframe.py:54 +msgid "Connection" +msgstr "" + +#: optionsframe.py:55 +msgid "Log" +msgstr "" + +#: optionsframe.py:56 +msgid "Commands" +msgstr "" + +#: optionsframe.py:57 +msgid "Localization" +msgstr "" + +#: optionsframe.py:375 +msgid "Enable Log" +msgstr "" + +#: optionsframe.py:376 +msgid "Write Time" +msgstr "" + +#: optionsframe.py:377 +msgid "Clear Log" +msgstr "" + +#: optionsframe.py:378 +msgid "View Log" +msgstr "" + +#: optionsframe.py:379 +msgid "Path: {0}" +msgstr "" + +#: optionsframe.py:380 +msgid "Log Size: {0} Bytes" +msgstr "" + +#: optionsframe.py:381 optionsframe.py:1394 +msgid "Restart" +msgstr "" + +#: optionsframe.py:382 +msgid "Please restart {0}" +msgstr "" + +#: optionsframe.py:490 +msgid "Shutdown when finished" +msgstr "" + +#: optionsframe.py:491 +msgid "SUDO password" +msgstr "" + +#: optionsframe.py:543 +msgid "Playlist Start" +msgstr "" + +#: optionsframe.py:544 +msgid "Playlist Stop" +msgstr "" + +#: optionsframe.py:545 +msgid "Max Downloads" +msgstr "" + +#: optionsframe.py:603 +msgid "Retries" +msgstr "" + +#: optionsframe.py:604 +msgid "User Agent" +msgstr "" + +#: optionsframe.py:605 +msgid "Referer" +msgstr "" + +#: optionsframe.py:606 +msgid "Proxy" +msgstr "" + +#: optionsframe.py:680 +msgid "Username" +msgstr "" + +#: optionsframe.py:681 +msgid "Password" +msgstr "" + +#: optionsframe.py:682 +msgid "Video Password (vimeo, smotri)" +msgstr "" + +#: optionsframe.py:740 +msgid "high" +msgstr "" + +#: optionsframe.py:740 +msgid "low" +msgstr "" + +#: optionsframe.py:740 +msgid "mid" +msgstr "" + +#: optionsframe.py:743 +msgid "Convert to Audio" +msgstr "" + +#: optionsframe.py:744 +msgid "Keep Video" +msgstr "" + +#: optionsframe.py:745 +msgid "Audio Format" +msgstr "" + +#: optionsframe.py:746 +msgid "Audio Quality" +msgstr "" + +#: optionsframe.py:863 +msgid "default" +msgstr "" + +#: optionsframe.py:864 +msgid "none" +msgstr "" + +#: optionsframe.py:868 +msgid "Video Format" +msgstr "" + +#: optionsframe.py:869 +msgid "Mix Format" +msgstr "" + +#: optionsframe.py:928 +msgid "Restrict filenames (ASCII)" +msgstr "" + +#: optionsframe.py:929 +msgid "ID as Name" +msgstr "" + +#: optionsframe.py:930 +msgid "Title as Name" +msgstr "" + +#: optionsframe.py:931 +msgid "Custom Template (youtube-dl)" +msgstr "" + +#: optionsframe.py:1025 +msgid "Ignore Errors" +msgstr "" + +#: optionsframe.py:1026 +msgid "Open destination folder" +msgstr "" + +#: optionsframe.py:1027 +msgid "Write info to (.json) file" +msgstr "" + +#: optionsframe.py:1028 +msgid "Write description to file" +msgstr "" + +#: optionsframe.py:1029 +msgid "Write thumbnail to disk" +msgstr "" + +#: optionsframe.py:1030 +msgid "Filesize" +msgstr "" + +#: optionsframe.py:1031 +msgid "Min" +msgstr "" + +#: optionsframe.py:1032 +msgid "Max" +msgstr "" + +#: optionsframe.py:1145 +msgid "English" +msgstr "" + +#: optionsframe.py:1146 +msgid "Greek" +msgstr "" + +#: optionsframe.py:1147 +msgid "Portuguese" +msgstr "" + +#: optionsframe.py:1148 +msgid "French" +msgstr "" + +#: optionsframe.py:1149 +msgid "Italian" +msgstr "" + +#: optionsframe.py:1150 +msgid "Russian" +msgstr "" + +#: optionsframe.py:1151 +msgid "Spanish" +msgstr "" + +#: optionsframe.py:1152 +msgid "German" +msgstr "" + +#: optionsframe.py:1155 +msgid "Download subtitle file by language" +msgstr "" + +#: optionsframe.py:1156 +msgid "Download all available subtitles" +msgstr "" + +#: optionsframe.py:1157 +msgid "Download automatic subtitle file (YOUTUBE ONLY)" +msgstr "" + +#: optionsframe.py:1158 +msgid "Embed subtitles in the video (only for mp4 videos)" +msgstr "" + +#: optionsframe.py:1159 +msgid "Subtitles Language" +msgstr "" + +#: optionsframe.py:1253 +msgid "About" +msgstr "" + +#: optionsframe.py:1254 +msgid "Open" +msgstr "" + +#: optionsframe.py:1255 +msgid "Reset Options" +msgstr "" + +#: optionsframe.py:1256 +msgid "Save Path" +msgstr "" + +#: optionsframe.py:1257 +msgid "Settings File: {0}" +msgstr "" + +#: optionsframe.py:1258 +msgid "Choose Directory" +msgstr "" + +#: optionsframe.py:1343 +msgid "Command line arguments (e.g. --help)" +msgstr "" + +#: optionsframe.py:1395 +msgid "Localization Language" +msgstr "" + +#: optionsframe.py:1396 +msgid "In order for the changes to take effect please restart {0}" +msgstr "" + +#: optionsframe.py:1443 +msgid "Log Viewer" +msgstr "" + Binary files /tmp/IKNTQ6dKvW/youtube-dlg-0.3.5/youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.mo and /tmp/4om76Xph4T/youtube-dlg-0.3.7/youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.mo differ diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.po youtube-dlg-0.3.7/youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.po --- youtube-dlg-0.3.5/youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.po 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.po 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,439 @@ +# Youtube-dlG localization file. +# FIRST AUTHOR: Sotiris Papadopoulos , 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.3.6\n" +"POT-Creation-Date: 2015-03-09 18:21+EET\n" +"PO-Revision-Date: 2015-03-17 14:30+EET\n" +"Last-Translator: Thierry Roussel \n" +"Language-Team: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + +#: mainframe.py:80 +msgid "URLs" +msgstr "Lien(s)" + +#: mainframe.py:81 +msgid "Download" +msgstr "Télécharger" + +#: mainframe.py:82 +msgid "Update" +msgstr "Mise à jour" + +#: mainframe.py:83 optionsframe.py:43 +msgid "Options" +msgstr "" + +#: mainframe.py:84 +msgid "Error" +msgstr "Erreur" + +#: mainframe.py:85 +msgid "Stop" +msgstr "Arrêter" + +#: mainframe.py:86 +msgid "Info" +msgstr "" + +#: mainframe.py:87 +msgid "Welcome" +msgstr "Bienvenue" + +#: mainframe.py:88 +msgid "Successfully downloaded {0} url(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" +msgstr "Téléchargements réussis {0} lien(s) en {1} jour(s) {2} heure(s) {3} minute(s) {4} seconde(s)" + +#: mainframe.py:90 +msgid "Download completed" +msgstr "Téléchargement terminé" + +#: mainframe.py:91 +msgid "Downloading {0} url(s)" +msgstr "Téléchargement de {0} lien(s)" + +#: mainframe.py:92 +msgid "Stopping downloads" +msgstr "Arrêt des téléchargements" + +#: mainframe.py:93 +msgid "Downloads stopped" +msgstr "Téléchargements arrêtés" + +#: mainframe.py:94 +msgid "You need to provide at least one url" +msgstr "Vous devez fournir au moins 1 lien" + +#: mainframe.py:95 +msgid "Download started" +msgstr "Téléchargement démarré" + +#: mainframe.py:97 +msgid "Downloading latest youtube-dl. Please wait..." +msgstr "Mise à jour de youtube-dl. Merci de patienter" + +#: mainframe.py:98 +msgid "Youtube-dl download failed [{0}]" +msgstr "Echec de la mise à jour de Youtube-dl [{0}]" + +#: mainframe.py:99 +msgid "Youtube-dl downloaded correctly" +msgstr "Youtube-dl a été mis à jour" + +#: mainframe.py:101 +msgid "Unable to open directory: '{dir}'. The specified path does not exist" +msgstr "Impossible d'ouvrir le répertoire: '{dir}'. Le chemin n'existe pas" + +#: mainframe.py:103 +msgid "Error while shutting down. Make sure you typed the correct password" +msgstr "Erreur d'arrêt du système. Vérifiez le mot de passe saisi" + +#: mainframe.py:105 +msgid "Shutting down system" +msgstr "Arrêt du système" + +#: mainframe.py:107 +msgid "Title" +msgstr "Titre" + +#: mainframe.py:108 +msgid "Size" +msgstr "Taille" + +#: mainframe.py:109 +msgid "Percent" +msgstr "%" + +#: mainframe.py:110 +msgid "ETA" +msgstr "ETA" + +#: mainframe.py:111 +msgid "Speed" +msgstr "Vitesse" + +#: mainframe.py:112 +msgid "Status" +msgstr "Etat" + +#: optionsframe.py:45 +msgid "General" +msgstr "Général" + +#: optionsframe.py:46 +msgid "Video" +msgstr "Vidéo" + +#: optionsframe.py:47 +msgid "Audio" +msgstr "Audio" + +#: optionsframe.py:48 +msgid "Playlist" +msgstr "Playlist" + +#: optionsframe.py:49 +msgid "Output" +msgstr "Sortie" + +#: optionsframe.py:50 +msgid "Subtitles" +msgstr "Sous-titres" + +#: optionsframe.py:51 +msgid "Filesystem" +msgstr "Fichiers" + +#: optionsframe.py:52 +msgid "Shutdown" +msgstr "Arrêt" + +#: optionsframe.py:53 +msgid "Authentication" +msgstr "Authentification" + +#: optionsframe.py:54 +msgid "Connection" +msgstr "Connexion" + +#: optionsframe.py:55 +msgid "Log" +msgstr "Log" + +#: optionsframe.py:56 +msgid "Commands" +msgstr "Commandes" + +#: optionsframe.py:57 +msgid "Localization" +msgstr "Langue" + +#: optionsframe.py:375 +msgid "Enable Log" +msgstr "Activer les traces" + +#: optionsframe.py:376 +msgid "Write Time" +msgstr "Temps d'écriture" + +#: optionsframe.py:377 +msgid "Clear Log" +msgstr "Effacer les traces" + +#: optionsframe.py:378 +msgid "View Log" +msgstr "Afficher les traces" + +#: optionsframe.py:379 +msgid "Path: {0}" +msgstr "Répertoire: {0}" + +#: optionsframe.py:380 +msgid "Log Size: {0} Bytes" +msgstr "Taille des traces: {0} Octets" + +#: optionsframe.py:381 optionsframe.py:1394 +msgid "Restart" +msgstr "Redémarrer" + +#: optionsframe.py:382 +msgid "Please restart {0}" +msgstr "Veuillez redémarrer {0}" + +#: optionsframe.py:490 +msgid "Shutdown when finished" +msgstr "Arrêter une fois terminé" + +#: optionsframe.py:491 +msgid "SUDO password" +msgstr "" + +#: optionsframe.py:543 +msgid "Playlist Start" +msgstr "" + +#: optionsframe.py:544 +msgid "Playlist Stop" +msgstr "" + +#: optionsframe.py:545 +msgid "Max Downloads" +msgstr "Téléchargements Max" + +#: optionsframe.py:603 +msgid "Retries" +msgstr "Essais" + +#: optionsframe.py:604 +msgid "User Agent" +msgstr "" + +#: optionsframe.py:605 +msgid "Referer" +msgstr "" + +#: optionsframe.py:606 +msgid "Proxy" +msgstr "" + +#: optionsframe.py:680 +msgid "Username" +msgstr "Utilisateur" + +#: optionsframe.py:681 +msgid "Password" +msgstr "Mot de passe" + +#: optionsframe.py:682 +msgid "Video Password (vimeo, smotri)" +msgstr "" + +#: optionsframe.py:740 +msgid "high" +msgstr "haute" + +#: optionsframe.py:740 +msgid "low" +msgstr "basse" + +#: optionsframe.py:740 +msgid "mid" +msgstr "moyenne" + +#: optionsframe.py:743 +msgid "Convert to Audio" +msgstr "Convertir la piste Audio" + +#: optionsframe.py:744 +msgid "Keep Video" +msgstr "Conserver la piste Vidéo" + +#: optionsframe.py:745 +msgid "Audio Format" +msgstr "Format Audio" + +#: optionsframe.py:746 +msgid "Audio Quality" +msgstr "Qualité Audio" + +#: optionsframe.py:863 +msgid "default" +msgstr "par défaut" + +#: optionsframe.py:864 +msgid "none" +msgstr "aucune" + +#: optionsframe.py:868 +msgid "Video Format" +msgstr "Format Vidéo" + +#: optionsframe.py:869 +msgid "Mix Format" +msgstr "" + +#: optionsframe.py:928 +msgid "Restrict filenames (ASCII)" +msgstr "Convertir les noms (ASCII)" + +#: optionsframe.py:929 +msgid "ID as Name" +msgstr "Nom = ID" + +#: optionsframe.py:930 +msgid "Title as Name" +msgstr "Nom = Titre" + +#: optionsframe.py:931 +msgid "Custom Template (youtube-dl)" +msgstr "Format personnalisé" + +#: optionsframe.py:1025 +msgid "Ignore Errors" +msgstr "Ignorer les erreurs" + +#: optionsframe.py:1026 +msgid "Open destination folder" +msgstr "Ouvrir le répertoire de destination" + +#: optionsframe.py:1027 +msgid "Write info to (.json) file" +msgstr "Renseigner le fichier (.json)" + +#: optionsframe.py:1028 +msgid "Write description to file" +msgstr "Ecrire la description" + +#: optionsframe.py:1029 +msgid "Write thumbnail to disk" +msgstr "Enregistrer la miniature" + +#: optionsframe.py:1030 +msgid "Filesize" +msgstr "Taille de fichier" + +#: optionsframe.py:1031 +msgid "Min" +msgstr "" + +#: optionsframe.py:1032 +msgid "Max" +msgstr "" + +#: optionsframe.py:1145 +msgid "English" +msgstr "Anglais" + +#: optionsframe.py:1146 +msgid "Greek" +msgstr "Grec" + +#: optionsframe.py:1147 +msgid "Portuguese" +msgstr "Portugais" + +#: optionsframe.py:1148 +msgid "French" +msgstr "Français" + +#: optionsframe.py:1149 +msgid "Italian" +msgstr "Italien" + +#: optionsframe.py:1150 +msgid "Russian" +msgstr "Russe" + +#: optionsframe.py:1151 +msgid "Spanish" +msgstr "Espagnol" + +#: optionsframe.py:1152 +msgid "German" +msgstr "Allemand" + +#: optionsframe.py:1155 +msgid "Download subtitle file by language" +msgstr "Télécharger les sous-titres suivant la langue" + +#: optionsframe.py:1156 +msgid "Download all available subtitles" +msgstr "Télécharger tous les sous-titres disponibles" + +#: optionsframe.py:1157 +msgid "Download automatic subtitle file (YOUTUBE ONLY)" +msgstr "Télécharger le fichier sous-titre automatique (YOUTUBE ONLY)" + +#: optionsframe.py:1158 +msgid "Embed subtitles in the video (only for mp4 videos)" +msgstr "Ajouter les sous-titres à la vidéo (mp4 uniquement)" + +#: optionsframe.py:1159 +msgid "Subtitles Language" +msgstr "Langue sous-titres" + +#: optionsframe.py:1253 +msgid "About" +msgstr "A propos" + +#: optionsframe.py:1254 +msgid "Open" +msgstr "Ouvrir" + +#: optionsframe.py:1255 +msgid "Reset Options" +msgstr "Reset options" + +#: optionsframe.py:1256 +msgid "Save Path" +msgstr "Enregistrer sous..." + +#: optionsframe.py:1257 +msgid "Settings File: {0}" +msgstr "" + +#: optionsframe.py:1258 +msgid "Choose Directory" +msgstr "Sélectionner le répertoire" + +#: optionsframe.py:1343 +msgid "Command line arguments (e.g. --help)" +msgstr "" + +#: optionsframe.py:1395 +msgid "Localization Language" +msgstr "Langue" + +#: optionsframe.py:1396 +msgid "In order for the changes to take effect please restart {0}" +msgstr "Pour que les changements prennent effet, veuillez redémarrer {0}" + +#: optionsframe.py:1443 +msgid "Log Viewer" +msgstr "Afficher les traces" + diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/logmanager.py youtube-dlg-0.3.7/youtube_dl_gui/logmanager.py --- youtube-dlg-0.3.5/youtube_dl_gui/logmanager.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/logmanager.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,91 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module responsible for handling the log stuff. """ + +from __future__ import unicode_literals + +import os.path +from time import strftime + +from .utils import ( + get_encoding, + check_path +) + + +class LogManager(object): + + """Simple log manager for youtube-dl. + + This class is mainly used to log the youtube-dl STDERR. + + Attributes: + LOG_FILENAME (string): Filename of the log file. + TIME_TEMPLATE (string): Custom template to log the time. + MAX_LOGSIZE (int): Maximum size(Bytes) of the log file. + + Args: + config_path (string): Absolute path where LogManager should + store the log file. + + add_time (boolean): If True LogManager will also log the time. + + """ + + LOG_FILENAME = "log" + TIME_TEMPLATE = "[{time}] {error_msg}" + MAX_LOGSIZE = 524288 + + def __init__(self, config_path, add_time=False): + self.config_path = config_path + self.add_time = add_time + self.log_file = os.path.join(config_path, self.LOG_FILENAME) + self._encoding = get_encoding() + self._init_log() + self._auto_clear_log() + + def log_size(self): + """Return log file size in Bytes. """ + if not os.path.exists(self.log_file): + return 0 + + return os.path.getsize(self.log_file) + + def clear(self): + """Clear log file. """ + self._write('', 'w') + + def log(self, data): + """Log data to the log file. """ + self._write(data + '\n', 'a') + + def _write(self, data, mode): + """Write data to the log file. + + That's the main method for writing to the log file. + + Args: + data (string): String to write on the log file. + mode (string): Can be any IO mode supported by python. + + """ + check_path(self.config_path) + + with open(self.log_file, mode) as log: + if mode == 'a' and self.add_time: + msg = self.TIME_TEMPLATE.format(time=strftime('%c'), error_msg=data) + else: + msg = data + + log.write(msg.encode(self._encoding, 'ignore')) + + def _init_log(self): + """Initialize the log file if not exist. """ + if not os.path.exists(self.log_file): + self._write('', 'w') + + def _auto_clear_log(self): + """Auto clear the log file. """ + if self.log_size() > self.MAX_LOGSIZE: + self.clear() diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/LogManager.py youtube-dlg-0.3.7/youtube_dl_gui/LogManager.py --- youtube-dlg-0.3.5/youtube_dl_gui/LogManager.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/LogManager.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -#! /usr/bin/env python - -import wx - -from time import strftime -from .Utils import ( - fix_path, - file_exist, - get_filesize -) - -LOG_FILENAME = 'log' -LOG_FILESIZE = 524288 # 524288B = 512kB - -class LogManager(): - - def __init__(self, path, add_time=False): - self.path = fix_path(path) + LOG_FILENAME - self.add_time = add_time - self.init_log() - self.auto_clear_log() - - def auto_clear_log(self): - if self.size() > LOG_FILESIZE: - self.clear() - - def init_log(self): - if not file_exist(self.path): - self.clear() - - def size(self): - return get_filesize(self.path) - - def clear(self): - with open(self.path, 'w') as fl: - fl.write('') - - def write(self, data): - with open(self.path, 'a') as fl: - if self.add_time: - t = '[%s] ' % strftime('%c') - fl.write(t) - fl.write(data) - fl.write('\n') - -class LogGUI(wx.Frame): - - title = 'Log Viewer' - - def __init__(self, path, parent=None, id=-1): - wx.Frame.__init__(self, parent, id, self.title, size=(650, 200)) - - panel = wx.Panel(self) - textArea = wx.TextCtrl(panel, -1, style = wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL) - sizer = wx.BoxSizer() - sizer.Add(textArea, 1, wx.EXPAND) - panel.SetSizerAndFit(sizer) - - textArea.LoadFile(path) - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/mainframe.py youtube-dlg-0.3.7/youtube_dl_gui/mainframe.py --- youtube-dlg-0.3.5/youtube_dl_gui/mainframe.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/mainframe.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,637 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module responsible for the main app window. """ + +from __future__ import unicode_literals + +import gettext + +import wx +from wx.lib.pubsub import setuparg1 +from wx.lib.pubsub import pub as Publisher + +from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin + +from .optionsframe import OptionsFrame +from .updatemanager import ( + UPDATE_PUB_TOPIC, + UpdateThread +) +from .downloadmanager import ( + MANAGER_PUB_TOPIC, + WORKER_PUB_TOPIC, + DownloadManager +) + +from .utils import ( + get_icon_file, + shutdown_sys, + get_time, + open_dir +) +from .info import ( + __appname__ +) + + +class MainFrame(wx.Frame): + + """Main window class. + + This class is responsible for creating the main app window + and binding the events. + + Attributes: + wxEVT_TEXT_PASTE (int): Event type code for the wx.EVT_TEXT_PASTE + + BUTTONS_SIZE (tuple): Buttons size (width, height). + BUTTONS_SPACE (tuple): Space between buttons (width, height). + SIZE_20 (int): Constant size number. + SIZE_10 (int): Constant size number. + SIZE_5 (int): Constant size number. + + Labels area (strings): Strings for the widgets labels. + + STATUSLIST_COLUMNS (tuple): Tuple of tuples that contains informations + about the ListCtrl columns. First item is the column name. Second + item is the column position. Third item is the column label. + Fourth item is the column default width. Last item is a boolean + flag if True the current column is resizable. + + Args: + opt_manager (optionsmanager.OptionsManager): Object responsible for + handling the settings. + + log_manager (logmanager.LogManager): Object responsible for handling + the log stuff. + + parent (wx.Window): Frame parent. + + """ + wxEVT_TEXT_PASTE = 'wxClipboardTextEvent' + + BUTTONS_SIZE = (-1, 30) + BUTTONS_SPACE = (80, -1) + SIZE_20 = 20 + SIZE_10 = 10 + SIZE_5 = 5 + + # Labels area + URLS_LABEL = _("URLs") + DOWNLOAD_LABEL = _("Download") + UPDATE_LABEL = _("Update") + OPTIONS_LABEL = _("Options") + ERROR_LABEL = _("Error") + STOP_LABEL = _("Stop") + INFO_LABEL = _("Info") + WELCOME_MSG = _("Welcome") + SUCC_REPORT_MSG = _("Successfully downloaded {0} url(s) in {1} " + "day(s) {2} hour(s) {3} minute(s) {4} second(s)") + DL_COMPLETED_MSG = _("Download completed") + URL_REPORT_MSG = _("Downloading {0} url(s)") + CLOSING_MSG = _("Stopping downloads") + CLOSED_MSG = _("Downloads stopped") + PROVIDE_URL_MSG = _("You need to provide at least one url") + DOWNLOAD_STARTED = _("Download started") + + UPDATING_MSG = _("Downloading latest youtube-dl. Please wait...") + UPDATE_ERR_MSG = _("Youtube-dl download failed [{0}]") + UPDATE_SUCC_MSG = _("Youtube-dl downloaded correctly") + + OPEN_DIR_ERR = _("Unable to open directory: '{dir}'. " + "The specified path does not exist") + SHUTDOWN_ERR = _("Error while shutting down. " + "Make sure you typed the correct password") + SHUTDOWN_MSG = _("Shutting down system") + + VIDEO_LABEL = _("Title") + SIZE_LABEL = _("Size") + PERCENT_LABEL = _("Percent") + ETA_LABEL = _("ETA") + SPEED_LABEL = _("Speed") + STATUS_LABEL = _("Status") + ################################# + + # (column_name, column_index, column_label, minimum_width, resizable) + STATUSLIST_COLUMNS = ( + ('filename', 0, VIDEO_LABEL, 150, True), + ('filesize', 1, SIZE_LABEL, 80, False), + ('percent', 2, PERCENT_LABEL, 65, False), + ('eta', 3, ETA_LABEL, 45, False), + ('speed', 4, SPEED_LABEL, 90, False), + ('status', 5, STATUS_LABEL, 160, False) + ) + + def __init__(self, opt_manager, log_manager, parent=None): + wx.Frame.__init__(self, parent, title=__appname__, size=opt_manager.options['main_win_size']) + self.opt_manager = opt_manager + self.log_manager = log_manager + self.download_manager = None + self.update_thread = None + self.app_icon = get_icon_file() + + # Create the app icon + if self.app_icon is not None: + self.app_icon = wx.Icon(self.app_icon, wx.BITMAP_TYPE_PNG) + self.SetIcon(self.app_icon) + + # Create options frame + self._options_frame = OptionsFrame(self) + + # Create components + self._panel = wx.Panel(self) + + self._url_text = self._create_statictext(self.URLS_LABEL) + self._url_list = self._create_textctrl(wx.TE_MULTILINE | wx.TE_DONTWRAP, self._on_urllist_edit) + + self._download_btn = self._create_button(self.DOWNLOAD_LABEL, self._on_download) + self._update_btn = self._create_button(self.UPDATE_LABEL, self._on_update) + self._options_btn = self._create_button(self.OPTIONS_LABEL, self._on_options) + + self._status_list = ListCtrl(self.STATUSLIST_COLUMNS, + parent=self._panel, + style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) + + self._status_bar = self._create_statictext(self.WELCOME_MSG) + + self._set_buttons_width() + + # Bind extra events + self.Bind(wx.EVT_CLOSE, self._on_close) + + self._set_sizers() + + # Set threads wxCallAfter handlers using subscribe + self._set_publisher(self._update_handler, UPDATE_PUB_TOPIC) + self._set_publisher(self._status_list_handler, WORKER_PUB_TOPIC) + self._set_publisher(self._download_manager_handler, MANAGER_PUB_TOPIC) + + def _set_publisher(self, handler, topic): + """Sets a handler for the given topic. + + Args: + handler (function): Can be any function with one parameter + the message that the caller sends. + + topic (string): Can be any string that identifies the caller. + You can bind multiple handlers on the same topic or + multiple topics on the same handler. + + """ + Publisher.subscribe(handler, topic) + + def _set_buttons_width(self): + """Re-adjust buttons size on runtime so that all buttons + look the same. """ + widths = [ + self._download_btn.GetSize()[0], + self._update_btn.GetSize()[0], + self._options_btn.GetSize()[0], + ] + + max_width = -1 + + for item in widths: + if item > max_width: + max_width = item + + self._download_btn.SetMinSize((max_width, self.BUTTONS_SIZE[1])) + self._update_btn.SetMinSize((max_width, self.BUTTONS_SIZE[1])) + self._options_btn.SetMinSize((max_width, self.BUTTONS_SIZE[1])) + + self._panel.Layout() + + def _create_statictext(self, label): + statictext = wx.StaticText(self._panel, label=label) + return statictext + + def _create_textctrl(self, style=None, event_handler=None): + if style is None: + textctrl = wx.TextCtrl(self._panel) + else: + textctrl = wx.TextCtrl(self._panel, style=style) + + if event_handler is not None: + textctrl.Bind(wx.EVT_TEXT_PASTE, event_handler) + textctrl.Bind(wx.EVT_MIDDLE_DOWN, event_handler) + + return textctrl + + def _create_button(self, label, event_handler=None): + btn = wx.Button(self._panel, label=label, size=self.BUTTONS_SIZE) + + if event_handler is not None: + btn.Bind(wx.EVT_BUTTON, event_handler) + + return btn + + def _create_popup(self, text, title, style): + wx.MessageBox(text, title, style) + + def _set_sizers(self): + """Sets the sizers of the main window. """ + hor_sizer = wx.BoxSizer(wx.HORIZONTAL) + vertical_sizer = wx.BoxSizer(wx.VERTICAL) + + vertical_sizer.AddSpacer(self.SIZE_10) + + vertical_sizer.Add(self._url_text) + vertical_sizer.Add(self._url_list, 1, wx.EXPAND) + + vertical_sizer.AddSpacer(self.SIZE_10) + + buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) + buttons_sizer.Add(self._download_btn) + buttons_sizer.Add(self.BUTTONS_SPACE, 1) + buttons_sizer.Add(self._update_btn) + buttons_sizer.Add(self.BUTTONS_SPACE, 1) + buttons_sizer.Add(self._options_btn) + vertical_sizer.Add(buttons_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) + + vertical_sizer.AddSpacer(self.SIZE_10) + vertical_sizer.Add(self._status_list, 2, wx.EXPAND) + + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self._status_bar) + vertical_sizer.AddSpacer(self.SIZE_5) + + hor_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, border=self.SIZE_20) + + self._panel.SetSizer(hor_sizer) + + def _update_youtubedl(self): + """Update youtube-dl binary to the latest version. """ + self._update_btn.Disable() + self._download_btn.Disable() + self.update_thread = UpdateThread(self.opt_manager.options['youtubedl_path']) + + def _status_bar_write(self, msg): + """Display msg in the status bar. """ + self._status_bar.SetLabel(msg) + + def _reset_widgets(self): + """Resets GUI widgets after update or download process. """ + self._download_btn.SetLabel(self.DOWNLOAD_LABEL) + self._download_btn.Enable() + self._update_btn.Enable() + + def _print_stats(self): + """Display download stats in the status bar. """ + suc_downloads = self.download_manager.successful + dtime = get_time(self.download_manager.time_it_took) + + msg = self.SUCC_REPORT_MSG.format(suc_downloads, + dtime['days'], + dtime['hours'], + dtime['minutes'], + dtime['seconds']) + + self._status_bar_write(msg) + + def _after_download(self): + """Run tasks after download process has been completed. + + Note: + Here you can add any tasks you want to run after the + download process has been completed. + + """ + if self.opt_manager.options['shutdown']: + self.opt_manager.save_to_file() + success = shutdown_sys(self.opt_manager.options['sudo_password']) + + if success: + self._status_bar_write(self.SHUTDOWN_MSG) + else: + self._status_bar_write(self.SHUTDOWN_ERR) + else: + self._create_popup(self.DL_COMPLETED_MSG, self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION) + if self.opt_manager.options['open_dl_dir']: + success = open_dir(self.opt_manager.options['save_path']) + + if not success: + self._status_bar_write(self.OPEN_DIR_ERR.format(dir=self.opt_manager.options['save_path'])) + + def _status_list_handler(self, msg): + """downloadmanager.Worker thread handler. + + Handles messages from the Worker thread. + + Args: + See downloadmanager.Worker _talk_to_gui() method. + + """ + data = msg.data + + self._status_list.write(data) + + # Report number of urls been downloaded + msg = self.URL_REPORT_MSG.format(self.download_manager.active()) + self._status_bar_write(msg) + + def _download_manager_handler(self, msg): + """downloadmanager.DownloadManager thread handler. + + Handles messages from the DownloadManager thread. + + Args: + See downloadmanager.DownloadManager _talk_to_gui() method. + + """ + data = msg.data + + if data == 'finished': + self._print_stats() + self._reset_widgets() + self.download_manager = None + self._after_download() + elif data == 'closed': + self._status_bar_write(self.CLOSED_MSG) + self._reset_widgets() + self.download_manager = None + else: + self._status_bar_write(self.CLOSING_MSG) + + def _update_handler(self, msg): + """updatemanager.UpdateThread thread handler. + + Handles messages from the UpdateThread thread. + + Args: + See updatemanager.UpdateThread _talk_to_gui() method. + + """ + data = msg.data + + if data[0] == 'download': + self._status_bar_write(self.UPDATING_MSG) + elif data[0] == 'error': + self._status_bar_write(self.UPDATE_ERR_MSG.format(data[1])) + elif data[0] == 'correct': + self._status_bar_write(self.UPDATE_SUCC_MSG) + else: + self._reset_widgets() + self.update_thread = None + + def _get_urls(self): + """Returns urls list. """ + return self._url_list.GetValue().split('\n') + + def _start_download(self): + """Handles pre-download tasks & starts the download process. """ + self._status_list.clear() + self._status_list.load_urls(self._get_urls()) + + if self._status_list.is_empty(): + self._create_popup(self.PROVIDE_URL_MSG, + self.ERROR_LABEL, + wx.OK | wx.ICON_EXCLAMATION) + else: + self.download_manager = DownloadManager(self._status_list.get_items(), + self.opt_manager, + self.log_manager) + + self._status_bar_write(self.DOWNLOAD_STARTED) + self._download_btn.SetLabel(self.STOP_LABEL) + self._update_btn.Disable() + + def _paste_from_clipboard(self): + """Paste the content of the clipboard to the self._url_list widget. + It also adds a new line at the end of the data if not exist. + + """ + if not wx.TheClipboard.IsOpened(): + + if wx.TheClipboard.Open(): + if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): + + data = wx.TextDataObject() + wx.TheClipboard.GetData(data) + + data = data.GetText() + + if data[-1] != '\n': + data += '\n' + + self._url_list.WriteText(data) + + wx.TheClipboard.Close() + + def _on_urllist_edit(self, event): + """Event handler of the self._url_list widget. + + This method is triggered when the users pastes text into + the URLs list either by using CTRL+V or by using the middle + click of the mouse. + + """ + if event.ClassName == self.wxEVT_TEXT_PASTE: + self._paste_from_clipboard() + else: + wx.TheClipboard.UsePrimarySelection(True) + self._paste_from_clipboard() + wx.TheClipboard.UsePrimarySelection(False) + + # Dynamically add urls after download process has started + if self.download_manager is not None: + self._status_list.load_urls(self._get_urls(), self.download_manager.add_url) + + def _on_download(self, event): + """Event handler of the self._download_btn widget. + + This method is used when the download-stop button is pressed to + start or stop the download process. + + """ + if self.download_manager is None: + self._start_download() + else: + self.download_manager.stop_downloads() + + def _on_update(self, event): + """Event handler of the self._update_btn widget. + + This method is used when the update button is pressed to start + the update process. + + Note: + Currently there is not way to stop the update process. + + """ + self._update_youtubedl() + + def _on_options(self, event): + """Event handler of the self._options_btn widget. + + This method is used when the options button is pressed to show + the optios window. + + """ + self._options_frame.load_all_options() + self._options_frame.Show() + + def _on_close(self, event): + """Event handler for the wx.EVT_CLOSE event. + + This method is used when the user tries to close the program + to save the options and make sure that the download & update + processes are not running. + + """ + if self.download_manager is not None: + self.download_manager.stop_downloads() + self.download_manager.join() + + if self.update_thread is not None: + self.update_thread.join() + + # Store main-options frame size + self.opt_manager.options['main_win_size'] = self.GetSize() + self.opt_manager.options['opts_win_size'] = self._options_frame.GetSize() + + self._options_frame.save_all_options() + self.opt_manager.save_to_file() + self.Destroy() + + +class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): + + """Custom ListCtrl widget. + + Args: + columns (tuple): See MainFrame class STATUSLIST_COLUMNS attribute. + + """ + + def __init__(self, columns, *args, **kwargs): + wx.ListCtrl.__init__(self, *args, **kwargs) + ListCtrlAutoWidthMixin.__init__(self) + self.columns = columns + self._list_index = 0 + self._url_list = set() + self._set_columns() + + def write(self, data): + """Write data on ListCtrl row-column. + + Args: + data (dictionary): Dictionary that contains the data to be + written on the ListCtrl. In order for this method to + write the given data there must be an 'index' key that + identifies the current row and a corresponding key for + each item of the self.columns. + + Note: + Income data must contain all the columns keys else a KeyError will + be raised. Also there must be an 'index' key that identifies the + row to write the data. For a valid data dictionary see + downloaders.YoutubeDLDownloader self._data. + + """ + for column in self.columns: + column_key = column[0] + self._write_data(data[column_key], data['index'], column[1]) + + def load_urls(self, url_list, func=None): + """Load URLs from the url_list on the ListCtrl widget. + + Args: + url_list (list): List of strings that contains the URLs to add. + func (function): Callback function. It's used to add the URLs + on the download_manager. + + """ + for url in url_list: + url = url.replace(' ', '') + + if url and not self.has_url(url): + self.add_url(url) + + if func is not None: + # Custom hack to add url into download_manager + item = self._get_item(self._list_index - 1) + func(item) + + def has_url(self, url): + """Returns True if the url is aleady in the ListCtrl else False. + + Args: + url (string): URL string. + + """ + return url in self._url_list + + def add_url(self, url): + """Adds the given url in the ListCtrl. + + Args: + url (string): URL string. + + """ + self.InsertStringItem(self._list_index, url) + self._url_list.add(url) + self._list_index += 1 + + def clear(self): + """Clear the ListCtrl widget & reset self._list_index and + self._url_list. """ + self.DeleteAllItems() + self._list_index = 0 + self._url_list = set() + + def is_empty(self): + """Returns True if the list is empty else False. """ + return self._list_index == 0 + + def get_items(self): + """Returns a list of items inside the ListCtrl. + + Returns: + List of dictionaries that contains the 'url' and the + 'index'(row) for each item of the ListCtrl. + + """ + items = [] + + for row in xrange(self._list_index): + item = self._get_item(row) + items.append(item) + + return items + + def _write_data(self, data, row, column): + """Write data on row-column. """ + if isinstance(data, basestring): + self.SetStringItem(row, column, data) + + def _get_item(self, index): + """Returns the corresponding ListCtrl item for the given index. + + Args: + index (int): Index that identifies the row of the item. + Index must be smaller than the self._list_index. + + Returns: + Dictionary that contains the URL string of the row and the + row number(index). + + """ + item = self.GetItem(itemId=index, col=0) + data = dict(url=item.GetText(), index=index) + return data + + def _set_columns(self): + """Initializes ListCtrl columns. + See MainFrame STATUSLIST_COLUMNS attribute for more info. """ + for column in self.columns: + self.InsertColumn(column[1], column[2], width=wx.LIST_AUTOSIZE_USEHEADER) + + # If the column width obtained from wxLIST_AUTOSIZE_USEHEADER + # is smaller than the minimum allowed column width + # then set the column width to the minumum allowed size + if self.GetColumnWidth(column[1]) < column[3]: + self.SetColumnWidth(column[1], column[3]) + + if column[4]: + self.setResizeColumn(column[1]) diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/__main__.py youtube-dlg-0.3.7/youtube_dl_gui/__main__.py --- youtube-dlg-0.3.5/youtube_dl_gui/__main__.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/__main__.py 2015-04-01 15:26:52.000000000 +0000 @@ -1,15 +1,39 @@ -#! /usr/bin/env python +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg __main__ file. + +__main__ file is a python 'executable' file which calls the youtubedlg +main() function in order to start the app. It can be used to start +the app from the package directory OR it can be used to start the app +from a different directory after you have installed the youtube_dl_gui +package. + +Example: + In order to run the app from the package directory. + + $ cd + $ python __main__.py + + In order to run the app from /usr/local/bin etc.. AFTER + you have installed the package using setup.py. + + $ youtube-dl-gui + +""" + +from __future__ import unicode_literals import sys if __package__ is None and not hasattr(sys, "frozen"): # direct call of __main__.py import os.path - path = os.path.realpath(os.path.abspath(__file__)) - sys.path.append(os.path.dirname(os.path.dirname(path))) + PATH = os.path.realpath(os.path.abspath(__file__)) + sys.path.append(os.path.dirname(os.path.dirname(PATH))) import youtube_dl_gui + if __name__ == '__main__': youtube_dl_gui.main() - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/optionsframe.py youtube-dlg-0.3.7/youtube_dl_gui/optionsframe.py --- youtube-dlg-0.3.5/youtube_dl_gui/optionsframe.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/optionsframe.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,1463 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module responsible for the options window. """ + +from __future__ import unicode_literals + +import os +import gettext + +import wx + +from .version import __version__ + +from .info import ( + __descriptionfull__, + __licensefull__, + __projecturl__, + __appname__, + __author__ +) + +from .utils import TwoWayOrderedDict as twodict + + +class OptionsFrame(wx.Frame): + + """Youtubedlg options frame class. + + Attributes: + FRAME_TITLE (string): Options window title. + + *_TAB (string): Constant string with the name of each tab. + + Args: + parent (mainframe.MainFrame): Parent class. + + """ + + FRAME_TITLE = _("Options") + + GENERAL_TAB = _("General") + VIDEO_TAB = _("Video") + AUDIO_TAB = _("Audio") + PLAYLIST_TAB = _("Playlist") + OUTPUT_TAB = _("Output") + SUBTITLES_TAB = _("Subtitles") + FILESYS_TAB = _("Filesystem") + SHUTDOWN_TAB = _("Shutdown") + AUTH_TAB = _("Authentication") + CONNECTION_TAB = _("Connection") + LOG_TAB = _("Log") + CMD_TAB = _("Commands") + LOCALIZATION_TAB = _("Localization") + + def __init__(self, parent): + wx.Frame.__init__(self, parent, title=self.FRAME_TITLE, size=parent.opt_manager.options['opts_win_size']) + self.opt_manager = parent.opt_manager + self.log_manager = parent.log_manager + self.app_icon = parent.app_icon + + if self.app_icon is not None: + self.SetIcon(self.app_icon) + + # Create GUI + panel = wx.Panel(self) + notebook = wx.Notebook(panel) + + # Create Tabs + tab_args = (self, notebook) + + self.tabs = ( + (GeneralTab(*tab_args), self.GENERAL_TAB), + (VideoTab(*tab_args), self.VIDEO_TAB), + (AudioTab(*tab_args), self.AUDIO_TAB), + (PlaylistTab(*tab_args), self.PLAYLIST_TAB), + (OutputTab(*tab_args), self.OUTPUT_TAB), + (SubtitlesTab(*tab_args), self.SUBTITLES_TAB), + (FilesystemTab(*tab_args), self.FILESYS_TAB), + (ShutdownTab(*tab_args), self.SHUTDOWN_TAB), + (AuthenticationTab(*tab_args), self.AUTH_TAB), + (ConnectionTab(*tab_args), self.CONNECTION_TAB), + (LogTab(*tab_args), self.LOG_TAB), + (CMDTab(*tab_args), self.CMD_TAB), + (LocalizationTab(*tab_args), self.LOCALIZATION_TAB) + ) + + # Add tabs on notebook + for tab, label in self.tabs: + notebook.AddPage(tab, label) + + sizer = wx.BoxSizer() + sizer.Add(notebook, 1, wx.EXPAND) + panel.SetSizer(sizer) + + self.Bind(wx.EVT_CLOSE, self._on_close) + + self.load_all_options() + + def _on_close(self, event): + """Event handler for wx.EVT_CLOSE event. + + This method is used to save the options and hide the options window. + + """ + self.save_all_options() + self.Hide() + + def reset(self): + """Resets the default options. """ + self.opt_manager.load_default() + self.load_all_options() + + def load_all_options(self): + """Load options from optionsmanager.OptionsManager + on each tab. """ + for tab, _ in self.tabs: + tab.load_options() + + def save_all_options(self): + """Save options back to optionsmanager.OptionsManager + from each tab. """ + for tab, _ in self.tabs: + tab.save_options() + + +class TabPanel(wx.Panel): + + """Main tab class from which each tab inherits. + + Contains methods to create widgets, load options etc.. + + Attributes: + Each of this attributes is the Default one. In order to use a + different size you must overwrite the corresponding attribute + on the child object. + + CHECKBOX_SIZE (tuple): wx.Checkbox size (width, height). On Windows + we change the height value in order to look the same with Linux. + + BUTTONS_SIZE (tuple): wx.Button size (width, height) + + TEXTCTRL_SIZE (tuple): wx.TextCtrl size (width, height) + + SPINCTRL_SIZE (tuple): wx.SpinCtrl size (width, height) + + SIZE_* (int): Constant size number. + + Args: + parent (OptionsFrame): The parent of all tabs. + notebook (wx.Notebook): The container for each tab. + + """ + + CHECKBOX_SIZE = (-1, -1) + if os.name == 'nt': + CHECKBOX_SIZE = (-1, 25) + + BUTTONS_SIZE = (-1, -1) + TEXTCTRL_SIZE = (-1, -1) + SPINCTRL_SIZE = (70, 20) + + SIZE_80 = 80 + SIZE_50 = 50 + SIZE_40 = 40 + SIZE_30 = 30 + SIZE_20 = 20 + SIZE_15 = 15 + SIZE_10 = 10 + SIZE_5 = 5 + + def __init__(self, parent, notebook): + wx.Panel.__init__(self, notebook) + self.opt_manager = parent.opt_manager + self.log_manager = parent.log_manager + self.app_icon = parent.app_icon + + self.reset_handler = parent.reset + + def create_button(self, label, event_handler=None): + """Creates and returns an wx.Button. + + Args: + label (string): wx.Button label. + + event_handler (function): Can be any function with one parameter + the event item. + + Note: + In order to change the button size you need to overwrite the + BUTTONS_SIZE attribute on the child class. + + """ + button = wx.Button(self, label=label, size=self.BUTTONS_SIZE) + + if event_handler is not None: + button.Bind(wx.EVT_BUTTON, event_handler) + + return button + + def create_checkbox(self, label, event_handler=None): + """Creates and returns an wx.CheckBox. + + Args: + label (string): wx.CheckBox label. + + event_handler (function): Can be any function with one parameter + the event item. + + Note: + In order to change the checkbox size you need to overwrite the + CHECKBOX_SIZE attribute on the child class. + + """ + checkbox = wx.CheckBox(self, label=label, size=self.CHECKBOX_SIZE) + + if event_handler is not None: + checkbox.Bind(wx.EVT_CHECKBOX, event_handler) + + return checkbox + + def create_textctrl(self, style=None): + """Creates and returns an wx.TextCtrl. + + Args: + style (long): Can be any valid wx.TextCtrl style. + + Note: + In order to change the textctrl size you need to overwrite the + TEXTCTRL_SIZE attribute on the child class. + + """ + if style is None: + textctrl = wx.TextCtrl(self, size=self.TEXTCTRL_SIZE) + else: + textctrl = wx.TextCtrl(self, size=self.TEXTCTRL_SIZE, style=style) + + return textctrl + + def create_combobox(self, choices, size=(-1, -1), event_handler=None): + """Creates and returns an wx.ComboBox. + + Args: + choices (list): List of strings that contains the choices for the + wx.ComboBox widget. + + size (tuple): wx.ComboBox size (width, height). + + event_handler (function): Can be any function with one parameter + the event item. + + """ + combobox = wx.ComboBox(self, choices=choices, size=size) + + if event_handler is not None: + combobox.Bind(wx.EVT_COMBOBOX, event_handler) + + return combobox + + def create_dirdialog(self, label): + """Creates and returns an wx.DirDialog. + + Args: + label (string): wx.DirDialog widget title. + + """ + dlg = wx.DirDialog(self, label) + return dlg + + def create_radiobutton(self, label, event_handler=None, style=None): + """Creates and returns an wx.RadioButton. + + Args: + label (string): wx.RadioButton label. + + event_handler (function): Can be any function with one parameter + the event item. + + style (long): Can be any valid wx.RadioButton style. + + """ + if style is None: + radiobutton = wx.RadioButton(self, label=label) + else: + radiobutton = wx.RadioButton(self, label=label, style=style) + + if event_handler is not None: + radiobutton.Bind(wx.EVT_RADIOBUTTON, event_handler) + + return radiobutton + + def create_spinctrl(self, spin_range=(0, 999)): + """Creates and returns an wx.SpinCtrl. + + Args: + spin_range (tuple): wx.SpinCtrl range (min, max). + + Note: + In order to change the size of the spinctrl widget you need + to overwrite the SPINCTRL_SIZE attribute on the child class. + + """ + spinctrl = wx.SpinCtrl(self, size=self.SPINCTRL_SIZE) + spinctrl.SetRange(*spin_range) + + return spinctrl + + def create_statictext(self, label): + """Creates and returns an wx.StaticText. + + Args: + label (string): wx.StaticText label. + + """ + statictext = wx.StaticText(self, label=label) + return statictext + + def create_popup(self, text, title, style): + """Creates an wx.MessageBox. + + Args: + text (string): wx.MessageBox message. + + title (string): wx.MessageBox title. + + style (long): Can be any valid wx.MessageBox style. + + """ + wx.MessageBox(text, title, style) + + def _set_sizer(self): + """Sets the sizer for the current panel. + + You need to overwrite this method on the child class in order + to set the panels sizers. + + """ + pass + + def _disable_items(self): + """Disables widgets. + + If you want any widgets to be disabled by default you specify + them in this method. + + Example: + mybutton.Disable() + + """ + pass + + def load_options(self): + """Load options from the optionsmanager.OptionsManager object + to the current tab. """ + pass + + def save_options(self): + """Save options of the current tab back to + optionsmanager.OptionsManager object. """ + pass + + +class LogTab(TabPanel): + + """Options frame log tab. + + Attributes: + Constant strings for the widgets. + + """ + + ENABLE_LABEL = _("Enable Log") + WRITE_LABEL = _("Write Time") + CLEAR_LABEL = _("Clear Log") + VIEW_LABEL = _("View Log") + PATH_LABEL = _("Path: {0}") + LOGSIZE_LABEL = _("Log Size: {0} Bytes") + RESTART_LABEL = _("Restart") + RESTART_MSG = _("Please restart {0}") + + def __init__(self, *args, **kwargs): + super(LogTab, self).__init__(*args, **kwargs) + + self.enable_checkbox = self.create_checkbox(self.ENABLE_LABEL, self._on_enable) + self.time_checkbox = self.create_checkbox(self.WRITE_LABEL, self._on_time) + self.clear_button = self.create_button(self.CLEAR_LABEL, self._on_clear) + self.view_button = self.create_button(self.VIEW_LABEL, self._on_view) + + self.log_path = self.create_statictext(self.PATH_LABEL.format(self._get_logpath())) + self.log_size = self.create_statictext(self.LOGSIZE_LABEL.format(self._get_logsize())) + + self._set_sizer() + self._disable_items() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_20) + main_sizer.Add(self.enable_checkbox, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.time_checkbox, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_15) + + buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) + buttons_sizer.Add(self.clear_button) + buttons_sizer.AddSpacer(self.SIZE_20) + buttons_sizer.Add(self.view_button) + + main_sizer.Add(buttons_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) + + main_sizer.AddSpacer(self.SIZE_20) + main_sizer.Add(self.log_path, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_10) + main_sizer.Add(self.log_size, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def _disable_items(self): + if self.log_manager is None: + self.time_checkbox.Disable() + self.clear_button.Disable() + self.view_button.Disable() + self.log_path.Hide() + self.log_size.Hide() + + def _get_logpath(self): + """Returns the path to the log file. """ + if self.log_manager is None: + return '' + return self.log_manager.log_file + + def _get_logsize(self): + """Returns the size(Bytes) of the log file. """ + if self.log_manager is None: + return 0 + return self.log_manager.log_size() + + def _set_logsize(self): + """Updates the self.log_size widget with the current log file size .""" + self.log_size.SetLabel(self.LOGSIZE_LABEL.format(self._get_logsize())) + + def _on_time(self, event): + """Event handler for self.time_checkbox. """ + self.log_manager.add_time = self.time_checkbox.GetValue() + + def _on_enable(self, event): + """Event handler for self.enable_checkbox. """ + self.create_popup(self.RESTART_MSG.format(__appname__), + self.RESTART_LABEL, + wx.OK | wx.ICON_INFORMATION) + + def _on_clear(self, event): + """Event handler for self.clear_button. """ + self.log_manager.clear() + self.log_size.SetLabel(self.LOGSIZE_LABEL.format(self._get_logsize())) + + def _on_view(self, event): + """Event handler for self.view_button. """ + logger_gui = LogGUI(self) + logger_gui.Show() + logger_gui.load(self.log_manager.log_file) + + def load_options(self): + self.enable_checkbox.SetValue(self.opt_manager.options['enable_log']) + self.time_checkbox.SetValue(self.opt_manager.options['log_time']) + self._set_logsize() + + def save_options(self): + self.opt_manager.options['enable_log'] = self.enable_checkbox.GetValue() + self.opt_manager.options['log_time'] = self.time_checkbox.GetValue() + + +class ShutdownTab(TabPanel): + + """Options frame shutdown tab. + + Attributes: + TEXTCTRL_SIZE (tuple): Overwrites the TEXTCTRL_SIZE attribute of + the TabPanel class. + + *_LABEL (string): Constant string label for the widgets. + + """ + + TEXTCTRL_SIZE = (250, 25) + + SHUTDOWN_LABEL = _("Shutdown when finished") + SUDO_LABEL = _("SUDO password") + + def __init__(self, *args, **kwargs): + super(ShutdownTab, self).__init__(*args, **kwargs) + + self.shutdown_checkbox = self.create_checkbox(self.SHUTDOWN_LABEL, self._on_shutdown_check) + self.sudo_text = self.create_statictext(self.SUDO_LABEL) + self.sudo_box = self.create_textctrl(wx.TE_PASSWORD) + + self._set_sizer() + self._disable_items() + + def _disable_items(self): + if os.name == 'nt': + self.sudo_text.Hide() + self.sudo_box.Hide() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_40) + main_sizer.Add(self.shutdown_checkbox, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_20) + main_sizer.Add(self.sudo_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.sudo_box, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def _on_shutdown_check(self, event): + """Event handler for self.shutdown_checkbox. """ + self.sudo_box.Enable(self.shutdown_checkbox.GetValue()) + + def load_options(self): + self.shutdown_checkbox.SetValue(self.opt_manager.options['shutdown']) + self.sudo_box.SetValue(self.opt_manager.options['sudo_password']) + self._on_shutdown_check(None) + + def save_options(self): + self.opt_manager.options['shutdown'] = self.shutdown_checkbox.GetValue() + self.opt_manager.options['sudo_password'] = self.sudo_box.GetValue() + + +class PlaylistTab(TabPanel): + + """Options frame playlist tab. + + Attributes: + *_LABEL (string): Constant string label for the widgets. + + """ + + START_LABEL = _("Playlist Start") + STOP_LABEL = _("Playlist Stop") + MAX_LABEL = _("Max Downloads") + + def __init__(self, *args, **kwargs): + super(PlaylistTab, self).__init__(*args, **kwargs) + + self.start_spinctrl = self.create_spinctrl((1, 999)) + self.stop_spinctrl = self.create_spinctrl() + self.max_spinctrl = self.create_spinctrl() + + self.start_text = self.create_statictext(self.START_LABEL) + self.stop_text = self.create_statictext(self.STOP_LABEL) + self.max_text = self.create_statictext(self.MAX_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_20) + main_sizer.Add(self.start_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.start_spinctrl, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_15) + main_sizer.Add(self.stop_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.stop_spinctrl, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_15) + main_sizer.Add(self.max_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.max_spinctrl, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def load_options(self): + self.start_spinctrl.SetValue(self.opt_manager.options['playlist_start']) + self.stop_spinctrl.SetValue(self.opt_manager.options['playlist_end']) + self.max_spinctrl.SetValue(self.opt_manager.options['max_downloads']) + + def save_options(self): + self.opt_manager.options['playlist_start'] = self.start_spinctrl.GetValue() + self.opt_manager.options['playlist_end'] = self.stop_spinctrl.GetValue() + self.opt_manager.options['max_downloads'] = self.max_spinctrl.GetValue() + + +class ConnectionTab(TabPanel): + + """Options frame connection tab. + + Attributes: + SPINCTRL_SIZE (tuple): Overwrites the SPINCTRL_SIZE attribute of + the TabPanel class. + + *_LABEL (string): Constant string label for widgets. + + """ + + SPINCTRL_SIZE = (60, -1) + + RETRIES_LABEL = _("Retries") + USERAGENT_LABEL = _("User Agent") + REF_LABEL = _("Referer") + PROXY_LABEL = _("Proxy") + + def __init__(self, *args, **kwargs): + super(ConnectionTab, self).__init__(*args, **kwargs) + + self.retries_spinctrl = self.create_spinctrl((1, 999)) + self.useragent_box = self.create_textctrl() + self.referer_box = self.create_textctrl() + self.proxy_box = self.create_textctrl() + + self.retries_text = self.create_statictext(self.RETRIES_LABEL) + self.useragent_text = self.create_statictext(self.USERAGENT_LABEL) + self.referer_text = self.create_statictext(self.REF_LABEL) + self.proxy_text = self.create_statictext(self.PROXY_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.HORIZONTAL) + vertical_sizer = wx.BoxSizer(wx.VERTICAL) + + vertical_sizer.AddSpacer(self.SIZE_10) + + retries_sizer = wx.BoxSizer(wx.HORIZONTAL) + retries_sizer.Add(self.retries_text) + retries_sizer.AddSpacer(self.SIZE_5) + retries_sizer.Add(self.retries_spinctrl) + vertical_sizer.Add(retries_sizer) + + vertical_sizer.AddSpacer(self.SIZE_10) + vertical_sizer.Add(self.useragent_text) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.useragent_box, flag=wx.EXPAND) + vertical_sizer.AddSpacer(self.SIZE_10) + vertical_sizer.Add(self.referer_text) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.referer_box, flag=wx.EXPAND) + vertical_sizer.AddSpacer(self.SIZE_10) + vertical_sizer.Add(self.proxy_text) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.proxy_box, flag=wx.EXPAND) + + main_sizer.AddSpacer(self.SIZE_10) + main_sizer.Add(vertical_sizer, 1, flag=wx.RIGHT, border=self.SIZE_40) + + self.SetSizer(main_sizer) + + def load_options(self): + self.proxy_box.SetValue(self.opt_manager.options['proxy']) + self.referer_box.SetValue(self.opt_manager.options['referer']) + self.retries_spinctrl.SetValue(self.opt_manager.options['retries']) + self.useragent_box.SetValue(self.opt_manager.options['user_agent']) + + def save_options(self): + self.opt_manager.options['proxy'] = self.proxy_box.GetValue() + self.opt_manager.options['referer'] = self.referer_box.GetValue() + self.opt_manager.options['retries'] = self.retries_spinctrl.GetValue() + self.opt_manager.options['user_agent'] = self.useragent_box.GetValue() + + +class AuthenticationTab(TabPanel): + + """Options frame authentication tab. + + Attributes: + TEXTCTRL_SIZE (tuple): Overwrites the TEXTCTRL_SIZE attribute of the + TabPanel class. + + *_LABEL (string): Constant string label for the widgets. + + """ + + TEXTCTRL_SIZE = (250, 25) + + USERNAME_LABEL = _("Username") + PASSWORD_LABEL = _("Password") + VIDEOPASS_LABEL = _("Video Password (vimeo, smotri)") + + def __init__(self, *args, **kwargs): + super(AuthenticationTab, self).__init__(*args, **kwargs) + + self.username_box = self.create_textctrl() + self.password_box = self.create_textctrl(wx.TE_PASSWORD) + self.videopass_box = self.create_textctrl(wx.TE_PASSWORD) + + self.username_text = self.create_statictext(self.USERNAME_LABEL) + self.password_text = self.create_statictext(self.PASSWORD_LABEL) + self.videopass_text = self.create_statictext(self.VIDEOPASS_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_15) + main_sizer.Add(self.username_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.username_box, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_15) + main_sizer.Add(self.password_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.password_box, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_15) + main_sizer.Add(self.videopass_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.videopass_box, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def load_options(self): + self.username_box.SetValue(self.opt_manager.options['username']) + self.password_box.SetValue(self.opt_manager.options['password']) + self.videopass_box.SetValue(self.opt_manager.options['video_password']) + + def save_options(self): + self.opt_manager.options['username'] = self.username_box.GetValue() + self.opt_manager.options['password'] = self.password_box.GetValue() + self.opt_manager.options['video_password'] = self.videopass_box.GetValue() + + +class AudioTab(TabPanel): + + """Options frame audio tab. + + Attributes: + AUDIO_QUALITY (TwoWayOrderedDict): Contains audio qualities. + + AUDIO_FORMATS (list): Contains audio formats. + See optionsmanager.OptionsManager 'audio_format' option + for available values. + + *_LABEL (string): Constant string label for the widgets. + + """ + AUDIO_QUALITY = twodict([("0", _("high")), ("5", _("mid")), ("9", _("low"))]) + AUDIO_FORMATS = ["mp3", "wav", "aac", "m4a", "vorbis"] + + TO_AUDIO_LABEL = _("Convert to Audio") + KEEP_VIDEO_LABEL = _("Keep Video") + AUDIO_FORMAT_LABEL = _("Audio Format") + AUDIO_QUALITY_LABEL = _("Audio Quality") + + def __init__(self, *args, **kwargs): + super(AudioTab, self).__init__(*args, **kwargs) + + self.to_audio_checkbox = self.create_checkbox(self.TO_AUDIO_LABEL, self._on_audio_check) + self.keep_video_checkbox = self.create_checkbox(self.KEEP_VIDEO_LABEL) + self.audioformat_combo = self.create_combobox(self.AUDIO_FORMATS, (160, 30)) + self.audioquality_combo = self.create_combobox(self.AUDIO_QUALITY.values(), (100, 25)) + + self.audioformat_text = self.create_statictext(self.AUDIO_FORMAT_LABEL) + self.audioquality_text = self.create_statictext(self.AUDIO_QUALITY_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_15) + main_sizer.Add(self.to_audio_checkbox, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.keep_video_checkbox, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_10) + main_sizer.Add(self.audioformat_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.audioformat_combo, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_10) + main_sizer.Add(self.audioquality_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.audioquality_combo, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def _on_audio_check(self, event): + """Event handler for self.to_audio_checkbox. """ + self.audioformat_combo.Enable(self.to_audio_checkbox.GetValue()) + self.audioquality_combo.Enable(self.to_audio_checkbox.GetValue()) + + def load_options(self): + self.to_audio_checkbox.SetValue(self.opt_manager.options['to_audio']) + self.keep_video_checkbox.SetValue(self.opt_manager.options['keep_video']) + self.audioformat_combo.SetValue(self.opt_manager.options['audio_format']) + self.audioquality_combo.SetValue(self.AUDIO_QUALITY[self.opt_manager.options['audio_quality']]) + self._on_audio_check(None) + + def save_options(self): + self.opt_manager.options['to_audio'] = self.to_audio_checkbox.GetValue() + self.opt_manager.options['keep_video'] = self.keep_video_checkbox.GetValue() + self.opt_manager.options['audio_format'] = self.audioformat_combo.GetValue() + self.opt_manager.options['audio_quality'] = self.AUDIO_QUALITY[self.audioquality_combo.GetValue()] + + +class VideoTab(TabPanel): + + """Options frame video tab. + + Attributes: + FORMATS (TwoWayOrderedDict): Contains video formats. This list + contains all the available video formats without the 'default' + and 'none' options. + + VIDEO_FORMATS (list): List that contains all the video formats + plus the 'default' one. + + SECOND_VIDEO_FORMATS (list): List that contains all the video formats + plus the 'none' one. + + COMBOBOX_SIZE (tuple): Overwrites the COMBOBOX_SIZE attribute of the + TabPanel class. + + *_LABEL (string): Constant string label for the widgets. + + """ + + FORMATS = twodict([ + ("17", "3gp [176x144]"), + ("36", "3gp [320x240]"), + ("5", "flv [400x240]"), + ("34", "flv [640x360]"), + ("35", "flv [854x480]"), + ("43", "webm [640x360]"), + ("44", "webm [854x480]"), + ("45", "webm [1280x720]"), + ("46", "webm [1920x1080]"), + ("18", "mp4 [640x360]"), + ("22", "mp4 [1280x720]"), + ("37", "mp4 [1920x1080]"), + ("38", "mp4 [4096x3072]"), + ("160", "mp4 144p (DASH)"), + ("133", "mp4 240p (DASH)"), + ("134", "mp4 360p (DASH)"), + ("135", "mp4 480p (DASH)"), + ("136", "mp4 720p (DASH)"), + ("137", "mp4 1080p (DASH)"), + ("264", "mp4 1440p (DASH)"), + ("138", "mp4 2160p (DASH)"), + ("242", "webm 240p (DASH)"), + ("243", "webm 360p (DASH)"), + ("244", "webm 480p (DASH)"), + ("247", "webm 720p (DASH)"), + ("248", "webm 1080p (DASH)"), + ("271", "webm 1440p (DASH)"), + ("272", "webm 2160p (DASH)"), + ("82", "mp4 360p (3D)"), + ("83", "mp4 480p (3D)"), + ("84", "mp4 720p (3D)"), + ("85", "mp4 1080p (3D)"), + ("100", "webm 360p (3D)"), + ("101", "webm 480p (3D)"), + ("102", "webm 720p (3D)"), + ("139", "m4a 48k (DASH AUDIO)"), + ("140", "m4a 128k (DASH AUDIO)"), + ("141", "m4a 256k (DASH AUDIO)"), + ("171", "webm 48k (DASH AUDIO)"), + ("172", "webm 256k (DASH AUDIO)") + ]) + + VIDEO_FORMATS = [_("default")] + FORMATS.values() + SECOND_VIDEO_FORMATS = [_("none")] + FORMATS.values() + + COMBOBOX_SIZE = (200, 30) + + VIDEO_FORMAT_LABEL = _("Video Format") + SEC_VIDEOFORMAT_LABEL = _("Mix Format") + + def __init__(self, *args, **kwargs): + super(VideoTab, self).__init__(*args, **kwargs) + + self.videoformat_combo = self.create_combobox(self.VIDEO_FORMATS, + self.COMBOBOX_SIZE, + self._on_videoformat) + self.sec_videoformat_combo = self.create_combobox(self.SECOND_VIDEO_FORMATS, + self.COMBOBOX_SIZE) + + self.videoformat_text = self.create_statictext(self.VIDEO_FORMAT_LABEL) + self.sec_videoformat_text = self.create_statictext(self.SEC_VIDEOFORMAT_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_30) + main_sizer.Add(self.videoformat_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.videoformat_combo, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_10) + main_sizer.Add(self.sec_videoformat_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + main_sizer.AddSpacer(self.SIZE_5) + main_sizer.Add(self.sec_videoformat_combo, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def _on_videoformat(self, event): + """Event handler for self.videoformat_combo. """ + condition = (self.videoformat_combo.GetValue() != self.VIDEO_FORMATS[0]) + self.sec_videoformat_combo.Enable(condition) + + def load_options(self): + self.videoformat_combo.SetValue(self.FORMATS.get(self.opt_manager.options['video_format'], self.VIDEO_FORMATS[0])) + self.sec_videoformat_combo.SetValue(self.FORMATS.get(self.opt_manager.options['second_video_format'], self.SECOND_VIDEO_FORMATS[0])) + self._on_videoformat(None) + + def save_options(self): + self.opt_manager.options['video_format'] = self.FORMATS.get(self.videoformat_combo.GetValue(), '0') + self.opt_manager.options['second_video_format'] = self.FORMATS.get(self.sec_videoformat_combo.GetValue(), '0') + + +class OutputTab(TabPanel): + + """Options frame output tab. + + Attributes: + TEXTCTRL_SIZE (tuple): Overwrites the TEXTCTRL_SIZE attribute of + the TabPanel class. + + * (string): Constant string label for the widgets. + + """ + + TEXTCTRL_SIZE = (300, 20) + + RESTRICT_LABEL = _("Restrict filenames (ASCII)") + ID_AS_NAME = _("ID as Name") + TITLE_AS_NAME = _("Title as Name") + CUST_TITLE = _("Custom Template (youtube-dl)") + + def __init__(self, *args, **kwargs): + super(OutputTab, self).__init__(*args, **kwargs) + + self.res_names_checkbox = self.create_checkbox(self.RESTRICT_LABEL) + self.id_rbtn = self.create_radiobutton(self.ID_AS_NAME, self._on_pick, wx.RB_GROUP) + self.title_rbtn = self.create_radiobutton(self.TITLE_AS_NAME, self._on_pick) + self.custom_rbtn = self.create_radiobutton(self.CUST_TITLE, self._on_pick) + self.title_template = self.create_textctrl() + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.HORIZONTAL) + vertical_sizer = wx.BoxSizer(wx.VERTICAL) + + vertical_sizer.AddSpacer(self.SIZE_15) + vertical_sizer.Add(self.res_names_checkbox) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.id_rbtn) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.title_rbtn) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.custom_rbtn) + vertical_sizer.AddSpacer(self.SIZE_10) + vertical_sizer.Add(self.title_template) + + main_sizer.Add(vertical_sizer, flag=wx.LEFT, border=self.SIZE_5) + + self.SetSizer(main_sizer) + + def _on_pick(self, event): + """Event handler for the radiobuttons. """ + self.title_template.Enable(self.custom_rbtn.GetValue()) + + def _get_output_format(self): + """Returns output_format string type base on the radiobuttons. + + See optionsmanager.OptionsManager 'output_format' option for more + informations. + + """ + if self.id_rbtn.GetValue(): + return 'id' + elif self.title_rbtn.GetValue(): + return 'title' + elif self.custom_rbtn.GetValue(): + return 'custom' + + def _set_output_format(self, output_format): + """Enables the corresponding radiobutton base on the output_format. """ + if output_format == 'id': + self.id_rbtn.SetValue(True) + elif output_format == 'title': + self.title_rbtn.SetValue(True) + elif output_format == 'custom': + self.custom_rbtn.SetValue(True) + + def load_options(self): + self._set_output_format(self.opt_manager.options['output_format']) + self.title_template.SetValue(self.opt_manager.options['output_template']) + self.res_names_checkbox.SetValue(self.opt_manager.options['restrict_filenames']) + self._on_pick(None) + + def save_options(self): + self.opt_manager.options['output_format'] = self._get_output_format() + self.opt_manager.options['output_template'] = self.title_template.GetValue() + self.opt_manager.options['restrict_filenames'] = self.res_names_checkbox.GetValue() + + +class FilesystemTab(TabPanel): + + """Options frame filesystem tab. + + Attributes: + FILESIZES (TwoWayOrderedDict): Contains filesize units. + + *_LABEL (string): Constant string label for the widgets. + + """ + + FILESIZES = twodict([ + ("", "Bytes"), + ("k", "Kilobytes"), + ("m", "Megabytes"), + ("g", "Gigabytes"), + ("t", "Terabytes"), + ("p", "Petabytes"), + ("e", "Exabytes"), + ("z", "Zettabytes"), + ("y", "Yottabytes") + ]) + + IGN_ERR_LABEL = _("Ignore Errors") + OPEN_DIR_LABEL = _("Open destination folder") + WRT_INFO_LABEL = _("Write info to (.json) file") + WRT_DESC_LABEL = _("Write description to file") + WRT_THMB_LABEL = _("Write thumbnail to disk") + FILESIZE_LABEL = _("Filesize") + MIN_LABEL = _("Min") + MAX_LABEL = _("Max") + + def __init__(self, *args, **kwargs): + super(FilesystemTab, self).__init__(*args, **kwargs) + + self.ign_err_checkbox = self.create_checkbox(self.IGN_ERR_LABEL) + self.open_dir_checkbox = self.create_checkbox(self.OPEN_DIR_LABEL) + self.write_info_checkbox = self.create_checkbox(self.WRT_INFO_LABEL) + self.write_desc_checkbox = self.create_checkbox(self.WRT_DESC_LABEL) + self.write_thumbnail_checkbox = self.create_checkbox(self.WRT_THMB_LABEL) + + self.min_filesize_spinner = self.create_spinctrl((0, 1024)) + self.max_filesize_spinner = self.create_spinctrl((0, 1024)) + self.min_filesize_combo = self.create_combobox(self.FILESIZES.values()) + self.max_filesize_combo = self.create_combobox(self.FILESIZES.values()) + self.min_text = self.create_statictext(self.MIN_LABEL) + self.max_text = self.create_statictext(self.MAX_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.HORIZONTAL) + + main_sizer.Add(self._set_left_sizer(), 1, wx.LEFT, border=self.SIZE_5) + main_sizer.Add(self._set_right_sizer(), 1, wx.EXPAND) + + self.SetSizer(main_sizer) + + def _set_left_sizer(self): + """Sets and returns the left BoxSizer. """ + sizer = wx.BoxSizer(wx.VERTICAL) + + sizer.AddSpacer(self.SIZE_15) + sizer.Add(self.ign_err_checkbox) + sizer.AddSpacer(self.SIZE_5) + sizer.Add(self.open_dir_checkbox) + sizer.AddSpacer(self.SIZE_5) + sizer.Add(self.write_desc_checkbox) + sizer.AddSpacer(self.SIZE_5) + sizer.Add(self.write_thumbnail_checkbox) + sizer.AddSpacer(self.SIZE_5) + sizer.Add(self.write_info_checkbox) + + return sizer + + def _set_right_sizer(self): + """Sets and returns the right BoxSizer. """ + static_box = wx.StaticBox(self, label=self.FILESIZE_LABEL) + + sizer = wx.StaticBoxSizer(static_box, wx.VERTICAL) + + sizer.AddSpacer(self.SIZE_20) + + sizer.Add(self.min_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + + sizer.AddSpacer(self.SIZE_5) + + hor_sizer = wx.BoxSizer(wx.HORIZONTAL) + hor_sizer.Add(self.min_filesize_spinner) + hor_sizer.AddSpacer(self.SIZE_10) + hor_sizer.Add(self.min_filesize_combo) + + sizer.Add(hor_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) + + sizer.AddSpacer(self.SIZE_10) + + sizer.Add(self.max_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + + sizer.AddSpacer(self.SIZE_5) + + hor_sizer = wx.BoxSizer(wx.HORIZONTAL) + hor_sizer.Add(self.max_filesize_spinner) + hor_sizer.AddSpacer(self.SIZE_10) + hor_sizer.Add(self.max_filesize_combo) + + sizer.Add(hor_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) + + return sizer + + def load_options(self): + self.open_dir_checkbox.SetValue(self.opt_manager.options['open_dl_dir']) + self.write_info_checkbox.SetValue(self.opt_manager.options['write_info']) + self.ign_err_checkbox.SetValue(self.opt_manager.options['ignore_errors']) + self.write_desc_checkbox.SetValue(self.opt_manager.options['write_description']) + self.write_thumbnail_checkbox.SetValue(self.opt_manager.options['write_thumbnail']) + self.min_filesize_spinner.SetValue(self.opt_manager.options['min_filesize']) + self.max_filesize_spinner.SetValue(self.opt_manager.options['max_filesize']) + self.min_filesize_combo.SetValue(self.FILESIZES[self.opt_manager.options['min_filesize_unit']]) + self.max_filesize_combo.SetValue(self.FILESIZES[self.opt_manager.options['max_filesize_unit']]) + + def save_options(self): + self.opt_manager.options['write_thumbnail'] = self.write_thumbnail_checkbox.GetValue() + self.opt_manager.options['write_description'] = self.write_desc_checkbox.GetValue() + self.opt_manager.options['ignore_errors'] = self.ign_err_checkbox.GetValue() + self.opt_manager.options['write_info'] = self.write_info_checkbox.GetValue() + self.opt_manager.options['open_dl_dir'] = self.open_dir_checkbox.GetValue() + self.opt_manager.options['min_filesize'] = self.min_filesize_spinner.GetValue() + self.opt_manager.options['max_filesize'] = self.max_filesize_spinner.GetValue() + self.opt_manager.options['min_filesize_unit'] = self.FILESIZES[self.min_filesize_combo.GetValue()] + self.opt_manager.options['max_filesize_unit'] = self.FILESIZES[self.max_filesize_combo.GetValue()] + + +class SubtitlesTab(TabPanel): + + """Options frame subtitles tab. + + Attributes: + SUBS_LANG (TwoWayOrderedDict): Contains subtitles languages. + + *_LABEL (string): Constant string label for the widgets. + + """ + SUBS_LANG = twodict([ + ("en", _("English")), + ("gr", _("Greek")), + ("pt", _("Portuguese")), + ("fr", _("French")), + ("it", _("Italian")), + ("ru", _("Russian")), + ("es", _("Spanish")), + ("de", _("German")) + ]) + + DL_SUBS_LABEL = _("Download subtitle file by language") + DL_ALL_SUBS_LABEL = _("Download all available subtitles") + DL_AUTO_SUBS_LABEL = _("Download automatic subtitle file (YOUTUBE ONLY)") + EMBED_SUBS_LABEL = _("Embed subtitles in the video (only for mp4 videos)") + SUBS_LANG_LABEL = _("Subtitles Language") + + def __init__(self, *args, **kwargs): + super(SubtitlesTab, self).__init__(*args, **kwargs) + + self.write_subs_checkbox = self.create_checkbox(self.DL_SUBS_LABEL, self._on_subs_pick) + self.write_all_subs_checkbox = self.create_checkbox(self.DL_ALL_SUBS_LABEL, self._on_subs_pick) + self.write_auto_subs_checkbox = self.create_checkbox(self.DL_AUTO_SUBS_LABEL, self._on_subs_pick) + self.embed_subs_checkbox = self.create_checkbox(self.EMBED_SUBS_LABEL) + self.subs_lang_combo = self.create_combobox(self.SUBS_LANG.values(), (140, 30)) + + self.subs_lang_text = self.create_statictext(self.SUBS_LANG_LABEL) + + self._set_sizer() + self._disable_items() + + def _disable_items(self): + self.embed_subs_checkbox.Disable() + self.subs_lang_combo.Disable() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.HORIZONTAL) + + vertical_sizer = wx.BoxSizer(wx.VERTICAL) + + vertical_sizer.AddSpacer(self.SIZE_15) + vertical_sizer.Add(self.write_subs_checkbox) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.write_all_subs_checkbox) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.write_auto_subs_checkbox) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.embed_subs_checkbox) + vertical_sizer.AddSpacer(self.SIZE_10) + vertical_sizer.Add(self.subs_lang_text, flag=wx.LEFT, border=self.SIZE_5) + vertical_sizer.AddSpacer(self.SIZE_5) + vertical_sizer.Add(self.subs_lang_combo, flag=wx.LEFT, border=self.SIZE_10) + + main_sizer.Add(vertical_sizer, flag=wx.LEFT, border=self.SIZE_5) + + self.SetSizer(main_sizer) + + def _on_subs_pick(self, event): + """Event handler for the write_subs checkboxes. """ + if self.write_subs_checkbox.GetValue(): + self.write_all_subs_checkbox.Disable() + self.write_auto_subs_checkbox.Disable() + self.embed_subs_checkbox.Enable() + self.subs_lang_combo.Enable() + elif self.write_all_subs_checkbox.GetValue(): + self.write_subs_checkbox.Disable() + self.write_auto_subs_checkbox.Disable() + elif self.write_auto_subs_checkbox.GetValue(): + self.write_subs_checkbox.Disable() + self.write_all_subs_checkbox.Disable() + self.embed_subs_checkbox.Enable() + else: + self.embed_subs_checkbox.Disable() + self.embed_subs_checkbox.SetValue(False) + self.subs_lang_combo.Disable() + self.write_subs_checkbox.Enable() + self.write_all_subs_checkbox.Enable() + self.write_auto_subs_checkbox.Enable() + + def load_options(self): + self.subs_lang_combo.SetValue(self.SUBS_LANG[self.opt_manager.options['subs_lang']]) + self.write_subs_checkbox.SetValue(self.opt_manager.options['write_subs']) + self.embed_subs_checkbox.SetValue(self.opt_manager.options['embed_subs']) + self.write_all_subs_checkbox.SetValue(self.opt_manager.options['write_all_subs']) + self.write_auto_subs_checkbox.SetValue(self.opt_manager.options['write_auto_subs']) + self._on_subs_pick(None) + + def save_options(self): + self.opt_manager.options['subs_lang'] = self.SUBS_LANG[self.subs_lang_combo.GetValue()] + self.opt_manager.options['write_subs'] = self.write_subs_checkbox.GetValue() + self.opt_manager.options['embed_subs'] = self.embed_subs_checkbox.GetValue() + self.opt_manager.options['write_all_subs'] = self.write_all_subs_checkbox.GetValue() + self.opt_manager.options['write_auto_subs'] = self.write_auto_subs_checkbox.GetValue() + + +class GeneralTab(TabPanel): + + """Options frame general tab. + + Attributes: + BUTTONS_SIZE (tuple): Overwrites the BUTTONS_SIZE attribute of the + TabPanel class. + + *_LABEL (string): Constant string label for the widgets. + + """ + + BUTTONS_SIZE = (110, 35) + + ABOUT_LABEL = _("About") + OPEN_LABEL = _("Open") + RESET_LABEL = _("Reset Options") + SAVEPATH_LABEL = _("Save Path") + SETTINGS_DIR_LABEL = _("Settings File: {0}") + PICK_DIR_LABEL = _("Choose Directory") + + def __init__(self, *args, **kwargs): + super(GeneralTab, self).__init__(*args, **kwargs) + + self.savepath_box = self.create_textctrl() + self.about_button = self.create_button(self.ABOUT_LABEL, self._on_about) + self.open_button = self.create_button(self.OPEN_LABEL, self._on_open) + self.reset_button = self.create_button(self.RESET_LABEL, self._on_reset) + + self.savepath_text = self.create_statictext(self.SAVEPATH_LABEL) + + cfg_file = self.SETTINGS_DIR_LABEL.format(self.opt_manager.settings_file) + self.cfg_file_dir = self.create_statictext(cfg_file) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_20) + main_sizer.Add(self.savepath_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + + main_sizer.AddSpacer(self.SIZE_10) + savepath_sizer = wx.BoxSizer(wx.HORIZONTAL) + savepath_sizer.Add(self.savepath_box, 1, wx.LEFT | wx.RIGHT, self.SIZE_80) + main_sizer.Add(savepath_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND) + + main_sizer.AddSpacer(self.SIZE_20) + buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) + buttons_sizer.Add(self.about_button) + buttons_sizer.Add(self.open_button, flag=wx.LEFT | wx.RIGHT, border=self.SIZE_50) + buttons_sizer.Add(self.reset_button) + main_sizer.Add(buttons_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) + + main_sizer.AddSpacer(self.SIZE_20) + main_sizer.Add(self.cfg_file_dir, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def _on_reset(self, event): + """Event handler of the self.reset_button. """ + self.reset_handler() + + def _on_open(self, event): + """Event handler of the self.open_button. """ + dlg = self.create_dirdialog(self.PICK_DIR_LABEL) + + if dlg.ShowModal() == wx.ID_OK: + self.savepath_box.SetValue(dlg.GetPath()) + + dlg.Destroy() + + def _on_about(self, event): + """Event handler of the self.about_button. """ + info = wx.AboutDialogInfo() + + if self.app_icon is not None: + info.SetIcon(self.app_icon) + + info.SetName(__appname__) + info.SetVersion(__version__) + info.SetDescription(__descriptionfull__) + info.SetWebSite(__projecturl__) + info.SetLicense(__licensefull__) + info.AddDeveloper(__author__) + + wx.AboutBox(info) + + def load_options(self): + self.savepath_box.SetValue(self.opt_manager.options['save_path']) + + def save_options(self): + self.opt_manager.options['save_path'] = self.savepath_box.GetValue() + + +class CMDTab(TabPanel): + + """Options frame command tab. + + Attributes: + CMD_LABEL (string): Constant string label for the widgets. + + """ + + CMD_LABEL = _("Command line arguments (e.g. --help)") + + def __init__(self, *args, **kwargs): + super(CMDTab, self).__init__(*args, **kwargs) + + self.cmd_args_box = self.create_textctrl() + self.cmd_args_text = self.create_statictext(self.CMD_LABEL) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_50) + main_sizer.Add(self.cmd_args_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + + main_sizer.AddSpacer(self.SIZE_10) + cmdbox_sizer = wx.BoxSizer(wx.HORIZONTAL) + cmdbox_sizer.Add(self.cmd_args_box, 1, wx.LEFT | wx.RIGHT, border=self.SIZE_80) + main_sizer.Add(cmdbox_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND) + + self.SetSizer(main_sizer) + + def load_options(self): + self.cmd_args_box.SetValue(self.opt_manager.options['cmd_args']) + + def save_options(self): + self.opt_manager.options['cmd_args'] = self.cmd_args_box.GetValue() + + +class LocalizationTab(TabPanel): + + """Options frame localization tab. + + Attributes: + COMBOBOX_SIZE (tuple): Tuple that contains the size(width, height) + of the combobox widget. + + LOCALE_NAMES (TwoWayOrderedDict): Stores the locale names. + + *_LABEL (string): Constant string label for the widgets. + + """ + + COMBOBOX_SIZE = (150, 30) + + LOCALE_NAMES = twodict([ + ('en_US', 'English'), + ('fr_FR', 'French'), + ('de_DE', 'German'), + ('ar_AR', 'Arabic') + ]) + + RESTART_LABEL = _("Restart") + LOCALE_LABEL = _("Localization Language") + RESTART_MSG = _("In order for the changes to take effect please restart {0}") + + def __init__(self, *args, **kwargs): + super(LocalizationTab, self).__init__(*args, **kwargs) + + self.locale_text = self.create_statictext(self.LOCALE_LABEL) + self.locale_box = self.create_combobox(self.LOCALE_NAMES.values(), self.COMBOBOX_SIZE, self._on_locale) + + self._set_sizer() + + def _set_sizer(self): + main_sizer = wx.BoxSizer(wx.VERTICAL) + + main_sizer.AddSpacer(self.SIZE_50) + main_sizer.Add(self.locale_text, flag=wx.ALIGN_CENTER_HORIZONTAL) + + main_sizer.AddSpacer(self.SIZE_10) + main_sizer.Add(self.locale_box, flag=wx.ALIGN_CENTER_HORIZONTAL) + + self.SetSizer(main_sizer) + + def _on_locale(self, event): + """Event handler for the self.locale_box widget. """ + self.create_popup(self.RESTART_MSG.format(__appname__), + self.RESTART_LABEL, + wx.OK | wx.ICON_INFORMATION) + + def load_options(self): + self.locale_box.SetValue(self.LOCALE_NAMES[self.opt_manager.options['locale_name']]) + + def save_options(self): + self.opt_manager.options['locale_name'] = self.LOCALE_NAMES[self.locale_box.GetValue()] + + +class LogGUI(wx.Frame): + + """Simple window for reading the STDERR. + + Attributes: + TITLE (string): Frame title. + FRAME_SIZE (tuple): Tuple that holds the frame size (width, height). + + Args: + parent (wx.Window): Frame parent. + + """ + + TITLE = _("Log Viewer") + FRAME_SIZE = (650, 200) + + def __init__(self, parent=None): + wx.Frame.__init__(self, parent, title=self.TITLE, size=self.FRAME_SIZE) + + panel = wx.Panel(self) + + self._text_area = wx.TextCtrl( + panel, + style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL + ) + + sizer = wx.BoxSizer() + sizer.Add(self._text_area, 1, wx.EXPAND) + panel.SetSizerAndFit(sizer) + + def load(self, filename): + """Load file content on the text area. """ + if os.path.exists(filename): + self._text_area.LoadFile(filename) + diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/OptionsHandler.py youtube-dlg-0.3.7/youtube_dl_gui/OptionsHandler.py --- youtube-dlg-0.3.5/youtube_dl_gui/OptionsHandler.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/OptionsHandler.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,191 +0,0 @@ -#! /usr/bin/env python - -from .version import __version__ -from .Utils import ( - get_HOME, - file_exist, - get_os_type, - fix_path, - makedir -) - -SETTINGS_FILENAME = 'settings' -LINUX_FILES_PATH = get_HOME() + '/.youtube-dl-gui' -WINDOWS_FILES_PATH = get_HOME() + '\\youtube-dl-gui' - -class OptionsHandler(): - - settings_abs_path = '' - - def __init__(self, statusBarWrite): - self.statusBarWrite = statusBarWrite - self.set_settings_path() - self.load_default() - self.load_settings() - - def load_settings(self): - if not file_exist(self.get_config_path()): - makedir(self.get_config_path()) - if file_exist(self.settings_abs_path): - self.load_from_file() - - def load_default(self): - self.savePath = get_HOME() - self.videoFormat = "default" - self.dashAudioFormat = "NO SOUND" - self.clearDashFiles = False - self.toAudio = False - self.keepVideo = False - self.audioFormat = "mp3" - self.audioQuality = "mid" - self.outputFormat = "title" - self.outputTemplate = '%(uploader)s/%(title)s.%(ext)s' - self.startTrack = 1 - self.endTrack = 0 - self.maxDownloads = 0 - self.minFileSize = "0" - self.maxFileSize = "0" - self.writeSubs = False - self.writeAllSubs = False - self.writeAutoSubs = False - self.embedSubs = False - self.subsLang = "English" - self.openDownloadDir = False - self.ignoreErrors = True - self.writeDescription = False - self.writeInfo = False - self.writeThumbnail = False - self.retries = 10 - self.userAgent = "" - self.referer = "" - self.proxy = "" - self.username = "" - self.password = "" - self.videoPass = "" - self.updatePath = self.get_config_path() - self.autoUpdate = False - self.cmdArgs = "" - self.enableLog = True - self.writeTimeToLog = True - - def get_config_path(self): - if get_os_type() == 'nt': - return WINDOWS_FILES_PATH - else: - return LINUX_FILES_PATH - - def set_settings_path(self): - self.settings_abs_path = self.get_config_path() - self.settings_abs_path = fix_path(self.settings_abs_path) - self.settings_abs_path += SETTINGS_FILENAME - - def read_from_file(self): - f = open(self.settings_abs_path, 'r') - options = f.readlines() - f.close() - return options - - def extract_options(self, options): - opts = [] - for option in options: - opt = option.split('=') - if not len(opt) < 2: - opts.append(opt[1].rstrip('\n')) - return opts - - def check_settings_file_version(self, options): - data = options.pop(0).rstrip('\n') - name, version = data.split('=') - if name == 'Version' and version == __version__: - return True - else: - return False - - def load_from_file(self): - options = self.read_from_file() - if self.check_settings_file_version(options): - opts = self.extract_options(options) - try: - self.savePath = opts[0].decode('utf8') - self.videoFormat = opts[1] - self.dashAudioFormat = opts[2] - self.clearDashFiles = opts[3] in ['True'] - self.toAudio = opts[4] in ['True'] - self.keepVideo = opts[5] in ['True'] - self.audioFormat = opts[6] - self.audioQuality = opts[7] - self.outputFormat = opts[8] - self.outputTemplate = opts[9] - self.startTrack = int(opts[10]) - self.endTrack = int(opts[11]) - self.maxDownloads = int(opts[12]) - self.minFileSize = opts[13] - self.maxFileSize = opts[14] - self.writeSubs = opts[15] in ['True'] - self.writeAllSubs = opts[16] in ['True'] - self.writeAutoSubs = opts[17] in ['True'] - self.embedSubs = opts[18] in ['True'] - self.subsLang = opts[19] - self.openDownloadDir = opts[20] in ['True'] - self.ignoreErrors = opts[21] in ['True'] - self.writeDescription = opts[22] in ['True'] - self.writeInfo = opts[23] in ['True'] - self.writeThumbnail = opts[24] in ['True'] - self.retries = int(opts[25]) - self.userAgent = opts[26] - self.referer = opts[27] - self.proxy = opts[28] - self.username = opts[29] - self.updatePath = opts[30].decode('utf8') - self.autoUpdate = opts[31] in ['True'] - self.cmdArgs = opts[32] - self.enableLog = opts[33] in ['True'] - self.writeTimeToLog = opts[34] in ['True'] - except: - self.statusBarWrite('Error while loading settings file. Loading default settings.') - self.load_default() - else: - self.statusBarWrite('Settings file version problem. Loading default settings.') - self.load_default() - - def save_to_file(self): - f = open(self.settings_abs_path, 'w') - f.write('Version='+__version__+'\n') - f.write('SavePath='+self.savePath.encode('utf-8')+'\n') - f.write('VideoFormat='+str(self.videoFormat)+'\n') - f.write('DashAudioFormat='+str(self.dashAudioFormat)+'\n') - f.write('ClearDashFiles='+str(self.clearDashFiles)+'\n') - f.write('ToAudio='+str(self.toAudio)+'\n') - f.write('KeepVideo='+str(self.keepVideo)+'\n') - f.write('AudioFormat='+str(self.audioFormat)+'\n') - f.write('AudioQuality='+str(self.audioQuality)+'\n') - f.write('OutputFormat='+str(self.outputFormat)+'\n') - f.write('OutputTemplate='+str(self.outputTemplate)+'\n') - f.write('StartTrack='+str(self.startTrack)+'\n') - f.write('EndTrack='+str(self.endTrack)+'\n') - f.write('MaxDownloads='+str(self.maxDownloads)+'\n') - f.write('MinFileSize='+str(self.minFileSize)+'\n') - f.write('MaxFileSize='+str(self.maxFileSize)+'\n') - f.write('WriteSubtitles='+str(self.writeSubs)+'\n') - f.write('WriteAllSubtitles='+str(self.writeAllSubs)+'\n') - f.write('WriteAutoSubtitles='+str(self.writeAutoSubs)+'\n') - f.write('EmbedSubs='+str(self.embedSubs)+'\n') - f.write('SubtitlesLanguage='+str(self.subsLang)+'\n') - f.write('OpenDownloadDirectory='+str(self.openDownloadDir)+'\n') - f.write('IgnoreErrors='+str(self.ignoreErrors)+'\n') - f.write('WriteDescription='+str(self.writeDescription)+'\n') - f.write('WriteInfo='+str(self.writeInfo)+'\n') - f.write('WriteThumbnail='+str(self.writeThumbnail)+'\n') - f.write('Retries='+str(self.retries)+'\n') - f.write('UserAgent='+str(self.userAgent)+'\n') - f.write('Referer='+str(self.referer)+'\n') - f.write('Proxy='+str(self.proxy)+'\n') - f.write('Username='+str(self.username)+'\n') - # We dont store password & videoPass for security reasons - f.write('UpdatePath='+self.updatePath.encode('utf-8')+'\n') - f.write('AutoUpdate='+str(self.autoUpdate)+'\n') - f.write('CmdArgs='+str(self.cmdArgs)+'\n') - f.write('EnableLog='+str(self.enableLog)+'\n') - f.write('WriteTimeToLog='+str(self.writeTimeToLog)+'\n') - f.close() - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/optionsmanager.py youtube-dlg-0.3.7/youtube_dl_gui/optionsmanager.py --- youtube-dlg-0.3.5/youtube_dl_gui/optionsmanager.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/optionsmanager.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,354 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module to handle settings. """ + +from __future__ import unicode_literals + +import json +import os.path + +from .utils import ( + encode_tuple, + decode_tuple, + check_path +) + + +class OptionsManager(object): + + """Handles youtubedlg options. + + This class is responsible for storing and retrieving the options. + + Attributes: + SETTINGS_FILENAME (string): Filename of the settings file. + SENSITIVE_KEYS (tuple): Contains the keys that we don't want + to store on the settings file. (SECURITY ISSUES). + + Args: + config_path (string): Absolute path where OptionsManager + should store the settings file. + + Note: + See load_default() method for available options. + + Example: + Access the options using the 'options' variable. + + opt_manager = OptionsManager('.') + opt_manager.options['save_path'] = '~/Downloads' + + """ + + SETTINGS_FILENAME = 'settings.json' + SENSITIVE_KEYS = ('sudo_password', 'password', 'video_password') + + def __init__(self, config_path): + self.config_path = config_path + self.settings_file = os.path.join(config_path, self.SETTINGS_FILENAME) + self.options = dict() + self.load_default() + self.load_from_file() + + def load_default(self): + """Load the default options. + + Note: + This method is automatically called by the constructor. + + Options Description: + + save_path (string): Path where youtube-dl should store the + downloaded file. Default is $HOME. + + video_format (string): Video format to download. + When this options is set to '0' youtube-dl will choose + the best video format available for the given URL. + + second_video_format (string): Video format to mix with the first + one (-f 18+17). + + to_audio (boolean): If True youtube-dl will post process the + video file. + + keep_video (boolen): If True youtube-dl will keep the video file + after post processing it. + + audio_format (string): Audio format of the post processed file. + Available values are "mp3", "wav", "aac", "m4a", "vorbis". + + audio_quality (string): Audio quality of the post processed file. + Available values are "9", "5", "0". The lowest the value the + better the quality. + + restrict_filenames (boolean): If True youtube-dl will restrict + the downloaded file filename to ASCII characters only. + + output_format (string): This option sets the downloaded file + output template. Available values are 'id', 'title', 'custom' + + 'id' -> '%(id)s.%(ext)s' + 'title' -> '%(title)s.%(ext)s' + 'custom' -> Use 'output_template' as output template. + + output_template (string): Can be any output template supported + by youtube-dl. + + playlist_start (int): Playlist index to start downloading. + + playlist_end (int): Playlist index to stop downloading. + + max_downloads (int): Maximum number of video files to download + from the given playlist. + + min_filesize (float): Minimum file size of the video file. + If the video file is smaller than the given size then + youtube-dl will abort the download process. + + max_filesize (float): Maximum file size of the video file. + If the video file is larger than the given size then + youtube-dl will abort the download process. + + min_filesize_unit (string): Minimum file size unit. + Available values: '', 'k', 'm', 'g', 'y', 'p', 'e', 'z', 'y'. + + max_filesize_unit (string): Maximum file size unit. + See 'min_filesize_unit' option for available values. + + write_subs (boolean): If True youtube-dl will try to download + the subtitles file for the given URL. + + write_all_subs (boolean): If True youtube-dl will try to download + all the available subtitles files for the given URL. + + write_auto_subs (boolean): If True youtube-dl will try to download + the automatic subtitles file for the given URL. + + embed_subs (boolean): If True youtube-dl will merge the subtitles + file with the video. (ONLY mp4 files). + + subs_lang (string): Language of the subtitles file to download. + Needs 'write_subs' option. + + ignore_errors (boolean): If True youtube-dl will ignore the errors + and continue the download process. + + open_dl_dir (boolean): If True youtube-dlg will open the + destination folder after download process has been completed. + + write_description (boolean): If True youtube-dl will write video + description to a .description file. + + write_info (boolean): If True youtube-dl will write video + metadata to a .info.json file. + + write_thumbnail (boolean): If True youtube-dl will write + thumbnail image to disk. + + retries (int): Number of youtube-dl retries. + + user_agent (string): Specify a custom user agent for youtube-dl. + + referer (string): Specify a custom referer to use if the video + access is restricted to one domain. + + proxy (string): Use the specified HTTP/HTTPS proxy. + + shutdown (boolean): If True youtube-dlg will turn the computer + off after the download process has been completed. + + sudo_password (string): SUDO password for the shutdown process if + the user does not have elevated privileges. + + username (string): Username to login with. + + password (string): Password to login with. + + video_password (string): Video password for the given URL. + + youtubedl_path (string): Absolute path to the youtube-dl binary. + Default is the self.config_path. You can change this option + to point on /usr/local/bin etc.. if you want to use the + youtube-dl binary on your system. This is also the directory + where youtube-dlg will auto download the youtube-dl if not + exists so you should make sure you have write access if you + want to update the youtube-dl binary from within youtube-dlg. + + cmd_args (string): String that contains extra youtube-dl options + seperated by spaces. + + enable_log (boolean): If True youtube-dlg will enable + the LogManager. See main() function under __init__(). + + log_time (boolean): See logmanager.LogManager add_time attribute. + + workers_number (int): Number of download workers that download manager + will spawn. Must be greater than zero. + + locale_name (string): Locale name (e.g. ru_RU). + + main_win_size (tuple): Main window size (width, height). + If window becomes to small the program will reset its size. + See _settings_are_valid method MIN_FRAME_SIZE. + + opts_win_size (tuple): Options window size (width, height). + If window becomes to small the program will reset its size. + See _settings_are_valid method MIN_FRAME_SIZE. + + """ + self.options = { + 'save_path': os.path.expanduser('~'), + 'video_format': '0', + 'second_video_format': '0', + 'to_audio': False, + 'keep_video': False, + 'audio_format': 'mp3', + 'audio_quality': '5', + 'restrict_filenames': False, + 'output_format': 'title', + 'output_template': '%(uploader)s/%(title)s.%(ext)s', + 'playlist_start': 1, + 'playlist_end': 0, + 'max_downloads': 0, + 'min_filesize': 0, + 'max_filesize': 0, + 'min_filesize_unit': '', + 'max_filesize_unit': '', + 'write_subs': False, + 'write_all_subs': False, + 'write_auto_subs': False, + 'embed_subs': False, + 'subs_lang': 'en', + 'ignore_errors': True, + 'open_dl_dir': True, + 'write_description': False, + 'write_info': False, + 'write_thumbnail': False, + 'retries': 10, + 'user_agent': '', + 'referer': '', + 'proxy': '', + 'shutdown': False, + 'sudo_password': '', + 'username': '', + 'password': '', + 'video_password': '', + 'youtubedl_path': self.config_path, + 'cmd_args': '', + 'enable_log': True, + 'log_time': False, + 'workers_number': 3, + 'locale_name': 'en_US', + 'main_win_size': (700, 490), + 'opts_win_size': (640, 270) + } + + def load_from_file(self): + """Load options from settings file. """ + if not os.path.exists(self.settings_file): + return + + with open(self.settings_file, 'rb') as settings_file: + try: + options = json.load(settings_file) + + if self._settings_are_valid(options): + self.options = options + except: + self.load_default() + + def save_to_file(self): + """Save options to settings file. """ + check_path(self.config_path) + + with open(self.settings_file, 'wb') as settings_file: + options = self._get_options() + json.dump(options, + settings_file, + indent=4, + separators=(',', ': ')) + + def _settings_are_valid(self, settings_dictionary): + """Check settings.json dictionary. + + Args: + settings_dictionary (dictionary): Options dictionary loaded + from the settings file. See load_from_file() method. + + Returns: + True if settings.json dictionary is valid, else False. + + """ + VALID_VIDEO_FORMAT = ('0', '17', '36', '5', '34', '35', '43', '44', '45', + '46', '18', '22', '37', '38', '160', '133', '134', '135', '136','137', + '264', '138', '242', '243', '244', '247', '248', '271', '272', '82', + '83', '84', '85', '100', '101', '102', '139', '140', '141', '171', '172') + + VALID_AUDIO_FORMAT = ('mp3', 'wav', 'aac', 'm4a', 'vorbis') + + VALID_AUDIO_QUALITY = ('0', '5', '9') + + VALID_OUTPUT_FORMAT = ('title', 'id', 'custom') + + VALID_FILESIZE_UNIT = ('', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y') + + VALID_SUB_LANGUAGE = ('en', 'gr', 'pt', 'fr', 'it', 'ru', 'es', 'de') + + MIN_FRAME_SIZE = 100 + + # Decode string formatted tuples back to normal tuples + settings_dictionary['main_win_size'] = decode_tuple(settings_dictionary['main_win_size']) + settings_dictionary['opts_win_size'] = decode_tuple(settings_dictionary['opts_win_size']) + + for key in self.options: + if key not in settings_dictionary: + return False + + if type(self.options[key]) != type(settings_dictionary[key]): + return False + + # Check if each key has a valid value + rules_dict = { + 'video_format': VALID_VIDEO_FORMAT, + 'second_video_format': VALID_VIDEO_FORMAT, + 'audio_format': VALID_AUDIO_FORMAT, + 'audio_quality': VALID_AUDIO_QUALITY, + 'output_format': VALID_OUTPUT_FORMAT, + 'min_filesize_unit': VALID_FILESIZE_UNIT, + 'max_filesize_unit': VALID_FILESIZE_UNIT, + 'subs_lang': VALID_SUB_LANGUAGE + } + + for key, valid_list in rules_dict.items(): + if settings_dictionary[key] not in valid_list: + return False + + # Check workers number value + if settings_dictionary['workers_number'] < 1: + return False + + # Check main-options frame size + for size in settings_dictionary['main_win_size']: + if size < MIN_FRAME_SIZE: + return False + + for size in settings_dictionary['opts_win_size']: + if size < MIN_FRAME_SIZE: + return False + + return True + + def _get_options(self): + """Return options dictionary without SENSITIVE_KEYS. """ + temp_options = self.options.copy() + + for key in self.SENSITIVE_KEYS: + temp_options[key] = '' + + # Encode normal tuples to string formatted tuples + temp_options['main_win_size'] = encode_tuple(temp_options['main_win_size']) + temp_options['opts_win_size'] = encode_tuple(temp_options['opts_win_size']) + + return temp_options + diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/OutputHandler.py youtube-dlg-0.3.7/youtube_dl_gui/OutputHandler.py --- youtube-dlg-0.3.5/youtube_dl_gui/OutputHandler.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/OutputHandler.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,192 +0,0 @@ -#! /usr/bin/env python - -from .Utils import ( - remove_empty_items, - string_to_array, - get_filename -) - -class DownloadHandler(): - - def __init__(self, ListCtrl): - self.ListCtrl = ListCtrl - self.finished = False - self.closed = False - self.error = False - self.init_handlers() - - def init_handlers(self): - ''' Initialise handlers ''' - self.handlers = [None for i in range(self.ListCtrl.ListIndex)] - - def _has_closed(self): - return self.closed - - def _has_finished(self): - return self.finished - - def _has_error(self): - return self.error - - def _add_empty_handler(self): - self.handlers.append(None) - - def handle(self, msg): - ''' Handles msg base to Signals.txt ''' - pack = msg.data - if pack.index == -1: - self.global_handler(pack) - else: - self.index_handler(pack) - - def global_handler(self, pack): - ''' Manage global index = -1 ''' - if pack.header == 'close': - self.closed = True - elif pack.header == 'finish': - self.finished = True - - def index_handler(self, pack): - ''' Manage handlers base on index ''' - if self.handlers[pack.index] == None: - self.handlers[pack.index] = IndexDownloadHandler(self.ListCtrl, pack.index) - self.handlers[pack.index].handle(pack) - if self.handlers[pack.index].has_error(): - self.error = True - -class IndexDownloadHandler(): - - def __init__(self, ListCtrl, index): - self.ListCtrl = ListCtrl - self.index = index - self.err = False - self.info = '' - - def has_error(self): - return self.err - - def handle(self, pack): - ''' Handle its pack for current index ''' - if pack.header == 'finish': - self.finish() - elif pack.header == 'close': - self.close() - elif pack.header == 'error': - self.error() - elif pack.header == 'playlist': - self.playlist(pack.data) - elif pack.header == 'youtube': - self.pre_proc() - elif pack.header == 'download': - self.download(pack.data) - elif pack.header == 'ffmpeg': - self.post_proc() - elif pack.header == 'remove': - self.remove() - elif pack.header == 'filename': - self.filename(pack.data) - - def finish(self): - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Finished') - - def close(self): - self.ListCtrl._write_data(self.index, 3, '') - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Stopped') - - def error(self): - self.err = True - self.ListCtrl._write_data(self.index, 3, '') - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Error') - - def pre_proc(self): - self.ListCtrl._write_data(self.index, 5, 'Pre-Processing %s' % self.info) - - def post_proc(self): - self.ListCtrl._write_data(self.index, 4, '') - self.ListCtrl._write_data(self.index, 5, 'Post-Processing %s' % self.info) - - def download(self, data): - self.ListCtrl._write_data(self.index, 1, data[0]) - self.ListCtrl._write_data(self.index, 2, data[1]) - self.ListCtrl._write_data(self.index, 3, data[2]) - self.ListCtrl._write_data(self.index, 4, data[3]) - self.ListCtrl._write_data(self.index, 5, 'Downloading %s' % self.info) - - def playlist(self, data): - self.ListCtrl._write_data(self.index, 1, '') - self.ListCtrl._write_data(self.index, 2, '') - self.ListCtrl._write_data(self.index, 3, '') - self.ListCtrl._write_data(self.index, 4, '') - self.info = '%s/%s' % (data[0], data[1]) - - def remove(self): - self.ListCtrl._write_data(self.index, 5, 'Removing DASH Files') - - def filename(self, fl): - self.ListCtrl._write_data(self.index, 0, get_filename(fl)) - -class DataPack(): - - def __init__(self, header, index=-1, data=None): - self.header = header - self.index = index - self.data = data - -class OutputFormatter(): - - def __init__(self, stdout): - dataPack = None - - self.stdout = remove_empty_items(string_to_array(stdout)) - # extract header from stdout - header = self.extract_header() - # extract special headers filename, playlist - header = self.set_filename_header(header) - header = self.set_playlist_header(header) - # extract data base on header - data = self.extract_data(header) - # extract special ignore header base on header, data - header = self.set_ignore_header(header, data) - # create data pack - self.dataPack = DataPack(header, data=data) - - def extract_header(self): - header = self.stdout.pop(0).replace('[', '').replace(']', '') - return header - - def extract_data(self, header): - ''' Extract data base on header ''' - if header == 'download': - if '%' in self.stdout[0]: - if self.stdout[0] != '100%': - ''' size, percent, eta, speed ''' - return [self.stdout[2], self.stdout[0], self.stdout[6], self.stdout[4]] - if header == 'playlist': - return [self.stdout[2], self.stdout[4]] - if header == 'filename': - return ' '.join(self.stdout[1:]) - return None - - def set_filename_header(self, header): - if header != 'ffmpeg': - if self.stdout[0] == 'Destination:': - return 'filename' - return header - - def set_playlist_header(self, header): - if header == 'download': - if self.stdout[0] == 'Downloading' and self.stdout[1] == 'video': - return 'playlist' - return header - - def set_ignore_header(self, header, data): - if header == 'download' and data == None: - return 'ignore' - return header - - def get_data(self): - return self.dataPack - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/parsers.py youtube-dlg-0.3.7/youtube_dl_gui/parsers.py --- youtube-dlg-0.3.5/youtube_dl_gui/parsers.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/parsers.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,194 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module responsible for parsing the options. """ + +from __future__ import unicode_literals + +import os.path + +from .utils import ( + remove_shortcuts, + to_string +) + + +class OptionHolder(object): + + """Simple data structure that holds informations for the given option. + + Args: + name (string): Option name. Must be a valid option name + from the optionsmanager.OptionsManager class. + See optionsmanager.OptionsManager load_default() method. + + flag (string): The option command line switch. + See https://github.com/rg3/youtube-dl/#options + + default_value (any): The option default value. Must be the same type + with the corresponding option from the optionsmanager.OptionsManager + class. + + requirements (list): The requirements for the given option. This + argument is a list of strings with the name of all the options + that this specific option needs. For example 'subs_lang' needs the + 'write_subs' option to be enabled. + + """ + + def __init__(self, name, flag, default_value, requirements=None): + self.name = name + self.flag = flag + self.requirements = requirements + self.default_value = default_value + + def is_boolean(self): + """Returns True if the option is a boolean switch else False. """ + return type(self.default_value) is bool + + def check_requirements(self, options_dict): + """Check if the required options are enabled. + + Args: + options_dict (dictionary): Dictionary with all the options. + + Returns: + True if any of the required options is enabled else False. + + """ + if self.requirements is None: + return True + + return any([options_dict[req] for req in self.requirements]) + + +class OptionsParser(object): + + """Parse optionsmanager.OptionsManager options. + + This class is responsible for turning some of the youtube-dlg options + to youtube-dl command line options. + + """ + + def __init__(self): + self._ydl_options = [ + OptionHolder('playlist_start', '--playlist-start', 1), + OptionHolder('playlist_end', '--playlist-end', 0), + OptionHolder('max_downloads', '--max-downloads', 0), + OptionHolder('username', '-u', ''), + OptionHolder('password', '-p', ''), + OptionHolder('video_password', '--video-password', ''), + OptionHolder('retries', '-R', 10), + OptionHolder('proxy', '--proxy', ''), + OptionHolder('user_agent', '--user-agent', ''), + OptionHolder('referer', '--referer', ''), + OptionHolder('ignore_errors', '-i', False), + OptionHolder('write_description', '--write-description', False), + OptionHolder('write_info', '--write-info-json', False), + OptionHolder('write_thumbnail', '--write-thumbnail', False), + OptionHolder('min_filesize', '--min-filesize', 0), + OptionHolder('max_filesize', '--max-filesize', 0), + OptionHolder('write_all_subs', '--all-subs', False), + OptionHolder('write_auto_subs', '--write-auto-sub', False), + OptionHolder('write_subs', '--write-sub', False), + OptionHolder('keep_video', '-k', False), + OptionHolder('restrict_filenames', '--restrict-filenames', False), + OptionHolder('save_path', '-o', ''), + OptionHolder('embed_subs', '--embed-subs', False, ['write_auto_subs', 'write_subs']), + OptionHolder('to_audio', '-x', False), + OptionHolder('audio_format', '--audio-format', '', ['to_audio']), + OptionHolder('video_format', '-f', '0'), + OptionHolder('subs_lang', '--sub-lang', '', ['write_subs']), + OptionHolder('audio_quality', '--audio-quality', '5', ['to_audio']) + ] + + def parse(self, options_dictionary): + """Parse optionsmanager.OptionsManager options. + + Parses the given options to youtube-dl command line arguments. + + Args: + options_dictionary (dictionary): Dictionary with all the options. + + Returns: + List of strings with all the youtube-dl command line options. + + """ + options_list = ['--newline'] + + # Create a copy of options_dictionary + # We don't want to edit the original options dictionary + # and change some of the options values like 'save_path' etc.. + options_dict = options_dictionary.copy() + + self._build_savepath(options_dict) + self._build_videoformat(options_dict) + self._build_filesizes(options_dict) + + # Parse basic youtube-dl command line options + for option in self._ydl_options: + if option.check_requirements(options_dict): + value = options_dict[option.name] + + if value != option.default_value: + options_list.append(option.flag) + + if not option.is_boolean(): + options_list.append(to_string(value)) + + # Parse cmd_args + for option in options_dict['cmd_args'].split(): + options_list.append(option) + + return options_list + + def _build_savepath(self, options_dict): + """Build the save path. + + We use this method to build the value of the 'save_path' option and + store it back to the options dictionary. + + Args: + options_dict (dictionary): Copy of the original options dictionary. + + """ + save_path = remove_shortcuts(options_dict['save_path']) + + if options_dict['output_format'] == 'id': + save_path = os.path.join(save_path, '%(id)s.%(ext)s') + elif options_dict['output_format'] == 'title': + save_path = os.path.join(save_path, '%(title)s.%(ext)s') + else: + save_path = os.path.join(save_path, options_dict['output_template']) + + options_dict['save_path'] = save_path + + def _build_videoformat(self, options_dict): + """Build the video format. + + We use this method to build the value of the 'video_format' option and + store it back to the options dictionary. + + Args: + options_dict (dictionary): Copy of the original options dictionary. + + """ + if options_dict['video_format'] != '0' and options_dict['second_video_format'] != '0': + options_dict['video_format'] = options_dict['video_format'] + '+' + options_dict['second_video_format'] + + def _build_filesizes(self, options_dict): + """Build the filesize options values. + + We use this method to build the values of 'min_filesize' and + 'max_filesize' options and store them back to options dictionary. + + Args: + options_dict (dictionary): Copy of the original options dictionary. + + """ + if options_dict['min_filesize']: + options_dict['min_filesize'] = to_string(options_dict['min_filesize']) + options_dict['min_filesize_unit'] + + if options_dict['max_filesize']: + options_dict['max_filesize'] = to_string(options_dict['max_filesize']) + options_dict['max_filesize_unit'] diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/updatemanager.py youtube-dlg-0.3.7/youtube_dl_gui/updatemanager.py --- youtube-dlg-0.3.5/youtube_dl_gui/updatemanager.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/updatemanager.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,96 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module to update youtube-dl binary. + +Attributes: + UPDATE_PUB_TOPIC (string): wxPublisher subscription topic of the + UpdateThread thread. + +""" + +from __future__ import unicode_literals + +import os.path +from threading import Thread +from urllib2 import urlopen, URLError, HTTPError + +from wx import CallAfter +from wx.lib.pubsub import setuparg1 +from wx.lib.pubsub import pub as Publisher + +from .utils import ( + YOUTUBEDL_BIN, + check_path +) + +UPDATE_PUB_TOPIC = 'update' + + +class UpdateThread(Thread): + + """Python Thread that downloads youtube-dl binary. + + Attributes: + LATEST_YOUTUBE_DL (string): URL with the latest youtube-dl binary. + DOWNLOAD_TIMEOUT (int): Download timeout in seconds. + + Args: + download_path (string): Absolute path where UpdateThread will download + the latest youtube-dl. + + quiet (boolean): If True UpdateThread won't send the finish signal + back to the caller. Finish signal can be used to make sure that + the UpdateThread has been completed in an asynchronous way. + + """ + + LATEST_YOUTUBE_DL = 'https://yt-dl.org/latest/' + DOWNLOAD_TIMEOUT = 20 + + def __init__(self, download_path, quiet=False): + super(UpdateThread, self).__init__() + self.download_path = download_path + self.quiet = quiet + self.start() + + def run(self): + self._talk_to_gui('download') + + source_file = self.LATEST_YOUTUBE_DL + YOUTUBEDL_BIN + destination_file = os.path.join(self.download_path, YOUTUBEDL_BIN) + + check_path(self.download_path) + + try: + stream = urlopen(source_file, timeout=self.DOWNLOAD_TIMEOUT) + + with open(destination_file, 'wb') as dest_file: + dest_file.write(stream.read()) + + self._talk_to_gui('correct') + except (HTTPError, URLError, IOError) as error: + self._talk_to_gui('error', unicode(error)) + + if not self.quiet: + self._talk_to_gui('finish') + + def _talk_to_gui(self, signal, data=None): + """Communicate with the GUI using wxCallAfter and wxPublisher. + + Args: + signal (string): Unique signal string that informs the GUI for the + update process. + + data (string): Can be any string data to pass along with the + given signal. Default is None. + + Note: + UpdateThread supports 4 signals. + 1) download: The update process started + 2) correct: The update process completed successfully + 3) error: An error occured while downloading youtube-dl binary + 4) finish: The update thread is ready to join + + """ + CallAfter(Publisher.sendMessage, UPDATE_PUB_TOPIC, (signal, data)) diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/UpdateThread.py youtube-dlg-0.3.7/youtube_dl_gui/UpdateThread.py --- youtube-dlg-0.3.5/youtube_dl_gui/UpdateThread.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/UpdateThread.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -#! /usr/bin/env python - -from wx import CallAfter -from wx.lib.pubsub import setuparg1 -from wx.lib.pubsub import pub as Publisher - -from threading import Thread -from urllib2 import urlopen, URLError, HTTPError - -from .Utils import ( - fix_path, - file_exist, - makedir -) - -LATEST_YOUTUBE_DL = 'https://yt-dl.org/latest/' -PUBLISHER_TOPIC = 'update' -DOWNLOAD_TIMEOUT = 20 - -class UpdateThread(Thread): - - def __init__(self, updatePath, youtubeDLFile): - super(UpdateThread, self).__init__() - self.youtubeDLFile = youtubeDLFile - self.updatePath = fix_path(updatePath) - self.url = LATEST_YOUTUBE_DL + youtubeDLFile - self.check_path() - self.start() - - def run(self): - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, "Downloading latest youtube-dl. Please wait...") - try: - f = urlopen(self.url, timeout=DOWNLOAD_TIMEOUT) - with open(self.updatePath + self.youtubeDLFile, 'wb') as lf: - lf.write(f.read()) - msg = 'Youtube-dl downloaded correctly' - except (HTTPError, URLError, IOError) as e: - msg = 'Youtube-dl download failed ' + str(e) - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, msg) - CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, 'finish') - - def check_path(self): - if not file_exist(self.updatePath): - makedir(self.updatePath) - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/utils.py youtube-dlg-0.3.7/youtube_dl_gui/utils.py --- youtube-dlg-0.3.5/youtube_dl_gui/utils.py 1970-01-01 00:00:00.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/utils.py 2015-04-01 15:26:52.000000000 +0000 @@ -0,0 +1,448 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +"""Youtubedlg module that contains util functions. + +Attributes: + _RANDOM_OBJECT (object): Object that it's used as a default parameter. + + YOUTUBEDL_BIN (string): Youtube-dl binary filename. + +""" + +from __future__ import unicode_literals + +import os +import sys +import locale +import subprocess + +from .info import __appname__ + + +_RANDOM_OBJECT = object() + + +YOUTUBEDL_BIN = 'youtube-dl' +if os.name == 'nt': + YOUTUBEDL_BIN += '.exe' + + +def remove_shortcuts(path): + """Return given path after removing the shortcuts. """ + path = path.replace('~', os.path.expanduser('~')) + return path + + +def absolute_path(filename): + """Return absolute path to the given file. """ + path = os.path.realpath(os.path.abspath(filename)) + return os.path.dirname(path).decode(get_encoding(), 'ignore') + + +def get_lib_path(): + """Return path to the current file. """ + return os.path.dirname(__file__).decode(get_encoding(), 'ignore') + + +def open_dir(path): + """Open path using default file navigator. + Return True if path exists else False. """ + path = remove_shortcuts(path) + + if not os.path.exists(path): + return False + + if os.name == 'nt': + os.startfile(path) + else: + subprocess.call(('xdg-open', path)) + + return True + + +def encode_tuple(tuple_to_encode): + """Turn size tuple into string. """ + return '%s/%s' % (tuple_to_encode[0], tuple_to_encode[1]) + +def decode_tuple(encoded_tuple): + """Turn tuple string back to tuple. """ + s = encoded_tuple.split('/') + return int(s[0]), int(s[1]) + + +def check_path(path): + """Create path if not exist. """ + if not os.path.exists(path): + os.makedirs(path) + + +def get_config_path(): + """Return user config path. + + Note: + Windows = %AppData% + app_name + Linux = ~/.config + app_name + + """ + if os.name == 'nt': + path = os.getenv('APPDATA') + else: + path = os.path.join(os.path.expanduser('~'), '.config') + + return os.path.join(path, __appname__.lower()) + + +def shutdown_sys(password=None): + """Shuts down the system. + Returns True if no errors occur else False. + + Args: + password (string): SUDO password for linux. + + Note: + On Linux you need to provide sudo password if you don't + have elevated privileges. + + """ + _stderr = subprocess.PIPE + _stdin = None + info = None + encoding = get_encoding() + + if os.name == 'nt': + cmd = ['shutdown', '/s', '/t', '1'] + + # Hide subprocess window + info = subprocess.STARTUPINFO() + info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + else: + if password: + _stdin = subprocess.PIPE + password = ('%s\n' % password).encode(encoding) + cmd = ['sudo', '-S', '/sbin/shutdown', '-h', 'now'] + else: + cmd = ['/sbin/shutdown', '-h', 'now'] + + cmd = [item.encode(encoding, 'ignore') for item in cmd] + + shutdown_proc = subprocess.Popen(cmd, + stderr=_stderr, + stdin=_stdin, + startupinfo=info) + + output = shutdown_proc.communicate(password)[1] + + return not output or output == "Password:" + + +def to_string(data): + """Convert data to string. + Works for both Python2 & Python3. """ + return '%s' % data + + +def get_time(seconds): + """Convert given seconds to days, hours, minutes and seconds. + + Args: + seconds (float): Time in seconds. + + Returns: + Dictionary that contains the corresponding days, hours, minutes + and seconds of the given seconds. + + """ + dtime = dict(seconds=0, minutes=0, hours=0, days=0) + + dtime['days'] = int(seconds / 86400) + dtime['hours'] = int(seconds % 86400 / 3600) + dtime['minutes'] = int(seconds % 86400 % 3600 / 60) + dtime['seconds'] = int(seconds % 86400 % 3600 % 60) + + return dtime + + +def get_encoding(): + """Return system encoding. """ + try: + encoding = locale.getpreferredencoding() + 'TEST'.encode(encoding) + except: + encoding = 'UTF-8' + + return encoding + + +def get_locale_file(): + """Search for youtube-dlg locale file. + + Returns: + The path to youtube-dlg locale file if exists else None. + + Note: + Paths that get_locale_file() func searches. + + __main__ dir, library dir, /usr/share/youtube-dlg/locale + + """ + DIR_NAME = 'locale' + + SEARCH_DIRS = [ + os.path.join(absolute_path(sys.argv[0]), DIR_NAME), + os.path.join(get_lib_path(), DIR_NAME), + os.path.join('/usr', 'share', __appname__.lower(), DIR_NAME) + ] + + for directory in SEARCH_DIRS: + if os.path.isdir(directory): + return directory + + return None + + +def get_icon_file(): + """Search for youtube-dlg app icon. + + Returns: + The path to youtube-dlg icon file if exists, else returns None. + + Note: + Paths that get_icon_file() function searches. + + __main__ dir, library dir, /usr/share/pixmaps, $XDG_DATA_DIRS + + """ + SIZES = ('256x256', '128x128', '64x64', '48x48', '32x32', '16x16') + ICON_NAME = 'youtube-dl-gui_%s.png' + + ICONS_LIST = [ICON_NAME % size for size in SIZES] + + search_dirs = [ + os.path.join(absolute_path(sys.argv[0]), 'icons'), + os.path.join(get_lib_path(), 'icons'), + ] + + # Append $XDG_DATA_DIRS on search_dirs + path = os.getenv('XDG_DATA_DIRS') + + if path is not None: + for xdg_path in path.split(':'): + xdg_path = os.path.join(xdg_path, 'icons', 'hicolor') + + for size in SIZES: + search_dirs.append(os.path.join(xdg_path, size, 'apps')) + + # Also append /usr/share/pixmaps on search_dirs + search_dirs.append('/usr/share/pixmaps') + + for directory in search_dirs: + for icon in ICONS_LIST: + icon_file = os.path.join(directory, icon) + + if os.path.exists(icon_file): + return icon_file + + return None + + +class TwoWayOrderedDict(dict): + + """Custom data structure which implements a two way ordrered dictionary. + + TwoWayOrderedDict it's a custom dictionary in which you can get the + key:value relationship but you can also get the value:key relationship. + It also remembers the order in which the items were inserted and supports + almost all the features of the build-in dict. + + Note: + Ways to create a new dictionary. + + *) d = TwoWayOrderedDict(a=1, b=2) (Unordered) + *) d = TwoWayOrderedDict({'a': 1, 'b': 2}) (Unordered) + + *) d = TwoWayOrderedDict([('a', 1), ('b', 2)]) (Ordered) + *) d = TwoWayOrderedDict(zip(['a', 'b', 'c'], [1, 2, 3])) (Ordered) + + Examples: + >>> d = TwoWayOrderedDict(a=1, b=2) + >>> d['a'] + 1 + >>> d[1] + 'a' + >>> print d + TwoWayOrderedDict([('a', 1), ('b', 2)]) + + """ + + _PREV = 0 + _KEY = 1 + _NEXT = 2 + + def __init__(self, *args, **kwargs): + self._items = item = [] + self._items += [item, None, item] # Double linked list [prev, key, next] + self._items_map = {} # Map link list items into keys to speed up lookup + self._load(args, kwargs) + + def __setitem__(self, key, value): + if key in self: + # If self[key] == key for example {'b': 'b'} and we + # do d['b'] = 2 then we dont want to remove the 'b' + # from our linked list because we will lose the order + if self[key] in self._items_map and key != self[key]: + self._remove_mapped_key(self[key]) + + dict.__delitem__(self, self[key]) + + if value in self: + # If value == key we dont have to remove the + # value from the items_map because the value is + # the key and we want to keep the key in our + # linked list in order to keep the order. + if value in self._items_map and key != value: + self._remove_mapped_key(value) + + if self[value] in self._items_map: + self._remove_mapped_key(self[value]) + + # Check if self[value] is in the dict + # for cases like {'a': 'a'} where we + # have only one copy instead of {'a': 1, 1: 'a'} + if self[value] in self: + dict.__delitem__(self, self[value]) + + if key not in self._items_map: + last = self._items[self._PREV] # self._items prev always points to the last item + last[self._NEXT] = self._items[self._PREV] = self._items_map[key] = [last, key, self._items] + + dict.__setitem__(self, key, value) + dict.__setitem__(self, value, key) + + def __delitem__(self, key): + if self[key] in self._items_map: + self._remove_mapped_key(self[key]) + + if key in self._items_map: + self._remove_mapped_key(key) + + dict.__delitem__(self, self[key]) + + # Check if key is in the dict + # for cases like {'a': 'a'} where we + # have only one copy instead of {'a': 1, 1: 'a'} + if key in self: + dict.__delitem__(self, key) + + def __len__(self): + return len(self._items_map) + + def __iter__(self): + curr = self._items[self._NEXT] + while curr is not self._items: + yield curr[self._KEY] + curr = curr[self._NEXT] + + def __reversed__(self): + curr = self._items[self._PREV] + while curr is not self._items: + yield curr[self._KEY] + curr = curr[self._PREV] + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.items() == other.items() + return False + + def __ne__(self, other): + return not self == other + + def _remove_mapped_key(self, key): + """Remove the given key both from the linked list + and the map dictionary. """ + prev, __, next = self._items_map.pop(key) + prev[self._NEXT] = next + next[self._PREV] = prev + + def _load(self, args, kwargs): + """Load items into our dictionary. """ + for item in args: + if type(item) == dict: + item = item.iteritems() + + for key, value in item: + self[key] = value + + for key, value in kwargs.items(): + self[key] = value + + def items(self): + return [(key, self[key]) for key in self] + + def values(self): + return [self[key] for key in self] + + def keys(self): + return list(self) + + def pop(self, key, default=_RANDOM_OBJECT): + try: + value = self[key] + + del self[key] + except KeyError as error: + if default == _RANDOM_OBJECT: + raise error + + value = default + + return value + + def popitem(self, last=True): + """Remove and return a (key, value) pair from the dictionary. + If the dictionary is empty calling popitem() raises a KeyError. + + Args: + last (bool): When False popitem() will remove the first item + from the list. + + Note: + popitem() is useful to destructively iterate over a dictionary. + + Raises: + KeyError + + """ + if not self: + raise KeyError('popitem(): dictionary is empty') + + if last: + __, key, __ = self._items[self._PREV] + else: + __, key, __ = self._items[self._NEXT] + + value = self.pop(key) + + return key, value + + def update(self, *args, **kwargs): + self._load(args, kwargs) + + def setdefault(self, key, default=None): + try: + return self[key] + except KeyError: + self[key] = default + return default + + def copy(self): + return self.__class__(self.items()) + + def clear(self): + self._items = item = [] + self._items += [item, None, item] + self._items_map = {} + dict.clear(self) diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/Utils.py youtube-dlg-0.3.7/youtube_dl_gui/Utils.py --- youtube-dlg-0.3.5/youtube_dl_gui/Utils.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/Utils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -#! /usr/bin/env python - -import os -import sys -import locale -import subprocess - -def remove_empty_items(array): - return [x for x in array if x != ''] - -def remove_spaces(string): - return string.replace(' ', '') - -def string_to_array(string, char=' '): - return string.split(char) - -def preferredencoding(): - try: - pref = locale.getpreferredencoding() - u'TEST'.encode(pref) - except: - pref = 'UTF-8' - return pref - -def get_encoding(): - if sys.version_info >= (3, 0): - return None - if sys.platform == 'win32': - # Refer to http://stackoverflow.com/a/9951851/35070 - return preferredencoding() - return None - -def encode_list(data_list, encoding): - return [x.encode(encoding, 'ignore') for x in data_list] - -def video_is_dash(video): - return "DASH" in video - -def have_dash_audio(audio): - return audio != "NO SOUND" - -def remove_file(filename): - os.remove(filename) - -def get_path_seperator(): - return '\\' if os.name == 'nt' else '/' - -def fix_path(path): - if path != '' and path[-1:] != get_path_seperator(): - path += get_path_seperator() - return path - -def get_HOME(): - return os.path.expanduser("~") - -def add_PATH(path): - os.environ["PATH"] += os.pathsep + path - -def abs_path(path): - path_list = path.split(get_path_seperator()) - for i in range(len(path_list)): - if path_list[i] == '~': - path_list[i] = get_HOME() - return get_path_seperator().join(path_list) - -def file_exist(filename): - return os.path.exists(filename) - -def get_os_type(): - return os.name - -def get_filesize(path): - return os.path.getsize(path) - -def makedir(path): - os.makedirs(path) - -def icon_path(icon_path, file_path): - L = len(icon_path) - file_path = os.path.abspath(file_path).split(get_path_seperator()) - for index, item in reversed(list(enumerate(icon_path))): - file_path[index - L] = item - return get_path_seperator().join(file_path) - -def get_filename(path): - return path.split(get_path_seperator())[-1] - -def open_dir(path): - if os.name == 'nt': - os.startfile(path) - else: - subprocess.call(('xdg-open', path)) - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/version.py youtube-dlg-0.3.7/youtube_dl_gui/version.py --- youtube-dlg-0.3.5/youtube_dl_gui/version.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/version.py 2015-04-01 15:26:52.000000000 +0000 @@ -1 +1,5 @@ -__version__ = '0.3.5' +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +__version__ = '0.3.7' diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/YoutubeDLGUI.py youtube-dlg-0.3.7/youtube_dl_gui/YoutubeDLGUI.py --- youtube-dlg-0.3.5/youtube_dl_gui/YoutubeDLGUI.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/YoutubeDLGUI.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1285 +0,0 @@ -#! /usr/bin/env python - -''' -This file contains all gui classes -MainFrame -Custom wx.ListCtrl class -OptionsFrame - GeneralPanel - AudioPanel - ConnectionPanel - VideoPanel - FilesystemPanel - SubtitlesPanel - OtherPanel - UpdatePanel - AuthenticationPanel - PlaylistPanel -''' - -import wx -from wx.lib.pubsub import setuparg1 -from wx.lib.pubsub import pub as Publisher - -from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin - -from .version import __version__ -from .UpdateThread import UpdateThread -from .DownloadThread import DownloadManager -from .OptionsHandler import OptionsHandler -from .YoutubeDLInterpreter import YoutubeDLInterpreter -from .OutputHandler import DownloadHandler -from .LogManager import LogManager, LogGUI -from .Utils import ( - video_is_dash, - have_dash_audio, - get_os_type, - file_exist, - fix_path, - abs_path, - icon_path, - open_dir, - remove_spaces -) - -if get_os_type() == 'nt': - YOUTUBE_DL_FILENAME = 'youtube-dl.exe' -else: - YOUTUBE_DL_FILENAME = 'youtube-dl' - -TITLE = 'Youtube-dlG' - -AUDIOFORMATS = ["mp3", "wav", "aac", "m4a"] - -VIDEOFORMATS = ["default", - "mp4 [1280x720]", - "mp4 [640x360]", - "webm [640x360]", - "flv [400x240]", - "3gp [320x240]", - "mp4 1080p(DASH)", - "mp4 720p(DASH)", - "mp4 480p(DASH)", - "mp4 360p(DASH)"] - -DASH_AUDIO_FORMATS = ["NO SOUND", - "DASH m4a audio 128k", - "DASH webm audio 48k"] - -LANGUAGES = ["English", - "Greek", - "Portuguese", - "French", - "Italian", - "Russian", - "Spanish", - "German"] - -ICON = icon_path(['icons', 'ytube.png'], __file__) - -class MainFrame(wx.Frame): - - def __init__(self, parent=None, id=-1): - wx.Frame.__init__(self, parent, id, TITLE+' '+__version__, size=(600, 420)) - - # init gui - self.InitGUI() - - # bind events - self.Bind(wx.EVT_BUTTON, self.OnDownload, self.downloadButton) - self.Bind(wx.EVT_BUTTON, self.OnUpdate, self.updateButton) - self.Bind(wx.EVT_BUTTON, self.OnOptions, self.optionsButton) - self.Bind(wx.EVT_TEXT, self.OnTrackListChange, self.trackList) - self.Bind(wx.EVT_CLOSE, self.OnClose) - - # set app icon - icon = wx.Icon(ICON, wx.BITMAP_TYPE_ICO) - self.SetIcon(icon) - - # set publisher for update thread - Publisher.subscribe(self.update_handler, "update") - - # set publisher for download thread - Publisher.subscribe(self.download_handler, "download") - - # init Options and DownloadHandler objects - self.optionsList = OptionsHandler(self.status_bar_write) - self.downloadHandler = None - - # init log manager - self.logManager = None - if self.optionsList.enableLog: - self.logManager = LogManager( - self.optionsList.get_config_path(), - self.optionsList.writeTimeToLog - ) - - # init some thread variables - self.downloadThread = None - self.updateThread = None - - # init urlList for evt_text on self.trackList - self.urlList = [] - - # check & update libraries (youtube-dl) - self.check_if_youtube_dl_exist() - if (self.optionsList.autoUpdate): - self.status_bar_write("Auto update enable") - self.update_youtube_dl() - - def InitGUI(self): - self.panel = wx.Panel(self) - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - urlTextBox = wx.BoxSizer(wx.HORIZONTAL) - urlTextBox.Add(wx.StaticText(self.panel, label='URLs'), flag = wx.TOP, border=10) - mainBoxSizer.Add(urlTextBox, flag = wx.LEFT, border=20) - - trckListBox = wx.BoxSizer(wx.HORIZONTAL) - self.trackList = wx.TextCtrl(self.panel, size=(-1, 120), style = wx.TE_MULTILINE | wx.TE_DONTWRAP) - trckListBox.Add(self.trackList, 1) - mainBoxSizer.Add(trckListBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=20) - - buttonsBox = wx.BoxSizer(wx.HORIZONTAL) - self.downloadButton = wx.Button(self.panel, label='Download', size=(90, 30)) - buttonsBox.Add(self.downloadButton) - self.updateButton = wx.Button(self.panel, label='Update', size=(90, 30)) - buttonsBox.Add(self.updateButton, flag = wx.LEFT | wx.RIGHT, border=80) - self.optionsButton = wx.Button(self.panel, label='Options', size=(90, 30)) - buttonsBox.Add(self.optionsButton) - mainBoxSizer.Add(buttonsBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.TOP, border=10) - - stListBox = wx.BoxSizer(wx.HORIZONTAL) - self.statusList = ListCtrl(self.panel, style = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) - stListBox.Add(self.statusList, 1, flag = wx.EXPAND) - mainBoxSizer.Add(stListBox, 1, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=20) - - stBarBox = wx.BoxSizer(wx.HORIZONTAL) - self.statusBar = wx.StaticText(self.panel, label='Author: Sotiris Papadopoulos') - stBarBox.Add(self.statusBar, flag = wx.TOP | wx.BOTTOM, border=5) - mainBoxSizer.Add(stBarBox, flag = wx.LEFT, border=20) - - self.panel.SetSizer(mainBoxSizer) - - def check_if_youtube_dl_exist(self): - path = fix_path(self.optionsList.updatePath)+YOUTUBE_DL_FILENAME - if not file_exist(path): - self.status_bar_write("Youtube-dl is missing, trying to download it...") - self.update_youtube_dl() - - def update_youtube_dl(self): - self.downloadButton.Disable() - self.updateThread = UpdateThread(self.optionsList.updatePath, YOUTUBE_DL_FILENAME) - - def status_bar_write(self, msg): - self.statusBar.SetLabel(msg) - - def fin_task(self, msg): - self.status_bar_write(msg) - self.downloadButton.SetLabel('Download') - self.updateButton.Enable() - self.downloadThread.join() - self.downloadThread = None - self.downloadHandler = None - self.urlList = [] - self.finished_popup() - self.open_destination_dir() - - def open_destination_dir(self): - if self.optionsList.openDownloadDir: - open_dir(self.optionsList.savePath) - - def download_handler(self, msg): - self.downloadHandler.handle(msg) - if self.downloadHandler._has_closed(): - self.status_bar_write('Stoping downloads') - if self.downloadHandler._has_finished(): - if self.downloadHandler._has_error(): - if self.logManager != None: - msg = 'An error occured while downloading. See Options>Log.' - else: - msg = 'An error occured while downloading' - else: - msg = 'Done' - self.fin_task(msg) - - def update_handler(self, msg): - if msg.data == 'finish': - self.downloadButton.Enable() - self.updateThread.join() - self.updateThread = None - else: - self.status_bar_write(msg.data) - - def stop_download(self): - self.downloadThread.close() - - def load_tracklist(self, trackList): - for url in trackList: - url = remove_spaces(url) - if url != '': - self.urlList.append(url) - self.statusList._add_item(url) - - def start_download(self): - self.statusList._clear_list() - self.load_tracklist(self.trackList.GetValue().split('\n')) - if not self.statusList._is_empty(): - options = YoutubeDLInterpreter(self.optionsList, YOUTUBE_DL_FILENAME).get_options() - self.downloadThread = DownloadManager( - options, - self.statusList._get_items(), - self.optionsList.clearDashFiles, - self.logManager - ) - self.downloadHandler = DownloadHandler(self.statusList) - self.status_bar_write('Download started') - self.downloadButton.SetLabel('Stop') - self.updateButton.Disable() - else: - self.no_url_popup() - - def save_options(self): - self.optionsList.save_to_file() - - def finished_popup(self): - wx.MessageBox('Downloads completed.', 'Info', wx.OK | wx.ICON_INFORMATION) - - def no_url_popup(self): - wx.MessageBox('You need to provide at least one url.', 'Error', wx.OK | wx.ICON_EXCLAMATION) - - def OnTrackListChange(self, event): - if self.downloadThread != None: - ''' Get current url list from trackList textCtrl ''' - curList = self.trackList.GetValue().split('\n') - ''' For each url in current url list ''' - for url in curList: - ''' Remove spaces from url ''' - url = remove_spaces(url) - ''' If url is not in self.urlList (original downloads list) and url is not empty ''' - if url not in self.urlList and url != '': - ''' Add url into original download list ''' - self.urlList.append(url) - ''' Add handler for url ''' - self.downloadHandler._add_empty_handler() - ''' Add url into statusList ''' - self.statusList._add_item(url) - ''' Retrieve last item as {url:url, index:indexNo} ''' - item = self.statusList._get_last_item() - ''' Pass that item into downloadThread ''' - self.downloadThread._add_download_item(item) - - def OnDownload(self, event): - if self.downloadThread != None: - self.stop_download() - else: - self.start_download() - - def OnUpdate(self, event): - if (self.downloadThread == None and self.updateThread == None): - self.update_youtube_dl() - - def OnOptions(self, event): - optionsFrame = OptionsFrame(self.optionsList, parent=self, logger=self.logManager) - optionsFrame.Show() - - def OnClose(self, event): - self.save_options() - self.Destroy() - -class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): - ''' Custom ListCtrl class ''' - def __init__(self, parent=None, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): - wx.ListCtrl.__init__(self, parent, id, pos, size, style) - ListCtrlAutoWidthMixin.__init__(self) - self.InsertColumn(0, 'Video', width=150) - self.InsertColumn(1, 'Size', width=80) - self.InsertColumn(2, 'Percent', width=65) - self.InsertColumn(3, 'ETA', width=45) - self.InsertColumn(4, 'Speed', width=90) - self.InsertColumn(5, 'Status', width=160) - self.setResizeColumn(0) - self.ListIndex = 0 - - ''' Add single item on list ''' - def _add_item(self, item): - self.InsertStringItem(self.ListIndex, item) - self.ListIndex += 1 - - ''' Write data on index, column ''' - def _write_data(self, index, column, data): - self.SetStringItem(index, column, data) - - ''' Clear list and set index to 0''' - def _clear_list(self): - self.DeleteAllItems() - self.ListIndex = 0 - - ''' Return True if list is empty ''' - def _is_empty(self): - return self.ListIndex == 0 - - ''' Get last item inserted, Returns dictionary ''' - def _get_last_item(self): - data = {} - last_item = self.GetItem(itemId=self.ListIndex-1, col=0) - data['url'] = last_item.GetText() - data['index'] = self.ListIndex-1 - return data - - ''' Retrieve all items [start, self.ListIndex), Returns list ''' - def _get_items(self, start=0): - items = [] - for row in range(start, self.ListIndex): - item = self.GetItem(itemId=row, col=0) - data = {} - data['url'] = item.GetText() - data['index'] = row - items.append(data) - return items - -class LogPanel(wx.Panel): - - size = '' - path = '' - win_box_border = 0 - - def __init__(self, parent, optList, log): - wx.Panel.__init__(self, parent) - - self.SetBoxBorder() - self.optList = optList - self.log = log - self.set_data() - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - enLogBox = wx.BoxSizer(wx.HORIZONTAL) - self.enableLogChk = wx.CheckBox(self, label='Enable Log') - enLogBox.Add(self.enableLogChk) - mainBoxSizer.Add(enLogBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=20+self.win_box_border) - - wrTimeBox = wx.BoxSizer(wx.HORIZONTAL) - self.enableTimeChk = wx.CheckBox(self, label='Write Time') - wrTimeBox.Add(self.enableTimeChk) - mainBoxSizer.Add(wrTimeBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=5+self.win_box_border) - - butBox = wx.BoxSizer(wx.HORIZONTAL) - self.clearLogButton = wx.Button(self, label='Clear Log') - butBox.Add(self.clearLogButton) - self.viewLogButton = wx.Button(self, label='View Log') - butBox.Add(self.viewLogButton, flag = wx.LEFT, border=20) - mainBoxSizer.Add(butBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=15) - - if self.optList.enableLog: - self.SetDataSizers(mainBoxSizer) - - self.SetSizer(mainBoxSizer) - - self.Bind(wx.EVT_CHECKBOX, self.OnEnable, self.enableLogChk) - self.Bind(wx.EVT_CHECKBOX, self.OnTime, self.enableTimeChk) - self.Bind(wx.EVT_BUTTON, self.OnClear, self.clearLogButton) - self.Bind(wx.EVT_BUTTON, self.OnView, self.viewLogButton) - - def SetBoxBorder(self): - ''' Set border for windows ''' - if get_os_type() == 'nt': - self.win_box_border = 10 - - def SetDataSizers(self, box): - logPathText = wx.BoxSizer(wx.HORIZONTAL) - logPathText.Add(wx.StaticText(self, label='Path: ' + self.path)) - box.Add(logPathText, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=20) - logSizeText = wx.BoxSizer(wx.HORIZONTAL) - self.sizeText = wx.StaticText(self, label='Log Size: ' + self.size) - logSizeText.Add(self.sizeText) - box.Add(logSizeText, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=10) - - def set_data(self): - if self.log != None: - self.size = str(self.log.size()) + ' Bytes' - self.path = self.log.path - - def OnTime(self, event): - if self.log != None: - self.log.add_time = self.enableTimeChk.GetValue() - - def OnEnable(self, event): - if self.enableLogChk.GetValue(): - self.enableTimeChk.Enable() - else: - self.enableTimeChk.Disable() - self.restart_popup() - - def OnClear(self, event): - if self.log != None: - self.log.clear() - self.sizeText.SetLabel('Log Size: 0 Bytes') - - def OnView(self, event): - if self.log != None: - log_gui = LogGUI(self.path, parent=self) - log_gui.Show() - - def load_options(self): - self.enableLogChk.SetValue(self.optList.enableLog) - self.enableTimeChk.SetValue(self.optList.writeTimeToLog) - if self.optList.enableLog == False: - self.enableTimeChk.Disable() - if self.log == None: - self.clearLogButton.Disable() - self.viewLogButton.Disable() - - def save_options(self): - self.optList.enableLog = self.enableLogChk.GetValue() - self.optList.writeTimeToLog = self.enableTimeChk.GetValue() - - def restart_popup(self): - wx.MessageBox('Please restart ' + TITLE, 'Restart', wx.OK | wx.ICON_INFORMATION) - -class UpdatePanel(wx.Panel): - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - text = '''Enter the path where youtube-dlG should -download the latest youtube-dl.''' - - helpText = wx.BoxSizer(wx.HORIZONTAL) - helpText.Add(wx.StaticText(self, label=text), flag = wx.TOP, border=10) - mainBoxSizer.Add(helpText, flag = wx.LEFT, border=80) - - pathText = wx.BoxSizer(wx.HORIZONTAL) - pathText.Add(wx.StaticText(self, label='Path'), flag = wx.TOP, border=20) - mainBoxSizer.Add(pathText, flag = wx.LEFT, border=95) - - upPathBox = wx.BoxSizer(wx.HORIZONTAL) - self.updatePathBox = wx.TextCtrl(self) - upPathBox.Add(self.updatePathBox, 1, flag = wx.TOP, border=5) - mainBoxSizer.Add(upPathBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=90) - - autoUpBox = wx.BoxSizer(wx.HORIZONTAL) - self.autoUpdateChk = wx.CheckBox(self, label='Auto Update youtube-dl') - autoUpBox.Add(self.autoUpdateChk, flag = wx.TOP, border=30) - mainBoxSizer.Add(autoUpBox, flag = wx.LEFT, border=100) - - self.SetSizer(mainBoxSizer) - - def load_options(self): - self.updatePathBox.SetValue(self.optList.updatePath) - self.autoUpdateChk.SetValue(self.optList.autoUpdate) - - def save_options(self): - self.optList.updatePath = abs_path(self.updatePathBox.GetValue()) - self.optList.autoUpdate = self.autoUpdateChk.GetValue() - -class PlaylistPanel(wx.Panel): - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.optList = optList - mainBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Playlist Options'), wx.VERTICAL) - - mainBoxSizer.Add((-1, 10)) - - startBox = wx.BoxSizer(wx.HORIZONTAL) - startBox.Add(wx.StaticText(self, label='Start'), flag = wx.RIGHT, border=32) - self.startSpnr = wx.SpinCtrl(self, size=(60, -1)) - self.startSpnr.SetRange(1, 999) - startBox.Add(self.startSpnr) - mainBoxSizer.Add(startBox, flag = wx.TOP | wx.LEFT, border=20) - - stopBox = wx.BoxSizer(wx.HORIZONTAL) - stopBox.Add(wx.StaticText(self, label='Stop'), flag = wx.RIGHT, border=34) - self.stopSpnr = wx.SpinCtrl(self, size=(60, -1)) - self.stopSpnr.SetRange(0, 999) - stopBox.Add(self.stopSpnr) - mainBoxSizer.Add(stopBox, flag = wx.TOP | wx.LEFT, border=20) - - maxBox = wx.BoxSizer(wx.HORIZONTAL) - maxBox.Add(wx.StaticText(self, label='Max DLs'), flag = wx.RIGHT, border=self.get_border()) - self.maxSpnr = wx.SpinCtrl(self, size=(60, -1)) - self.maxSpnr.SetRange(0, 999) - maxBox.Add(self.maxSpnr) - mainBoxSizer.Add(maxBox, flag = wx.TOP | wx.LEFT, border=20) - - self.SetSizer(mainBoxSizer) - - def get_border(self): - if get_os_type() == 'nt': - return 16 - return 10 - - def load_options(self): - self.startSpnr.SetValue(self.optList.startTrack) - self.stopSpnr.SetValue(self.optList.endTrack) - self.maxSpnr.SetValue(self.optList.maxDownloads) - - def save_options(self): - self.optList.startTrack = self.startSpnr.GetValue() - self.optList.endTrack = self.stopSpnr.GetValue() - self.optList.maxDownloads = self.maxSpnr.GetValue() - -class ConnectionPanel(wx.Panel): - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - retBox = wx.BoxSizer(wx.HORIZONTAL) - retBox.Add(wx.StaticText(self, label='Retries'), flag = wx.RIGHT, border=8) - self.retriesSpnr = wx.SpinCtrl(self, size=(50, -1)) - self.retriesSpnr.SetRange(1, 99) - retBox.Add(self.retriesSpnr) - mainBoxSizer.Add(retBox, flag = wx.LEFT | wx.TOP, border=10) - - uaText = wx.BoxSizer(wx.HORIZONTAL) - uaText.Add(wx.StaticText(self, label='User Agent'), flag = wx.LEFT, border=15) - mainBoxSizer.Add(uaText, flag = wx.TOP, border=10) - - mainBoxSizer.Add((-1, 5)) - - uaBox = wx.BoxSizer(wx.HORIZONTAL) - self.userAgentBox = wx.TextCtrl(self) - uaBox.Add(self.userAgentBox, 1, flag = wx.LEFT, border=10) - mainBoxSizer.Add(uaBox, flag = wx.EXPAND | wx.RIGHT, border=200) - - refText = wx.BoxSizer(wx.HORIZONTAL) - refText.Add(wx.StaticText(self, label='Referer'), flag = wx.LEFT, border=15) - mainBoxSizer.Add(refText, flag = wx.TOP, border=10) - - mainBoxSizer.Add((-1, 5)) - - refBox = wx.BoxSizer(wx.HORIZONTAL) - self.refererBox = wx.TextCtrl(self) - refBox.Add(self.refererBox, 1, flag = wx.LEFT, border=10) - mainBoxSizer.Add(refBox, flag = wx.EXPAND | wx.RIGHT, border=200) - - prxyText = wx.BoxSizer(wx.HORIZONTAL) - prxyText.Add(wx.StaticText(self, label='Proxy'), flag = wx.LEFT, border=15) - mainBoxSizer.Add(prxyText, flag = wx.TOP, border=10) - - mainBoxSizer.Add((-1, 5)) - - prxyBox = wx.BoxSizer(wx.HORIZONTAL) - self.proxyBox = wx.TextCtrl(self) - prxyBox.Add(self.proxyBox, 1, flag = wx.LEFT, border=10) - mainBoxSizer.Add(prxyBox, flag = wx.EXPAND | wx.RIGHT, border=100) - - self.SetSizer(mainBoxSizer) - - def load_options(self): - self.userAgentBox.SetValue(self.optList.userAgent) - self.refererBox.SetValue(self.optList.referer) - self.proxyBox.SetValue(self.optList.proxy) - self.retriesSpnr.SetValue(self.optList.retries) - - def save_options(self): - self.optList.userAgent = self.userAgentBox.GetValue() - self.optList.referer = self.refererBox.GetValue() - self.optList.proxy = self.proxyBox.GetValue() - self.optList.retries = self.retriesSpnr.GetValue() - -class AuthenticationPanel(wx.Panel): - - def __init__(self, parent, optList): - wx.Panel.__init__(self,parent) - - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - usrTextBox = wx.BoxSizer(wx.HORIZONTAL) - usrTextBox.Add(wx.StaticText(self, label='Username')) - mainBoxSizer.Add(usrTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=15) - - usrBox = wx.BoxSizer(wx.HORIZONTAL) - self.usernameBox = wx.TextCtrl(self, size=(-1, 25)) - usrBox.Add(self.usernameBox, 1, flag = wx.TOP, border=5) - mainBoxSizer.Add(usrBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=160) - - passTextBox = wx.BoxSizer(wx.HORIZONTAL) - passTextBox.Add(wx.StaticText(self, label='Password')) - mainBoxSizer.Add(passTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=15) - - passBox = wx.BoxSizer(wx.HORIZONTAL) - self.passwordBox = wx.TextCtrl(self, size=(-1, 25), style = wx.TE_PASSWORD) - passBox.Add(self.passwordBox, 1, flag = wx.TOP, border=5) - mainBoxSizer.Add(passBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=160) - - vPassTextBox = wx.BoxSizer(wx.HORIZONTAL) - vPassTextBox.Add(wx.StaticText(self, label='Video Password (vimeo, smotri)')) - mainBoxSizer.Add(vPassTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=15) - - vPassBox = wx.BoxSizer(wx.HORIZONTAL) - self.videopassBox = wx.TextCtrl(self, size=(-1, 25), style = wx.TE_PASSWORD) - vPassBox.Add(self.videopassBox, 1, flag = wx.TOP, border=5) - mainBoxSizer.Add(vPassBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=160) - - self.SetSizer(mainBoxSizer) - - def load_options(self): - self.usernameBox.SetValue(self.optList.username) - self.passwordBox.SetValue(self.optList.password) - self.videopassBox.SetValue(self.optList.videoPass) - - def save_options(self): - self.optList.username = self.usernameBox.GetValue() - self.optList.password = self.passwordBox.GetValue() - self.optList.videoPass = self.videopassBox.GetValue() - -class AudioPanel(wx.Panel): - - win_box_border = 0 - quality = ['high', 'mid', 'low'] - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.SetBoxBorder() - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - toaBox = wx.BoxSizer(wx.HORIZONTAL) - self.toAudioChk = wx.CheckBox(self, label='Convert to Audio') - toaBox.Add(self.toAudioChk) - mainBoxSizer.Add(toaBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=15+self.win_box_border) - - keepVBox = wx.BoxSizer(wx.HORIZONTAL) - self.keepVideoChk = wx.CheckBox(self, label='Keep Video') - keepVBox.Add(self.keepVideoChk) - mainBoxSizer.Add(keepVBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=5+self.win_box_border) - - afTextBox = wx.BoxSizer(wx.HORIZONTAL) - afTextBox.Add(wx.StaticText(self, label='Audio Format')) - mainBoxSizer.Add(afTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=10) - - afComboBox = wx.BoxSizer(wx.HORIZONTAL) - self.audioFormatCombo = wx.ComboBox(self, choices=AUDIOFORMATS, size=(160, 30)) - afComboBox.Add(self.audioFormatCombo) - mainBoxSizer.Add(afComboBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=5) - - aqTextBox = wx.BoxSizer(wx.HORIZONTAL) - aqTextBox.Add(wx.StaticText(self, label='Audio Quality')) - mainBoxSizer.Add(aqTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=10) - - aqComboBox = wx.BoxSizer(wx.HORIZONTAL) - self.audioQualityCombo = wx.ComboBox(self, choices=self.quality, size=(80, 25)) - aqComboBox.Add(self.audioQualityCombo) - mainBoxSizer.Add(aqComboBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=5) - - self.SetSizer(mainBoxSizer) - - self.Bind(wx.EVT_CHECKBOX, self.OnAudioCheck, self.toAudioChk) - - def SetBoxBorder(self): - ''' Set border for windows ''' - if get_os_type() == 'nt': - self.win_box_border = 5 - - def OnAudioCheck(self, event): - if self.toAudioChk.GetValue(): - self.keepVideoChk.Enable() - self.audioFormatCombo.Enable() - self.audioQualityCombo.Enable() - else: - self.keepVideoChk.Disable() - self.audioFormatCombo.Disable() - self.audioQualityCombo.Disable() - - def load_options(self): - self.toAudioChk.SetValue(self.optList.toAudio) - self.keepVideoChk.SetValue(self.optList.keepVideo) - self.audioFormatCombo.SetValue(self.optList.audioFormat) - self.audioQualityCombo.SetValue(self.optList.audioQuality) - if self.optList.toAudio == False: - self.keepVideoChk.Disable() - self.audioFormatCombo.Disable() - self.audioQualityCombo.Disable() - - def save_options(self): - self.optList.toAudio = self.toAudioChk.GetValue() - self.optList.keepVideo = self.keepVideoChk.GetValue() - self.optList.audioFormat = self.audioFormatCombo.GetValue() - self.optList.audioQuality = self.audioQualityCombo.GetValue() - -class VideoPanel(wx.Panel): - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - vfTextBox = wx.BoxSizer(wx.HORIZONTAL) - vfTextBox.Add(wx.StaticText(self, label='Video Format')) - mainBoxSizer.Add(vfTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=20) - - vfComboBox = wx.BoxSizer(wx.HORIZONTAL) - self.videoFormatCombo = wx.ComboBox(self, choices=VIDEOFORMATS, size=(160, 30)) - vfComboBox.Add(self.videoFormatCombo) - mainBoxSizer.Add(vfComboBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=5) - - daTextBox = wx.BoxSizer(wx.HORIZONTAL) - daTextBox.Add(wx.StaticText(self, label='DASH Audio')) - mainBoxSizer.Add(daTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=10) - - daComboBox = wx.BoxSizer(wx.HORIZONTAL) - self.dashAudioFormatCombo = wx.ComboBox(self, choices=DASH_AUDIO_FORMATS, size=(160, 30)) - daComboBox.Add(self.dashAudioFormatCombo) - mainBoxSizer.Add(daComboBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=5) - - clrDashBox = wx.BoxSizer(wx.HORIZONTAL) - self.clearDashFilesChk = wx.CheckBox(self, label='Clear DASH audio/video files') - clrDashBox.Add(self.clearDashFilesChk) - mainBoxSizer.Add(clrDashBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=20) - - self.SetSizer(mainBoxSizer) - - self.Bind(wx.EVT_COMBOBOX, self.OnVideoFormatPick, self.videoFormatCombo) - self.Bind(wx.EVT_COMBOBOX, self.OnAudioFormatPick, self.dashAudioFormatCombo) - - def OnAudioFormatPick(self, event): - if have_dash_audio(self.dashAudioFormatCombo.GetValue()): - self.clearDashFilesChk.Enable() - else: - self.clearDashFilesChk.SetValue(False) - self.clearDashFilesChk.Disable() - - def OnVideoFormatPick(self, event): - if video_is_dash(self.videoFormatCombo.GetValue()): - self.dashAudioFormatCombo.Enable() - if have_dash_audio(self.dashAudioFormatCombo.GetValue()): - self.clearDashFilesChk.Enable() - else: - self.clearDashFilesChk.SetValue(False) - self.clearDashFilesChk.Disable() - self.dashAudioFormatCombo.Disable() - - def load_options(self): - self.videoFormatCombo.SetValue(self.optList.videoFormat) - self.dashAudioFormatCombo.SetValue(self.optList.dashAudioFormat) - self.clearDashFilesChk.SetValue(self.optList.clearDashFiles) - if not video_is_dash(self.optList.videoFormat): - self.dashAudioFormatCombo.Disable() - if not have_dash_audio(self.optList.dashAudioFormat): - self.clearDashFilesChk.SetValue(False) - self.clearDashFilesChk.Disable() - - def save_options(self): - self.optList.videoFormat = self.videoFormatCombo.GetValue() - self.optList.dashAudioFormat = self.dashAudioFormatCombo.GetValue() - self.optList.clearDashFiles = self.clearDashFilesChk.GetValue() - -class OutputPanel(wx.Panel): - - win_box_border = 0 - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.SetBoxBorder() - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - idBox = wx.BoxSizer(wx.HORIZONTAL) - self.idAsNameChk = wx.CheckBox(self, label='ID as Name') - idBox.Add(self.idAsNameChk, flag = wx.LEFT, border=5) - mainBoxSizer.Add(idBox, flag = wx.TOP, border=15) - - titleBox = wx.BoxSizer(wx.HORIZONTAL) - self.titleBoxChk = wx.CheckBox(self, label='Title as Name') - titleBox.Add(self.titleBoxChk, flag = wx.LEFT, border=5) - mainBoxSizer.Add(titleBox, flag = wx.TOP, border=5+self.win_box_border) - - customChkBox = wx.BoxSizer(wx.HORIZONTAL) - self.customTitleChk = wx.CheckBox(self, label='Custom Template (youtube-dl)') - customChkBox.Add(self.customTitleChk, flag = wx.LEFT, border=5) - mainBoxSizer.Add(customChkBox, flag = wx.TOP, border=5+self.win_box_border) - - mainBoxSizer.Add((-1, 10)) - - customBox = wx.BoxSizer(wx.HORIZONTAL) - self.customTitleBox = wx.TextCtrl(self) - customBox.Add(self.customTitleBox, 1, flag = wx.RIGHT, border=300) - mainBoxSizer.Add(customBox, flag = wx.EXPAND | wx.LEFT, border=5) - - self.SetSizer(mainBoxSizer) - - self.Bind(wx.EVT_CHECKBOX, self.OnId, self.idAsNameChk) - self.Bind(wx.EVT_CHECKBOX, self.OnTitle, self.titleBoxChk) - self.Bind(wx.EVT_CHECKBOX, self.OnCustom, self.customTitleChk) - - def OnId(self, event): - self.group_load('id') - - def OnTitle(self, event): - self.group_load('title') - - def OnCustom(self, event): - self.group_load('custom') - - def SetBoxBorder(self): - ''' Set border for windows ''' - if get_os_type() == 'nt': - self.win_box_border = 10 - - def group_load(self, oformat): - if oformat == 'id': - self.idAsNameChk.SetValue(True) - self.titleBoxChk.SetValue(False) - self.customTitleChk.SetValue(False) - self.customTitleBox.Disable() - elif oformat == 'title': - self.idAsNameChk.SetValue(False) - self.titleBoxChk.SetValue(True) - self.customTitleChk.SetValue(False) - self.customTitleBox.Disable() - elif oformat == 'custom': - self.idAsNameChk.SetValue(False) - self.titleBoxChk.SetValue(False) - self.customTitleChk.SetValue(True) - self.customTitleBox.Enable() - - def get_output_format(self): - if self.idAsNameChk.GetValue(): - return 'id' - elif self.titleBoxChk.GetValue(): - return 'title' - elif self.customTitleChk.GetValue(): - return 'custom' - - def load_options(self): - self.group_load(self.optList.outputFormat) - self.customTitleBox.SetValue(self.optList.outputTemplate) - - def save_options(self): - self.optList.outputTemplate = self.customTitleBox.GetValue() - self.optList.outputFormat = self.get_output_format() - -class FilesystemPanel(wx.Panel): - - win_box_border = 0 - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.SetBoxBorder() - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.HORIZONTAL) - leftBoxSizer = wx.BoxSizer(wx.VERTICAL) - rightBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Filesize (e.g. 50k or 44.6m)'), wx.VERTICAL) - - self.SetLeftBox(leftBoxSizer) - self.SetRightBox(rightBoxSizer) - - mainBoxSizer.Add(leftBoxSizer, 1, flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=10) - mainBoxSizer.Add(rightBoxSizer, 1, flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=10) - - self.SetSizer(mainBoxSizer) - - def SetBoxBorder(self): - ''' Set border for windows ''' - if get_os_type() == 'nt': - self.win_box_border = 15 - - def SetLeftBox(self, box): - ignrBox = wx.BoxSizer(wx.HORIZONTAL) - self.ignoreErrorsChk = wx.CheckBox(self, label='Ignore Errors') - ignrBox.Add(self.ignoreErrorsChk, flag = wx.LEFT, border=5) - box.Add(ignrBox, flag = wx.TOP, border=20) - - wrtDescBox = wx.BoxSizer(wx.HORIZONTAL) - self.writeDescriptionChk = wx.CheckBox(self, label='Write description to file') - wrtDescBox.Add(self.writeDescriptionChk, flag = wx.LEFT, border=5) - box.Add(wrtDescBox, flag = wx.TOP, border=5+self.win_box_border) - - wrtInfoBox = wx.BoxSizer(wx.HORIZONTAL) - self.writeInfoChk = wx.CheckBox(self, label='Write info to (.json) file') - wrtInfoBox.Add(self.writeInfoChk, flag = wx.LEFT, border=5) - box.Add(wrtInfoBox, flag = wx.TOP, border=5+self.win_box_border) - - wrtThumBox = wx.BoxSizer(wx.HORIZONTAL) - self.writeThumbnailChk = wx.CheckBox(self, label='Write thumbnail to disk') - wrtThumBox.Add(self.writeThumbnailChk, flag = wx.LEFT, border=5) - box.Add(wrtThumBox, flag = wx.TOP, border=5+self.win_box_border) - - openDirBox = wx.BoxSizer(wx.HORIZONTAL) - self.openDirChk = wx.CheckBox(self, label='Open destination folder when done') - openDirBox.Add(self.openDirChk, flag = wx.LEFT, border=5) - box.Add(openDirBox, flag = wx.TOP, border=5+self.win_box_border) - - def SetRightBox(self, box): - minBox = wx.BoxSizer(wx.HORIZONTAL) - minBox.Add(wx.StaticText(self, label='Min'), flag = wx.RIGHT, border=12) - self.minFilesizeBox = wx.TextCtrl(self, size=(80, -1)) - minBox.Add(self.minFilesizeBox) - box.Add(minBox, flag = wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, border=10) - - maxBox = wx.BoxSizer(wx.HORIZONTAL) - maxBox.Add(wx.StaticText(self, label='Max'), flag = wx.RIGHT, border=8) - self.maxFilesizeBox = wx.TextCtrl(self, size=(80, -1)) - maxBox.Add(self.maxFilesizeBox) - box.Add(maxBox, flag = wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, border=10) - - def load_options(self): - self.writeDescriptionChk.SetValue(self.optList.writeDescription) - self.writeInfoChk.SetValue(self.optList.writeInfo) - self.writeThumbnailChk.SetValue(self.optList.writeThumbnail) - self.ignoreErrorsChk.SetValue(self.optList.ignoreErrors) - self.openDirChk.SetValue(self.optList.openDownloadDir) - self.minFilesizeBox.SetValue(self.optList.minFileSize) - self.maxFilesizeBox.SetValue(self.optList.maxFileSize) - - def save_options(self): - self.optList.writeDescription = self.writeDescriptionChk.GetValue() - self.optList.writeInfo = self.writeInfoChk.GetValue() - self.optList.writeThumbnail = self.writeThumbnailChk.GetValue() - self.optList.ignoreErrors = self.ignoreErrorsChk.GetValue() - self.optList.openDownloadDir = self.openDirChk.GetValue() - self.optList.minFileSize = self.minFilesizeBox.GetValue() - self.optList.maxFileSize = self.maxFilesizeBox.GetValue() - self.check_input() - - def check_input(self): - self.optList.minFileSize.replace('-', '') - self.optList.maxFileSize.replace('-', '') - if self.optList.minFileSize == '': - self.optList.minFileSize = '0' - if self.optList.maxFileSize == '': - self.optList.maxFileSize = '0' - -class SubtitlesPanel(wx.Panel): - - win_box_border = 0 - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.SetBoxBorder() - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - dlSubsBox = wx.BoxSizer(wx.HORIZONTAL) - self.writeSubsChk = wx.CheckBox(self, label='Download subtitle file by language') - dlSubsBox.Add(self.writeSubsChk, flag = wx.LEFT, border=10) - mainBoxSizer.Add(dlSubsBox, flag = wx.TOP, border=15) - - dlAllSubBox = wx.BoxSizer(wx.HORIZONTAL) - self.writeAllSubsChk = wx.CheckBox(self, label='Download all available subtitles') - dlAllSubBox.Add(self.writeAllSubsChk, flag = wx.LEFT, border=10) - mainBoxSizer.Add(dlAllSubBox, flag = wx.TOP, border=5+self.win_box_border) - - dlAutoSubBox = wx.BoxSizer(wx.HORIZONTAL) - self.writeAutoSubsChk = wx.CheckBox(self, label='Download automatic subtitle file (YOUTUBE ONLY)') - dlAutoSubBox.Add(self.writeAutoSubsChk, flag = wx.LEFT, border=10) - mainBoxSizer.Add(dlAutoSubBox, flag = wx.TOP, border=5+self.win_box_border) - - embSubBox = wx.BoxSizer(wx.HORIZONTAL) - self.embedSubsChk = wx.CheckBox(self, label='Embed subtitles in the video (only for mp4 videos)') - self.embedSubsChk.Disable() - embSubBox.Add(self.embedSubsChk, flag = wx.LEFT, border=10) - mainBoxSizer.Add(embSubBox, flag = wx.TOP, border=5+self.win_box_border) - - slangTextBox = wx.BoxSizer(wx.HORIZONTAL) - slangTextBox.Add(wx.StaticText(self, label='Subtitles Language'), flag = wx.LEFT, border=15) - mainBoxSizer.Add(slangTextBox, flag = wx.TOP, border=10+self.win_box_border) - - slangBox = wx.BoxSizer(wx.HORIZONTAL) - self.subsLangCombo = wx.ComboBox(self, choices=LANGUAGES, size=(140, 30)) - slangBox.Add(self.subsLangCombo, flag = wx.LEFT, border=10) - mainBoxSizer.Add(slangBox, flag = wx.TOP, border=5) - - self.SetSizer(mainBoxSizer) - - self.Bind(wx.EVT_CHECKBOX, self.OnWriteSubsChk, self.writeSubsChk) - self.Bind(wx.EVT_CHECKBOX, self.OnWriteAllSubsChk, self.writeAllSubsChk) - self.Bind(wx.EVT_CHECKBOX, self.OnWriteAutoSubsChk, self.writeAutoSubsChk) - - def SetBoxBorder(self): - ''' Set border for windows ''' - if get_os_type() == 'nt': - self.win_box_border = 10 - - def subs_are_on(self): - return self.writeAutoSubsChk.GetValue() or self.writeSubsChk.GetValue() - - def OnWriteAutoSubsChk(self, event): - if self.writeAutoSubsChk.GetValue(): - self.writeAllSubsChk.Disable() - self.writeSubsChk.Disable() - self.subsLangCombo.Disable() - self.embedSubsChk.Enable() - else: - self.writeAllSubsChk.Enable() - self.writeSubsChk.Enable() - self.subsLangCombo.Enable() - self.embedSubsChk.Disable() - self.embedSubsChk.SetValue(False) - - def OnWriteSubsChk(self, event): - if self.writeSubsChk.GetValue(): - self.writeAllSubsChk.Disable() - self.writeAutoSubsChk.Disable() - self.embedSubsChk.Enable() - else: - self.writeAllSubsChk.Enable() - self.writeAutoSubsChk.Enable() - self.embedSubsChk.Disable() - self.embedSubsChk.SetValue(False) - - def OnWriteAllSubsChk(self, event): - if self.writeAllSubsChk.GetValue(): - self.writeSubsChk.Disable() - self.subsLangCombo.Disable() - self.writeAutoSubsChk.Disable() - else: - self.writeSubsChk.Enable() - self.subsLangCombo.Enable() - self.writeAutoSubsChk.Enable() - - def load_options(self): - self.writeSubsChk.Enable() - self.subsLangCombo.Enable() - self.writeAllSubsChk.Enable() - self.writeAutoSubsChk.Enable() - self.writeSubsChk.SetValue(self.optList.writeSubs) - self.writeAllSubsChk.SetValue(self.optList.writeAllSubs) - self.subsLangCombo.SetValue(self.optList.subsLang) - self.writeAutoSubsChk.SetValue(self.optList.writeAutoSubs) - self.embedSubsChk.SetValue(self.optList.embedSubs) - if self.optList.writeSubs: - self.writeAllSubsChk.Disable() - self.writeAutoSubsChk.Disable() - self.embedSubsChk.Enable() - if self.optList.writeAllSubs: - self.writeSubsChk.Disable() - self.subsLangCombo.Disable() - self.writeAutoSubsChk.Disable() - if self.optList.writeAutoSubs: - self.writeAllSubsChk.Disable() - self.writeSubsChk.Disable() - self.subsLangCombo.Disable() - self.embedSubsChk.Enable() - if not self.subs_are_on(): - self.embedSubsChk.Disable() - - def save_options(self): - self.optList.writeSubs = self.writeSubsChk.GetValue() - self.optList.writeAllSubs = self.writeAllSubsChk.GetValue() - self.optList.subsLang = self.subsLangCombo.GetValue() - self.optList.writeAutoSubs = self.writeAutoSubsChk.GetValue() - self.optList.embedSubs = self.embedSubsChk.GetValue() - -class GeneralPanel(wx.Panel): - - def __init__(self, parent, optList, resetHandler): - wx.Panel.__init__(self, parent) - - self.optList = optList - self.resetHandler = resetHandler - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - svTextBox = wx.BoxSizer(wx.HORIZONTAL) - svTextBox.Add(wx.StaticText(self, label='Save Path')) - mainBoxSizer.Add(svTextBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=20) - - svPathBox = wx.BoxSizer(wx.HORIZONTAL) - self.savePathBox = wx.TextCtrl(self) - svPathBox.Add(self.savePathBox, 1, flag = wx.TOP, border=10) - mainBoxSizer.Add(svPathBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=40) - - buttonsBox = wx.BoxSizer(wx.HORIZONTAL) - self.aboutButton = wx.Button(self, label='About', size=(110, 40)) - buttonsBox.Add(self.aboutButton) - self.openButton = wx.Button(self, label='Open', size=(110, 40)) - buttonsBox.Add(self.openButton, flag = wx.LEFT | wx.RIGHT, border=50) - self.resetButton = wx.Button(self, label='Reset Options', size=(110, 40)) - buttonsBox.Add(self.resetButton) - mainBoxSizer.Add(buttonsBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.TOP, border=20) - - setngsBox = wx.BoxSizer(wx.HORIZONTAL) - text = 'Settings: ' + self.optList.settings_abs_path - setngsBox.Add(wx.StaticText(self, label=text), flag = wx.TOP, border=20) - mainBoxSizer.Add(setngsBox, flag = wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, border=10) - - self.SetSizer(mainBoxSizer) - - self.Bind(wx.EVT_BUTTON, self.OnAbout, self.aboutButton) - self.Bind(wx.EVT_BUTTON, self.OnOpen, self.openButton) - self.Bind(wx.EVT_BUTTON, self.OnReset, self.resetButton) - - def OnReset(self, event): - self.resetHandler() - - def OnOpen(self, event): - dlg = wx.DirDialog(None, "Choose directory") - if dlg.ShowModal() == wx.ID_OK: - self.savePathBox.SetValue(dlg.GetPath()) - dlg.Destroy() - - def OnAbout(self, event): - description = '''A cross platform front-end GUI of -the popular youtube-dl written in Python.''' - - license = '''This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to ''' - - info = wx.AboutDialogInfo() - - info.SetIcon(wx.Icon(ICON, wx.BITMAP_TYPE_ICO)) - info.SetName(TITLE) - info.SetVersion(__version__) - info.SetDescription(description) - info.SetWebSite('http://mrs0m30n3.github.io/youtube-dl-gui/') - info.SetLicense(license) - info.AddDeveloper('Sotiris Papadopoulos') - wx.AboutBox(info) - - def load_options(self): - self.savePathBox.SetValue(self.optList.savePath) - - def save_options(self): - self.optList.savePath = abs_path(self.savePathBox.GetValue()) - -class OtherPanel(wx.Panel): - - def __init__(self, parent, optList): - wx.Panel.__init__(self, parent) - - self.optList = optList - mainBoxSizer = wx.BoxSizer(wx.VERTICAL) - - textBox = wx.BoxSizer(wx.HORIZONTAL) - textBox.Add(wx.StaticText(self, label='Command line arguments (e.g. --help)'), flag = wx.TOP, border=30) - mainBoxSizer.Add(textBox, flag = wx.LEFT, border=50) - - inputBox = wx.BoxSizer(wx.HORIZONTAL) - self.cmdArgsBox = wx.TextCtrl(self) - inputBox.Add(self.cmdArgsBox, 1, flag = wx.TOP, border=10) - mainBoxSizer.Add(inputBox, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border=50) - - self.SetSizer(mainBoxSizer) - - def load_options(self): - self.cmdArgsBox.SetValue(self.optList.cmdArgs) - - def save_options(self): - self.optList.cmdArgs = self.cmdArgsBox.GetValue() - -class OptionsFrame(wx.Frame): - - def __init__(self, optionsList, parent=None, id=-1, logger=None): - wx.Frame.__init__(self, parent, id, "Options", size=(580, 250)) - - self.optionsList = optionsList - - panel = wx.Panel(self) - notebook = wx.Notebook(panel) - - self.generalTab = GeneralPanel(notebook, self.optionsList, self.reset) - self.audioTab = AudioPanel(notebook, self.optionsList) - self.connectionTab = ConnectionPanel(notebook, self.optionsList) - self.videoTab = VideoPanel(notebook, self.optionsList) - self.filesysTab = FilesystemPanel(notebook, self.optionsList) - self.subtitlesTab = SubtitlesPanel(notebook, self.optionsList) - self.otherTab = OtherPanel(notebook, self.optionsList) - self.updateTab = UpdatePanel(notebook, self.optionsList) - self.authTab = AuthenticationPanel(notebook, self.optionsList) - self.videoselTab = PlaylistPanel(notebook, self.optionsList) - self.logTab = LogPanel(notebook, self.optionsList, logger) - self.outputTab = OutputPanel(notebook, self.optionsList) - - notebook.AddPage(self.generalTab, "General") - notebook.AddPage(self.videoTab, "Video") - notebook.AddPage(self.audioTab, "Audio") - notebook.AddPage(self.outputTab, "Output") - notebook.AddPage(self.videoselTab, "Playlist") - notebook.AddPage(self.subtitlesTab, "Subtitles") - notebook.AddPage(self.filesysTab, "Filesystem") - notebook.AddPage(self.connectionTab, "Connection") - notebook.AddPage(self.authTab, "Authentication") - notebook.AddPage(self.updateTab, "Update") - notebook.AddPage(self.logTab, "Log") - notebook.AddPage(self.otherTab, "Commands") - - sizer = wx.BoxSizer() - sizer.Add(notebook, 1, wx.EXPAND) - panel.SetSizer(sizer) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - self.load_all_options() - - def OnClose(self, event): - self.save_all_options() - if not file_exist(fix_path(self.optionsList.updatePath)+YOUTUBE_DL_FILENAME): - self.wrong_youtubedl_path() - self.Destroy() - - def wrong_youtubedl_path(self): - text = '''The path under Options>Update is invalid -please do one of the following: - *) restart youtube-dlG - *) click the update button - *) change the path to point where youtube-dl is''' - wx.MessageBox(text, 'Error', wx.OK | wx.ICON_EXCLAMATION) - - def reset(self): - self.optionsList.load_default() - self.load_all_options() - - def load_all_options(self): - self.generalTab.load_options() - self.audioTab.load_options() - self.connectionTab.load_options() - self.videoTab.load_options() - self.filesysTab.load_options() - self.subtitlesTab.load_options() - self.otherTab.load_options() - self.updateTab.load_options() - self.authTab.load_options() - self.videoselTab.load_options() - self.logTab.load_options() - self.outputTab.load_options() - - def save_all_options(self): - self.generalTab.save_options() - self.audioTab.save_options() - self.connectionTab.save_options() - self.videoTab.save_options() - self.filesysTab.save_options() - self.subtitlesTab.save_options() - self.otherTab.save_options() - self.updateTab.save_options() - self.authTab.save_options() - self.videoselTab.save_options() - self.logTab.save_options() - self.outputTab.save_options() - diff -Nru youtube-dlg-0.3.5/youtube_dl_gui/YoutubeDLInterpreter.py youtube-dlg-0.3.7/youtube_dl_gui/YoutubeDLInterpreter.py --- youtube-dlg-0.3.5/youtube_dl_gui/YoutubeDLInterpreter.py 2014-04-07 12:59:32.000000000 +0000 +++ youtube-dlg-0.3.7/youtube_dl_gui/YoutubeDLInterpreter.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,182 +0,0 @@ -#! /usr/bin/env python - -''' -Parse OptionHandler object into list -and call youtube_dl.main(list) using -subprocess (we use this method to let -youtube_dl.main() handle all the hard -work) -''' - -from .Utils import ( - video_is_dash, - get_os_type, - fix_path, - add_PATH -) - -LANGUAGES = {"English":"en", - "Greek":"gr", - "Portuguese":"pt", - "French":"fr", - "Italian":"it", - "Russian":"ru", - "Spanish":"es", - "German":"de"} - -VIDEOFORMATS = {"default":"0", - "mp4 [1280x720]":"22", - "mp4 [640x360]":"18", - "webm [640x360]":"43", - "flv [400x240]":"5", - "3gp [320x240]":"36", - "mp4 1080p(DASH)":"137", - "mp4 720p(DASH)":"136", - "mp4 480p(DASH)":"135", - "mp4 360p(DASH)":"134"} - -DASH_AUDIO_FORMATS = {"NO SOUND":"None", - "DASH m4a audio 128k":"140", - "DASH webm audio 48k":"171"} - -AUDIO_Q = {"high":"0", - "mid":"5", - "low":"9"} - -class YoutubeDLInterpreter(): - - def __init__(self, optionsList, youtubeDLFile): - self.youtubeDLFile = youtubeDLFile - self.optionsList = optionsList - self.opts = [] - self.set_os() - self.set_progress_opts() - self.set_output_opts() - self.set_auth_opts() - self.set_connection_opts() - self.set_video_opts() - self.set_playlist_opts() - self.set_filesystem_opts() - self.set_subtitles_opts() - self.set_audio_opts() - self.set_other_opts() - - def get_options(self): - return self.opts - - def set_os(self): - if get_os_type() == 'nt': - self.opts = [self.youtubeDLFile] - add_PATH(self.optionsList.updatePath) - else: - path = fix_path(self.optionsList.updatePath) - self.opts = ['python', path + self.youtubeDLFile] - - def set_progress_opts(self): - ''' Do NOT change this option ''' - self.opts.append('--newline') - - def set_playlist_opts(self): - if self.optionsList.startTrack != 1: - self.opts.append('--playlist-start') - self.opts.append(str(self.optionsList.startTrack)) - if self.optionsList.endTrack != 0: - self.opts.append('--playlist-end') - self.opts.append(str(self.optionsList.endTrack)) - if self.optionsList.maxDownloads != 0: - self.opts.append('--max-downloads') - self.opts.append(str(self.optionsList.maxDownloads)) - if self.optionsList.minFileSize != '0': - self.opts.append('--min-filesize') - self.opts.append(self.optionsList.minFileSize) - if self.optionsList.maxFileSize != '0': - self.opts.append('--max-filesize') - self.opts.append(self.optionsList.maxFileSize) - - def set_auth_opts(self): - if self.optionsList.username != '': - self.opts.append('-u') - self.opts.append(self.optionsList.username) - if self.optionsList.password != '': - self.opts.append('-p') - self.opts.append(self.optionsList.password) - if self.optionsList.videoPass != '': - self.opts.append('--video-password') - self.opts.append(self.optionsList.videoPass) - - def set_connection_opts(self): - if self.optionsList.retries != 10: - self.opts.append('-R') - self.opts.append(str(self.optionsList.retries)) - if self.optionsList.proxy != '': - self.opts.append('--proxy') - self.opts.append(self.optionsList.proxy) - if self.optionsList.userAgent != '': - self.opts.append('--user-agent') - self.opts.append(self.optionsList.userAgent) - if self.optionsList.referer != '': - self.opts.append('--referer') - self.opts.append(self.optionsList.referer) - - def set_video_opts(self): - if self.optionsList.videoFormat != 'default': - self.opts.append('-f') - if video_is_dash(self.optionsList.videoFormat): - vf = VIDEOFORMATS[self.optionsList.videoFormat] - af = DASH_AUDIO_FORMATS[self.optionsList.dashAudioFormat] - if af != 'None': - self.opts.append(vf+'+'+af) - else: - self.opts.append(vf) - else: - self.opts.append(VIDEOFORMATS[self.optionsList.videoFormat]) - - def set_filesystem_opts(self): - if self.optionsList.ignoreErrors: - self.opts.append('-i') - if self.optionsList.writeDescription: - self.opts.append('--write-description') - if self.optionsList.writeInfo: - self.opts.append('--write-info-json') - if self.optionsList.writeThumbnail: - self.opts.append('--write-thumbnail') - - def set_subtitles_opts(self): - if self.optionsList.writeAllSubs: - self.opts.append('--all-subs') - if (self.optionsList.writeAutoSubs): - self.opts.append('--write-auto-sub') - if self.optionsList.writeSubs: - self.opts.append('--write-sub') - if self.optionsList.subsLang != 'English': - self.opts.append('--sub-lang') - self.opts.append(LANGUAGES[self.optionsList.subsLang]) - if self.optionsList.embedSubs: - self.opts.append('--embed-subs') - - def set_output_opts(self): - path = fix_path(self.optionsList.savePath) - self.opts.append('-o') - if self.optionsList.outputFormat == 'id': - self.opts.append(path + '%(id)s.%(ext)s') - elif self.optionsList.outputFormat == 'title': - self.opts.append(path + '%(title)s.%(ext)s') - elif self.optionsList.outputFormat == 'custom': - self.opts.append(path + self.optionsList.outputTemplate) - - def set_audio_opts(self): - if self.optionsList.toAudio: - self.opts.append('-x') - self.opts.append('--audio-format') - self.opts.append(self.optionsList.audioFormat) - if self.optionsList.audioQuality != 'mid': - self.opts.append('--audio-quality') - self.opts.append(AUDIO_Q[self.optionsList.audioQuality]) - if self.optionsList.keepVideo: - self.opts.append('-k') - - def set_other_opts(self): - if self.optionsList.cmdArgs != '': - for option in self.optionsList.cmdArgs.split(): - self.opts.append(option) -