Repository: MrS0m30n3/youtube-dl-gui Branch: master Commit: c5c18e55cb7e Files: 50 Total size: 419.0 KB Directory structure: gitextract_jcqvi4mb/ ├── .github/ │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── .tx/ │ └── config ├── AUTHORS ├── ChangeLog ├── LICENSE ├── MANIFEST.in ├── README.md ├── TODO ├── devscripts/ │ ├── bump-version.sh │ ├── check-translation.py │ ├── new-locale.py │ ├── update-authors.sh │ └── update-locales.sh ├── docs/ │ ├── faqs.md │ └── localization_howto.md ├── setup.py ├── tests/ │ ├── __init__.py │ ├── test_ditem.py │ ├── test_dlist.py │ ├── test_parsers.py │ ├── test_utils.py │ └── test_widgets.py ├── youtube-dl-gui.1 └── youtube_dl_gui/ ├── __init__.py ├── __main__.py ├── data/ │ └── pixmaps/ │ └── icons-license ├── downloaders.py ├── downloadmanager.py ├── formats.py ├── info.py ├── locale/ │ ├── ar_SA/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── cs_CZ/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── en_US/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── es_ES/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── fr_FR/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── it_IT/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── ja_JP/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── ko_KR/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ ├── pt_BR/ │ │ └── LC_MESSAGES/ │ │ └── youtube_dl_gui.po │ └── ru_RU/ │ └── LC_MESSAGES/ │ └── youtube_dl_gui.po ├── logmanager.py ├── mainframe.py ├── optionsframe.py ├── optionsmanager.py ├── parsers.py ├── updatemanager.py ├── utils.py ├── version.py └── widgets.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ## Please follow the guide below - You will be asked some questions and requested to provide some information, please read them **carefully** and answer **honestly** - Put an `x` into all the boxes [ ] relevant to your issue (like that [x]) - Use *Preview* tab to see how your issue will actually look like ### WARNING All invalid issues will be rejected!! --- ### Before going further - If your problem is a bug with **youtube-dl** or a request for new site support please report it [here](https://github.com/rg3/youtube-dl/issues) - Make sure you are using the *latest* **youtube-dl-gui** version (Click the `Settings` icon and then `About` to view the current version) - Make sure you are using the *latest* **youtube-dl** version (Click the `Settings` icon and then `Update` to update to the latest **youtube-dl** version) - Make sure you searched the bugtracker for similar issues **including closed ones** - Make sure to read the [FAQs](https://github.com/MrS0m30n3/youtube-dl-gui/blob/master/docs/faqs.md) file - [ ] **I think** my problem is **NOT** with **youtube-dl** - [ ] I've **verified** and **i assure** that I'm running youtube-dl-gui **0.4** - [ ] **I assure** that i am using the latest version of **youtube-dl** - [ ] [Searched](https://github.com/MrS0m30n3/youtube-dl-gui/issues) bugtracker - [ ] I've read the FAQs file --- ### What is the purpose of your *issue*? - [ ] Bug report - [ ] Feature request (request for a new functionality) - [ ] Question - [ ] Other Please remove any sections between (---) if they are not related to your issue --- ### Bug report #### If the problem occurs when downloading a URL please provide the full verbose output as follows: 1. Restart **youtube-dl-gui** 1. Go to `Options > Extra` tab 2. Enable **Debug youtube-dl** 3. Go to `Options > Advanced` tab and **Clear** your log content 4. Try to download the URL 5. Copy the **whole** log content and insert it between the ``` part below ``` delete me and insert your log content here ``` #### What operating system do you use ? #### List of actions to perform to reproduce the problem: 1. .. 2. .. 3. .. #### What is the expected behaviour ? #### What happens instead ? --- ### Feature request (request for a new functionality) Please make sure that the requested feature is **NOT** already in the [TODO](https://github.com/MrS0m30n3/youtube-dl-gui/blob/master/TODO) list - [ ] I've **verified** and **i assure** that my requested feature is **NOT** in the TODO list #### What operating system do you use ? --- ================================================ FILE: .gitignore ================================================ MANIFEST *.pyc *.mo dist/ build/ ================================================ FILE: .tx/config ================================================ [main] host = https://www.transifex.com [youtube-dl-gui.resources] file_filter = youtube_dl_gui/locale//LC_MESSAGES/youtube_dl_gui.po source_file = youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po source_lang = en_US type = PO ================================================ FILE: AUTHORS ================================================ # Authors ordered by first contribution. MrS0m30n3 Henrique Pereira Fironet Max Bruckner Sergey M․ Marcin Nowicki dnlsrl David Wales Nikita Bystrov nodiscc Leo Wzukw todool Yousuf 'Jay' Philips memnoth Peter Stevenson <2e0pgs@gmail.com> Swyter Sebastian Wagner saad snosi cleitonme # Generated by update-authors.sh script ================================================ FILE: ChangeLog ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Added - ChangeLog - 'flac' in available audio formats (#234) - French translation (#203) - Japanese translation (#226) - Italian translation (#231) - Czech translation (#233) - Automatic locale detection during first run (#235) - Man page (#259) - Option to disable youtube-dl updates (#21) ### Fixed - Bug in utils.convert_item function - Bug in downloaders.YoutubeDLDownloader (#244) ### Changed - Update timeout from 20 to 10 seconds (#244) ================================================ FILE: 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 ================================================ FILE: MANIFEST.in ================================================ include TODO include AUTHORS include LICENSE include README.md include MANIFEST.in include ChangeLog include youtube-dl-gui.1 include tests/*.py include devscripts/* include docs/* include youtube_dl_gui/data/pixmaps/icons-license recursive-include youtube_dl_gui/data *.png *.ico recursive-include youtube_dl_gui/locale *.po recursive-exclude youtube_dl_gui/locale *.mo ================================================ FILE: README.md ================================================ [![Donations Badge](https://yourdonation.rocks/images/badge.svg)](https://mrs0m30n3.github.io/youtube-dl-gui/donate.html) # youtube-dlG A cross platform front-end GUI of the popular [youtube-dl](https://rg3.github.io/youtube-dl/) media downloader written in wxPython. [Supported sites](https://rg3.github.io/youtube-dl/supportedsites.html) ## Screenshots ![youtube-dl-gui main window](https://raw.githubusercontent.com/MrS0m30n3/youtube-dl-gui/gh-pages/images/ydlg_ui.gif) ## Requirements * [Python 2.7.3+](https://www.python.org/downloads) * [wxPython 3](https://wxpython.org/download.php) * [TwoDict](https://pypi.python.org/pypi/twodict) * [GNU gettext](https://www.gnu.org/software/gettext/) (to build the package) * [FFmpeg](https://ffmpeg.org/download.html) (optional, to post process video files) ## Downloads * [Source (.zip)](https://github.com/MrS0m30n3/youtube-dl-gui/archive/0.4.zip) * [Source (.tar.gz)](https://github.com/MrS0m30n3/youtube-dl-gui/archive/0.4.tar.gz) * [PyPi](https://pypi.python.org/pypi/youtube-dlg/0.4) * [Ubuntu PPA](http://ppa.launchpad.net/nilarimogard/webupd8/ubuntu/pool/main/y/youtube-dlg/) * [Arch AUR](https://aur.archlinux.org/packages/youtube-dl-gui-git/) * [Slackware SlackBuild](https://slackbuilds.org/repository/14.2/network/youtube-dl-gui/) * [openSUSE](https://software.opensuse.org/package/youtube-dl-gui) * [Windows Installer](https://github.com/MrS0m30n3/youtube-dl-gui/releases/download/0.4/youtube-dl-gui-0.4-win-setup.zip) * [Windows Portable](https://github.com/MrS0m30n3/youtube-dl-gui/releases/download/0.4/youtube-dl-gui-0.4-win-portable.zip) ## Installation ### Install From Source 1. Download & extract the source 2. Change directory into *youtube-dl-gui-0.4* 3. Run `python setup.py install` ### Install PyPi 1. Run `pip install youtube-dlg` ### Install Windows Installer 1. Download & extract the Windows installer 2. Run the `setup.exe` file ## Contributing * **Add support for new language:** See [localization howto](docs/localization_howto.md) * **Report a bug:** See [issues](https://github.com/MrS0m30n3/youtube-dl-gui/issues) ## Authors See [AUTHORS](AUTHORS) file ## License The [Public Domain License](LICENSE) ## Frequently Asked Questions See [FAQs](docs/faqs.md) file ## Thanks Thanks to everyone who contributed to this project and to [@philipzae](https://github.com/philipzae) for designing the new UI layout. ================================================ FILE: TODO ================================================ Release 0.4.1 ============= * Intergrity check youtube-dl bin * Non-Windows shutdown using D-Bus instead of 'shutdown' * Custom youtube-dl format selection filters (e.g. -f best[height<=360]) * Remember list of urls after closing & re-opening * Context menu add new option "Go to file" or change the behaviour of "Open destination" * Context menu "Report Failed URL to Github" (see: #16) * Icons theme selection Features ======== * Improve playlist downloads * Mix formats option * About dialog show youtube-dl version (probably will have to create new frame) * Settings menu add "Statistics" Localization ============ * Add support for right to left languages (hebrew, arabic) * Fix paths on R2L layouts Other ===== * Re-structure package * Refactor * Review - rewrite threads communications * Add support for Python 3.* * Logging system using the Python 'logging' module * Use youtube-dl directly from python instead of using the subprocess module Probably wont add ================= * ListCtrl double click to "Rename" * Option to enable-disable items deletion from the filesystem * Add '--recode-video' to Formats tab * Auto video format detection * Change 'Warning' status to 'Finished (*)' or something similar? (see: issue #131) * Use proxy during update phase (see: issue #244) ================================================ FILE: devscripts/bump-version.sh ================================================ #!/bin/bash # Author: Sotiris Papadopoulos # Last-Revision: 2017-04-17 # Script to bump the version and automatically update related files # # Usage: ./bump_version.sh PACKAGE="youtube_dl_gui" FILES=`cat <" exit 1 fi cd .. new_version=$1 cur_version=`grep version "$PACKAGE/version.py" | cut -d"'" -f2` echo "Current version = $cur_version" echo "New version = $new_version" echo if version_le $new_version $cur_version; then echo "New version must be greater than the current version, exiting..." exit 1 fi for file in $FILES; do update_version $cur_version $new_version $file done echo "Done" ================================================ FILE: devscripts/check-translation.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- """ Author: Sotiris Papadopoulos Last-Revision: 2017-04-19 Script to automatically check PO files """ from __future__ import unicode_literals import os import sys import logging import argparse from time import sleep from datetime import datetime, timedelta, tzinfo try: import polib import google_translate except ImportError as error: print(error) sys.exit(1) WTIME = 2.0 # Time in seconds to wait between requests to avoid ban PACKAGE = "youtube_dl_gui" PO_FILENAME = "{}.po".format(PACKAGE) LOCALE_PATH_TMPL = os.path.join(PACKAGE, "locale", "{lang}", "LC_MESSAGES", PO_FILENAME) logging.basicConfig(level=logging.ERROR) def parse(): """Parse command line arguments.""" parser = argparse.ArgumentParser(description="Script to automatically check PO files") parser.add_argument("language", help="language of the PO file to check") parser.add_argument("-w", "--werror", action="store_true", help="treat all warning messages as errors") parser.add_argument("-o", "--only-headers", action="store_true", help="check only the PO file headers") parser.add_argument("-n", "--no-translate", action="store_true", help="do not use the translator to check 'msgstr' fields") parser.add_argument("-t", "--tlang", help="force a different language on the translator than the one given") return parser.parse_args() class UTC_Offset_Timezone(tzinfo): """Class that represents a UTC offset in the format +/-0000.""" def __init__(self, offset_string): self.offset = timedelta(seconds=UTC_Offset_Timezone.parse_offset(offset_string)) def utcoffset(self, dt): return self.offset + self.dst(dt) def dst(self, dt): return timedelta(0) @staticmethod def parse_offset(offset_string): """Parse the offset string into seconds.""" if len(offset_string) != 5: raise ValueError("Invalid length for offset string ({})".format(offset_string)) hours = offset_string[1:3] minutes = offset_string[3:5] offset = int(hours) * 3600 + int(minutes) * 60 if offset_string[0] == "-": return -1 * offset return offset def parse_date(date_string): """Parse date string into an aware datetime object.""" # Just a small list with the most common timezones offset_list = [ ("JST", "0900"), ("EEST", "0300"), ("EET", "0200"), ("GMT", "0000"), ("UTC", "0000") ] # Replace all the timezones with the offset for item in offset_list: timezone, offset = item date_string = date_string.replace(timezone, offset) datetime_string = date_string[:16] offset_string = date_string[16:] naive_date = datetime.strptime(datetime_string, "%Y-%m-%d %H:%M") # Create & return an aware datetime object based on the offset return naive_date.replace(tzinfo=UTC_Offset_Timezone(offset_string)) # Print helpers def my_print(msg, char="*", value=None, exit=False): """Print 'msg', debug 'value' and exit if 'exit' is True.""" print("[{}] {}".format(char, msg)) if value is not None: print("\tvalue= \"{}\"".format(value)) if exit: sys.exit(1) def perror(msg, value=None): my_print(msg, "-", value, True) def pwarn(msg, value=None, exit=False): my_print(msg, "!", value, exit) def pinfo(msg): my_print(msg) ############################# def main(args): os.chdir("..") # setup pot_file_path = LOCALE_PATH_TMPL.format(lang="en_US") po_file_path = LOCALE_PATH_TMPL.format(lang=args.language) if not os.path.exists(pot_file_path): perror("Failed to locate POT file, exiting...", pot_file_path) if not os.path.exists(po_file_path): perror("Failed to locate PO file, exiting...", po_file_path) pot_file = polib.pofile(pot_file_path) po_file = polib.pofile(po_file_path) # check headers pinfo("Checking PO headers") pot_headers = pot_file.metadata po_headers = po_file.metadata if pot_headers["Project-Id-Version"] != po_headers["Project-Id-Version"]: pwarn("'Project-Id-Version' headers do not match", exit=args.werror) if pot_headers["POT-Creation-Date"] != po_headers["POT-Creation-Date"]: pwarn("'POT-Creation-Date' headers do not match", exit=args.werror) po_creation_date = parse_date(po_headers["POT-Creation-Date"]) po_revision_date = parse_date(po_headers["PO-Revision-Date"]) # Aware datetimes convert to UTC automatically when comparing if po_revision_date <= po_creation_date: pwarn("PO file seems outdated", exit=args.werror) if "Language" in po_headers and po_headers["Language"] != args.language: pwarn("'Language' header does not match with the given language", po_headers["Language"], args.werror) pinfo("Last-Translator: {}".format(po_headers["Last-Translator"])) # check translations if args.only_headers: sys.exit(0) pinfo("Checking translations, this might take a while...") pot_msgid = [entry.msgid for entry in pot_file] po_msgid = [entry.msgid for entry in po_file] # lists to hold reports missing_msgid = [] not_translated = [] same_msgstr = [] with_typo = [] verify_trans = [] fuzzy_trans = po_file.fuzzy_entries() for msgid in pot_msgid: if msgid not in po_msgid: missing_msgid.append(msgid) # Init translator only if the '--no-translate' flag is NOT set translator = None if not args.no_translate: translator = google_translate.GoogleTranslator(timeout=5.0, retries=2, wait_time=WTIME) # Set source language for GoogleTranslator if args.tlang is not None: src_lang = args.tlang pinfo("Forcing '{}' as the translator's source language".format(src_lang)) else: # Get a valid source language for Google # for example convert 'ar_SA' to 'ar' or 'zh_CN' to 'zh-CN' src_lang = args.language if src_lang not in translator._lang_dict: src_lang = src_lang.replace("_", "-") if src_lang not in translator._lang_dict: src_lang = src_lang.split("-")[0] # Keep entries that need further analysis using the translator further_analysis = [] for entry in po_file: if not entry.translated(): not_translated.append(entry) elif entry.msgid == entry.msgstr: same_msgstr.append(entry) else: further_analysis.append(entry) if translator is not None and further_analysis: # eta = (items_to_analyze * (WTIME + avg_ms)) - WTIME # We subtract WTIME at the end because there is no wait for the last item on the list # avg_msg = 200ms eta_seconds = (len(further_analysis) * (WTIME + 0.2)) - WTIME eta_seconds = int(round(eta_seconds)) eta = timedelta(seconds=eta_seconds) pinfo("Approximate time to check translations online: {}".format(eta)) # Pass translations as a list since GoogleTranslator can handle them words_dict = translator.get_info_dict([entry.msgstr for entry in further_analysis], "en", src_lang) for index, word_dict in enumerate(words_dict): # Get the corresponding POEntry since the words_dict does not contain those entry = further_analysis[index] if word_dict is not None: if word_dict["has_typo"]: with_typo.append(entry) if word_dict["translation"].lower() != entry.msgid.lower(): found = False # Check verbs, nouns, adverbs, etc.. for key in word_dict["extra"]: if entry.msgid.lower() in word_dict["extra"][key].keys(): found = True break if not found: verify_trans.append((entry, word_dict["translation"])) # time to report print("=" * 25 + "Report" + "=" * 25) if missing_msgid: print("Missing msgids") for msgid in missing_msgid: print(" \"{}\"".format(msgid)) if not_translated: print("Not translated") for entry in not_translated: print(" line: {} msgid: \"{}\"".format(entry.linenum, entry.msgid)) if same_msgstr: print("Same msgstr") for entry in same_msgstr: print(" line: {} msgid: \"{}\"".format(entry.linenum, entry.msgid)) if with_typo: print("With typo") for entry in with_typo: print(" line: {} msgid: \"{}\" msgstr: \"{}\"".format(entry.linenum, entry.msgid, entry.msgstr)) if verify_trans: print("Verify translation") for item in verify_trans: entry, translation = item print(" line: {} msgid: \"{}\" trans: \"{}\"".format(entry.linenum, entry.msgid, translation)) if fuzzy_trans: print("Fuzzy translations") for entry in fuzzy_trans: print(" line: {} msgid: \"{}\"".format(entry.linenum, entry.msgid)) total = len(missing_msgid) + len(not_translated) + len(same_msgstr) + len(with_typo) + len(verify_trans) + len(fuzzy_trans) print("") print("Missing msgids\t\t: {}".format(len(missing_msgid))) print("Not translated\t\t: {}".format(len(not_translated))) print("Same msgstr\t\t: {}".format(len(same_msgstr))) print("With typo\t\t: {}".format(len(with_typo))) print("Verify translation\t: {}".format(len(verify_trans))) print("Fuzzy translations\t: {}".format(len(fuzzy_trans))) print("Total\t\t\t: {}".format(total)) print("") print("Total entries\t\t: {}".format(len(po_file))) if __name__ == "__main__": try: main(parse()) except KeyboardInterrupt: print("KeyboardInterrupt") ================================================ FILE: devscripts/new-locale.py ================================================ #!/usr/bin/env python """ Author: Sotiris Papadopoulos Last-Revision: 2017-01-30 Script to add support for a new language Usage : ./new-locale.py Example : ./new-locale.py en_US """ import os import sys import shutil PACKAGE = "youtube_dl_gui" LOCALE_PATH_TMPL = os.path.join(PACKAGE, "locale", "{lang}", "LC_MESSAGES") PO_FILE_TMPL = os.path.join("{parent_dir}", "youtube_dl_gui.po") def error(msg): print("[-]{0}".format(msg)) sys.exit(1) def output(msg): print("[*]{0}".format(msg)) def manage_directory(): """Allow script calls from the 'devscripts' dir and the package dir.""" if os.path.basename(os.getcwd()) == "devscripts": os.chdir("..") def main(lang_code): manage_directory() target_dir = LOCALE_PATH_TMPL.format(lang=lang_code) default_dir = LOCALE_PATH_TMPL.format(lang="en_US") target_po = PO_FILE_TMPL.format(parent_dir=target_dir) source_po = PO_FILE_TMPL.format(parent_dir=default_dir) if os.path.exists(target_dir): error("Locale '{0}' already exists, exiting...".format(lang_code)) output("Creating directory: '{0}'".format(target_dir)) os.makedirs(target_dir) output("Creating PO file: '{0}'".format(target_po)) shutil.copy(source_po, target_po) output("Done") if __name__ == "__main__": if len(sys.argv) == 2: main(sys.argv[1]) else: print("Usage : {0} ".format(sys.argv[0])) print("Example : {0} en_US".format(sys.argv[0])) ================================================ FILE: devscripts/update-authors.sh ================================================ #!/bin/bash # Author: Sotiris Papadopoulos # Last-Revision: 2017-04-17 # Script to update the AUTHORS file # # Usage: ./update-authors.sh cd .. git log --reverse --format="%aN <%aE>" | python -c " import sys authors = set() sys.stdout.write('# Authors ordered by first contribution.\n\n') for line in sys.stdin: username, _ = line.split('<') if username not in authors: authors.add(username) sys.stdout.write(line) sys.stdout.write('\n# Generated by update-authors.sh script\n') " > AUTHORS ================================================ FILE: devscripts/update-locales.sh ================================================ #!/bin/bash # Author: Sotiris Papadopoulos # Last-Revision: 2017-01-30 # Script to update all locale files and rebuild the MO files # # Usage: ./update_locales.sh PACKAGE="youtube_dl_gui" PO_FILE="$PACKAGE.po" MO_FILE="$PACKAGE.mo" cd .. VERSION=$(grep version "$PACKAGE/version.py" | cut -d"'" -f2) DIRS=$(find "$PACKAGE/locale" -mindepth 2 -maxdepth 2) echo "[*]Creating new .PO file" pygettext.py -v -o new.po "$PACKAGE/*.py" #vim new.po echo "[*]Updating old .PO files" for dir in $DIRS; do msgmerge --update --no-wrap -v "$dir/$PO_FILE" new.po # Strip empty headers sed -i "/: \\n/d" "$dir/$PO_FILE" # Upate version sed -i "s/Project-Id-Version:.*\\\n/Project-Id-Version: youtube-dlg $VERSION\\\n/g" "$dir/$PO_FILE" done echo read -p "Open files for revision?(y/n) " ch if [ $ch = 'y' ]; then for dir in $DIRS; do vim "$dir/$PO_FILE" done fi echo "[*]Building .MO files" for dir in $DIRS; do msgfmt --use-fuzzy --output-file "$dir/$MO_FILE" "$dir/$PO_FILE" done echo "[*]Done" ================================================ FILE: docs/faqs.md ================================================ # Frequently Asked Questions * **How can I make sure I'm getting the best quality possible?**: Don't force any output format, leave the **default** format selected in the main window (*default = highest quality*). Read https://github.com/rg3/youtube-dl#format-selection for more details. * **Post processing takes too long**: There should be no post-processing if you leave the video format to default (which defaults to the best format) and did not check convert to audio or embed subtitles, otherwise the file will be re-encoded to the format you selected (which takes time/CPU resources). * **The website I'm trying to download from is not supported**: Youtube-dl-gui uses [youtube-dl](https://github.com/rg3/youtube-dl) in the backend to download files. Youtube-dl provides a list of [extractors](https://github.com/rg3/youtube-dl/tree/master/youtube_dl/extractor) to work with each particular site. If you'd like to request support for a new website, please submit it to youtube-dl's [issue tracker](https://github.com/rg3/youtube-dl/issues). * **How do I change the naming pattern for downloaded files?**: You can change the naming pattern by picking a different filename format under the Options>General tab. You can also use a custom pattern by setting the option to "Custom" and editing the output template field. For more infomations on the output template see [youtube-dl's output template section](https://github.com/rg3/youtube-dl/blob/master/README.md#output-template). * **When is the next release coming?**: Youtube-dl-gui does not have a release schedule, next release will come when it's ready. * **How can i log the youtube-dl's debug output?**: Just go to Options>Extra tab and enable the "Debug youtube-dl" option. * **I don't see my language in the available subtitles languages**: You can't see it because it's not there, feel free to open a new pull-request and add support for your language (it's literally one line of code you don't need years of experience). * **I'm on OS-X and i get 'No module named wx' error**: You need to install wxPython. If you have [homebrew](https://brew.sh/) installed you can just run: `brew install wxpython` * **Is there an option to change the maximum parallel downloads?**: You can change the number of max parallel downloads by editing the "workers_number" option in your settings.json file. Note that you need to restart youtube-dl-gui for the changes to take place. settings.json file location: * Windows: `%appdata%\youtube-dlg\settings.json` * Linux: `~/.config/youtube-dlg/settings.json` * **Not all formats reported by youtube-dl '-F' are available in youtube-dl-gui**: Unfortunately it is not possible to support all the video formats that youtube-dl provides. If you want to use a "custom" format you can follow this steps: 1. Set the download format to "default" 2. Go to Options>Extra tab 3. Add `-f your_custom_format` in the commands box 4. Download your video as you would normally do * **How can i use a youtube-dl option that's not available in youtube-dl-gui?**: You can add extra youtube-dl command line options in the commands box under the Options>Extra tab. * **Can i download only the subtitles file?**: If the video file is presented youtube-dl-gui will go ahead and download only the subtitles file. If the video file is NOT presented you can add the `--skip-download` option, which will skip the video download phase. If you are not happy with the above options you should use a different tool for the job since youtube-dl-gui is not a subtitles downloader. * **I'm using the HLS downloader and i don't see any progress report**: That's a known issue you should use the native HLS implementation by enabling the "Prefer Native HLS" option under the Options>Extra tab. NOTE that the native HLS implementation **might not work on every site**. For more info you can read issue [#49](https://github.com/MrS0m30n3/youtube-dl-gui/issues/49). * **I'm using the HLS downloader and the 'stop' button won't work**: That's also a known issue with the HLS downloader on Windows. You should use the native HLS implementation or wait for the download to complete normally. For more info you can read issue [#49](https://github.com/MrS0m30n3/youtube-dl-gui/issues/49). * **Is it possible to use a youtube-dl version other than the official one?**: You can use your own version of youtube-dl by editing the "youtubedl_path" option in your settings.json file and make it point to your own binary (e.g. /usr/local/bin). Note that if youtube-dl-gui does not have write permissions to this new directory the "update" option in the GUI will fail. Also, note that changing the "youtubedl_path" won't change the update source which is hardcoded for now to "https://yt-dl.org/latest/". * **The program crashes frequently or pretends to succeed downloading the files (Windows)**: Follow [youtube-dl instructions](https://github.com/rg3/youtube-dl#the-exe-throws-an-error-due-to-missing-msvcr100dll) on updating your Visual C++ Redistributable. * **How come 1080p YouTube downloads are not working?**: Try disabling native HLS. You can disable the "Prefer Native HLS" option under the Options>Extra tab. ================================================ FILE: docs/localization_howto.md ================================================ # Localization Guide - [Transifex](https://www.transifex.com/youtube-dl-gui/public/) ## 🔴 DISCLAIMER **By sending a translation you agree to publish your work under the [UNLICENSE](https://unlicense.org/) license!** ## Contents * [Translators](localization_howto.md#translators) * [Testers](localization_howto.md#testers) * [Devs](localization_howto.md#devs) * [FAQs](localization_howto.md#faqs) ## Translators ### Requirements * A modern browser * A Transifex account, [sign-up](https://www.transifex.com/signup/) ### Notes * If your language is currently not supported you can make a request for new language support. * When you request support for a new language, the language code should be in the format **en_US** and NOT just **en**. * Variables such as **{0}**, **{1}**, **{dir}**, **{0:.1f}**, etc should be copied exactly as they appear in the translation box. * Variables represent a word that will be replaced by real data (name, number, etc). * Variables can be moved around the string in order to make the most logical translation. * When new strings for translation are available you will get inbox notifications. * For help you can leave a comment with @username or send a direct message to one of the maintainers. * Maintainer usernames are: `MrS0m30n3`, `nodiscc` ### Gettings Started 1. [Sign-in](https://www.transifex.com/signin/) to Transifex 2. [Join a translation team](https://docs.transifex.com/getting-started/translators#joining-a-translation-team) 3. [Start translating using the web editor](https://docs.transifex.com/translation/translating-with-the-web-editor) ### Help * [Translators getting started](https://docs.transifex.com/getting-started/translators) * [Translating offline](https://docs.transifex.com/translation/offline) * [Using the glossary](https://docs.transifex.com/translation/using-the-glossary) * For help you can [leave a comment or open an issue](https://docs.transifex.com/translation/tools-in-the-editor#comments-and-issues) ## Testers ### Requirements * Check [project requirements](../README.md#requirements) * [Git](https://git-scm.com/downloads) * [Transifex CLI client](https://docs.transifex.com/client/installing-the-client) * Some kind of text editor to edit some code (notepad++, nano, etc are sufficient) * A Transifex account, [sign-up](https://www.transifex.com/signup/) ### Notes * The instructions below assume basic knowledge of the command line (OS independent). * The **language code** being used should be in the format `_` (e.g. en_US). * You can locally edit the translation file (PO) by opening it using a simple editor and editing the **msgstr** fields. * You can find the translation file (PO) after downloading it under the `youtube_dl_gui/locale//LC_MESSAGES/` directory. * In order to get the translations from Transifex **your account needs permissions to access the project**. ### Getting Started 1. Open a terminal 2. Test that Git works: `git --version` 3. Test that Transifex CLI client works: `tx --version` 4. Clone upstream using Git: `git clone https://github.com/MrS0m30n3/youtube-dl-gui` 5. Change to project directory: `cd youtube-dl-gui` 6. Pull the translation you want to test from Transifex (**Auth needed**): `tx pull --force -l ` 7. Make the language appear under **Options>General** tab (only for new languages): 1. Open the **optionsframe.py** under the **youtube_dl_gui** directory 2. Search for the **LOCALE_NAMES** attribute 3. Add the new language to it (in our example `('el_GR', 'Greek'),`) 4. Don't forget to save your changes ```python LOCALE_NAMES = twodict([ + ('el_GR', 'Greek'), # language_code, language_name ('ar_SA', 'Arabic'), ('cs_CZ', 'Czech'), ... ``` 8. Build the binary translation files (MO): `python setup.py build_trans` 9. Test the translations by running youtube-dl-gui and selecting your language: `python -m youtube_dl_gui` 10. Make changes locally in your translation file (PO) and go back to step 8 to test them ### Help * [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) * [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) * [Command line basics Linux](https://lifehacker.com/5633909/who-needs-a-mouse-learn-to-use-the-command-line-for-almost-anything) ## Devs ### Requirements * See [Testers](localization_howto.md#testers) requirements ### Notes * Read [Testers](localization_howto.md#testers) notes first. * Binary translation files (MO) are ignored and you should not push them upstream. * Don't forget to update the [ChangeLog](../ChangeLog) after adding a new language. * You can gather all extra requirements below using **pip**. ### Getting Started #### Add a new language under Options>General tab 1. Open the **optionsframe.py** file 2. Search for the **LOCALE_NAMES** attribute 3. Add the new language to it and make sure to **sort alphabetically** based on the language name ```python LOCALE_NAMES = twodict([ ('en_US', 'English'), ('fr_FR', 'French'), + ('el_GR', 'Greek'), ('it_IT', 'Italian'), ... ``` #### Build the binary translation files (MO) 1. Just run the setup script: `python setup.py build_trans` #### Automatically check translations using Google translate (Requires: polib & doodle_translate) 1. Change directory to `devscripts` 2. Run the check script: `python check-translation.py ` #### Get translations from Transifex (Requires: Permissions to access project) * Pull everything: `tx pull -a` * Pull reviewed: `tx pull --mode reviewed -a` * Pull everything (force): `tx pull -a -f` * Pull specific language: `tx pull -l ` * Pull only completed translations (100%): `tx pull -a --minimum-perc=100` #### Update source strings (Only Maintainers, Requires: python-gettext) 1. Change directory to `devscripts` 2. Run the `update-locales.sh` script (also builds MO files) 3. Push changes to Transifex: `tx push --source --translations` #### Add support for new language locally (DEPRECATED, ONLY TESTING) 1. Change directory to `devscripts` 2. Run the new locale script: `python new-locale.py ` ### Help * [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) * [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) * [PO file headers](https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html) * [GNU gettext manual](https://www.gnu.org/software/gettext/manual/html_node/index.html#SEC_Contents) * Transifex [user roles](https://docs.transifex.com/teams/understanding-user-roles) * Transifex [CLI client introduction](https://docs.transifex.com/client/introduction) ## FAQs * **Translations unnecessarily having country codes?**: Some languages have dialects in different countries. For example, `de_AT` is used for Austria, and `pt_BR` for Brazil. The country code serves to distinguish the dialects. Also, using a single format (*ll_CC*) instead of multiple for the locale name simplifies some implementation specific things. ================================================ FILE: setup.py ================================================ #!/usr/bin/env python2 # -*- coding: utf-8 -*- """Youtube-dlg setup file. Examples: Windows:: python setup.py py2exe Linux:: python setup.py install Build source distribution:: python setup.py sdist Build platform distribution:: python setup.py bdist Build the translations:: python setup.py build_trans Build with updates disabled:: python setup.py build --no-updates Requirements: * GNU gettext utilities Notes: 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/ Basic steps of the setup:: * Run pre-build tasks * Call setup handler based on OS & options * Set up hicolor icons (if supported by platform) * Set up fallback pixmaps icon (if supported by platform) * Set up package level pixmaps icons (*.png) * Set up package level i18n files (*.mo) * Set up scripts (executables) (if supported by platform) * Run setup """ from distutils import cmd, log from distutils.core import setup from distutils.command.build import build import os import sys import glob from shutil import copyfile from subprocess import call 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 youtube_dl_gui import ( __author__, __appname__, __contact__, __version__, __license__, __projecturl__, __description__, __packagename__, __descriptionfull__ ) # Setup can not handle unicode __packagename__ = str(__packagename__) def on_windows(): """Returns True if OS is Windows.""" return os.name == "nt" class BuildBin(cmd.Command): description = "build the youtube-dl-gui binary file" user_options = [] def initialize_options(self): self.scripts_dir = None def finalize_options(self): self.scripts_dir = os.path.join("build", "_scripts") def run(self): if not os.path.exists(self.scripts_dir): os.makedirs(self.scripts_dir) copyfile(os.path.join(__packagename__, "__main__.py"), os.path.join(self.scripts_dir, "youtube-dl-gui")) class BuildTranslations(cmd.Command): description = "build the translation files" user_options = [] def initialize_options(self): self.exec_name = None self.search_pattern = None def finalize_options(self): if on_windows(): self.exec_name = "msgfmt.exe" else: self.exec_name = "msgfmt" self.search_pattern = os.path.join(__packagename__, "locale", "*", "LC_MESSAGES", "youtube_dl_gui.po") def run(self): for po_file in glob.glob(self.search_pattern): mo_file = po_file.replace(".po", ".mo") try: log.info("building MO file for '{}'".format(po_file)) call([self.exec_name, "-o", mo_file, po_file]) except OSError: log.error("could not locate file '{}', exiting...".format(self.exec_name)) sys.exit(1) class Build(build): """Overwrite the default 'build' behaviour.""" sub_commands = [ ("build_bin", None), ("build_trans", None) ] + build.sub_commands build.user_options.append(("no-updates", None, "build with updates disabled")) def initialize_options(self): build.initialize_options(self) self.no_updates = None def run(self): build.run(self) if self.no_updates: self.__disable_updates() def __disable_updates(self): lib_dir = os.path.join(self.build_lib, __packagename__) target_file = "optionsmanager.py" # Options file should be available from previous build commands optionsfile = os.path.join(lib_dir, target_file) data = None with open(optionsfile, "r") as input_file: data = input_file.readlines() if data is None: log.error("building with updates disabled failed!") sys.exit(1) for index, line in enumerate(data): if "'disable_update': False" in line: log.info("disabling updates") data[index] = line.replace("False", "True") break with open(optionsfile, "w") as output_file: output_file.writelines(data) # Overwrite cmds cmdclass = { "build": Build, "build_bin": BuildBin, "build_trans": BuildTranslations } def linux_setup(): scripts = [] data_files = [] package_data = {} # Add hicolor icons for path in glob.glob("youtube_dl_gui/data/icons/hicolor/*x*"): size = os.path.basename(path) dst = "share/icons/hicolor/{size}/apps".format(size=size) src = "{icon_path}/apps/youtube-dl-gui.png".format(icon_path=path) data_files.append((dst, [src])) # Add fallback icon, see issue #14 data_files.append( ("share/pixmaps", ["youtube_dl_gui/data/pixmaps/youtube-dl-gui.png"]) ) # Add man page data_files.append( ("share/man/man1", ["youtube-dl-gui.1"]) ) # Add pixmaps icons (*.png) & i18n files package_data[__packagename__] = [ "data/pixmaps/*.png", "locale/*/LC_MESSAGES/*.mo" ] # Add scripts scripts.append("build/_scripts/youtube-dl-gui") setup_params = { "scripts": scripts, "data_files": data_files, "package_data": package_data } return setup_params def windows_setup(): def normal_setup(): package_data = {} # Add pixmaps icons (*.png) & i18n files package_data[__packagename__] = [ "data\\pixmaps\\*.png", "locale\\*\\LC_MESSAGES\\*.mo" ] setup_params = { "package_data": package_data } return setup_params def py2exe_setup(): windows = [] data_files = [] # py2exe dependencies & options # TODO change directory for ffmpeg.exe & ffprobe.exe dependencies = [ "C:\\Windows\\System32\\ffmpeg.exe", "C:\\Windows\\System32\\ffprobe.exe", "C:\\python27\\DLLs\\MSVCP90.dll" ] options = { "includes": ["wx.lib.pubsub.*", "wx.lib.pubsub.core.*", "wx.lib.pubsub.core.arg1.*"] } ############################################# # Add py2exe deps & pixmaps icons (*.png) data_files.extend([ ("", dependencies), ("data\\pixmaps", glob.glob("youtube_dl_gui\\data\\pixmaps\\*.png")), ]) # We have to manually add the translation files since py2exe cant do it for lang in os.listdir("youtube_dl_gui\\locale"): dst = os.path.join("locale", lang, "LC_MESSAGES") src = os.path.join("youtube_dl_gui", dst, "youtube_dl_gui.mo") data_files.append((dst, [src])) # Add GUI executable details windows.append({ "script": "build\\_scripts\\youtube-dl-gui", "icon_resources": [(0, "youtube_dl_gui\\data\\pixmaps\\youtube-dl-gui.ico")] }) setup_params = { "windows": windows, "data_files": data_files, "options": {"py2exe": options} } return setup_params if PY2EXE: return py2exe_setup() return normal_setup() if on_windows(): params = windows_setup() else: params = linux_setup() setup( author = __author__, name = __appname__, version = __version__, license = __license__, author_email = __contact__, url = __projecturl__, description = __description__, long_description = __descriptionfull__, packages = [__packagename__], cmdclass = cmdclass, **params ) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/test_ditem.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """Contains test cases for the DownloadItem object.""" from __future__ import unicode_literals import sys import os.path import unittest PATH = os.path.realpath(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(os.path.dirname(PATH))) try: from youtube_dl_gui.downloadmanager import DownloadItem except ImportError as error: print error sys.exit(1) class TestItemInit(unittest.TestCase): """Test case for DownloadItem init.""" def test_init(self): url = "url" options = ["-f", "flv"] ditem = DownloadItem(url, options) self.assertEqual(ditem.stage, "Queued") self.assertEqual(ditem.url, url) self.assertEqual(ditem.options, options) self.assertEqual(ditem.object_id, hash(url + unicode(options))) self.assertEqual(ditem.path, "") self.assertEqual(ditem.filenames, []) self.assertEqual(ditem.extensions, []) self.assertEqual(ditem.filesizes, []) self.assertEqual( ditem.progress_stats, {"filename": url, "extension": "-", "filesize": "-", "percent": "0%", "speed": "-", "eta": "-", "status": "Queued", "playlist_size": "", "playlist_index": ""} ) class TestGetFiles(unittest.TestCase): """Test case for DownloadItem get_files method.""" def setUp(self): self.ditem = DownloadItem("url", ["-f", "flv"]) def test_get_files(self): path = os.path.join("/home", "user", "downloads") self.ditem.path = path self.ditem.filenames = ["file1", "file2"] self.ditem.extensions = [".mp4", ".m4a"] self.assertEqual(self.ditem.get_files(), [os.path.join(path, "file1" + ".mp4"), os.path.join(path, "file2" + ".m4a")]) def test_get_files_no_data(self): self.assertEqual(self.ditem.get_files(), []) class TestItemComparison(unittest.TestCase): """Test case for DownloadItem __eq__ method.""" def test_equal_true(self): ditem1 = DownloadItem("url", ["-f", "flv"]) ditem2 = DownloadItem("url", ["-f", "flv"]) self.assertTrue(ditem1 == ditem2) def test_equal_false(self): ditem1 = DownloadItem("url", ["-f", "flv"]) ditem2 = DownloadItem("url2", ["-f", "flv"]) self.assertFalse(ditem1 == ditem2) ditem1 = DownloadItem("url", ["-f", "flv"]) ditem2 = DownloadItem("url", ["-f", "mp4"]) self.assertFalse(ditem1 == ditem2) class TestSetItemStage(unittest.TestCase): """Test case for DownloadItem stage setter.""" def setUp(self): self.ditem = DownloadItem("url", ["-f", "flv"]) def test_set_stage_valid(self): self.ditem.stage = "Queued" self.assertEqual(self.ditem.stage, "Queued") self.assertEqual(self.ditem.progress_stats["status"], "Queued") self.ditem.stage = "Active" self.assertEqual(self.ditem.stage, "Active") self.assertEqual(self.ditem.progress_stats["status"], "Pre Processing") self.ditem.stage = "Completed" self.assertEqual(self.ditem.stage, "Completed") self.assertEqual(self.ditem.progress_stats["status"], "Finished") self.ditem.stage = "Paused" self.assertEqual(self.ditem.stage, "Paused") self.assertEqual(self.ditem.progress_stats["status"], "Paused") self.ditem.stage = "Error" self.assertEqual(self.ditem.stage, "Error") self.assertEqual(self.ditem.progress_stats["status"], "Error") def test_set_stage_invalid(self): raised = False try: self.ditem.stage = "some other status" except ValueError: raised = True self.assertTrue(raised) class TestUpdateStats(unittest.TestCase): """Test case for DownloadItem update_stats method.""" def setUp(self): self.ditem = DownloadItem("url", ["-f", "flv"]) def test_update_stats(self): path = os.path.join("/home", "user") # 1st playlist item self.ditem.update_stats({"filename": "somefilename.f1", "extension": ".mp4", "filesize": "9.45MiB", "percent": "2.0%", "speed": "200.00KiB/s", "eta": "00:38", "status": "Downloading", "path": path, "playlist_size": "10", "playlist_index": "1"}) self.assertEqual(self.ditem.path, path) self.assertEqual(self.ditem.filenames, ["somefilename.f1"]) self.assertEqual(self.ditem.extensions, [".mp4"]) # Do not update filesizes unless percentage is 100% # See https://github.com/MrS0m30n3/youtube-dl-gui/issues/162 self.assertEqual(self.ditem.filesizes, []) self.assertEqual( self.ditem.progress_stats, {"filename": "somefilename.f1", "extension": ".mp4", "filesize": "9.45MiB", "percent": "2.0%", "speed": "200.00KiB/s", "eta": "00:38", "status": "Downloading", "playlist_size": "10", "playlist_index": "1"} ) # Since the percentage is 100% this should update the filesizes list self.ditem.update_stats({"filesize": "9.45MiB", "percent": "100%", "speed": "", "eta": "", "status": "Downloading"}) self.assertEqual(self.ditem.filesizes, [9909043.20]) self.ditem.update_stats({"filename": "somefilename.f2", "extension": ".m4a", "filesize": "2.22MiB", "percent": "33.0%", "speed": "200.00KiB/s", "eta": "00:20", "status": "Downloading", "path": path}) self.assertEqual(self.ditem.path, path) self.assertEqual(self.ditem.filenames, ["somefilename.f1", "somefilename.f2"]) self.assertEqual(self.ditem.extensions, [".mp4", ".m4a"]) self.assertEqual(self.ditem.filesizes, [9909043.20]) self.assertEqual( self.ditem.progress_stats, {"filename": "somefilename.f2", "extension": ".m4a", "filesize": "2.22MiB", "percent": "33.0%", "speed": "200.00KiB/s", "eta": "00:20", "status": "Downloading", "playlist_size": "10", "playlist_index": "1"} ) # Since the percentage is 100% this should update the filesizes list self.ditem.update_stats({"filesize": "2.22MiB", "percent": "100%", "speed": "", "eta": "", "status": "Downloading"}) self.assertEqual(self.ditem.filesizes, [9909043.20, 2327838.72]) # Moving to the 2nd playlist item self.ditem.update_stats({"filename": "someotherfilename.f1", "extension": ".mp4", "filesize": "10.25MiB", "percent": "50.0%", "speed": "200.00KiB/s", "eta": "00:38", "status": "Downloading", "path": path, "playlist_size": "10", "playlist_index": "2"}) # We must reset filenames, extensions & filesizes lists when changing playlist index # else the filesizes for the post processed files will be wrong self.assertEqual(self.ditem.filenames, ["someotherfilename.f1"]) self.assertEqual(self.ditem.extensions, [".mp4"]) self.assertEqual(self.ditem.filesizes, []) self.assertEqual( self.ditem.progress_stats, {"filename": "someotherfilename.f1", "extension": ".mp4", "filesize": "10.25MiB", "percent": "50.0%", "speed": "200.00KiB/s", "eta": "00:38", "status": "Downloading", "playlist_size": "10", "playlist_index": "2"} ) # Since the percentage is 100% this should update the filesizes list self.ditem.update_stats({"filesize": "10.25MiB", "percent": "100%", "speed": "", "eta": "", "status": "Downloading"}) self.assertEqual(self.ditem.filesizes, [10747904.0]) self.ditem.update_stats({"filename": "someotherfilename.f2", "extension": ".m4a", "filesize": "3.33MiB", "percent": "33.0%", "speed": "200.00KiB/s", "eta": "00:30", "status": "Downloading", "path": path}) self.assertEqual(self.ditem.path, path) self.assertEqual(self.ditem.filenames, ["someotherfilename.f1", "someotherfilename.f2"]) self.assertEqual(self.ditem.extensions, [".mp4", ".m4a"]) self.assertEqual(self.ditem.filesizes, [10747904.0]) self.assertEqual( self.ditem.progress_stats, {"filename": "someotherfilename.f2", "extension": ".m4a", "filesize": "3.33MiB", "percent": "33.0%", "speed": "200.00KiB/s", "eta": "00:30", "status": "Downloading", "playlist_size": "10", "playlist_index": "2"} ) # Since the percentage is 100% this should update the filesizes list self.ditem.update_stats({"filesize": "3.33MiB", "percent": "100%", "speed": "", "eta": "", "status": "Downloading"}) self.assertEqual(self.ditem.filesizes, [10747904.0, 3491758.08]) # Let's move to the 3rd playlist item # Here we mimic youtube-dl's "max downloads limit reached" # this line should not reset the filenames, extensions & filesizes lists # since we will lose the ability to play the last playlist item self.ditem.update_stats({"status": "Downloading", "playlist_size": "10", "playlist_index": "3"}) self.assertEqual(self.ditem.filenames, ["someotherfilename.f1", "someotherfilename.f2"]) self.assertEqual(self.ditem.extensions, [".mp4", ".m4a"]) self.assertEqual(self.ditem.filesizes, [10747904.0, 3491758.08]) self.assertEqual( self.ditem.progress_stats, {"filename": "someotherfilename.f2", "extension": ".m4a", "filesize": "3.33MiB", "percent": "100%", "speed": "-", "eta": "-", "status": "Downloading", "playlist_size": "10", "playlist_index": "3"} ) def test_update_stats_invalid_input(self): self.assertRaises(AssertionError, self.ditem.update_stats, []) def test_update_stats_empty_strings(self): self.ditem.update_stats({"filename": "", "extension": "", "filesize": "", "percent": "", "speed": "", "eta": "", "status": "", "playlist_size": "", "playlist_index": ""}) self.assertEqual( self.ditem.progress_stats, {"filename": "url", "extension": "-", "filesize": "-", "percent": "0%", "speed": "-", "eta": "-", "status": "Queued", "playlist_size": "", "playlist_index": ""} ) def test_update_stats_not_string(self): self.ditem.update_stats({"filename": None, "status": 1234, "eta": False}) self.assertEqual(self.ditem.progress_stats["filename"], "url") self.assertEqual(self.ditem.progress_stats["status"], "Queued") self.assertEqual(self.ditem.progress_stats["eta"], "-") class TestDownloadItemPrivate(unittest.TestCase): """Test case for private method of the DownloadItem.""" def test_set_stage(self): ditem = DownloadItem("url", ["-f", "flv"]) active_status = ["Pre Processing", "Downloading", "Post Processing"] complete_status = ["Finished", "Warning", "Already Downloaded"] error_status = ["Error", "Stopped", "Filesize Abort"] for status in active_status: ditem._set_stage(status) self.assertEqual(ditem.stage, "Active") for status in complete_status: ditem._set_stage(status) self.assertEqual(ditem.stage, "Completed") for status in error_status: ditem._set_stage(status) self.assertEqual(ditem.stage, "Error") def test_calc_post_proc_size(self): # REFACTOR Not an actual method # should transfer to TestUpdateStats ditem = DownloadItem("url", ["-f", "flv"]) ditem.update_stats({"filename": "file.f123", "extension": ".webm", "filesize": "10.00MiB", "percent": "100%", "speed": "", "eta": "", "status": "Downloading", "path": "/home/user"}) ditem.update_stats({"filename": "file.f456", "extension": ".m4a", "filesize": "3.45MiB", "percent": "100%", "speed": "", "eta": "", "status": "Downloading", "path": "/home/user"}) # Mimic youtube-dl post process behaviour ditem.update_stats({"filename": "file", "extension": ".webm", "percent": "100%", "speed": "", "eta": "", "status": "Post Processing"}) self.assertEqual(ditem.filesizes, [10485760.00, 3617587.20, 14103347.20]) self.assertEqual( ditem.progress_stats, {"filename": "file", "extension": ".webm", "filesize": "13.45MiB", "percent": "100%", "speed": "-", "eta": "-", "status": "Post Processing", "playlist_size": "", "playlist_index": ""} ) class TestReset(unittest.TestCase): """Test case for the DownloadItem reset method.""" def setUp(self): self.ditem = DownloadItem("url", ["-f", "flv"]) def test_reset_completed_stage(self): self.ditem._stage = "Completed" self.ditem.path = os.path.join("/home", "user") self.ditem.filenames = ["file"] self.ditem.extensions = [".mp4"] self.ditem.filesizes = [123456.00] self.ditem.progress_stats = { "filename": "file", "extension": ".mp4", "filsize": "6.66MiB", "percent": "100%", "speed": "-", "eta": "-", "status": "Finished", "playlist_size": "", "playlist_index": "" } self.ditem.reset() self.assertEqual(self.ditem._stage, "Queued") self.assertEqual(self.ditem.path, "") self.assertEqual(self.ditem.filenames, []) self.assertEqual(self.ditem.extensions, []) self.assertEqual(self.ditem.filesizes, []) self.assertEqual( self.ditem.progress_stats, {"filename": "url", "extension": "-", "filesize": "-", "percent": "0%", "speed": "-", "eta": "-", "status": "Queued", "playlist_size": "", "playlist_index": ""} ) def test_reset_error_stage(self): self.ditem._stage = "Error" self.ditem.path = os.path.join("/home", "user") self.ditem.filenames = ["file1", "file2", "file"] self.ditem.extensions = [".mp4", ".m4a", ".mp4"] self.ditem.filesizes = [1234.00, 3421.00, 4655.00] self.ditem.progress_stats = { "filename": "file", "extension": ".mp4", "filsize": "9.45MiB", "percent": "100%", "speed": "-", "eta": "-", "status": "Error", "playlist_size": "10", "playlist_index": "8" } self.ditem.reset() self.assertEqual(self.ditem._stage, "Queued") self.assertEqual(self.ditem.path, "") self.assertEqual(self.ditem.filenames, []) self.assertEqual(self.ditem.extensions, []) self.assertEqual(self.ditem.filesizes, []) self.assertEqual( self.ditem.progress_stats, {"filename": "url", "extension": "-", "filesize": "-", "percent": "0%", "speed": "-", "eta": "-", "status": "Queued", "playlist_size": "", "playlist_index": ""} ) def test_reset_paused_stage(self): self.ditem._stage = "Paused" # No need to change filanames, extension, etc # since everything in pause state has the default value self.ditem.reset() self.assertEqual(self.ditem._stage, "Queued") def test_reset_active_stage(self): self.ditem._stage = "Active" self.ditem.path = os.path.join("/home", "user") self.ditem.filenames = ["file1"] self.ditem.extensions = [".mp4"] self.ditem.filesizes = [] self.ditem.progress_stats = { "filename": "file1", "extension": ".mp4", "filsize": "9.45MiB", "percent": "75.5%", "speed": "200.00KiB/s", "eta": "00:10", "status": "Downloading" } self.assertRaises(RuntimeError, self.ditem.reset) def main(): unittest.main() if __name__ == '__main__': main() ================================================ FILE: tests/test_dlist.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """Contains test cases for the DownloadList object.""" from __future__ import unicode_literals import sys import os.path import unittest PATH = os.path.realpath(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(os.path.dirname(PATH))) try: import mock from youtube_dl_gui.downloadmanager import DownloadList, synchronized except ImportError as error: print error sys.exit(1) class TestInit(unittest.TestCase): """Test case for the DownloadList init.""" def test_init(self): mocks = [mock.Mock(object_id=0), mock.Mock(object_id=1)] dlist = DownloadList(mocks) self.assertEqual(dlist._items_list, [0, 1]) self.assertEqual(dlist._items_dict, {0: mocks[0], 1: mocks[1]}) def test_init_empty(self): dlist = DownloadList() self.assertEqual(dlist._items_list, []) self.assertEqual(dlist._items_dict, {}) def test_init_invalid_args(self): self.assertRaises(AssertionError, DownloadList, {}) self.assertRaises(AssertionError, DownloadList, ()) self.assertRaises(AssertionError, DownloadList, False) class TestInsert(unittest.TestCase): """Test case for the DownloadList insert method.""" def test_insert(self): mock_ditem = mock.Mock(object_id=0) dlist = DownloadList() dlist.insert(mock_ditem) self.assertEqual(dlist._items_list, [0]) self.assertEqual(dlist._items_dict, {0: mock_ditem}) class TestRemove(unittest.TestCase): """Test case for the DownloadList remove method.""" def setUp(self): self.mocks = [mock.Mock(object_id=0), mock.Mock(object_id=1), mock.Mock(object_id=2)] self.dlist = DownloadList(self.mocks) def test_remove(self): self.assertTrue(self.dlist.remove(1)) self.assertEqual(self.dlist._items_list, [0, 2]) self.assertEqual(self.dlist._items_dict, {0: self.mocks[0], 2: self.mocks[2]}) def test_remove_not_exist(self): self.assertRaises(KeyError, self.dlist.remove, 3) def test_remove_active(self): self.mocks[1].stage = "Active" self.assertFalse(self.dlist.remove(1)) self.assertEqual(self.dlist._items_list, [0, 1, 2]) self.assertEqual(self.dlist._items_dict, {0: self.mocks[0], 1: self.mocks[1], 2: self.mocks[2]}) class TestFetchNext(unittest.TestCase): """Test case for the DownloadList fetch_next method.""" def test_fetch_next(self): items_count = 3 mocks = [mock.Mock(object_id=i, stage="Queued") for i in range(items_count)] dlist = DownloadList(mocks) for i in range(items_count): self.assertEqual(dlist.fetch_next(), mocks[i]) mocks[i].stage = "Active" self.assertIsNone(dlist.fetch_next()) for i in range(items_count): mocks[i].stage = "Completed" self.assertIsNone(dlist.fetch_next()) mocks[1].stage = "Queued" # Re-queue item self.assertEqual(dlist.fetch_next(), mocks[1]) def test_fetch_next_empty_list(self): dlist = DownloadList() self.assertIsNone(dlist.fetch_next()) class TestMoveUp(unittest.TestCase): """Test case for the DownloadList move_up method.""" def setUp(self): mocks = [mock.Mock(object_id=i, stage="Queued") for i in range(3)] self.dlist = DownloadList(mocks) def test_move_up(self): self.assertTrue(self.dlist.move_up(1)) self.assertEqual(self.dlist._items_list, [1, 0, 2]) def test_move_up_already_on_top(self): self.assertFalse(self.dlist.move_up(0)) self.assertEqual(self.dlist._items_list, [0, 1, 2]) def test_move_up_not_exist(self): self.assertRaises(ValueError, self.dlist.move_up, 666) class TestMoveDown(unittest.TestCase): """Test case for the DownloadList move_down method.""" def setUp(self): mocks = [mock.Mock(object_id=i, stage="Queued") for i in range(3)] self.dlist = DownloadList(mocks) def test_move_down(self): self.assertTrue(self.dlist.move_down(1)) self.assertEqual(self.dlist._items_list, [0, 2, 1]) def test_move_down_already_on_bottom(self): self.assertFalse(self.dlist.move_down(2)) self.assertEqual(self.dlist._items_list, [0, 1, 2]) def test_move_down_not_exist(self): self.assertRaises(ValueError, self.dlist.move_down, 666) class TestGetItem(unittest.TestCase): """Test case for the DownloadList get_item method.""" def test_get_item(self): mocks = [mock.Mock(object_id=i) for i in range(3)] dlist = DownloadList(mocks) self.assertEqual(dlist.get_item(0), mocks[0]) self.assertEqual(dlist.get_item(2), mocks[2]) def test_get_item_not_exist(self): dlist = DownloadList() self.assertRaises(KeyError, dlist.get_item, 0) class TestGetLength(unittest.TestCase): """Test case for the DownloadList __len__ method.""" def test_get_length(self): dlist = DownloadList([mock.Mock(), mock.Mock()]) self.assertEqual(len(dlist), 2) def test_get_length_empty_list(self): dlist = DownloadList() self.assertEqual(len(dlist), 0) class TestHasItem(unittest.TestCase): """Test case for the DownloadList has_item method.""" def setUp(self): mock_ditem = mock.Mock(object_id=1337) self.dlist = DownloadList([mock_ditem]) def test_has_item_true(self): self.assertTrue(self.dlist.has_item(1337)) def test_has_item_false(self): self.assertFalse(self.dlist.has_item(1000)) class TestGetItems(unittest.TestCase): """Test case for the DownloadList get_items method.""" def test_get_items(self): mocks = [mock.Mock() for _ in range(3)] dlist = DownloadList(mocks) self.assertEqual(dlist.get_items(), mocks) def test_get_items_empty_list(self): dlist = DownloadList() self.assertEqual(dlist.get_items(), []) class TestClear(unittest.TestCase): """Test case for the DownloadList clear method.""" def test_clear(self): dlist = DownloadList([mock.Mock() for _ in range(3)]) self.assertEqual(len(dlist), 3) dlist.clear() self.assertEqual(len(dlist), 0) class TestChangeStage(unittest.TestCase): """Test case for the DownloadList change_stage method.""" def setUp(self): self.mocks = [mock.Mock(object_id=i, stage="Queued") for i in range(3)] self.dlist = DownloadList(self.mocks) def test_change_stage(self): self.dlist.change_stage(0, "Active") self.assertEqual(self.mocks[0].stage, "Active") def test_change_stage_id_not_exist(self): self.assertRaises(KeyError, self.dlist.change_stage, 3, "Active") class TestIndex(unittest.TestCase): """Test case for the DownloadList index method.""" def setUp(self): self.mocks = [mock.Mock(object_id=i) for i in range(3)] self.dlist = DownloadList(self.mocks) def test_index(self): self.assertEqual(self.dlist.index(2), 2) def test_index_not_exist(self): self.assertEqual(self.dlist.index(3), -1) class TestSynchronizeDecorator(unittest.TestCase): def test_synchronize(self): mock_func = mock.Mock() mock_lock = mock.Mock() decorated_func = synchronized(mock_lock)(mock_func) self.assertEqual(decorated_func(1, a=2), mock_func.return_value) mock_func.assert_called_once_with(1, a=2) mock_lock.acquire.assert_called_once() mock_lock.release.assert_called_once() def main(): unittest.main() if __name__ == '__main__': main() ================================================ FILE: tests/test_parsers.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """Contains test cases for the parsers module.""" from __future__ import unicode_literals import sys import os.path import unittest PATH = os.path.realpath(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(os.path.dirname(PATH))) try: from youtube_dl_gui.parsers import OptionsParser except ImportError as error: print error sys.exit(1) class TestParse(unittest.TestCase): """Test case for OptionsParser parse method.""" def setUp(self): # Create the options_dict based on the OptionHolder # items inside the OptionsParser object self.options_dict = {item.name:item.default_value for item in OptionsParser()._ydl_options} # Add extra options used by the OptionsParser.parse method self.options_dict["save_path"] = "/home/user/Workplace/test/youtube" self.options_dict["cmd_args"] = "" self.options_dict["output_format"] = 1 # Title self.options_dict["second_video_format"] = "0" self.options_dict["min_filesize_unit"] = "" self.options_dict["max_filesize_unit"] = "" def check_options_parse(self, expected_options): options_parser = OptionsParser() self.assertItemsEqual(options_parser.parse(self.options_dict), expected_options) def test_parse_to_audio_requirement_bug(self): """Test case for the 'to_audio' requirement.""" self.options_dict["audio_quality"] = "9" self.options_dict["audio_format"] = "mp3" self.options_dict["embed_thumbnail"] = True expected_cmd_list = ["--newline", "-x", "--audio-format", "mp3", "--embed-thumbnail", "--audio-quality", "9", "-o", "/home/user/Workplace/test/youtube/%(title)s.%(ext)s"] self.check_options_parse(expected_cmd_list) # Setting 'to_audio' to True should return the same results # since the '-x' flag is already set on audio extraction self.options_dict["to_audio"] = True self.check_options_parse(expected_cmd_list) def test_parse_cmd_args_with_quotes(self): """Test the youtube-dl cmd line args parsing when quotes are presented. See: https://github.com/MrS0m30n3/youtube-dl-gui/issues/54 """ self.options_dict["video_format"] = "mp4" # Test with three quoted 'cmd_args' self.options_dict["cmd_args"] = "--recode-video mkv --postprocessor-args \"-codec copy -report\"" expected_cmd_list = ["--newline", "-f", "mp4", "-o", "/home/user/Workplace/test/youtube/%(title)s.%(ext)s", "--recode-video", "mkv", "--postprocessor-args", "-codec copy -report"] self.check_options_parse(expected_cmd_list) # Test with two quoted 'cmd_args' self.options_dict["cmd_args"] = "--postprocessor-args \"-y -report\"" expected_cmd_list = ["--newline", "-f", "mp4", "-o", "/home/user/Workplace/test/youtube/%(title)s.%(ext)s", "--postprocessor-args", "-y -report"] self.check_options_parse(expected_cmd_list) # Test with one quoted 'cmd_arg' followed by other cmd line args self.options_dict["cmd_args"] = "--postprocessor-args \"-y\" -v" expected_cmd_list = ["--newline", "-f", "mp4", "-o", "/home/user/Workplace/test/youtube/%(title)s.%(ext)s", "--postprocessor-args", "-y", "-v"] self.check_options_parse(expected_cmd_list) # Test the example presented in issue #54 self.options_dict["cmd_args"] = "-f \"(mp4)[width<1300]\"" self.options_dict["video_format"] = "0" # Set video format to 'default' expected_cmd_list = ["--newline", "-o", "/home/user/Workplace/test/youtube/%(title)s.%(ext)s", "-f", "(mp4)[width<1300]"] self.check_options_parse(expected_cmd_list) def main(): unittest.main() if __name__ == '__main__': main() ================================================ FILE: tests/test_utils.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- """Contains test cases for the utils.py module.""" from __future__ import unicode_literals import sys import os.path import unittest PATH = os.path.realpath(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(os.path.dirname(PATH))) try: import mock from youtube_dl_gui import utils except ImportError as error: print error sys.exit(1) class TestToBytes(unittest.TestCase): """Test case for the to_bytes method.""" def test_to_bytes_bytes(self): self.assertEqual(utils.to_bytes("596.00B"), 596.00) self.assertEqual(utils.to_bytes("133.55B"), 133.55) def test_to_bytes_kilobytes(self): self.assertEqual(utils.to_bytes("1.00KiB"), 1024.00) self.assertEqual(utils.to_bytes("5.55KiB"), 5683.20) def test_to_bytes_megabytes(self): self.assertEqual(utils.to_bytes("13.64MiB"), 14302576.64) self.assertEqual(utils.to_bytes("1.00MiB"), 1048576.00) def test_to_bytes_gigabytes(self): self.assertEqual(utils.to_bytes("1.00GiB"), 1073741824.00) self.assertEqual(utils.to_bytes("1.55GiB"), 1664299827.20) def test_to_bytes_terabytes(self): self.assertEqual(utils.to_bytes("1.00TiB"), 1099511627776.00) class TestFormatBytes(unittest.TestCase): """Test case for the format_bytes method.""" def test_format_bytes_bytes(self): self.assertEqual(utils.format_bytes(518.00), "518.00B") def test_format_bytes_kilobytes(self): self.assertEqual(utils.format_bytes(1024.00), "1.00KiB") def test_format_bytes_megabytes(self): self.assertEqual(utils.format_bytes(1048576.00), "1.00MiB") def test_format_bytes_gigabytes(self): self.assertEqual(utils.format_bytes(1073741824.00), "1.00GiB") def test_format_bytes_terabytes(self): self.assertEqual(utils.format_bytes(1099511627776.00), "1.00TiB") class TestBuildCommand(unittest.TestCase): """Test case for the build_command method.""" def setUp(self): self.url = "https://www.youtube.com/watch?v=aaaaaaaaaaa&list=AAAAAAAAAAA" self.options = ["-o", None, "-f", "mp4", "--ignore-config"] self.result = "{{ydl_bin}} -o \"{{tmpl}}\" -f mp4 --ignore-config \"{url}\"".format(url=self.url) def run_tests(self, ydl_bin, tmpl): """Run the main test. Args: ydl_bin (str): Name of the youtube-dl binary tmpl (str): Youtube-dl output template """ utils.YOUTUBEDL_BIN = ydl_bin self.options[1] = tmpl # Plug the template in our options result = self.result.format(ydl_bin=ydl_bin, tmpl=tmpl) self.assertEqual(utils.build_command(self.options, self.url), result) def test_build_command_with_spaces_linux(self): tmpl = "/home/user/downloads/%(upload_date)s/%(id)s_%(playlist_id)s - %(format)s.%(ext)s" self.run_tests("youtube-dl", tmpl) def test_build_command_without_spaces_linux(self): tmpl = "/home/user/downloads/%(id)s.%(ext)s" self.run_tests("youtube-dl", tmpl) def test_build_command_with_spaces_windows(self): tmpl = "C:\\downloads\\%(upload_date)s\\%(id)s_%(playlist_id)s - %(format)s.%(ext)s" self.run_tests("youtube-dl.exe", tmpl) def test_build_command_without_spaces_windows(self): tmpl = "C:\\downloads\\%(id)s.%(ext)s" self.run_tests("youtube-dl.exe", tmpl) class TestConvertItem(unittest.TestCase): """Test case for the convert_item function.""" def setUp(self): self.input_list_u = ["v1", "v2", "v3"] self.input_list_s = [str("v1"), str("v2"), str("v3")] self.input_tuple_u = ("v1", "v2", "v3") self.input_tuple_s = (str("v1"), str("v2"), str("v3")) self.input_dict_u = {"k1": "v1", "k2": "v2"} self.input_dict_s = {str("k1"): str("v1"), str("k2"): str("v2")} def check_iter(self, iterable, iter_type, is_unicode): check_type = unicode if is_unicode else str iterable = utils.convert_item(iterable, is_unicode) self.assertIsInstance(iterable, iter_type) for item in iterable: if iter_type == dict: self.assertIsInstance(iterable[item], check_type) self.assertIsInstance(item, check_type) def test_convert_item_unicode_str(self): self.assertIsInstance(utils.convert_item("test"), str) def test_convert_item_unicode_unicode(self): self.assertIsInstance(utils.convert_item("test", True), unicode) def test_convert_item_str_unicode(self): self.assertIsInstance(utils.convert_item(str("test"), True), unicode) def test_convert_item_str_str(self): self.assertIsInstance(utils.convert_item(str("test")), str) def test_convert_item_list_empty(self): self.assertEqual(len(utils.convert_item([])), 0) def test_convert_item_dict_empty(self): self.assertEqual(len(utils.convert_item({})), 0) def test_convert_item_list_unicode_str(self): self.check_iter(self.input_list_u, list, False) def test_convert_item_list_str_unicode(self): self.check_iter(self.input_list_s, list, True) def test_convert_item_tuple_unicode_str(self): self.check_iter(self.input_tuple_u, tuple, False) def test_convert_item_tuple_str_unicode(self): self.check_iter(self.input_tuple_s, tuple, True) def test_convert_item_dict_unicode_str(self): self.check_iter(self.input_dict_u, dict, False) def test_convert_item_dict_str_unicode(self): self.check_iter(self.input_dict_s, dict, True) class TestGetDefaultLang(unittest.TestCase): """Test case for the get_default_lang function.""" @mock.patch("youtube_dl_gui.utils.locale_getdefaultlocale") def run_tests(self, ret_value, result, mock_getdefaultlocale): """Run the main test. Args: ret_value (tuple): Return tuple of the locale.getdefaultlocale module result (unicode): Result we want to see mock_getdefaultlocale (MagicMock): Mock object """ mock_getdefaultlocale.return_value = ret_value lang = utils.get_default_lang() mock_getdefaultlocale.assert_called_once() self.assertEqual(lang, result) def test_get_default_lang(self): self.run_tests(("it_IT", "UTF-8"), "it_IT") def test_get_default_lang_none(self): self.run_tests((None, None), "en_US") def test_get_default_lang_empty(self): self.run_tests(("", ""), "en_US") def main(): unittest.main() if __name__ == "__main__": main() ================================================ FILE: tests/test_widgets.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- """Contains test cases for the widgets.py module.""" from __future__ import unicode_literals import sys import os.path import unittest PATH = os.path.realpath(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(os.path.dirname(PATH))) try: import wx import mock from youtube_dl_gui.widgets import ( ListBoxWithHeaders, CustomComboBox, ListBoxPopup ) except ImportError as error: print error sys.exit(1) class TestListBoxWithHeaders(unittest.TestCase): """Test cases for the ListBoxWithHeaders widget.""" def setUp(self): self.app = wx.App() self.frame = wx.Frame(None) self.listbox = ListBoxWithHeaders(self.frame) self.listbox.add_header("Header") self.listbox.add_items(["item%s" % i for i in xrange(10)]) def tearDown(self): self.frame.Destroy() def test_find_string_header_found(self): self.assertEqual(self.listbox.FindString("Header"), 0) def test_find_string_header_not_found(self): self.assertEqual(self.listbox.FindString("Header2"), wx.NOT_FOUND) def test_find_string_item_found(self): self.assertEqual(self.listbox.FindString("item1"), 2) def test_find_string_item_not_found(self): self.assertEqual(self.listbox.FindString("item"), wx.NOT_FOUND) def test_get_string_header(self): self.assertEqual(self.listbox.GetString(0), "Header") def test_get_string_item(self): self.assertEqual(self.listbox.GetString(10), "item9") def test_get_string_item_not_found(self): self.assertEqual(self.listbox.GetString(11), "") def test_get_string_item_negative_index(self): self.assertEqual(self.listbox.GetString(-1), "") def test_insert_items(self): self.listbox.SetSelection(1) self.listbox.InsertItems(["new_item1", "new_item2"], 1) self.assertEqual(self.listbox.GetString(1), "new_item1") self.assertEqual(self.listbox.GetString(2), "new_item2") self.assertEqual(self.listbox.GetString(3), "item0") self.assertTrue(self.listbox.IsSelected(3)) # Old selection + 2 def test_set_selection_header(self): self.listbox.SetSelection(0) self.assertFalse(self.listbox.IsSelected(0)) def test_set_selection_item_valid_index(self): self.listbox.SetSelection(1) self.assertEqual(self.listbox.GetSelection(), 1) def test_set_selection_item_invalid_index(self): self.listbox.SetSelection(1) self.assertEqual(self.listbox.GetSelection(), 1) self.listbox.SetSelection(wx.NOT_FOUND) self.assertEqual(self.listbox.GetSelection(), wx.NOT_FOUND) def test_set_string_item(self): self.listbox.SetString(1, "item_mod0") self.assertEqual(self.listbox.GetString(1), "item_mod0") def test_set_string_header(self): self.listbox.SetString(0, "New header") self.assertEqual(self.listbox.GetString(0), "New header") # Make sure that the header is not selectable self.listbox.SetSelection(0) self.assertFalse(self.listbox.IsSelected(0)) def test_set_string_selection_header(self): self.assertFalse(self.listbox.SetStringSelection("Header")) self.assertFalse(self.listbox.IsSelected(0)) def test_set_string_selection_item(self): self.assertTrue(self.listbox.SetStringSelection("item1")) self.assertTrue(self.listbox.IsSelected(2)) def test_get_string_selection(self): self.listbox.SetSelection(1) self.assertEqual(self.listbox.GetStringSelection(), "item0") def test_get_string_selection_empty(self): self.assertEqual(self.listbox.GetStringSelection(), "") # wx.ItemContainer methods def test_append(self): self.listbox.Append("item666") self.assertEqual(self.listbox.GetString(11), "item666") def test_append_items(self): self.listbox.AppendItems(["new_item1", "new_item2"]) self.assertEqual(self.listbox.GetString(11), "new_item1") self.assertEqual(self.listbox.GetString(12), "new_item2") def test_clear(self): self.listbox.Clear() self.assertEqual(self.listbox.GetItems(), []) def test_delete(self): self.listbox.Delete(0) self.assertEqual(self.listbox.GetString(0), "item0") # Test item selection self.listbox.SetSelection(0) self.assertTrue(self.listbox.IsSelected(0)) # Test object extra methods def test_add_header(self): self.listbox.add_header("Header2") self.listbox.SetSelection(11) self.assertFalse(self.listbox.IsSelected(11)) @mock.patch("wx.ListBox.Append") def test_add_item_with_prefix(self, mock_append): self.listbox.add_item("new_item") mock_append.assert_called_once_with(ListBoxWithHeaders.TEXT_PREFIX + "new_item") @mock.patch("wx.ListBox.Append") def test_add_item_without_prefix(self, mock_append): self.listbox.add_item("new_item", with_prefix=False) mock_append.assert_called_once_with("new_item") @mock.patch("wx.ListBox.AppendItems") def test_add_items_with_prefix(self, mock_append): self.listbox.add_items(["new_item1", "new_item2"]) mock_append.assert_called_once_with([ListBoxWithHeaders.TEXT_PREFIX + "new_item1", ListBoxWithHeaders.TEXT_PREFIX + "new_item2"]) @mock.patch("wx.ListBox.AppendItems") def test_add_items_without_prefix(self, mock_append): self.listbox.add_items(["new_item1", "new_item2"], with_prefix=False) mock_append.assert_called_once_with(["new_item1", "new_item2"]) class TestCustomComboBox(unittest.TestCase): """Test cases for the CustomComboBox widget.""" def setUp(self): self.app = wx.App() self.frame = wx.Frame(None) self.combobox = CustomComboBox(self.frame) # Call directly the ListBoxWithHeaders methods self.combobox.listbox.GetControl().add_header("Header") self.combobox.listbox.GetControl().add_items(["item%s" % i for i in xrange(10)]) def tearDown(self): self.frame.Destroy() def test_init(self): combobox = CustomComboBox(self.frame, -1, "item1", choices=["item0", "item1", "item2"]) self.assertEqual(combobox.GetValue(), "item1") self.assertEqual(combobox.GetCount(), 3) self.assertEqual(combobox.GetSelection(), 1) # wx.ComboBox methods # Not all of them since most of them are calls to ListBoxWithHeaders # methods and we already have tests for those def test_is_list_empty_false(self): self.assertFalse(self.combobox.IsListEmpty()) def test_is_list_empty_true(self): self.combobox.Clear() self.assertTrue(self.combobox.IsListEmpty()) def test_is_text_empty_false(self): self.combobox.SetValue("somevalue") self.assertFalse(self.combobox.IsTextEmpty()) def test_is_text_empty_true(self): self.assertTrue(self.combobox.IsTextEmpty()) def test_set_selection_item(self): self.combobox.SetSelection(1) self.assertEqual(self.combobox.GetSelection(), 1) self.assertEqual(self.combobox.GetValue(), "item0") def test_set_selection_header(self): self.combobox.SetSelection(0) self.assertEqual(self.combobox.GetSelection(), wx.NOT_FOUND) self.assertEqual(self.combobox.GetValue(), "") def test_set_string_selection_item(self): self.combobox.SetStringSelection("item0") self.assertEqual(self.combobox.GetStringSelection(), "item0") self.assertEqual(self.combobox.GetValue(), "item0") def test_set_string_selection_header(self): self.combobox.SetStringSelection("Header") self.assertEqual(self.combobox.GetStringSelection(), "") self.assertEqual(self.combobox.GetValue(), "") def test_set_string_selection_invalid_string(self): self.combobox.SetStringSelection("abcde") self.assertEqual(self.combobox.GetStringSelection(), "") self.assertEqual(self.combobox.GetValue(), "") # wx.ItemContainer methods def test_clear(self): self.combobox.SetValue("value") self.combobox.Clear() self.assertEqual(self.combobox.GetCount(), 0) self.assertTrue(self.combobox.IsTextEmpty()) def test_append(self): self.combobox.Append("item10") self.assertEqual(self.combobox.GetCount(), 12) def test_append_items(self): self.combobox.AppendItems(["item10", "item11"]) self.assertEqual(self.combobox.GetCount(), 13) def test_delete(self): self.combobox.Delete(1) self.assertEqual(self.combobox.GetString(1), "item1") # wx.TextEntry methods def test_get_value(self): self.combobox.SetValue("value") self.assertEqual(self.combobox.GetValue(), "value") def main(): unittest.main() if __name__ == '__main__': main() ================================================ FILE: youtube-dl-gui.1 ================================================ .\" [program name] [section] [date YYYY-MM-DD] [version] [empty] .TH YOUTUBE\-DL\-GUI 1 "2018-01-13" "Version 0.4" "" .SH NAME youtube\-dl\-gui \- cross platform graphical user interface for youtube\-dl. .SH SYNOPSIS .B youtube\-dl\-gui .SH DESCRIPTION Youtube\-dl\-gui is a graphical frontend of the popular youtube\-dl command\-line program. As such, it does not take any command\-line parameters. You may configure the program through the graphical settings dialog, or the configuration file. .SH FILES .\" .IP text indent_size (.IP = Indented Paragraph) .IP "\fB$HOME/.config/youtube\-dlg/settings.json\fR" 4 Configuration file. .\" .IP text indent_size (.IP = Indented Paragraph) .IP "\fB$HOME/.config/youtube\-dlg/log\fR" 4 Log file. .SH NOTES FAQS: https://github.com/MrS0m30n3/youtube-dl-gui/blob/master/docs/faqs.md .SH SEE ALSO .BR youtube-dl (1) ================================================ FILE: youtube_dl_gui/__init__.py ================================================ #!/usr/bin/env python2 # -*- coding: utf-8 -*- """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 gettext import os.path try: import wx except ImportError as error: print error sys.exit(1) __packagename__ = "youtube_dl_gui" # For package use from .version import __version__ from .info import ( __author__, __appname__, __contact__, __license__, __projecturl__, __licensefull__, __description__, __descriptionfull__, ) gettext.install(__packagename__) from .formats import reload_strings from .logmanager import LogManager from .optionsmanager import OptionsManager from .utils import ( get_config_path, get_locale_file, os_path_exists, YOUTUBEDL_BIN ) # 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(__packagename__, locale_dir, [opt_manager.options['locale_name']]).install(unicode=True) except IOError: opt_manager.options['locale_name'] = 'en_US' gettext.install(__packagename__) reload_strings() from .mainframe import MainFrame def main(): """The real main. Creates and calls the main app windows. """ youtubedl_path = os.path.join(opt_manager.options["youtubedl_path"], YOUTUBEDL_BIN) app = wx.App() frame = MainFrame(opt_manager, log_manager) frame.Show() if opt_manager.options["disable_update"] and not os_path_exists(youtubedl_path): wx.MessageBox(_("Failed to locate youtube-dl and updates are disabled"), _("Error"), wx.OK | wx.ICON_ERROR) frame.close() app.MainLoop() ================================================ FILE: youtube_dl_gui/__main__.py ================================================ #!/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))) import youtube_dl_gui if __name__ == '__main__': youtube_dl_gui.main() ================================================ FILE: youtube_dl_gui/data/pixmaps/icons-license ================================================ icons from: https://www.iconfinder.com/iconsets/google-material-design-icons license: https://creativecommons.org/licenses/by-sa/3.0 ================================================ FILE: youtube_dl_gui/downloaders.py ================================================ #!/usr/bin/env python2 # -*- coding: utf-8 -*- """Python module to download videos. This module contains the actual downloaders responsible for downloading the video files. """ from __future__ import unicode_literals import re import os import sys import locale import signal import subprocess from time import sleep from Queue import Queue from threading import Thread from .utils import convert_item class PipeReader(Thread): """Helper class to avoid deadlocks when reading from subprocess pipes. This class uses python threads and queues in order to read from subprocess pipes in an asynchronous way. Attributes: WAIT_TIME (float): Time in seconds to sleep. Args: queue (Queue.Queue): Python queue to store the output of the subprocess. Warnings: All the operations are based on 'str' types. The caller has to convert the queued items back to 'unicode' if he needs to. """ WAIT_TIME = 0.1 def __init__(self, queue): super(PipeReader, self).__init__() self._filedescriptor = None self._running = True self._queue = queue self.start() def run(self): # Flag to ignore specific lines ignore_line = False while self._running: if self._filedescriptor is not None: for line in iter(self._filedescriptor.readline, str('')): # Ignore ffmpeg stderr if str('ffmpeg version') in line: ignore_line = True if not ignore_line: self._queue.put_nowait(line) self._filedescriptor = None ignore_line = False sleep(self.WAIT_TIME) def attach_filedescriptor(self, filedesc): """Attach a filedescriptor to the PipeReader. """ self._filedescriptor = filedesc def join(self, timeout=None): self._running = False super(PipeReader, self).join(timeout) class YoutubeDLDownloader(object): """Python class for downloading videos using youtube-dl & subprocess. Attributes: OK, ERROR, STOPPED, ALREADY, FILESIZE_ABORT, WARNING (int): Integers that describe the return code from the download() method. The larger the number the higher is the hierarchy of the code. Codes with smaller hierachy cannot overwrite codes with higher hierarchy. Args: youtubedl_path (string): Absolute path to youtube-dl binary. data_hook (function): Optional callback function to retrieve download process data. log_data (function): Optional callback function to write data to the log file. Warnings: The caller is responsible for calling the close() method after he has finished with the object in order for the object to be able to properly close down itself. 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 WARNING = 1 ERROR = 2 FILESIZE_ABORT = 3 ALREADY = 4 STOPPED = 5 def __init__(self, youtubedl_path, data_hook=None, log_data=None): self.youtubedl_path = youtubedl_path self.data_hook = data_hook self.log_data = log_data self._return_code = self.OK self._proc = None self._stderr_queue = Queue() self._stderr_reader = PipeReader(self._stderr_queue) 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. There are 6 different return codes. OK (0): The download process completed successfully. WARNING (1): A warning occured during the download process. ERROR (2): An error occured during the download process. FILESIZE_ABORT (3): The corresponding url video file was larger or smaller from the given filesize limit. ALREADY (4): The given url is already downloaded. STOPPED (5): The download process was stopped by the user. """ self._return_code = self.OK cmd = self._get_cmd(url, options) self._create_process(cmd) if self._proc is not None: self._stderr_reader.attach_filedescriptor(self._proc.stderr) while self._proc_is_alive(): stdout = self._proc.stdout.readline().rstrip() stdout = convert_item(stdout, to_unicode=True) if stdout: data_dict = extract_data(stdout) self._extract_info(data_dict) self._hook_data(data_dict) # Read stderr after download process has been completed # We don't need to read stderr in real time while not self._stderr_queue.empty(): stderr = self._stderr_queue.get_nowait().rstrip() stderr = convert_item(stderr, to_unicode=True) self._log(stderr) if self._is_warning(stderr): self._set_returncode(self.WARNING) else: self._set_returncode(self.ERROR) # Set return code to ERROR if we could not start the download process # or the childs return code is greater than zero # NOTE: In Linux if the called script is just empty Python exits # normally (ret=0), so we cant detect this or similar cases # using the code below # NOTE: In Unix a negative return code (-N) indicates that the child # was terminated by signal N (e.g. -9 = SIGKILL) if self._proc is None or self._proc.returncode > 0: self._return_code = self.ERROR if self._proc is not None and self._proc.returncode > 0: self._log('Child process exited with non-zero code: {}'.format(self._proc.returncode)) 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(): if os.name == 'nt': # os.killpg is not available on Windows # See: https://bugs.python.org/issue5115 self._proc.kill() # When we kill the child process on Windows the return code # gets set to 1, so we want to reset the return code back to 0 # in order to avoid creating logging output in the download(...) # method self._proc.returncode = 0 else: os.killpg(self._proc.pid, signal.SIGKILL) self._set_returncode(self.STOPPED) def close(self): """Destructor like function for the object. """ self._stderr_reader.join() def _set_returncode(self, code): """Set self._return_code only if the hierarchy of the given code is higher than the current self._return_code. """ if code >= self._return_code: self._return_code = code 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. """ data_dictionary = {} if self._return_code == self.OK: data_dictionary['status'] = 'Finished' elif self._return_code == self.ERROR: data_dictionary['status'] = 'Error' data_dictionary['speed'] = '' data_dictionary['eta'] = '' elif self._return_code == self.WARNING: data_dictionary['status'] = 'Warning' data_dictionary['speed'] = '' data_dictionary['eta'] = '' elif self._return_code == self.STOPPED: data_dictionary['status'] = 'Stopped' data_dictionary['speed'] = '' data_dictionary['eta'] = '' elif self._return_code == self.ALREADY: data_dictionary['status'] = 'Already Downloaded' else: data_dictionary['status'] = 'Filesize Abort' self._hook_data(data_dictionary) def _extract_info(self, data): """Extract informations about the download process from the given data. Args: data (dict): 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(). """ if 'status' in data: if data['status'] == 'Already Downloaded': # Set self._return_code to already downloaded # and trash that key self._set_returncode(self.ALREADY) data['status'] = None if data['status'] == 'Filesize Abort': # Set self._return_code to filesize abort # and trash that key self._set_returncode(self.FILESIZE_ABORT) data['status'] = None def _log(self, data): """Log data using the callback function. """ if self.log_data is not None: self.log_data(data) def _hook_data(self, data): """Pass data back to the caller. """ if self.data_hook is not None: self.data_hook(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 _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 _create_process(self, cmd): """Create new subprocess. Args: cmd (list): Python list that contains the command to execute. """ info = preexec = None # Keep a unicode copy of cmd for the log ucmd = cmd if os.name == 'nt': # Hide subprocess window info = subprocess.STARTUPINFO() info.dwFlags |= subprocess.STARTF_USESHOWWINDOW else: # Make subprocess the process group leader # in order to kill the whole process group with os.killpg preexec = os.setsid # Encode command for subprocess # Refer to http://stackoverflow.com/a/9951851/35070 if sys.version_info < (3, 0): cmd = convert_item(cmd, to_unicode=False) try: self._proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=preexec, startupinfo=info) except (ValueError, OSError) as error: self._log('Failed to start process: {}'.format(ucmd)) self._log(convert_item(str(error), to_unicode=True)) def extract_data(stdout): """Extract data from youtube-dl stdout. Args: stdout (string): String that contains the youtube-dl stdout. Returns: Python dictionary. The returned dictionary can be empty if there are no data to extract else it may contain one or more of the following keys: 'status' : Contains the status of the download process. 'path' : Destination path. 'extension' : The file extension. 'filename' : The filename without the extension. 'percent' : The percentage of the video being downloaded. 'eta' : Estimated time for the completion of the download process. 'speed' : Download speed. 'filesize' : The size of the video file being downloaded. 'playlist_index' : The playlist index of the current video file being downloaded. 'playlist_size' : The number of videos in the playlist. """ # REFACTOR def extract_filename(input_data): path, fullname = os.path.split(input_data.strip("\"")) filename, extension = os.path.splitext(fullname) return path, filename, extension data_dictionary = {} if not stdout: return data_dictionary # We want to keep the spaces in order to extract filenames with # multiple whitespaces correctly. We also keep a copy of the old # 'stdout' for backward compatibility with the old code stdout_with_spaces = stdout.split(' ') stdout = stdout.split() stdout[0] = stdout[0].lstrip('\r') if stdout[0] == '[download]': data_dictionary['status'] = 'Downloading' # Get path, filename & extension if stdout[1] == 'Destination:': path, filename, extension = extract_filename(' '.join(stdout_with_spaces[2:])) data_dictionary['path'] = path data_dictionary['filename'] = filename data_dictionary['extension'] = extension # Get progress info if '%' in stdout[1]: if stdout[1] == '100%': data_dictionary['speed'] = '' data_dictionary['eta'] = '' data_dictionary['percent'] = '100%' data_dictionary['filesize'] = stdout[3] 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] # Remove the 'and merged' part from stdout when using ffmpeg to merge the formats if stdout[-3] == 'downloaded' and stdout [-1] == 'merged': stdout = stdout[:-2] stdout_with_spaces = stdout_with_spaces[:-2] data_dictionary['percent'] = '100%' # Get file already downloaded status if stdout[-1] == 'downloaded': data_dictionary['status'] = 'Already Downloaded' path, filename, extension = extract_filename(' '.join(stdout_with_spaces[1:-4])) data_dictionary['path'] = path data_dictionary['filename'] = filename data_dictionary['extension'] = extension # Get filesize abort status if stdout[-1] == 'Aborting.': data_dictionary['status'] = 'Filesize Abort' elif stdout[0] == '[hlsnative]': # native hls extractor # see: https://github.com/rg3/youtube-dl/blob/master/youtube_dl/downloader/hls.py#L54 data_dictionary['status'] = 'Downloading' if len(stdout) == 7: segment_no = float(stdout[6]) current_segment = float(stdout[4]) # Get the percentage percent = '{0:.1f}%'.format(current_segment / segment_no * 100) data_dictionary['percent'] = percent elif stdout[0] == '[ffmpeg]': data_dictionary['status'] = 'Post Processing' # Get final extension after merging process if stdout[1] == 'Merging': path, filename, extension = extract_filename(' '.join(stdout_with_spaces[4:])) data_dictionary['path'] = path data_dictionary['filename'] = filename data_dictionary['extension'] = extension # Get final extension ffmpeg post process simple (not file merge) if stdout[1] == 'Destination:': path, filename, extension = extract_filename(' '.join(stdout_with_spaces[2:])) data_dictionary['path'] = path data_dictionary['filename'] = filename data_dictionary['extension'] = extension # Get final extension after recoding process if stdout[1] == 'Converting': path, filename, extension = extract_filename(' '.join(stdout_with_spaces[8:])) data_dictionary['path'] = path data_dictionary['filename'] = filename data_dictionary['extension'] = extension elif stdout[0][0] != '[' or stdout[0] == '[debug]': pass # Just ignore this output else: data_dictionary['status'] = 'Pre Processing' return data_dictionary ================================================ FILE: youtube_dl_gui/downloadmanager.py ================================================ #!/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, RLock, Lock ) 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, os_path_exists, format_bytes, to_string, to_bytes ) MANAGER_PUB_TOPIC = 'dlmanager' WORKER_PUB_TOPIC = 'dlworker' _SYNC_LOCK = RLock() # Decorator that adds thread synchronization to a function def synchronized(lock): def _decorator(func): def _wrapper(*args, **kwargs): lock.acquire() ret_value = func(*args, **kwargs) lock.release() return ret_value return _wrapper return _decorator class DownloadItem(object): """Object that represents a download. Attributes: STAGES (tuple): Main stages of the download item. ACTIVE_STAGES (tuple): Sub stages of the 'Active' stage. COMPLETED_STAGES (tuple): Sub stages of the 'Completed' stage. ERROR_STAGES (tuple): Sub stages of the 'Error' stage. Args: url (string): URL that corresponds to the download item. options (list): Options list to use during the download phase. """ STAGES = ("Queued", "Active", "Paused", "Completed", "Error") ACTIVE_STAGES = ("Pre Processing", "Downloading", "Post Processing") COMPLETED_STAGES = ("Finished", "Warning", "Already Downloaded") ERROR_STAGES = ("Error", "Stopped", "Filesize Abort") def __init__(self, url, options): self.url = url self.options = options self.object_id = hash(url + to_string(options)) self.reset() @property def stage(self): return self._stage @stage.setter def stage(self, value): if value not in self.STAGES: raise ValueError(value) if value == "Queued": self.progress_stats["status"] = value if value == "Active": self.progress_stats["status"] = self.ACTIVE_STAGES[0] if value == "Completed": self.progress_stats["status"] = self.COMPLETED_STAGES[0] if value == "Paused": self.progress_stats["status"] = value if value == "Error": self.progress_stats["status"] = self.ERROR_STAGES[0] self._stage = value def reset(self): if hasattr(self, "_stage") and self._stage == self.STAGES[1]: raise RuntimeError("Cannot reset an 'Active' item") self._stage = self.STAGES[0] self.path = "" self.filenames = [] self.extensions = [] self.filesizes = [] self.default_values = { "filename": self.url, "extension": "-", "filesize": "-", "percent": "0%", "speed": "-", "eta": "-", "status": self.stage, "playlist_size": "", "playlist_index": "" } self.progress_stats = dict(self.default_values) # Keep track when the 'playlist_index' changes self.playlist_index_changed = False def get_files(self): """Returns a list that contains all the system files bind to this object.""" files = [] for index, item in enumerate(self.filenames): filename = item + self.extensions[index] files.append(os.path.join(self.path, filename)) return files def update_stats(self, stats_dict): """Updates the progress_stats dict from the given dictionary.""" assert isinstance(stats_dict, dict) for key in stats_dict: if key in self.progress_stats: value = stats_dict[key] if not isinstance(value, basestring) or not value: self.progress_stats[key] = self.default_values[key] else: self.progress_stats[key] = value # Extract extra stuff if "playlist_index" in stats_dict: self.playlist_index_changed = True if "filename" in stats_dict: # Reset filenames, extensions & filesizes lists when changing playlist item if self.playlist_index_changed: self.filenames = [] self.extensions = [] self.filesizes = [] self.playlist_index_changed = False self.filenames.append(stats_dict["filename"]) if "extension" in stats_dict: self.extensions.append(stats_dict["extension"]) if "path" in stats_dict: self.path = stats_dict["path"] if "filesize" in stats_dict: if stats_dict["percent"] == "100%" and len(self.filesizes) < len(self.filenames): filesize = stats_dict["filesize"].lstrip("~") # HLS downloader etc self.filesizes.append(to_bytes(filesize)) if "status" in stats_dict: # If we are post processing try to calculate the size of # the output file since youtube-dl does not if stats_dict["status"] == self.ACTIVE_STAGES[2] and len(self.filesizes) == 2: post_proc_filesize = self.filesizes[0] + self.filesizes[1] self.filesizes.append(post_proc_filesize) self.progress_stats["filesize"] = format_bytes(post_proc_filesize) self._set_stage(stats_dict["status"]) def _set_stage(self, status): if status in self.ACTIVE_STAGES: self._stage = self.STAGES[1] if status in self.COMPLETED_STAGES: self._stage = self.STAGES[3] if status in self.ERROR_STAGES: self._stage = self.STAGES[4] def __eq__(self, other): return self.object_id == other.object_id class DownloadList(object): """List like data structure that contains DownloadItems. Args: items (list): List that contains DownloadItems. """ def __init__(self, items=None): assert isinstance(items, list) or items is None if items is None: self._items_dict = {} # Speed up lookup self._items_list = [] # Keep the sequence else: self._items_list = [item.object_id for item in items] self._items_dict = {item.object_id: item for item in items} @synchronized(_SYNC_LOCK) def clear(self): """Removes all the items from the list even the 'Active' ones.""" self._items_list = [] self._items_dict = {} @synchronized(_SYNC_LOCK) def insert(self, item): """Inserts the given item to the list. Does not check for duplicates. """ self._items_list.append(item.object_id) self._items_dict[item.object_id] = item @synchronized(_SYNC_LOCK) def remove(self, object_id): """Removes an item from the list. Removes the item with the corresponding object_id from the list if the item is not in 'Active' state. Returns: True on success else False. """ if self._items_dict[object_id].stage != "Active": self._items_list.remove(object_id) del self._items_dict[object_id] return True return False @synchronized(_SYNC_LOCK) def fetch_next(self): """Returns the next queued item on the list. Returns: Next queued item or None if no other item exist. """ for object_id in self._items_list: cur_item = self._items_dict[object_id] if cur_item.stage == "Queued": return cur_item return None @synchronized(_SYNC_LOCK) def move_up(self, object_id): """Moves the item with the corresponding object_id up to the list.""" index = self._items_list.index(object_id) if index > 0: self._swap(index, index - 1) return True return False @synchronized(_SYNC_LOCK) def move_down(self, object_id): """Moves the item with the corresponding object_id down to the list.""" index = self._items_list.index(object_id) if index < (len(self._items_list) - 1): self._swap(index, index + 1) return True return False @synchronized(_SYNC_LOCK) def get_item(self, object_id): """Returns the DownloadItem with the given object_id.""" return self._items_dict[object_id] @synchronized(_SYNC_LOCK) def has_item(self, object_id): """Returns True if the given object_id is in the list else False.""" return object_id in self._items_list @synchronized(_SYNC_LOCK) def get_items(self): """Returns a list with all the items.""" return [self._items_dict[object_id] for object_id in self._items_list] @synchronized(_SYNC_LOCK) def change_stage(self, object_id, new_stage): """Change the stage of the item with the given object_id.""" self._items_dict[object_id].stage = new_stage @synchronized(_SYNC_LOCK) def index(self, object_id): """Get the zero based index of the item with the given object_id.""" if object_id in self._items_list: return self._items_list.index(object_id) return -1 @synchronized(_SYNC_LOCK) def __len__(self): return len(self._items_list) def _swap(self, index1, index2): self._items_list[index1], self._items_list[index2] = self._items_list[index2], self._items_list[index1] class DownloadManager(Thread): """Manages the download process. Attributes: WAIT_TIME (float): Time in seconds to sleep. Args: download_list (DownloadList): List that contains items to download. 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, parent, download_list, opt_manager, log_manager=None): super(DownloadManager, self).__init__() self.parent = parent self.opt_manager = opt_manager self.log_manager = log_manager self.download_list = download_list self._time_it_took = 0 self._successful = 0 self._running = True # Init the custom workers thread pool log_lock = None if log_manager is None else Lock() wparams = (opt_manager, self._youtubedl_path(), log_manager, log_lock) self._workers = [Worker(*wparams) for _ in xrange(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 run(self): if not self.opt_manager.options["disable_update"]: self._check_youtubedl() self._time_it_took = time.time() while self._running: item = self.download_list.fetch_next() if item is not None: worker = self._get_worker() if worker is not None: worker.download(item.url, item.options, item.object_id) self.download_list.change_stage(item.object_id, "Active") if item is None and self._jobs_done(): break time.sleep(self.WAIT_TIME) # Close all the workers for worker in self._workers: worker.close() # Join and collect for worker in self._workers: worker.join() self._successful += worker.successful 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.download_list) return len(self.download_list) 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 def add_url(self, url): """Add given url to the download_list. Args: url (dict): 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.download_list.append(url) def send_to_worker(self, data): """Send data to the Workers. Args: data (dict): Python dictionary that holds the 'index' which is used to identify the Worker thread and the data which can be any of the Worker's class valid data. For a list of valid data keys see __init__() under the Worker class. """ if 'index' in data: for worker in self._workers: if worker.has_index(data['index']): worker.update_data(data) 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 4 signals. 1) closing: The download process is closing. 2) closed: The download process has closed. 3) finished: The download process was completed normally. 4) report_active: Signal the gui to read the number of active downloads using the active() method. """ 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()) and self.parent.update_thread is None: self.parent.update_thread = UpdateThread(self.opt_manager.options['youtubedl_path'], True) self.parent.update_thread.join() self.parent.update_thread = None def _get_worker(self): for worker in self._workers: if worker.available(): return worker return None 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 class Worker(Thread): """Simple worker which downloads the given url using a downloader from the downloaders.py 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. log_manager (logmanager.LogManager): Check DownloadManager description. log_lock (threading.Lock): Synchronization lock for the log_manager. If the log_manager is set (not None) then the caller has to make sure that the log_lock is also set. Note: For available data keys see self._data under the __init__() method. """ WAIT_TIME = 0.1 def __init__(self, opt_manager, youtubedl, log_manager=None, log_lock=None): super(Worker, self).__init__() self.opt_manager = opt_manager self.log_manager = log_manager self.log_lock = log_lock self._downloader = YoutubeDLDownloader(youtubedl, self._data_hook, self._log_data) self._options_parser = OptionsParser() self._successful = 0 self._running = True self._options = None self._wait_for_reply = False self._data = { 'playlist_index': None, 'playlist_size': None, 'new_filename': None, 'extension': None, 'filesize': None, 'filename': None, 'percent': None, 'status': None, 'index': None, 'speed': None, 'path': None, 'eta': None, 'url': None } self.start() def run(self): while self._running: if self._data['url'] is not None: #options = self._options_parser.parse(self.opt_manager.options) ret_code = self._downloader.download(self._data['url'], self._options) if (ret_code == YoutubeDLDownloader.OK or ret_code == YoutubeDLDownloader.ALREADY or ret_code == YoutubeDLDownloader.WARNING): self._successful += 1 # Ask GUI for name updates #self._talk_to_gui('receive', {'source': 'filename', 'dest': 'new_filename'}) # Wait until you get a reply #while self._wait_for_reply: #time.sleep(self.WAIT_TIME) self._reset() time.sleep(self.WAIT_TIME) # Call the destructor function of YoutubeDLDownloader object self._downloader.close() def download(self, url, options, object_id): """Download given item. Args: item (dict): 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._data['url'] = url self._options = options self._data['index'] = object_id 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._data['url'] is None def has_index(self, index): """Return True if index is equal to self._data['index'] else False. """ return self._data['index'] == index def update_data(self, data): """Update self._data from the given data. """ if self._wait_for_reply: # Update data only if a receive request has been issued for key in data: self._data[key] = data[key] self._wait_for_reply = False @property def successful(self): """Return the number of successful downloads for current worker. """ return self._successful def _reset(self): """Reset self._data back to the original state. """ for key in self._data: self._data[key] = None def _log_data(self, data): """Callback method for self._downloader. This method is used to write the given data in a synchronized way to the log file using the self.log_manager and the self.log_lock. Args: data (string): String to write to the log file. """ if self.log_manager is not None: self.log_lock.acquire() self.log_manager.log(data) self.log_lock.release() def _data_hook(self, data): """Callback method for self._downloader. This method updates self._data and sends the updates back to the GUI using the self._talk_to_gui() method. Args: data (dict): Python dictionary which contains information about the download process. For more info see the extract_data() function under the downloaders.py module. """ ## Temp dictionary which holds the updates #temp_dict = {} ## Update each key #for key in data: #if self._data[key] != data[key]: #self._data[key] = data[key] #temp_dict[key] = data[key] ## Build the playlist status if there is an update ## REFACTOR re-implement this on DownloadItem or ListCtrl level? ##if self._data['playlist_index'] is not None: ##if 'status' in temp_dict or 'playlist_index' in temp_dict: ##temp_dict['status'] = '{status} {index}/{size}'.format( ##status=self._data['status'], ##index=self._data['playlist_index'], ##size=self._data['playlist_size'] ##) #if len(temp_dict): #self._talk_to_gui('send', temp_dict) self._talk_to_gui('send', data) def _talk_to_gui(self, signal, data): """Communicate with the GUI using wxCallAfter and wxPublisher. Send/Ask data to/from the GUI. Note that if the signal is 'receive' then the Worker will wait until it receives a reply from the GUI. Args: signal (string): Unique string that informs the GUI about the communication procedure. data (dict): Python dictionary which holds the data to be sent back to the GUI. If the signal is 'send' then the dictionary contains the updates for the GUI (e.g. percentage, eta). If the signal is 'receive' then the dictionary contains exactly three keys. The 'index' (row) from which we want to retrieve the data, the 'source' which identifies a column in the wxListCtrl widget and the 'dest' which tells the wxListCtrl under which key to store the retrieved data. Note: Worker class supports 2 signals. 1) send: The Worker sends data back to the GUI (e.g. Send status updates). 2) receive: The Worker asks data from the GUI (e.g. Receive the name of a file). Structure: ('send', {'index': , data_to_send*}) ('receive', {'index': , 'source': 'source_key', 'dest': 'destination_key'}) """ data['index'] = self._data['index'] if signal == 'receive': self._wait_for_reply = True CallAfter(Publisher.sendMessage, WORKER_PUB_TOPIC, (signal, data)) ================================================ FILE: youtube_dl_gui/formats.py ================================================ # -*- coding: UTF-8 -*- import gettext from .utils import TwoWayOrderedDict as tdict OUTPUT_FORMATS = tdict([ (0, _("ID")), (1, _("Title")), (2, _("Title + ID")), (4, _("Title + Quality")), (5, _("Title + ID + Quality")), (3, _("Custom")) ]) DEFAULT_FORMATS = tdict([ ("0", _("default")) ]) VIDEO_FORMATS = tdict([ ("3gp", "3gp"), ("17", "3gp [144p]"), ("36", "3gp [240p]"), ("flv", "flv"), ("5", "flv [240p]"), ("34", "flv [360p]"), ("35", "flv [480p]"), ("webm", "webm"), ("43", "webm [360p]"), ("44", "webm [480p]"), ("45", "webm [720p]"), ("46", "webm [1080p]"), ("mp4", "mp4"), ("18", "mp4 [360p]"), ("22", "mp4 [720p]"), ("37", "mp4 [1080p]"), ("38", "mp4 [4K]"), ("160", "mp4 [144p] (DASH Video)"), ("133", "mp4 [240p] (DASH Video)"), ("134", "mp4 [360p] (DASH Video)"), ("135", "mp4 [480p] (DASH Video)"), ("136", "mp4 [720p] (DASH Video)"), ("137", "mp4 [1080p] (DASH Video)"), ("264", "mp4 [1440p] (DASH Video)"), ("138", "mp4 [2160p] (DASH Video)"), ("242", "webm [240p] (DASH Video)"), ("243", "webm [360p] (DASH Video)"), ("244", "webm [480p] (DASH Video)"), ("247", "webm [720p] (DASH Video)"), ("248", "webm [1080p] (DASH Video)"), ("271", "webm [1440p] (DASH Video)"), ("272", "webm [2160p] (DASH Video)"), ("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)") ]) AUDIO_FORMATS = tdict([ ("mp3", "mp3"), ("wav", "wav"), ("aac", "aac"), ("m4a", "m4a"), ("vorbis", "vorbis"), ("opus", "opus"), ("flac", "flac") ]) FORMATS = DEFAULT_FORMATS.copy() FORMATS.update(VIDEO_FORMATS) FORMATS.update(AUDIO_FORMATS) def reload_strings(): # IF YOU DONT WANT YOUR EYES TO BLEED STOP HERE # YOU HAVE BEEN WARNED # DO NOT LOOK THE CODE BELOW # # # # # # # # #NOTE Remove # Code is so messed up that i need to reload strings else # the translations wont work on the about gettext tags global OUTPUT_FORMATS global DEFAULT_FORMATS global VIDEO_FORMATS global AUDIO_FORMATS global FORMATS OUTPUT_FORMATS = tdict([ (0, _("ID")), (1, _("Title")), (2, _("Title + ID")), (4, _("Title + Quality")), (5, _("Title + ID + Quality")), (3, _("Custom")) ]) DEFAULT_FORMATS = tdict([ ("0", _("default")) ]) VIDEO_FORMATS = tdict([ ("3gp", "3gp"), ("17", "3gp [144p]"), ("36", "3gp [240p]"), ("flv", "flv"), ("5", "flv [240p]"), ("34", "flv [360p]"), ("35", "flv [480p]"), ("webm", "webm"), ("43", "webm [360p]"), ("44", "webm [480p]"), ("45", "webm [720p]"), ("46", "webm [1080p]"), ("mp4", "mp4"), ("18", "mp4 [360p]"), ("22", "mp4 [720p]"), ("37", "mp4 [1080p]"), ("38", "mp4 [4K]"), ("160", "mp4 [144p] (DASH Video)"), ("133", "mp4 [240p] (DASH Video)"), ("134", "mp4 [360p] (DASH Video)"), ("135", "mp4 [480p] (DASH Video)"), ("136", "mp4 [720p] (DASH Video)"), ("137", "mp4 [1080p] (DASH Video)"), ("264", "mp4 [1440p] (DASH Video)"), ("138", "mp4 [2160p] (DASH Video)"), ("242", "webm [240p] (DASH Video)"), ("243", "webm [360p] (DASH Video)"), ("244", "webm [480p] (DASH Video)"), ("247", "webm [720p] (DASH Video)"), ("248", "webm [1080p] (DASH Video)"), ("271", "webm [1440p] (DASH Video)"), ("272", "webm [2160p] (DASH Video)"), ("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)") ]) AUDIO_FORMATS = tdict([ ("mp3", "mp3"), ("wav", "wav"), ("aac", "aac"), ("m4a", "m4a"), ("vorbis", "vorbis"), ("opus", "opus"), ("flac", "flac") ]) FORMATS = DEFAULT_FORMATS.copy() FORMATS.update(VIDEO_FORMATS) FORMATS.update(AUDIO_FORMATS) ================================================ FILE: youtube_dl_gui/info.py ================================================ # -*- 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__ = 'https://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 ''' ================================================ FILE: youtube_dl_gui/locale/ar_SA/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # snosi , 2017. msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-06-22 17:52+0100\n" "Last-Translator: snosi \n" "Language-Team: snosi\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" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "العنوان" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "العنوان + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "العنوان + الجودة" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "العنوان + ID + الجودة" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "مخصص" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "افتراضي (دون تعديل)" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "ضع الرابط هنا" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "تحديث" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "الخيارات" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "إيقاف" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "معلومات" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "مرحباً" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "تحذير" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "إضافة" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "قائمة التنزيلات" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "حذف" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "تشغيل" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "فوق" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "اسفل" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "أعد التحميل" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "توقيف مؤقت" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "إبدأ" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "عن" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "شاهد السِجِل" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "انتهاء التنزيلات {0} URL(s) في {1} يوم {2} ساعة {3} دقيقة {4} ثانية" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "اكتمال التنزيلات" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "مجموع العمليات : {0:.1f}% | في الانتظار ({1}) موقف مؤقتا ({2}) نشط ({3}) اكتملت ({4}) أخطاء ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "إيقاف التنزيل" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "تم إيقاف التنزيل" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "يجب ان تضع رابطا واحدا على الاقل" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "بدأ التنزيل" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "اختر مكان الحفظ" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "جاري التنزيل . انتظر حتى تكتمل التنزيلات" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "جاري التحديث" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "...يرجى الإنتظار youtube-dl تحديث . " #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "فشل تحديث youtube-dl [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "youtube-dl اكتمل تحديث" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "فعل لفتح الدليل : '{dir}'. المسار غير موجود" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "تعذر إيقاف الجهاز . تأكد من كلمة المرور" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "إيقاف تشغيل النظام" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "نوع الملف (الصيغة)" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "الحجم" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "النِسبة المئوية" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "الوقت المتبقي" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "سرعة التنزيل" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "الحالة" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "جد الرابط" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "الحصول على أمر" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "افتح وجهة" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "اعادة الادخال" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "استئتاف" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "الفيديو" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "الصوتي" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "لم يتم تحديد أي عنصر. يرجى اختيار إجراء" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "حذف الكل " #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "حذف المكتمل" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "هل انت متأكد من حذف هذا العنصر ؟" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "لا يمكن الحذف الآن" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "لم يكتمل العنصر بعد" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "قيد التحديث .يرجى الانتظار" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "تم تعطيل التسجيل" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "إيقاف التشغيل" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "إيقاف التشغيل في {0} ثانية(s)" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "لا توجد عناصر للتنزيل" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "هل تريد الخروج من البرنامج ؟" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "خروج" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "إلغاء" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "موافق" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "إسترجاع الإعدادات الافتراضية" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "إنهاء" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "عام" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "الصيغ" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "التنزيلات" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "متقدم" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "إضافي" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "اللغة" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "اسم الملف الصيغة" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "اسم الملف خيارات" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "قيّد اسم الملف الى ASCII" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "مزيد من الخيارات" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "التأكيد عند الإنهاء" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "التأكيد عند حذف عنصر" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "نبهني عند انتهاء التنزيلات" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "اوقف التشغيل عند انتهاء التنزيلات" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "كلمة مرور المسؤول" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "تطبق التغييرات بعد اعادة التشغيل {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "إعادة التشغيل" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "عالي" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "منخفض" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "متوسط" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "صيغ الفيديو" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "صيغ الصوت" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "خيارات مابعد العملية" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "احتفظ بالملفات الأصلية" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "استخرج ملف صوتي من ملف الفيديو" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "تضمين الصورة المصغرة في الملف الصوتي" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "أضف بيانات وصفية للملف" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "جودة الصوت" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "الانجليزية" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "الفرنسية" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "الالمانية" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "اليونانية" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "العبرية" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "الايطالية" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "البرتغالية" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "الروسية" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "الاسبانية" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "السويدية" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "التركية" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "لاشيء" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "الترجمة التلقائية (YOUTUBE ONLY)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "كل الترجمات المتوفرة" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "الترجمات حسب اللغة" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "الترجمات" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "خيارات الترجمة" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "تضمين ترجمات مصاحبة مع ملف الفيديو (mp4 فقط)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "قائمة التشغيل" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "اقصى" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "حجم الملف" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "اقل" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "فحص" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "إستيثاق" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "اسم المستخدم" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "كلمة المرور" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "كلمة المرور الخاصة بالفيديو" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "الشبكة" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "الخادم الوكيل (البروكسي)" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "مستخدم وكيل" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "يدل" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "تسجيل" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "تفعيل السجّل" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "عرض" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "مسح" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Youtube-dl خيارات موجه الاوامر (e.g. --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "خيارات إضافية" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "youtube-dl تصحيح أخطاء" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "تجاهل الأخطاء" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "youtube-dl تجاهل إعدادات" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "No mtime" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "الأصلي HLS تفضيل" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "عارض السجل" ================================================ FILE: youtube_dl_gui/locale/cs_CZ/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-10-08 19:27+0200\n" "Last-Translator: Pavel Řehák \n" "Language-Team: \n" "Language: cs_CZ\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" "X-Generator: Poedit 1.8.7.1\n" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "Název" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "Název + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "Název + Kvalita" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "Název + ID + Kvalita" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "Vlastní" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "výchozí" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "Níže vložte URL adresy" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "Aktualizovat" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "Volby" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "Zastavit" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "Informace" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "Vítejte" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "Upozornění" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "Přidat" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "Seznam stahování" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "Smazat" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "Přehrát" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "Nahoru" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "Dolů" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "Obnovit" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "Pozastavit" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "Spustit" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "O aplikaci" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "Zobrazit záznam" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "Úspěšně staženo {0} URL za {1} dní {2} hodin {3} minut {4} sekund" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "Stahování dokončeno" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "Celkový postup: {0:.1f}% | Ve frontě ({1}) Pozastaveno ({2}) Aktivní ({3}) Dokončené ({4}) Chyby ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "Stahování se zastavuje" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "Stahování zastaveno" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "Je třeba zadat alespoň jednu URL adresu" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "Stahování zahájeno" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "Vyberte adresář" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "Stahuje se. Počkejte prosím, až se dokončí všechna stahování" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "Aktualizace již probíhá" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "Stahuje se nejnovější youtube-dl. Počkejte prosím..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Stahování youtube-dl selhalo [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Youtube-dl byl úspěšně stažen" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "Nelze otevřít adresář: '{dir}'. Uvedená cesta neexistuje" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "Při vypínání nastala chyba. Ujistěte se, že jste zadali správné heslo" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "Systém se vypíná" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "Přípona" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "Velikost" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "Procenta" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "ETA" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "Rychlost" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "Stav" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "Kopírovat URL" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "Kopírovat příkaz" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "Otevřít cíl" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "Znovu vložit" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "Pokračovat" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "Video" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "Zvuk" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "Není vybrána žádná položka. Prosím vyberte akci" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "Odstranit vše" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "Odstranit dokončené" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "Jste si jistí, že chcete odstranit vybranou položku?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "Položka je aktivní, nelze ji odstranit" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "Položka není dokončena" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "Probíhá aktualizace. Počkejte prosím, až se aktualizace dokončí" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "Záznamy jsou zakázány" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "Vypnout" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "Vypnutí za {0} sekund" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "Nejsou žádné položky ke stažení" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "Jste si jistí, že chcete aplikaci ukončit?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "Ukončit" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "Zrušit" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "OK" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "Resetovat" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "Zavřít" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "Obecné" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "Formáty" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "Stahování" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "Pokročilé" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "Extra" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "Jazyk" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "Formát názvu souboru" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "Volby pro název souboru" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "Omezit názvy souborů na ASCII" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "Další volby" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "Potvrdit při ukončení" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "Potvrdit mazání položky" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "Informovat mě, když je stahování dokončeno" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "Vypnout, když je stahování dokončeno" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "Heslo SUDO" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "Aby se projevily změny, prosím restartujte {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "Restartovat" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "vysoká" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "nízká" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "střední" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "Video formáty" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "Zvukové formáty" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "Volby po stažení" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "Zachovat původní soubory" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "Extrahovat zvuk z video souboru" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "Vložit náhled do zvukového souboru" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "Přidat do souboru metadata" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "Kvalita zvuku" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "Angličtina" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "Francouzština" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "Němčina" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Řečtina" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Hebrejština" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Italština" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Portugalština" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Ruština" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Španělština" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Švédština" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Turečtina" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "Žádné" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "Automatické titulky (POUZE YOUTUBE)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "Všechny dostupné titulky" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "Titulky dle jazyka" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "Titulky" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "Volby pro titulky" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "Vložit titulky do video souboru (POUZE mp4)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "Seznam k přehrání" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "Max" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "Velikost souboru" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "Min" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "Opakování" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "Ověření" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "Uživatelské jméno" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "Heslo" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "Heslo videa" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "Síť" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "Proxy" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "Uživatelský agent" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "Záznamy" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "Povolit záznam" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "Zobrazit" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "Vymazat" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Volby příkazového řádku pro youtube-dl (např. --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "Další volby" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Debug youtube-dl" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "Ignorovat chyby" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Ignorovat nastavení youtube-dl" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "Preferovat nativní HLS" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "Prohlížeč záznamu událostí" ================================================ FILE: youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-06-15 17:14+EEST\n" "Last-Translator: Sotiris Papadopoulos \n" "Language-Team: en\n" "Language: \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" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "" #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "" ================================================ FILE: youtube_dl_gui/locale/es_ES/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # Ismael Ferreras Morezuelas , 2016-2017. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-06-15 17:55+EEST\n" "Last-Translator: Ismael Ferreras Morezuelas \n" "Language-Team: Spanish <>\n" "Language: es_ES\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 2.91.7\n" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "Identificador" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "Título" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "Título + identificador" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "Título + calidad" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "Título + identificador + calidad" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "Personalizado" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "predeterminada" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "Pega direcciones URL en la parte inferior" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "Actualizar" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "Ajustes" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "Detener" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "Info" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "Bienvenid@" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "Advertencia" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "Añadir" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "Descargar lista" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "Borrar" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "Reproducir" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "Subir" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "Bajar" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "Recargar" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "Pausar" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "Iniciar" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "Acerca de" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "Ver registro" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "Se han terminado de descargar {0} elementos en {1} días, {2} horas, {3} minutos y {4} segundos." #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "Se han completado todas las descargas." #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "Progreso total: {0:.1f}% | Restantes ({1}) En pausa ({2}) Activas ({3}) Terminadas ({4}) Fallidas ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "Parando descargas en curso." #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "Se han detenido todas las descargas en curso." #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "Es necesario proporcionar al menos una dirección de descarga para poder empezar." #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "Comenzando descargas." #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "Elegir carpeta" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "Ha empezado la descarga, un momento." #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "Ya se está actualizando." #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "Descargando última versión de youtube-dl. Un momento..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "No se ha podido descargar youtube-dl [{0}]." #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "El nuevo youtube-dl se ha descargado sin problemas." #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "No se ha podido abrir la carpeta: «{dir}». La ruta no existe." #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "No se ha podido apagar el equipo. Asegúrate de que la contraseña esté bien escrita." #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "Apagando el equipo" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "Extensión" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "Tamaño" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "Porcentaje" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "Tiempo estimado" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "Velocidad" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "Estado" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "Copiar dirección URL al portapapeles" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "Copiar los argumentos de descarga al portapapeles" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "Abrir carpeta de descarga" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "Reintroducir" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "Reanudar" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "Vídeo" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "Audio" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "No has seleccionado nada, elige una acción." #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "Borrar todo" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "Borrado completado." #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "¿Seguro que quieres borrar los elementos seleccionados?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "El elemento se está utilizando y no se puede borrar." #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "El elemento todavía no se ha descargado." #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "Hay una descarga en curso, espera a que termine." #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "El registro de errores está desactivado." #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "Apagar" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "Apagando el equipo en {0} seg." #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "No hay nada para descargar." #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "¿Seguro que quieres salir?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "Salir" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "Cancelar" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "Aceptar" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "Reiniciar" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "Cerrar" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "General" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "Formatos" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "Descargas" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "Avanzado" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "Extra" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "Idioma" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "Nomenclatura de archivos" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "Opciones de nomenclatura" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "Limitar nombres de archivo a ASCII" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "Más opciones" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "Confirmar salida" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "Confirmar al borrar archivos" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "Avisarme cuando terminen las descargas" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "Apagar al terminar las descargas" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "Contraseña de superusuario" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "Reinicia {0} para que los cambios surtan efecto." #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "Reiniciar" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "Alta" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "Baja" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "Intermedia" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "Formatos de vídeo" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "Formatos de audio" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "Opciones de posproceso" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "Conservar los archivos originales" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "Extraer el audio del vídeo" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "Incluir una miniatura en el archivo de audio" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "Incluir metadatos en el archivo" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "Calidad del audio" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "Inglés" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "Francés" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "Alemán" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Griego" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Hebreo" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Italiano" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Portugués" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Ruso" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Español" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Sueco" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Turco" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "Ninguno" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "Descargar archivo con subtítulos automáticos (sólo en YouTube)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "Descargar todos los subtítulos disponibles" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "Idioma de subtítulos" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "Subtítulos" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "Opciones de subtítulos" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "Insertar los subtítulos en el vídeo (sólo para vídeos en mp4)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "Lista de reproducción" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "Máximo" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "Tamaño de archivo" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "Mínimo" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "Reintentos" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "Autenticación" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "Nombre de usuario" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "Contraseña" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "Contraseña del vídeo" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "Red" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "Proxy" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "Agente de usuario" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "Página de origen" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "Registro de errores" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "Registrar los errores del programa" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "Ver registro" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "Borrar registro" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Argumentos adicionales para Youtube-dl (p. ej. --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "Otras opciones" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Depurar youtube-dl" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "Ignorar errores" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Ignorar los ajustes de youtube-dl" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "Sin mtime" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "Utilizar HLS nativo cuando sea posible" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "Visor de errores" ================================================ FILE: youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-08-22 08:44+0200\n" "Last-Translator: fat115 \n" "Language-Team: fr\n" "Language: 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" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "Titre" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "Titre + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "Titre + Qualité" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "Titre + ID + Qualité" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "Personnalisé" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "par défaut" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "Entrer les URLs ci-dessous" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "Mise à jour" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "Options" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "Arrêter" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "Info" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "Bienvenue" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "Avertissement" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "Ajouter" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "Liste de téléchargements" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "Effacer" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "Lire" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "Monter" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "Descendre" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "Recharger" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "Pause" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "Démarrer" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "À propos" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "Afficher le journal" #: youtube_dl_gui/mainframe.py:117 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} URL(s) en {1} jour(s) {2} heure(s) {3} minute(s) {4} seconde(s)" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "Téléchargements terminés" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "Progression totale : {0:.1f}% | En file ({1}) En pause ({2}) Actif ({3}) Complété ({4}) Erreur ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "Arrêt des téléchargements" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "Téléchargements arrêtés" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "Vous devez fournir au moins une URL" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "Téléchargements démarrés" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "Sélectionner le répertoire" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "Téléchargement en cours. Merci d'attendre qu'ils soient tous terminés" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "Mise à jour déjà en cours" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "Mise à jour de youtube-dl. Merci de patienter..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Echec de la mise à jour de youtube-dl [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Mise à jour de youtube-dl réussie." #: youtube_dl_gui/mainframe.py:134 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" #: youtube_dl_gui/mainframe.py:136 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" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "Arrêt du système" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "Extension" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "Taille" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "Pourcents" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "ETA" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "Vitesse" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "État" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "Récupérer l'URL" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "Récupérer la commande" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "Ouvrir le répertoire de destination" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "Re-entrer" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "Reprendre" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "Vidéo" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "Audio" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "Pas d'élément sélectionné. Choisissez une action" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "Tout enlever" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "Enlèvement terminé" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "Êtes vous sûr de vouloir enlever les éléments sélectionnées ?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "Élément actif, ne peut être enlevé" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "Élément incomplet" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "Mise à jour en cours. Veuillez attendre qu'elle soit terminée" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "La journalisation est désactivée" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "Arrêt" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "Arrêt dans {0} seconde(s)" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "Pas d'élément à télécharger" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "Êtes vous sûr de vouloir quitter ?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "Quitter" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "Annuler" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "OK" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "Remise à zéro" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "Fermer" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "Général" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "Formats" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "Téléchargements" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "Avancé" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "Extra" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "Langue" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "Format du nom de fichier" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "Options des noms de fichiers" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "Convertir les noms en ASCII" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "Plus d'options" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "Confirmation de sortie" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "Confirmation d'effacement" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "M'informer de la fin des téléchargements" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "Arrêter à la fin des téléchargements" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "Mot de passe SUDO" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "Pour que les changements prennent effet, veuillez redémarrer {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "Redémarrer" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "haute" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "basse" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "moyenne" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "Formats Vidéo" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "Formats Audio" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "Options post-traitement" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "Conserver les fichiers originaux" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "Extraire l'audio du fichier vidéo" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "Inclure une vignette dans le fichier audio" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "Ajouter les métadonnées au fichier" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "Qualité Audio" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "Anglais" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "Français" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "Allemand" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Grec" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Hébreu" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Italien" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Portugais" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Russe" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Espagnol" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Suédois" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Turc" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "Aucun" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "Sous-titre automatique (YOUTUBE uniquement)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "Tous les sous-titres disponibles" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "Sous-titres par langue" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "Sous-titres" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "Options de sous-titres" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "Inclure les sous-titres dans la vidéo (mp4 uniquement)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "Liste de lecture" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "Max" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "Taille de fichier" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "Min" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "Essais" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "Authentification" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "Utilisateur" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "Mot de passe" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "Mot de passe vidéo" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "Réseau" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "Proxy" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "User agent" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "Referer" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "Journalisation" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "Activer le journal" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "Afficher le journal" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "Effacer le journal" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Options de ligne de commande youtube-dl (ex : --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "Options supplémentaires" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Déboguer youtube-dl" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "Ignorer les erreurs" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Ignorer config youtube-dl" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "Pas de mtime" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "Préférer HLS natif" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "Journal" ================================================ FILE: youtube_dl_gui/locale/it_IT/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-07-21 09:00+0200\n" "Last-Translator: Luigi Baldoni \n" "Language-Team: en\n" "Language: it_IT\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" "X-Generator: Poedit 1.8.9\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Language: it_IT\n" "X-Source-Language: C\n" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "Titolo" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "Titolo + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "Titolo + qualità" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "Titolo + ID + qualità" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "Personalizzato" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "predefinito" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "Immetti URL qui sotto" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "Aggiorna" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "Opzioni" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "Fine" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "Informazioni" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "Benvenuto" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "Avviso" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "Aggiungi" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "Elenco scaricamenti" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "Elimina" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "Riproduci" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "Su" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "Giù" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "Ricarica" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "Metti in pausa" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "Inizio" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "Informazioni" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "Mostra registro" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "Scaricate correttamente {0} URL in {1} giorni {2} ore {3} minuti {4} secondi" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "Scaricamenti completati" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "Progresso complessivo: {0:.1f}% | Accodati ({1}) In pausa ({2}) Attivi ({3}) Completati ({4}) Con errori ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "Arresto scaricamenti" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "Scaricamenti arrestati" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "Devi fornire almeno una URL" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "Scaricamenti avviati" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "Scegli cartella" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "Scaricamento in corso. Attendi che tutti gli scaricamenti vengano completati" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "Aggiornamento già in corso" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "Scaricamento youtube-dl più recente. Attendere..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Scaricamento youtube-dl non riuscito [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Youtube-dl scaricato con successo" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "Impossibile aprire la cartella: '{dir}'. Il percorso indicato non esiste" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "Errore durante l'arresto. Assicurati di aver inserito la password esatta" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "Arresto del sistema in corso" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "Estensione" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "Size" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "Percentuale" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "ETA" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "Velocità" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "Stato" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "Copia URL" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "Copia comando" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "Apri destinazione" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "Reinserisci" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "Riprendi" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "Video" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "Audio" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "Nessun elemento selezionato. Decidere un'azione" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "Rimuovi tutti" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "Rimuovi completati" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "Sei sicuro di voler rimuovere gli elementi selezionati?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "L'elemento è attivo, impossibile rimuoverlo" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "L'elemento non è stato completato" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "Aggiornamento in corso. Attendi che l'aggiornamento sia completo" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "Il registro è disabilitato" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "Arresto" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "Arresto in {0} secondi" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "Nessun elemento da scaricare" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "Sei sicuro di voler uscire?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "Uscita" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "Annulla" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "OK" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "Azzera" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "Chiudi" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "Generale" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "Formati" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "Scaricamenti" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "Avanzate" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "Extra" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "Lingua" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "Formato nome file" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "Opzioni nome file" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "Limita nomi file ad ASCII" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "Più opzioni" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "Chiedi conferma all'uscita" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "Chiedi conferma per eliminare elementi" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "Informami al termine degli scaricamenti" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "Arresto al termine degli scaricamenti" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "password SUDO" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "Per attivare i cambiamenti è necessario riavviare {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "Riavvia" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "alta" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "bassa" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "media" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "Formati video" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "Formati audio" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "Opzioni post-elaborazione" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "Conserva file originali" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "Estrai audio dal file video" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "Incorpora miniatura nel file audio" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "Aggiungi metadati al file" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "Qualità audio" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "Inglese" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "Francese" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "Tedesco" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Greco" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Ebraico" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Italiano" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Portoghese" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Russo" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Spagnolo" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Svedese" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Turco" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "Nessuno" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "Sottotitoli automatici (SOLO YOUTUBE)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "Tutti i sottotitoli disponibili" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "Sottotitoli per lingua" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "Sottotitoli" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "Opzioni sottotitoli" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "Incorpora sottotitoli nel file video (SOLO mp4)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "Scaletta" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "Massimo" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "Dimensione file" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "Minimo" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "Tentativi" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "Autenticazione" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "Nome utente" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "Password" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "Password video" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "Rete" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "Proxy" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "User agent" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "Referer" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "Registro" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "Abilita registro" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "Visualizza" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "Pulisci" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Opzioni a linea di comando di youtube-dl (ad es. --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "Opzioni extra" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Debug di youtube-dl" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "Ignora errori" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Ignora configurazione youtube-dl" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "No mtime" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "HLS nativo preferito" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "Visualizzatore registro" ================================================ FILE: youtube_dl_gui/locale/ja_JP/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-09-26 00:44+JST\n" "Last-Translator: Mitsuya Tsujikawa \n" "Language-Team: ja\n" "Language: JP\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" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "タイトル" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "タイトル + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "タイトル + 品質" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "タイトル + ID + 品質" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "カスタム" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "既定値" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "下の欄にURLを入力してください" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "更新" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "設定" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "停止" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "情報" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "ようこそ" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "警告" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "追加" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "ダウンロードリスト" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "削除" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "再生" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "上へ" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "下へ" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "再読込み" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "一時停止" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "ダウンロード開始" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "製品情報" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "ログの表示" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "正常にダウンロードしました: {0} URL(s) in {1} 日 {2} 時 {3} 分 {4} 秒" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "ダウンロード完了" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "進捗状況: {0:.1f}% | 待機 ({1}) 停止 ({2}) 進行中 ({3}) 完了 ({4}) エラー ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "ダウンロードを停止中" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "ダウンロードを停止しました" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "最低でも1つはURLを入力する必要があります" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "ダウンロードを開始しました" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "フォルダを選択してください" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "ダウンロード中です。すべて完了するまでお待ちください。" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "更新中です" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "youtube-dlの最新版をダウンロードしています。お待ちください..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Youtube-dlのダウンロードに失敗しました [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Youtube-dlのダウンロードに成功しました" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "フォルダを開くことができません: '{dir}'。指定されたパスが存在しません。" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "シャットダウンの際にエラーが発生しました。必ず正確なパスワードを入力したことを確認してください" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "システムをシャットダウンします" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "拡張子" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "サイズ" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "パーセント" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "ETA" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "速度" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "状態" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "URLをコピー" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "コマンドをコピー" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "保存先フォルダを開く" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "再登録" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "再開" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "動画" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "音声" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "項目が選択されていません。操作項目を選択してください" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "全消去" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "消去完了" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "選択アイテムを消去してよろしいですか?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "使用中のため削除できません" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "未完了のアイテムです" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "アップデート中です。完了までお待ちください" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "ロギング無効" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "シャットダウン" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "{0} 秒後にシャットダウンします" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "ダウンロードする項目がありません" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "終了してよろしいですか?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "終了" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "キャンセル" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "OK" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "リセット" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "閉じる" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "一般" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "形式" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "ダウンロード" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "高度" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "その他" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "言語" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "ファイル名の形式" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "ファイル名のオプション" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "ファイル名をASCIIに制限する" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "詳細設定" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "終了時の確認" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "アイテム削除時の確認" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "ダウンロード完了時に通知" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "ダウンロード完了後シャットダウン" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "SUDO パスワード" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "変更を反映するに再起動してください {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "再起動" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "高" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "低" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "中" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "動画形式" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "音声形式" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "後処理の設定" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "オリジナルファイルを残す" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "動画から音声を抽出" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "音声ファイルにサムネイル埋め込み" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "メタデータの追加" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "音声品質" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "英語" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "フランス語" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "ドイツ語" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "ギリシャ語" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "ヘブライ語" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "イタリア語" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "ポルトガル語" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "ロシア語" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "スペイン語" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "スウェーデン語" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "トルコ語" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "なし" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "自動字幕 (Youtubeのみ)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "すべての字幕" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "字幕言語" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "字幕" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "字幕設定" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "動画に字幕埋め込み (mp4のみ)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "プレイリスト" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "最大" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "ファイルサイズ" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "最小" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "リトライ" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "認証情報" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "アカウント" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "パスワード" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "ビデオパスワード" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "ネットワーク" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "プロキシ" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "ユーザーエージェント(UA)" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "リファラー(Referer)" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "ロギング" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "ログを有効化" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "表示" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "消去" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Youtube-dl コマンドラインオプション (例 --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "追加設定" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "youtube-dlのデバッグ" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "エラーを無視" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "youtube-dlのconfig無視" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "mtime無し" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "ネイティブHLS優先" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "ログビュワー" ================================================ FILE: youtube_dl_gui/locale/ko_KR/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-02-22 02:49+GMT\n" "Last-Translator: Yi Soo, An \n" "Language-Team: kr\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: vim\n" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "제목" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "제목 + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "제목 + 화질" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "제목 + ID + 화질" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "사용자 설정" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "기본값" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "URL을 입력하세요." #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "업데이트" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "옵션" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "정지" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "정보" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "환영합니다." #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "주의" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "추가" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "다운로드 목록" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "삭제" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "재생" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "위로" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "아래로" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "새로고침" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "일시정지" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "시작" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "정보" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "로그 보기" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "{0}개의 URL을 받는데 총 {1}일 {2}시간 {3}분 {4}초가 걸렸습니다." #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "다운로드 완료" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "진행 상태: {0:.1f}% | 대기 ({1}) 일시정지 ({2}) 진행중 ({3}) 완료 ({4}) 오류 ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "다운로드 정지중" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "다운로드 정지" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "최소 한 개 이상의 URL을 입력해야 합니다." #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "다운로드 시작됨" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "저장 경로" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "다운로드가 완료될 때까지 기다려주세요." #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "업데이트 진행중입니다." #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "최신 youtube-dl을 다운로드하고 있습니다. 잠시만 기다려주세요." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Youtube-dl 다운로드 실패 [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Youtube-dl 다운로드가 완료되었습니다." #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "'{dir}'을 열 수 없습니다. 올바른 경로가 아닙니다." #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "시스템을 종료할 수 없습니다. 패스워드가 올바른지 확인하세요." #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "시스템 종료 중.." #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "확장자" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "크기" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "퍼센트" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "기타" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "속도" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "상태" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "클립보드로 URL 복사" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "커맨드라인 명령어 복사" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "폴더 열기" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "대기열에 넣기" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "이어받기" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "비디오" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "오디오" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "선택된 아이템이 없습니다. 다음 중 하나를 실행하시겠습니까?" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "전체삭제" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "완료된 것만 삭제" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "해당 아이템을 삭제하시겠습니까?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "삭제할 수 없습니다." #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "다운로드가 완료되지 않았습니다." #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "업데이트가 완료될 때까지 기다려주세요." #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "로깅이 비활성화되었습니다." #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "시스템 종료" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "{0}초 후에 시스템이 종료됩니다." #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "다운로드할 아이템이 없습니다." #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "종료하시겠습니까?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "종료" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "취소" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "확인" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "초기화" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "닫기" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "일반" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "포맷" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "다운로드" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "고급설정" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "기타" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "언어" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "파일이름 형식" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "파일이름 옵션" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "ASCII 문자로 제한" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "추가옵션" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "종료시 확인" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "아이템 삭제시 확인" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "완료시 알림" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "완료시 시스템종료" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "SUDO 암호" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "설정값을 적용하기 위해 {0}을 재시작하세요." #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "재시작" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "높음" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "낮음" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "중간" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "비디오 형식" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "오디오 형식" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "후처리 옵션" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "원본파일 유지" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "오디오 추출" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "오디오에 섬네일 추가" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "메타데이터 추가" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "오디오 음질" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "English" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "French" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "German" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Greek" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Hebrew" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Italian" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Portuguese" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Russian" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Spanish" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Swedish" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Turkish" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "없음" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "오토매틱 자막 (유튜브 전용)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "사용가능한 모든 자막" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "언어에 따른 자막" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "자막" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "자막 옵션" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "비디오에 자막 삽입 (mp4 전용)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "재생목록" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "최대" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "파일크기" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "최소" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "재시도 횟수" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "인증" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "사용자 이름" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "비밀번호" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "비디오 비밀번호" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "네트워크" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "프록시" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "User agent" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "Referer" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "로그" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "로그 사용" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "보기" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "초기화" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Youtube-dl 커맨드라인 옵션 (예: --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "기타 옵션" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Youtube-dl 디버그" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "오류 무시" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Youtube-dl 설정 무시" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "mtime 사용안함" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "네이티브 HLS 사용" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "로그 뷰어" ================================================ FILE: youtube_dl_gui/locale/pt_BR/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-06-16 11:27-0300\n" "Last-Translator: Cleiton Meurer \n" "Language-Team: pt_BR\n" "Language: pt_BR\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" "X-Generator: Poedit 2.0.2\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "ID" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "Título" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "Título + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "Título + Qualidade" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "Título + ID + Qualidade" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "Personalizar" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "Padrão" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "Insira as URLs abaixo" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "Atualizar" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "Opções" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "Parar" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "Informações" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "Bem Vindo" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "Atenção" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "Adicionar" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "Lista de Download" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "Apagar" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "Tocar" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "Subir" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "Descer" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "Recarregar" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "Pausar" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "Iniciar" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "Sobre" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "Ver Log" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "Baixado com sucesso {0} url(s) em {1} dia(s) {2} hora(s) {3} minuto(s) {4} segundo(s)" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "Downloads completo" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "Progresso total: {0:.1f}% | Fila ({1}) Pausado ({2}) Ativo ({3}) Completo ({4}) Com erros ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "Parar downloads" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "Downloads parados" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "Você precisa fornecer pelo menos um endereço" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "Downloads iniciados" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "Escolher pasta" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "Download em progresso. Aguarde todos os downloads para concluir" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "Atualização já em andamento" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "Atualizando para ultima versão youtube-dl. Aguarde..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Youtube-dl download falhou [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Baixado com sucesso do youtube-dl" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "Não é possível abrir diretório: '{dir}'. O caminho especificado não existe" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "Erro ao desligar. Certifique-se de que você digitou a senha correta" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "Desligar sistema" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "Formato" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "Tamanho" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "Porcentagem" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "ETA" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "Velocidade" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "Status" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "Obter URL" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "Comandos" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "Abrir pasta de destino" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "Re-abrir" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "Resumir" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "Video" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "Audio" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "Nenhum item selecionado. Escolha uma ação" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "Apagar tudo" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "Remover completo" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "Tem certeza de que deseja remover itens selecionados?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "O item está ativo, não pode remover" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "O item não está completo" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "Atualização em andamento. Aguarde até a atualização ser concluída" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "O log está desativado" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "Desligar" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "Desligando em {0} segundo(s)" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "Nenhum item para download" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "Você tem certeza que quer sair?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "Sair" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "Cancelar" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "OK" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "Reset" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "Fechar" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "Geral" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "Formatos" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "Downloads" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "Avançado" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "Extra" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "Idiomas" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "Nome do Arquivo" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "Opções de nome de arquivo" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "Restringem nomes de arquivos (ASCII)" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "Mais opções" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "Confirme antes de sair" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "Confirmar apagar de itens" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "Avisar após a conclusão do download" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "Desligamento após conclusão do download" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "SUDO senha" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "Para que as alterações tenham efeito, reinicie {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "Reiniciar" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "Alto" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "Baixo" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "Medio" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "Formato do Video" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "Formato do Audio" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "Opções pós-processo" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "Manter arquivos originais" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "Extraia áudio do arquivo de vídeo" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "Incorporar miniatura no arquivo de áudio" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "Adicionar metadados ao arquivo" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "Qualidade do Audio" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "Inglês" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "Francês" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "Alemão" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Grego" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Hebraico" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Italiano" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Português" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Russo" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Espanhol" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Sueco" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Turco" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "Nenhum" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "Baixar arquivos de legendas automáticamente (YOUTUBE SOMENTE)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "Todas as legendas disponíveis" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "Legendas por idioma" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "Legenda" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "Opções de legenda" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "Incorporar as legendas do vídeo (apenas para vídeos mp4)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "Playlist" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "Max" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "Tamanho" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "Min" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "Tentativas" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "Autenticação" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "Usuario" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "Senha" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "Senha do Video" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "Rede" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "Proxy" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "User Agent" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "Referente" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "Log" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "Habilitar Log" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "Ver Log" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "Limpar Log" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Argumentos da linha de comando (e.x. --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "Opções extras" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Depurar youtube-dl" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "Ignorar erros" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Ignorar configurações do youtube-dl" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "Sem mtime" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "Preferir HLS nativo" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "Ver log" ================================================ FILE: youtube_dl_gui/locale/ru_RU/LC_MESSAGES/youtube_dl_gui.po ================================================ # Youtube-dlG localization file. # FIRST AUTHOR: Sotiris Papadopoulos , 2015. # Nikita «Arttse» Bystrov , 2015. msgid "" msgstr "" "Project-Id-Version: youtube-dlg 0.4\n" "POT-Creation-Date: 2018-01-15 16:42+EET\n" "PO-Revision-Date: 2017-06-18 18:20+EEST\n" "Last-Translator: Nikita «Arttse» Bystrov \n" "Language-Team: ru\n" "Language: ru\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" #: youtube_dl_gui/__init__.py:91 msgid "Error" msgstr "" #: youtube_dl_gui/__init__.py:91 msgid "Failed to locate youtube-dl and updates are disabled" msgstr "" #: youtube_dl_gui/formats.py:9 youtube_dl_gui/formats.py:109 msgid "ID" msgstr "" #: youtube_dl_gui/formats.py:10 youtube_dl_gui/formats.py:110 #: youtube_dl_gui/mainframe.py:140 msgid "Title" msgstr "Заголовок" #: youtube_dl_gui/formats.py:11 youtube_dl_gui/formats.py:111 msgid "Title + ID" msgstr "Заголовок + ID" #: youtube_dl_gui/formats.py:12 youtube_dl_gui/formats.py:112 msgid "Title + Quality" msgstr "Заголовок + Качество" #: youtube_dl_gui/formats.py:13 youtube_dl_gui/formats.py:113 msgid "Title + ID + Quality" msgstr "Заголовок + ID + Качество" #: youtube_dl_gui/formats.py:14 youtube_dl_gui/formats.py:114 msgid "Custom" msgstr "Пользовательский" #: youtube_dl_gui/formats.py:19 youtube_dl_gui/formats.py:119 #: youtube_dl_gui/mainframe.py:503 youtube_dl_gui/mainframe.py:506 msgid "default" msgstr "по умолчанию" #: youtube_dl_gui/mainframe.py:97 msgid "Enter URLs below" msgstr "Введите URL-адреса ниже" #: youtube_dl_gui/mainframe.py:98 msgid "Update" msgstr "Обновить" #: youtube_dl_gui/mainframe.py:99 youtube_dl_gui/optionsframe.py:41 msgid "Options" msgstr "Настройки" #: youtube_dl_gui/mainframe.py:100 youtube_dl_gui/optionsframe.py:584 msgid "Stop" msgstr "Остановить" #: youtube_dl_gui/mainframe.py:101 msgid "Info" msgstr "Информация" #: youtube_dl_gui/mainframe.py:102 msgid "Welcome" msgstr "Добро пожаловать" #: youtube_dl_gui/mainframe.py:103 msgid "Warning" msgstr "Внимание" #: youtube_dl_gui/mainframe.py:105 msgid "Add" msgstr "Добавить" #: youtube_dl_gui/mainframe.py:106 msgid "Download list" msgstr "Список для скачивания" #: youtube_dl_gui/mainframe.py:107 youtube_dl_gui/mainframe.py:516 #: youtube_dl_gui/mainframe.py:534 msgid "Delete" msgstr "Удалить" #: youtube_dl_gui/mainframe.py:108 msgid "Play" msgstr "Воспроизвести" #: youtube_dl_gui/mainframe.py:109 msgid "Up" msgstr "Вверх" #: youtube_dl_gui/mainframe.py:110 msgid "Down" msgstr "Вниз" #: youtube_dl_gui/mainframe.py:111 msgid "Reload" msgstr "Перезапустить" #: youtube_dl_gui/mainframe.py:112 youtube_dl_gui/mainframe.py:448 #: youtube_dl_gui/mainframe.py:649 msgid "Pause" msgstr "Пауза" #: youtube_dl_gui/mainframe.py:113 youtube_dl_gui/mainframe.py:865 #: youtube_dl_gui/mainframe.py:866 youtube_dl_gui/optionsframe.py:582 msgid "Start" msgstr "Запустить" #: youtube_dl_gui/mainframe.py:114 msgid "About" msgstr "О программе" #: youtube_dl_gui/mainframe.py:115 msgid "View Log" msgstr "Просмотреть журнал" #: youtube_dl_gui/mainframe.py:117 msgid "Successfully downloaded {0} URL(s) in {1} day(s) {2} hour(s) {3} minute(s) {4} second(s)" msgstr "Успешно скачано {0} по указанным URL-адресам за {1} дней {2} часов {3} минут {4} секунд" #: youtube_dl_gui/mainframe.py:119 msgid "Downloads completed" msgstr "Скачивание завершено" #: youtube_dl_gui/mainframe.py:120 msgid "Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})" msgstr "Общий прогресс: {0:.1f}% | В очереди ({1}) На паузе ({2}) Активных ({3}) Завершенных ({4}) С ошибками ({5})" #: youtube_dl_gui/mainframe.py:121 msgid "Stopping downloads" msgstr "Производится остановка скачивания" #: youtube_dl_gui/mainframe.py:122 msgid "Downloads stopped" msgstr "Скачивание остановлено" #: youtube_dl_gui/mainframe.py:123 msgid "You need to provide at least one URL" msgstr "Вы должны указать хотя бы один URL-адрес" #: youtube_dl_gui/mainframe.py:124 msgid "Downloads started" msgstr "Началось скачивание" #: youtube_dl_gui/mainframe.py:125 msgid "Choose Directory" msgstr "Выбрать директорию" #: youtube_dl_gui/mainframe.py:127 msgid "Download in progress. Please wait for all downloads to complete" msgstr "Скачивание в процессе. Пожалуйста, подождите, пока все загрузки будут завершены" #: youtube_dl_gui/mainframe.py:128 msgid "Update already in progress" msgstr "Обновление уже в процессе" #: youtube_dl_gui/mainframe.py:130 msgid "Downloading latest youtube-dl. Please wait..." msgstr "Загрузка последней версии youtube-dl. Пожалуйста, подождите..." #: youtube_dl_gui/mainframe.py:131 msgid "Youtube-dl download failed [{0}]" msgstr "Не удалось скачать youtube-dl [{0}]" #: youtube_dl_gui/mainframe.py:132 msgid "Successfully downloaded youtube-dl" msgstr "Youtube-dl успешно скачан" #: youtube_dl_gui/mainframe.py:134 msgid "Unable to open directory: '{dir}'. The specified path does not exist" msgstr "Невозможно открыть каталог: '{dir}'. Указанный путь не существует" #: youtube_dl_gui/mainframe.py:136 msgid "Error while shutting down. Make sure you typed the correct password" msgstr "Ошибка при выключении. Убедитесь, что вы ввели корректный пароль вашей учетной записи администратора" #: youtube_dl_gui/mainframe.py:138 msgid "Shutting down system" msgstr "Выключение системы" #: youtube_dl_gui/mainframe.py:141 msgid "Extension" msgstr "Расширение" #: youtube_dl_gui/mainframe.py:142 msgid "Size" msgstr "Размер" #: youtube_dl_gui/mainframe.py:143 msgid "Percent" msgstr "Процент" #: youtube_dl_gui/mainframe.py:144 msgid "ETA" msgstr "Осталось" #: youtube_dl_gui/mainframe.py:145 msgid "Speed" msgstr "Скорость скачивания" #: youtube_dl_gui/mainframe.py:146 msgid "Status" msgstr "Статус" #: youtube_dl_gui/mainframe.py:235 msgid "Get URL" msgstr "Скопировать URL-адрес" #: youtube_dl_gui/mainframe.py:236 msgid "Get command" msgstr "Скопировать команду для CLI" #: youtube_dl_gui/mainframe.py:237 msgid "Open destination" msgstr "Открыть папку назначения" #: youtube_dl_gui/mainframe.py:238 msgid "Re-enter" msgstr "Повторить" #: youtube_dl_gui/mainframe.py:458 msgid "Resume" msgstr "Возобновить" #: youtube_dl_gui/mainframe.py:480 msgid "Video" msgstr "Видео" #: youtube_dl_gui/mainframe.py:484 msgid "Audio" msgstr "Аудио" #: youtube_dl_gui/mainframe.py:516 msgid "No items selected. Please pick an action" msgstr "Ни одного элемента не выбрано. Пожалуйста, выберите действие" #: youtube_dl_gui/mainframe.py:516 msgid "Remove all" msgstr "Удалить все" #: youtube_dl_gui/mainframe.py:516 msgid "Remove completed" msgstr "Удалить завершенные" #: youtube_dl_gui/mainframe.py:534 msgid "Are you sure you want to remove selected items?" msgstr "Вы действительно хотите удалить выбранные элементы?" #: youtube_dl_gui/mainframe.py:546 msgid "Item is active, cannot remove" msgstr "Элемент активен, его нельзя удалить" #: youtube_dl_gui/mainframe.py:579 msgid "Item is not completed" msgstr "Элемент не завершен" #: youtube_dl_gui/mainframe.py:668 msgid "Update in progress. Please wait for the update to complete" msgstr "Обновление в процессе. Пожалуйста, дождитесь завершения обновления" #: youtube_dl_gui/mainframe.py:716 msgid "Logging is disabled" msgstr "Журнал отключен" #: youtube_dl_gui/mainframe.py:891 msgid "Shutdown" msgstr "Выключение" #: youtube_dl_gui/mainframe.py:891 msgid "Shutting down in {0} second(s)" msgstr "Выключение через {0} секунд" #: youtube_dl_gui/mainframe.py:980 msgid "No items to download" msgstr "Нет ни одного элемента для скачивания" #: youtube_dl_gui/mainframe.py:1040 msgid "Updates are disabled for your system. Please use the system's package manager to update youtube-dl." msgstr "" #: youtube_dl_gui/mainframe.py:1065 msgid "Are you sure you want to exit?" msgstr "Вы уверены, что хотите выйти?" #: youtube_dl_gui/mainframe.py:1065 msgid "Exit" msgstr "Выход" #: youtube_dl_gui/mainframe.py:1306 youtube_dl_gui/mainframe.py:1456 msgid "Cancel" msgstr "Отмена" #: youtube_dl_gui/mainframe.py:1455 msgid "OK" msgstr "" #: youtube_dl_gui/optionsframe.py:65 msgid "Reset" msgstr "Сбросить" #: youtube_dl_gui/optionsframe.py:66 msgid "Close" msgstr "Закрыть" #: youtube_dl_gui/optionsframe.py:72 msgid "General" msgstr "Общие" #: youtube_dl_gui/optionsframe.py:73 msgid "Formats" msgstr "Форматы" #: youtube_dl_gui/optionsframe.py:74 msgid "Downloads" msgstr "Загрузки" #: youtube_dl_gui/optionsframe.py:75 msgid "Advanced" msgstr "Дополнительные" #: youtube_dl_gui/optionsframe.py:76 msgid "Extra" msgstr "Особые" #: youtube_dl_gui/optionsframe.py:310 msgid "Language" msgstr "Язык" #: youtube_dl_gui/optionsframe.py:313 msgid "Filename format" msgstr "Формат имени файла" #: youtube_dl_gui/optionsframe.py:318 msgid "Filename options" msgstr "Параметры имени файла" #: youtube_dl_gui/optionsframe.py:319 msgid "Restrict filenames to ASCII" msgstr "Ограничить имена файлов в ASCII" #: youtube_dl_gui/optionsframe.py:321 msgid "More options" msgstr "Больше параметров" #: youtube_dl_gui/optionsframe.py:322 msgid "Confirm on exit" msgstr "Подтверждение при выходе" #: youtube_dl_gui/optionsframe.py:323 msgid "Confirm item deletion" msgstr "Подтверждение при удалении элемента" #: youtube_dl_gui/optionsframe.py:324 msgid "Inform me on download completion" msgstr "Сообщите мне о завершении загрузки" #: youtube_dl_gui/optionsframe.py:326 msgid "Shutdown on download completion" msgstr "Выключение по завершении загрузки" #: youtube_dl_gui/optionsframe.py:337 msgid "SUDO password" msgstr "Пароль от вашей учетной записи (администратора)" #: youtube_dl_gui/optionsframe.py:415 youtube_dl_gui/optionsframe.py:816 msgid "In order for the changes to take effect please restart {0}" msgstr "Для того, чтобы изменения вступили в силу, пожалуйста, перезапустите {0}" #: youtube_dl_gui/optionsframe.py:416 youtube_dl_gui/optionsframe.py:817 msgid "Restart" msgstr "Перезагрузка" #: youtube_dl_gui/optionsframe.py:463 msgid "high" msgstr "высокое" #: youtube_dl_gui/optionsframe.py:463 msgid "low" msgstr "низкое" #: youtube_dl_gui/optionsframe.py:463 msgid "mid" msgstr "среднее" #: youtube_dl_gui/optionsframe.py:468 msgid "Video formats" msgstr "Формат видео" #: youtube_dl_gui/optionsframe.py:471 msgid "Audio formats" msgstr "Формат аудио" #: youtube_dl_gui/optionsframe.py:474 msgid "Post-Process options" msgstr "Параметры пост-обработки" #: youtube_dl_gui/optionsframe.py:475 msgid "Keep original files" msgstr "Сохранять исходные файлы" #: youtube_dl_gui/optionsframe.py:476 msgid "Extract audio from video file" msgstr "Извлечь аудио из видео файла" #: youtube_dl_gui/optionsframe.py:477 msgid "Embed thumbnail in audio file" msgstr "Добавить эскиз в аудиофайл" #: youtube_dl_gui/optionsframe.py:478 msgid "Add metadata to file" msgstr "Добавить метаданные в файл" #: youtube_dl_gui/optionsframe.py:480 msgid "Audio quality" msgstr "Качество аудио" #: youtube_dl_gui/optionsframe.py:538 msgid "English" msgstr "Английский" #: youtube_dl_gui/optionsframe.py:539 msgid "French" msgstr "Французский" #: youtube_dl_gui/optionsframe.py:540 msgid "German" msgstr "Немецкий" #: youtube_dl_gui/optionsframe.py:541 msgid "Greek" msgstr "Греческий" #: youtube_dl_gui/optionsframe.py:542 msgid "Hebrew" msgstr "Иврит" #: youtube_dl_gui/optionsframe.py:543 msgid "Italian" msgstr "Итальянский" #: youtube_dl_gui/optionsframe.py:544 msgid "Portuguese" msgstr "Португальский" #: youtube_dl_gui/optionsframe.py:545 msgid "Russian" msgstr "Русский" #: youtube_dl_gui/optionsframe.py:546 msgid "Spanish" msgstr "Испанский" #: youtube_dl_gui/optionsframe.py:547 msgid "Swedish" msgstr "Шведский" #: youtube_dl_gui/optionsframe.py:548 msgid "Turkish" msgstr "Турецкий" #: youtube_dl_gui/optionsframe.py:564 msgid "None" msgstr "Никакие" #: youtube_dl_gui/optionsframe.py:565 msgid "Automatic subtitles (YOUTUBE ONLY)" msgstr "Скачать файл авто-субтитров (ТОЛЬКО YOUTUBE)" #: youtube_dl_gui/optionsframe.py:566 msgid "All available subtitles" msgstr "Скачать все имеющиеся субтитры" #: youtube_dl_gui/optionsframe.py:567 msgid "Subtitles by language" msgstr "Язык субтитров" #: youtube_dl_gui/optionsframe.py:573 msgid "Subtitles" msgstr "Субтитры" #: youtube_dl_gui/optionsframe.py:577 msgid "Subtitles options" msgstr "Параметры субтитров" #: youtube_dl_gui/optionsframe.py:578 msgid "Embed subtitles into video file (mp4 ONLY)" msgstr "Встроить субтитры в видео (только mp4)" #: youtube_dl_gui/optionsframe.py:580 msgid "Playlist" msgstr "Плейлист" #: youtube_dl_gui/optionsframe.py:586 youtube_dl_gui/optionsframe.py:591 msgid "Max" msgstr "Максимум" #: youtube_dl_gui/optionsframe.py:589 msgid "Filesize" msgstr "Размер файла" #: youtube_dl_gui/optionsframe.py:594 msgid "Min" msgstr "Минимум" #: youtube_dl_gui/optionsframe.py:723 msgid "Retries" msgstr "Количество попыток" #: youtube_dl_gui/optionsframe.py:726 msgid "Authentication" msgstr "Аутентификация" #: youtube_dl_gui/optionsframe.py:728 msgid "Username" msgstr "Имя пользователя" #: youtube_dl_gui/optionsframe.py:730 msgid "Password" msgstr "Пароль" #: youtube_dl_gui/optionsframe.py:732 msgid "Video password" msgstr "Пароль на видео" #: youtube_dl_gui/optionsframe.py:735 msgid "Network" msgstr "Сеть" #: youtube_dl_gui/optionsframe.py:737 msgid "Proxy" msgstr "Прокси" #: youtube_dl_gui/optionsframe.py:739 msgid "User agent" msgstr "Строка агента пользователя" #: youtube_dl_gui/optionsframe.py:741 msgid "Referer" msgstr "Реферер" #: youtube_dl_gui/optionsframe.py:744 msgid "Logging" msgstr "Логирование" #: youtube_dl_gui/optionsframe.py:746 msgid "Enable log" msgstr "Вести журнал" #: youtube_dl_gui/optionsframe.py:747 msgid "View" msgstr "Просмотреть" #: youtube_dl_gui/optionsframe.py:748 msgid "Clear" msgstr "Очистить" #: youtube_dl_gui/optionsframe.py:858 msgid "Youtube-dl command line options (e.g. --help)" msgstr "Параметры командной строки youtube-dl (например --help)" #: youtube_dl_gui/optionsframe.py:861 msgid "Extra options" msgstr "Особые параметры" #: youtube_dl_gui/optionsframe.py:863 msgid "Debug youtube-dl" msgstr "Отладка youtube-dl" #: youtube_dl_gui/optionsframe.py:864 msgid "Ignore errors" msgstr "Игнорировать ошибки" #: youtube_dl_gui/optionsframe.py:865 msgid "Ignore youtube-dl config" msgstr "Игнорировать конфигурацию youtube-dl" #: youtube_dl_gui/optionsframe.py:866 msgid "No mtime" msgstr "Не использовать mtime" #: youtube_dl_gui/optionsframe.py:867 msgid "Prefer native HLS" msgstr "Использовать нативный HLS" #: youtube_dl_gui/optionsframe.py:928 msgid "Log Viewer" msgstr "Просмотрщик журнала" ================================================ FILE: youtube_dl_gui/logmanager.py ================================================ #!/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 ( os_path_exists, 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 # Bytes 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. Args: data (string): String to write to the log file. """ if isinstance(data, basestring): 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() ================================================ FILE: youtube_dl_gui/mainframe.py ================================================ #!/usr/bin/env python2 # -*- coding: utf-8 -*- """Youtubedlg module responsible for the main app window. """ from __future__ import unicode_literals import os import gettext import wx from wx.lib.pubsub import setuparg1 #NOTE Should remove deprecated from wx.lib.pubsub import pub as Publisher from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin from .parsers import OptionsParser from .optionsframe import ( OptionsFrame, LogGUI ) from .updatemanager import ( UPDATE_PUB_TOPIC, UpdateThread ) from .downloadmanager import ( MANAGER_PUB_TOPIC, WORKER_PUB_TOPIC, DownloadManager, DownloadList, DownloadItem ) from .utils import ( get_pixmaps_dir, build_command, get_icon_file, shutdown_sys, remove_file, open_file, get_time ) from .widgets import CustomComboBox from .formats import ( DEFAULT_FORMATS, VIDEO_FORMATS, AUDIO_FORMATS, FORMATS ) from .info import ( __descriptionfull__, __licensefull__, __projecturl__, __appname__, __author__ ) from .version import __version__ class MainFrame(wx.Frame): """Main window class. This class is responsible for creating the main app window and binding the events. Attributes: FRAMES_MIN_SIZE (tuple): Tuple that contains the minumum width, height of the frame. Labels area (strings): Strings for the widgets labels. STATUSLIST_COLUMNS (dict): Python dictionary which holds informations about the wxListCtrl columns. For more informations read the comments above the STATUSLIST_COLUMNS declaration. 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. """ FRAMES_MIN_SIZE = (560, 360) # Labels area URLS_LABEL = _("Enter URLs below") UPDATE_LABEL = _("Update") OPTIONS_LABEL = _("Options") STOP_LABEL = _("Stop") INFO_LABEL = _("Info") WELCOME_MSG = _("Welcome") WARNING_LABEL = _("Warning") ADD_LABEL = _("Add") DOWNLOAD_LIST_LABEL = _("Download list") DELETE_LABEL = _("Delete") PLAY_LABEL = _("Play") UP_LABEL = _("Up") DOWN_LABEL = _("Down") RELOAD_LABEL = _("Reload") PAUSE_LABEL = _("Pause") START_LABEL = _("Start") ABOUT_LABEL = _("About") VIEWLOG_LABEL = _("View Log") SUCC_REPORT_MSG = _("Successfully downloaded {0} URL(s) in {1} " "day(s) {2} hour(s) {3} minute(s) {4} second(s)") DL_COMPLETED_MSG = _("Downloads completed") URL_REPORT_MSG = _("Total Progress: {0:.1f}% | Queued ({1}) Paused ({2}) Active ({3}) Completed ({4}) Error ({5})") CLOSING_MSG = _("Stopping downloads") CLOSED_MSG = _("Downloads stopped") PROVIDE_URL_MSG = _("You need to provide at least one URL") DOWNLOAD_STARTED = _("Downloads started") CHOOSE_DIRECTORY = _("Choose Directory") DOWNLOAD_ACTIVE = _("Download in progress. Please wait for all downloads to complete") UPDATE_ACTIVE = _("Update already in progress") UPDATING_MSG = _("Downloading latest youtube-dl. Please wait...") UPDATE_ERR_MSG = _("Youtube-dl download failed [{0}]") UPDATE_SUCC_MSG = _("Successfully downloaded youtube-dl") 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") EXTENSION_LABEL = _("Extension") SIZE_LABEL = _("Size") PERCENT_LABEL = _("Percent") ETA_LABEL = _("ETA") SPEED_LABEL = _("Speed") STATUS_LABEL = _("Status") ################################# # STATUSLIST_COLUMNS # # Dictionary which contains the columns for the wxListCtrl widget. # Each key represents a column and holds informations about itself. # Structure informations: # column_key: (column_number, column_label, minimum_width, is_resizable) # STATUSLIST_COLUMNS = { 'filename': (0, VIDEO_LABEL, 150, True), 'extension': (1, EXTENSION_LABEL, 60, False), 'filesize': (2, SIZE_LABEL, 80, False), 'percent': (3, PERCENT_LABEL, 65, False), 'eta': (4, ETA_LABEL, 45, False), 'speed': (5, SPEED_LABEL, 90, False), 'status': (6, STATUS_LABEL, 160, False) } def __init__(self, opt_manager, log_manager, parent=None): super(MainFrame, self).__init__(parent, wx.ID_ANY, __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 = None #REFACTOR Get and set on __init__.py self._download_list = DownloadList() # Set up youtube-dl options parser self._options_parser = OptionsParser() # Get the pixmaps directory self._pixmaps_path = get_pixmaps_dir() # Set the Timer self._app_timer = wx.Timer(self) # Set the app icon app_icon_path = get_icon_file() if app_icon_path is not None: self.app_icon = wx.Icon(app_icon_path, wx.BITMAP_TYPE_PNG) self.SetIcon(self.app_icon) bitmap_data = ( ("down", "arrow_down_32px.png"), ("up", "arrow_up_32px.png"), ("play", "camera_32px.png"), ("start", "cloud_download_32px.png"), ("delete", "delete_32px.png"), ("folder", "folder_32px.png"), ("pause", "pause_32px.png"), ("resume", "play_arrow_32px.png"), ("reload", "reload_32px.png"), ("settings", "settings_20px.png"), ("stop", "stop_32px.png") ) self._bitmaps = {} for item in bitmap_data: target, name = item self._bitmaps[target] = wx.Bitmap(os.path.join(self._pixmaps_path, name)) # Set the data for all the wx.Button items # name, label, size, event_handler buttons_data = ( ("delete", self.DELETE_LABEL, (-1, -1), self._on_delete, wx.BitmapButton), ("play", self.PLAY_LABEL, (-1, -1), self._on_play, wx.BitmapButton), ("up", self.UP_LABEL, (-1, -1), self._on_arrow_up, wx.BitmapButton), ("down", self.DOWN_LABEL, (-1, -1), self._on_arrow_down, wx.BitmapButton), ("reload", self.RELOAD_LABEL, (-1, -1), self._on_reload, wx.BitmapButton), ("pause", self.PAUSE_LABEL, (-1, -1), self._on_pause, wx.BitmapButton), ("start", self.START_LABEL, (-1, -1), self._on_start, wx.BitmapButton), ("savepath", "...", (35, -1), self._on_savepath, wx.Button), ("add", self.ADD_LABEL, (-1, -1), self._on_add, wx.Button) ) # Set the data for the settings menu item # label, event_handler settings_menu_data = ( (self.OPTIONS_LABEL, self._on_options), (self.UPDATE_LABEL, self._on_update), (self.VIEWLOG_LABEL, self._on_viewlog), (self.ABOUT_LABEL, self._on_about) ) statuslist_menu_data = ( (_("Get URL"), self._on_geturl), (_("Get command"), self._on_getcmd), (_("Open destination"), self._on_open_dest), (_("Re-enter"), self._on_reenter) ) # Create options frame self._options_frame = OptionsFrame(self) # Create frame components self._panel = wx.Panel(self) self._url_text = self._create_statictext(self.URLS_LABEL) #REFACTOR Move to buttons_data self._settings_button = self._create_bitmap_button(self._bitmaps["settings"], (30, 30), self._on_settings) self._url_list = self._create_textctrl(wx.TE_MULTILINE | wx.TE_DONTWRAP, self._on_urllist_edit) self._folder_icon = self._create_static_bitmap(self._bitmaps["folder"], self._on_open_path) self._path_combobox = ExtComboBox(self._panel, 5, style=wx.CB_READONLY) self._videoformat_combobox = CustomComboBox(self._panel, style=wx.CB_READONLY) self._download_text = self._create_statictext(self.DOWNLOAD_LIST_LABEL) self._status_list = ListCtrl(self.STATUSLIST_COLUMNS, parent=self._panel, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) # Dictionary to store all the buttons self._buttons = {} for item in buttons_data: name, label, size, evt_handler, parent = item button = parent(self._panel, size=size) if parent == wx.Button: button.SetLabel(label) elif parent == wx.BitmapButton: button.SetToolTip(wx.ToolTip(label)) if name in self._bitmaps: button.SetBitmap(self._bitmaps[name], wx.TOP) if evt_handler is not None: button.Bind(wx.EVT_BUTTON, evt_handler) self._buttons[name] = button self._status_bar = self.CreateStatusBar() # Create extra components self._settings_menu = self._create_menu_item(settings_menu_data) self._statuslist_menu = self._create_menu_item(statuslist_menu_data) # Overwrite the menu hover event to avoid changing the statusbar self.Bind(wx.EVT_MENU_HIGHLIGHT, lambda event: None) # Bind extra events self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_statuslist_right_click, self._status_list) self.Bind(wx.EVT_TEXT, self._update_savepath, self._path_combobox) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._update_pause_button, self._status_list) self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._update_pause_button, self._status_list) self.Bind(wx.EVT_CLOSE, self._on_close) self.Bind(wx.EVT_TIMER, self._on_timer, self._app_timer) self._videoformat_combobox.Bind(wx.EVT_COMBOBOX, self._update_videoformat) # Set threads wxCallAfter handlers self._set_publisher(self._update_handler, UPDATE_PUB_TOPIC) self._set_publisher(self._download_worker_handler, WORKER_PUB_TOPIC) self._set_publisher(self._download_manager_handler, MANAGER_PUB_TOPIC) # Set up extra stuff self.Center() self.SetMinSize(self.FRAMES_MIN_SIZE) self._status_bar_write(self.WELCOME_MSG) self._update_videoformat_combobox() self._path_combobox.LoadMultiple(self.opt_manager.options["save_path_dirs"]) self._path_combobox.SetValue(self.opt_manager.options["save_path"]) self._set_layout() self._url_list.SetFocus() def _create_menu_item(self, items): menu = wx.Menu() for item in items: label, evt_handler = item menu_item = menu.Append(-1, label) menu.Bind(wx.EVT_MENU, evt_handler, menu_item) return menu def _on_statuslist_right_click(self, event): selected = event.GetIndex() if selected != -1: self._status_list.deselect_all() self._status_list.Select(selected, on=1) self.PopupMenu(self._statuslist_menu) def _on_reenter(self, event): selected = self._status_list.get_selected() if selected != -1: object_id = self._status_list.GetItemData(selected) download_item = self._download_list.get_item(object_id) if download_item.stage != "Active": self._status_list.remove_row(selected) self._download_list.remove(object_id) options = self._options_parser.parse(self.opt_manager.options) download_item = DownloadItem(download_item.url, options) download_item.path = self.opt_manager.options["save_path"] if not self._download_list.has_item(download_item.object_id): self._status_list.bind_item(download_item) self._download_list.insert(download_item) def reset(self): self._update_videoformat_combobox() self._path_combobox.LoadMultiple(self.opt_manager.options["save_path_dirs"]) self._path_combobox.SetValue(self.opt_manager.options["save_path"]) def _on_open_dest(self, event): selected = self._status_list.get_selected() if selected != -1: object_id = self._status_list.GetItemData(selected) download_item = self._download_list.get_item(object_id) if download_item.path: open_file(download_item.path) def _on_open_path(self, event): open_file(self._path_combobox.GetValue()) def _on_geturl(self, event): selected = self._status_list.get_selected() if selected != -1: object_id = self._status_list.GetItemData(selected) download_item = self._download_list.get_item(object_id) url = download_item.url if not wx.TheClipboard.IsOpened(): clipdata = wx.TextDataObject() clipdata.SetText(url) wx.TheClipboard.Open() wx.TheClipboard.SetData(clipdata) wx.TheClipboard.Close() def _on_getcmd(self, event): selected = self._status_list.get_selected() if selected != -1: object_id = self._status_list.GetItemData(selected) download_item = self._download_list.get_item(object_id) cmd = build_command(download_item.options, download_item.url) if not wx.TheClipboard.IsOpened(): clipdata = wx.TextDataObject() clipdata.SetText(cmd) wx.TheClipboard.Open() wx.TheClipboard.SetData(clipdata) wx.TheClipboard.Close() def _on_timer(self, event): total_percentage = 0.0 queued = paused = active = completed = error = 0 for item in self._download_list.get_items(): if item.stage == "Queued": queued += 1 if item.stage == "Paused": paused += 1 if item.stage == "Active": active += 1 total_percentage += float(item.progress_stats["percent"].split('%')[0]) if item.stage == "Completed": completed += 1 if item.stage == "Error": error += 1 # REFACTOR Store percentage as float in the DownloadItem? # REFACTOR DownloadList keep track for each item stage? items_count = active + completed + error + queued total_percentage += completed * 100.0 + error * 100.0 if items_count: total_percentage /= items_count msg = self.URL_REPORT_MSG.format(total_percentage, queued, paused, active, completed, error) if self.update_thread is None: # Dont overwrite the update messages self._status_bar_write(msg) def _update_pause_button(self, event): selected_rows = self._status_list.get_all_selected() label = _("Pause") bitmap = self._bitmaps["pause"] for row in selected_rows: object_id = self._status_list.GetItemData(row) download_item = self._download_list.get_item(object_id) if download_item.stage == "Paused": # If we find one or more items in Paused # state set the button functionality to resume label = _("Resume") bitmap = self._bitmaps["resume"] break self._buttons["pause"].SetLabel(label) self._buttons["pause"].SetToolTip(wx.ToolTip(label)) self._buttons["pause"].SetBitmap(bitmap, wx.TOP) def _update_videoformat_combobox(self): self._videoformat_combobox.Clear() self._videoformat_combobox.add_items(list(DEFAULT_FORMATS.values()), False) vformats = [] for vformat in self.opt_manager.options["selected_video_formats"]: vformats.append(FORMATS[vformat]) aformats = [] for aformat in self.opt_manager.options["selected_audio_formats"]: aformats.append(FORMATS[aformat]) if vformats: self._videoformat_combobox.add_header(_("Video")) self._videoformat_combobox.add_items(vformats) if aformats: self._videoformat_combobox.add_header(_("Audio")) self._videoformat_combobox.add_items(aformats) current_index = self._videoformat_combobox.FindString(FORMATS[self.opt_manager.options["selected_format"]]) if current_index == wx.NOT_FOUND: self._videoformat_combobox.SetSelection(0) else: self._videoformat_combobox.SetSelection(current_index) self._update_videoformat(None) def _update_videoformat(self, event): self.opt_manager.options["selected_format"] = selected_format = FORMATS[self._videoformat_combobox.GetValue()] if selected_format in VIDEO_FORMATS: self.opt_manager.options["video_format"] = selected_format self.opt_manager.options["audio_format"] = "" #NOTE Set to default value, check parsers.py elif selected_format in AUDIO_FORMATS: self.opt_manager.options["video_format"] = DEFAULT_FORMATS[_("default")] self.opt_manager.options["audio_format"] = selected_format else: self.opt_manager.options["video_format"] = DEFAULT_FORMATS[_("default")] self.opt_manager.options["audio_format"] = "" def _update_savepath(self, event): self.opt_manager.options["save_path"] = self._path_combobox.GetValue() def _on_delete(self, event): index = self._status_list.get_next_selected() if index == -1: dlg = ButtonsChoiceDialog(self, [_("Remove all"), _("Remove completed")], _("No items selected. Please pick an action"), _("Delete")) ret_code = dlg.ShowModal() dlg.Destroy() #REFACTOR Maybe add this functionality directly to DownloadList? if ret_code == 1: for ditem in self._download_list.get_items(): if ditem.stage != "Active": self._status_list.remove_row(self._download_list.index(ditem.object_id)) self._download_list.remove(ditem.object_id) if ret_code == 2: for ditem in self._download_list.get_items(): if ditem.stage == "Completed": self._status_list.remove_row(self._download_list.index(ditem.object_id)) self._download_list.remove(ditem.object_id) else: if self.opt_manager.options["confirm_deletion"]: dlg = wx.MessageDialog(self, _("Are you sure you want to remove selected items?"), _("Delete"), wx.YES_NO | wx.ICON_QUESTION) result = dlg.ShowModal() == wx.ID_YES dlg.Destroy() else: result = True if result: while index >= 0: object_id = self._status_list.GetItemData(index) selected_download_item = self._download_list.get_item(object_id) if selected_download_item.stage == "Active": self._create_popup(_("Item is active, cannot remove"), self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) else: #if selected_download_item.stage == "Completed": #dlg = wx.MessageDialog(self, "Do you want to remove the files associated with this item?", "Remove files", wx.YES_NO | wx.ICON_QUESTION) #result = dlg.ShowModal() == wx.ID_YES #dlg.Destroy() #if result: #for cur_file in selected_download_item.get_files(): #remove_file(cur_file) self._status_list.remove_row(index) self._download_list.remove(object_id) index -= 1 index = self._status_list.get_next_selected(index) self._update_pause_button(None) def _on_play(self, event): selected_rows = self._status_list.get_all_selected() if selected_rows: for selected_row in selected_rows: object_id = self._status_list.GetItemData(selected_row) selected_download_item = self._download_list.get_item(object_id) if selected_download_item.stage == "Completed": if selected_download_item.filenames: filename = selected_download_item.get_files()[-1] open_file(filename) else: self._create_popup(_("Item is not completed"), self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION) def _on_arrow_up(self, event): index = self._status_list.get_next_selected() if index != -1: while index >= 0: object_id = self._status_list.GetItemData(index) download_item = self._download_list.get_item(object_id) new_index = index - 1 if new_index < 0: new_index = 0 if not self._status_list.IsSelected(new_index): self._download_list.move_up(object_id) self._status_list.move_item_up(index) self._status_list._update_from_item(new_index, download_item) index = self._status_list.get_next_selected(index) def _on_arrow_down(self, event): index = self._status_list.get_next_selected(reverse=True) if index != -1: while index >= 0: object_id = self._status_list.GetItemData(index) download_item = self._download_list.get_item(object_id) new_index = index + 1 if new_index >= self._status_list.GetItemCount(): new_index = self._status_list.GetItemCount() - 1 if not self._status_list.IsSelected(new_index): self._download_list.move_down(object_id) self._status_list.move_item_down(index) self._status_list._update_from_item(new_index, download_item) index = self._status_list.get_next_selected(index, True) def _on_reload(self, event): selected_rows = self._status_list.get_all_selected() if not selected_rows: for index, item in enumerate(self._download_list.get_items()): if item.stage in ("Paused", "Completed", "Error"): # Store the old savepath because reset is going to remove it savepath = item.path item.reset() item.path = savepath self._status_list._update_from_item(index, item) else: for selected_row in selected_rows: object_id = self._status_list.GetItemData(selected_row) item = self._download_list.get_item(object_id) if item.stage in ("Paused", "Completed", "Error"): # Store the old savepath because reset is going to remove it savepath = item.path item.reset() item.path = savepath self._status_list._update_from_item(selected_row, item) self._update_pause_button(None) def _on_pause(self, event): selected_rows = self._status_list.get_all_selected() if selected_rows: #REFACTOR Use DoubleStageButton for this and check stage if self._buttons["pause"].GetLabel() == _("Pause"): new_state = "Paused" else: new_state = "Queued" for selected_row in selected_rows: object_id = self._status_list.GetItemData(selected_row) download_item = self._download_list.get_item(object_id) if download_item.stage == "Queued" or download_item.stage == "Paused": self._download_list.change_stage(object_id, new_state) self._status_list._update_from_item(selected_row, download_item) self._update_pause_button(None) def _on_start(self, event): if self.download_manager is None: if self.update_thread is not None and self.update_thread.is_alive(): self._create_popup(_("Update in progress. Please wait for the update to complete"), self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) else: self._start_download() else: self.download_manager.stop_downloads() def _on_savepath(self, event): dlg = wx.DirDialog(self, self.CHOOSE_DIRECTORY, self._path_combobox.GetStringSelection()) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self._path_combobox.Append(path) self._path_combobox.SetValue(path) self._update_savepath(None) dlg.Destroy() def _on_add(self, event): urls = self._get_urls() if not urls: self._create_popup(self.PROVIDE_URL_MSG, self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) else: self._url_list.Clear() options = self._options_parser.parse(self.opt_manager.options) for url in urls: download_item = DownloadItem(url, options) download_item.path = self.opt_manager.options["save_path"] if not self._download_list.has_item(download_item.object_id): self._status_list.bind_item(download_item) self._download_list.insert(download_item) def _on_settings(self, event): event_object_pos = event.EventObject.GetPosition() event_object_height = event.EventObject.GetSize()[1] event_object_pos = (event_object_pos[0], event_object_pos[1] + event_object_height) self.PopupMenu(self._settings_menu, event_object_pos) def _on_viewlog(self, event): if self.log_manager is None: self._create_popup(_("Logging is disabled"), self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) else: log_window = LogGUI(self) log_window.load(self.log_manager.log_file) log_window.Show() def _on_about(self, event): 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 _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 _create_statictext(self, label): return wx.StaticText(self._panel, label=label) def _create_bitmap_button(self, icon, size=(-1, -1), handler=None): button = wx.BitmapButton(self._panel, bitmap=icon, size=size, style=wx.NO_BORDER) if handler is not None: button.Bind(wx.EVT_BUTTON, handler) return button def _create_static_bitmap(self, icon, event_handler=None): static_bitmap = wx.StaticBitmap(self._panel, bitmap=icon) if event_handler is not None: static_bitmap.Bind(wx.EVT_LEFT_DCLICK, event_handler) return static_bitmap 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) if os.name == 'nt': # Enable CTRL+A on Windows def win_ctrla_eventhandler(event): if event.GetKeyCode() == wx.WXK_CONTROL_A: event.GetEventObject().SelectAll() event.Skip() textctrl.Bind(wx.EVT_CHAR, win_ctrla_eventhandler) return textctrl def _create_popup(self, text, title, style): wx.MessageBox(text, title, style) def _set_layout(self): """Sets the layout of the main window. """ main_sizer = wx.BoxSizer() panel_sizer = wx.BoxSizer(wx.VERTICAL) top_sizer = wx.BoxSizer(wx.HORIZONTAL) top_sizer.Add(self._url_text, 0, wx.ALIGN_BOTTOM | wx.BOTTOM, 5) top_sizer.AddSpacer((-1, -1), 1) top_sizer.Add(self._settings_button) panel_sizer.Add(top_sizer, 0, wx.EXPAND) panel_sizer.Add(self._url_list, 1, wx.EXPAND) mid_sizer = wx.BoxSizer(wx.HORIZONTAL) mid_sizer.Add(self._folder_icon) mid_sizer.AddSpacer((3, -1)) mid_sizer.Add(self._path_combobox, 2, wx.ALIGN_CENTER_VERTICAL) mid_sizer.AddSpacer((5, -1)) mid_sizer.Add(self._buttons["savepath"], flag=wx.ALIGN_CENTER_VERTICAL) mid_sizer.AddSpacer((10, -1), 1) mid_sizer.Add(self._videoformat_combobox, 1, wx.ALIGN_CENTER_VERTICAL) mid_sizer.AddSpacer((5, -1)) mid_sizer.Add(self._buttons["add"], flag=wx.ALIGN_CENTER_VERTICAL) panel_sizer.Add(mid_sizer, 0, wx.EXPAND | wx.ALL, 10) panel_sizer.Add(self._download_text, 0, wx.BOTTOM, 5) panel_sizer.Add(self._status_list, 2, wx.EXPAND) bottom_sizer = wx.BoxSizer(wx.HORIZONTAL) bottom_sizer.Add(self._buttons["delete"]) bottom_sizer.AddSpacer((5, -1)) bottom_sizer.Add(self._buttons["play"]) bottom_sizer.AddSpacer((5, -1)) bottom_sizer.Add(self._buttons["up"]) bottom_sizer.AddSpacer((5, -1)) bottom_sizer.Add(self._buttons["down"]) bottom_sizer.AddSpacer((5, -1)) bottom_sizer.Add(self._buttons["reload"]) bottom_sizer.AddSpacer((5, -1)) bottom_sizer.Add(self._buttons["pause"]) bottom_sizer.AddSpacer((10, -1), 1) bottom_sizer.Add(self._buttons["start"]) panel_sizer.Add(bottom_sizer, 0, wx.EXPAND | wx.TOP, 5) main_sizer.Add(panel_sizer, 1, wx.ALL | wx.EXPAND, 10) self._panel.SetSizer(main_sizer) self._panel.Layout() def _update_youtubedl(self): """Update youtube-dl binary to the latest version. """ if self.download_manager is not None and self.download_manager.is_alive(): self._create_popup(self.DOWNLOAD_ACTIVE, self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) elif self.update_thread is not None and self.update_thread.is_alive(): self._create_popup(self.UPDATE_ACTIVE, self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION) else: 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.SetStatusText(msg) def _reset_widgets(self): """Resets GUI widgets after update or download process. """ self._buttons["start"].SetLabel(_("Start")) self._buttons["start"].SetToolTip(wx.ToolTip(_("Start"))) self._buttons["start"].SetBitmap(self._bitmaps["start"], wx.TOP) 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']: dlg = ShutdownDialog(self, 60, _("Shutting down in {0} second(s)"), _("Shutdown")) result = dlg.ShowModal() == wx.ID_OK dlg.Destroy() if result: 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: if self.opt_manager.options["show_completion_popup"]: self._create_popup(self.DL_COMPLETED_MSG, self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION) def _download_worker_handler(self, msg): """downloadmanager.Worker thread handler. Handles messages from the Worker thread. Args: See downloadmanager.Worker _talk_to_gui() method. """ signal, data = msg.data download_item = self._download_list.get_item(data["index"]) download_item.update_stats(data) row = self._download_list.index(data["index"]) self._status_list._update_from_item(row, download_item) 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._app_timer.Stop() self._after_download() elif data == 'closed': self._status_bar_write(self.CLOSED_MSG) self._reset_widgets() self.download_manager = None self._app_timer.Stop() elif data == 'closing': self._status_bar_write(self.CLOSING_MSG) elif data == 'report_active': pass #NOTE Remove from here and downloadmanager #since now we have the wx.Timer to check progress 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 [line for line in self._url_list.GetValue().split('\n') if line] def _start_download(self): if self._status_list.is_empty(): self._create_popup(_("No items to download"), self.WARNING_LABEL, wx.OK | wx.ICON_EXCLAMATION) else: self._app_timer.Start(100) self.download_manager = DownloadManager(self, self._download_list, self.opt_manager, self.log_manager) self._status_bar_write(self.DOWNLOAD_STARTED) self._buttons["start"].SetLabel(self.STOP_LABEL) self._buttons["start"].SetToolTip(wx.ToolTip(self.STOP_LABEL)) self._buttons["start"].SetBitmap(self._bitmaps["stop"], wx.TOP) 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.GetEventType() == wx.EVT_TEXT_PASTE.typeId: self._paste_from_clipboard() else: wx.TheClipboard.UsePrimarySelection(True) self._paste_from_clipboard() wx.TheClipboard.UsePrimarySelection(False) 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. """ if self.opt_manager.options["disable_update"]: self._create_popup(_("Updates are disabled for your system. Please use the system's package manager to update youtube-dl."), self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION) else: 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 options 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.opt_manager.options["confirm_exit"]: dlg = wx.MessageDialog(self, _("Are you sure you want to exit?"), _("Exit"), wx.YES_NO | wx.ICON_QUESTION) result = dlg.ShowModal() == wx.ID_YES dlg.Destroy() else: result = True if result: self.close() def close(self): 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.opt_manager.options["save_path_dirs"] = self._path_combobox.GetStrings() self._options_frame.save_all_options() self.opt_manager.save_to_file() self.Destroy() class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): """Custom ListCtrl widget. Args: columns (dict): See MainFrame class STATUSLIST_COLUMNS attribute. """ def __init__(self, columns, *args, **kwargs): super(ListCtrl, self).__init__(*args, **kwargs) ListCtrlAutoWidthMixin.__init__(self) self.columns = columns self._list_index = 0 self._url_list = set() self._set_columns() def remove_row(self, row_number): self.DeleteItem(row_number) self._list_index -= 1 def move_item_up(self, row_number): self._move_item(row_number, row_number - 1) def move_item_down(self, row_number): self._move_item(row_number, row_number + 1) def _move_item(self, cur_row, new_row): self.Freeze() item = self.GetItem(cur_row) self.DeleteItem(cur_row) item.SetId(new_row) self.InsertItem(item) self.Select(new_row) self.Thaw() 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 bind_item(self, download_item): self.InsertStringItem(self._list_index, download_item.url) self.SetItemData(self._list_index, download_item.object_id) self._update_from_item(self._list_index, download_item) self._list_index += 1 def _update_from_item(self, row, download_item): progress_stats = download_item.progress_stats for key in self.columns: column = self.columns[key][0] if key == "status" and progress_stats["playlist_index"]: # Not the best place but we build the playlist status here status = "{0} {1}/{2}".format(progress_stats["status"], progress_stats["playlist_index"], progress_stats["playlist_size"]) self.SetStringItem(row, column, status) else: self.SetStringItem(row, column, progress_stats[key]) 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_selected(self): return self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) def get_all_selected(self): return [index for index in xrange(self._list_index) if self.IsSelected(index)] def deselect_all(self): for index in xrange(self._list_index): self.Select(index, on=0) def get_next_selected(self, start=-1, reverse=False): if start == -1: start = self._list_index - 1 if reverse else 0 else: # start from next item if reverse: start -= 1 else: start += 1 end = -1 if reverse else self._list_index step = -1 if reverse else 1 for index in xrange(start, end, step): if self.IsSelected(index): return index return -1 def _set_columns(self): """Initializes ListCtrl columns. See MainFrame STATUSLIST_COLUMNS attribute for more info. """ for column_item in sorted(self.columns.values()): self.InsertColumn(column_item[0], column_item[1], 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_item[0]) < column_item[2]: self.SetColumnWidth(column_item[0], column_item[2]) # Set auto-resize if enabled if column_item[3]: self.setResizeColumn(column_item[0]) # REFACTOR Extra widgets below should move to other module with widgets class ExtComboBox(wx.ComboBox): def __init__(self, parent, max_items=-1, *args, **kwargs): super(ExtComboBox, self).__init__(parent, *args, **kwargs) assert max_items > 0 or max_items == -1 self.max_items = max_items def Append(self, new_value): if self.FindString(new_value) == wx.NOT_FOUND: super(ExtComboBox, self).Append(new_value) if self.max_items != -1 and self.GetCount() > self.max_items: self.SetItems(self.GetStrings()[1:]) def SetValue(self, new_value): if self.FindString(new_value) == wx.NOT_FOUND: self.Append(new_value) self.SetSelection(self.FindString(new_value)) def LoadMultiple(self, items_list): for item in items_list: self.Append(item) class DoubleStageButton(wx.Button): def __init__(self, parent, labels, bitmaps, bitmap_pos=wx.TOP, *args, **kwargs): super(DoubleStageButton, self).__init__(parent, *args, **kwargs) assert isinstance(labels, tuple) and isinstance(bitmaps, tuple) assert len(labels) == 2 assert len(bitmaps) == 0 or len(bitmaps) == 2 self.labels = labels self.bitmaps = bitmaps self.bitmap_pos = bitmap_pos self._stage = 0 self._set_layout() def _set_layout(self): self.SetLabel(self.labels[self._stage]) if len(self.bitmaps): self.SetBitmap(self.bitmaps[self._stage], self.bitmap_pos) def change_stage(self): self._stage = 0 if self._stage else 1 self._set_layout() def set_stage(self, new_stage): assert new_stage == 0 or new_stage == 1 self._stage = new_stage self._set_layout() class ButtonsChoiceDialog(wx.Dialog): if os.name == "nt": STYLE = wx.DEFAULT_DIALOG_STYLE else: STYLE = wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX BORDER = 10 def __init__(self, parent, choices, message, *args, **kwargs): super(ButtonsChoiceDialog, self).__init__(parent, wx.ID_ANY, *args, style=self.STYLE, **kwargs) buttons = [] # Create components panel = wx.Panel(self) info_bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_MESSAGE_BOX) info_icon = wx.StaticBitmap(panel, wx.ID_ANY, info_bmp) msg_text = wx.StaticText(panel, wx.ID_ANY, message) buttons.append(wx.Button(panel, wx.ID_CANCEL, _("Cancel"))) for index, label in enumerate(choices): buttons.append(wx.Button(panel, index + 1, label)) # Get the maximum button width & height max_width = max_height = -1 for button in buttons: button_width, button_height = button.GetSize() if button_width > max_width: max_width = button_width if button_height > max_height: max_height = button_height max_width += 10 # Set buttons width & bind events for button in buttons: if button != buttons[0]: button.SetMinSize((max_width, max_height)) else: # On Close button change only the height button.SetMinSize((-1, max_height)) button.Bind(wx.EVT_BUTTON, self._on_close) # Set sizers vertical_sizer = wx.BoxSizer(wx.VERTICAL) message_sizer = wx.BoxSizer(wx.HORIZONTAL) message_sizer.Add(info_icon) message_sizer.AddSpacer((10, 10)) message_sizer.Add(msg_text, flag=wx.EXPAND) vertical_sizer.Add(message_sizer, 1, wx.ALL, border=self.BORDER) buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) for button in buttons[1:]: buttons_sizer.Add(button) buttons_sizer.AddSpacer((5, -1)) buttons_sizer.AddSpacer((-1, -1), 1) buttons_sizer.Add(buttons[0], flag=wx.ALIGN_RIGHT) vertical_sizer.Add(buttons_sizer, flag=wx.EXPAND | wx.ALL, border=self.BORDER) panel.SetSizer(vertical_sizer) width, height = panel.GetBestSize() self.SetSize((width, height * 1.3)) self.Center() def _on_close(self, event): self.EndModal(event.GetEventObject().GetId()) class ButtonsGroup(object): WIDTH = 0 HEIGHT = 1 def __init__(self, buttons_list=None, squared=False): if buttons_list is None: self._buttons_list = [] else: self._buttons_list = buttons_list self._squared = squared def set_size(self, size): assert len(size) == 2 width, height = size if width == -1: for button in self._buttons_list: cur_width = button.GetSize()[self.WIDTH] if cur_width > width: width = cur_width if height == -1: for button in self._buttons_list: cur_height = button.GetSize()[self.HEIGHT] if cur_height > height: height = cur_height if self._squared: width = height = (width if width > height else height) for button in self._buttons_list: button.SetMinSize((width, height)) def create_sizer(self, orient=wx.HORIZONTAL, space=-1): box_sizer = wx.BoxSizer(orient) for button in self._buttons_list: box_sizer.Add(button) if space != -1: box_sizer.AddSpacer((space, space)) return box_sizer def bind_event(self, event, event_handler): for button in self._buttons_list: button.Bind(event, event_handler) def disable_all(self): for button in self._buttons_list: button.Enable(False) def enable_all(self): for button in self._buttons_list: button.Enable(True) def add(self, button): self._buttons_list.append(button) class ShutdownDialog(wx.Dialog): if os.name == "nt": STYLE = wx.DEFAULT_DIALOG_STYLE else: STYLE = wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX TIMER_INTERVAL = 1000 # milliseconds BORDER = 10 def __init__(self, parent, timeout, message, *args, **kwargs): super(ShutdownDialog, self).__init__(parent, wx.ID_ANY, *args, style=self.STYLE, **kwargs) assert timeout > 0 self.timeout = timeout self.message = message # Create components panel = wx.Panel(self) info_bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_MESSAGE_BOX) info_icon = wx.StaticBitmap(panel, wx.ID_ANY, info_bmp) self.msg_text = msg_text = wx.StaticText(panel, wx.ID_ANY, self._get_message()) ok_button = wx.Button(panel, wx.ID_OK, _("OK")) cancel_button = wx.Button(panel, wx.ID_CANCEL, _("Cancel")) # Set layout vertical_sizer = wx.BoxSizer(wx.VERTICAL) message_sizer = wx.BoxSizer(wx.HORIZONTAL) message_sizer.Add(info_icon) message_sizer.AddSpacer((10, 10)) message_sizer.Add(msg_text, flag=wx.EXPAND) vertical_sizer.Add(message_sizer, 1, wx.ALL, border=self.BORDER) buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) buttons_sizer.Add(ok_button) buttons_sizer.AddSpacer((5, -1)) buttons_sizer.Add(cancel_button) vertical_sizer.Add(buttons_sizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=self.BORDER) panel.SetSizer(vertical_sizer) width, height = panel.GetBestSize() self.SetSize((width * 1.3, height * 1.3)) self.Center() # Set up timer self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self._on_timer, self.timer) self.timer.Start(self.TIMER_INTERVAL) def _get_message(self): return self.message.format(self.timeout) def _on_timer(self, event): self.timeout -= 1 self.msg_text.SetLabel(self._get_message()) if self.timeout <= 0: self.EndModal(wx.ID_OK) def Destroy(self): self.timer.Stop() return super(ShutdownDialog, self).Destroy() ================================================ FILE: youtube_dl_gui/optionsframe.py ================================================ #!/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 import wx.combo from wx.lib.art import flagart from .utils import ( TwoWayOrderedDict as twodict, os_path_exists, get_icon_file, os_sep ) from .info import __appname__ from .formats import ( OUTPUT_FORMATS, VIDEO_FORMATS, AUDIO_FORMATS ) #REFACTOR Move all formats, etc to formats.py class OptionsFrame(wx.Frame): """Youtubedlg options frame class. Args: parent (mainframe.MainFrame): Parent class. """ FRAME_TITLE = _("Options") FRAMES_MIN_SIZE = (500, 470) 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 = None # Set the app icon #REFACTOR Get icon from parent app_icon_path = get_icon_file() if app_icon_path is not None: self.app_icon = wx.Icon(app_icon_path, wx.BITMAP_TYPE_PNG) self.SetIcon(self.app_icon) self._was_shown = False # Create options frame basic components self.panel = wx.Panel(self) self.notebook = wx.Notebook(self.panel) self.separator_line = wx.StaticLine(self.panel) self.reset_button = wx.Button(self.panel, label=_("Reset")) self.close_button = wx.Button(self.panel, label=_("Close")) # Create tabs tab_args = (self, self.notebook) self.tabs = ( (GeneralTab(*tab_args), _("General")), (FormatsTab(*tab_args), _("Formats")), (DownloadsTab(*tab_args), _("Downloads")), (AdvancedTab(*tab_args), _("Advanced")), (ExtraTab(*tab_args), _("Extra")) ) # Add tabs on notebook for tab, label in self.tabs: self.notebook.AddPage(tab, label) # Bind events self.Bind(wx.EVT_BUTTON, self._on_reset, self.reset_button) self.Bind(wx.EVT_BUTTON, self._on_close, self.close_button) self.Bind(wx.EVT_CLOSE, self._on_close) self.SetMinSize(self.FRAMES_MIN_SIZE) self._set_layout() self.load_all_options() def _set_layout(self): main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(self.notebook, 1, wx.EXPAND | wx.ALL, border=5) main_sizer.Add(self.separator_line, 0, wx.EXPAND) buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) buttons_sizer.Add(self.reset_button) buttons_sizer.AddSpacer((5, -1)) buttons_sizer.Add(self.close_button) main_sizer.Add(buttons_sizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=5) self.panel.SetSizer(main_sizer) self.panel.Layout() def _on_close(self, event): """Event handler for wx.EVT_CLOSE event.""" self.save_all_options() #REFACTOR Parent create specific callback self.GetParent()._update_videoformat_combobox() self.Hide() def _on_reset(self, event): """Event handler for the reset button wx.EVT_BUTTON event.""" self.reset() self.GetParent().reset() def reset(self): """Reset the default options.""" self.opt_manager.load_default() self.load_all_options() def load_all_options(self): """Load all the options on each tab.""" for tab, _ in self.tabs: tab.load_options() def save_all_options(self): """Save all the options from all the tabs back to the OptionsManager.""" for tab, _ in self.tabs: tab.save_options() def Show(self, *args, **kwargs): # CenterOnParent can't go to main frame's __init__ as main frame may change # own position and options frame won't be centered on main frame anymore. if not self._was_shown: self._was_shown = True self.CenterOnParent() return wx.Frame.Show(self, *args, **kwargs) class TabPanel(wx.Panel): """Main tab class from which each tab inherits. Args: parent (OptionsFrame): The parent of all tabs. notebook (wx.Notebook): The container for each tab. Notes: In order to use a different size you must overwrite the below *_SIZE attributes on the corresponding child object. """ CHECKBOX_SIZE = (-1, -1) if os.name == "nt": # Make checkboxes look the same on Windows CHECKBOX_SIZE = (-1, 25) BUTTONS_SIZE = (-1, -1) TEXTCTRL_SIZE = (-1, -1) SPINCTRL_SIZE = (70, -1) CHECKLISTBOX_SIZE = (-1, 80) LISTBOX_SIZE = (-1, 80) def __init__(self, parent, notebook): super(TabPanel, self).__init__(notebook) #REFACTOR Maybe add methods to access those #save_options(key, value) #load_options(key) self.opt_manager = parent.opt_manager self.log_manager = parent.log_manager self.app_icon = parent.app_icon self.reset_handler = parent.reset # Shortcut methods below def crt_button(self, label, event_handler=None): 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 crt_checkbox(self, label, event_handler=None): 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 crt_textctrl(self, style=None): 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 crt_combobox(self, choices, size=(-1, -1), event_handler=None): combobox = wx.ComboBox(self, choices=choices, size=size, style=wx.CB_READONLY) if event_handler is not None: combobox.Bind(wx.EVT_COMBOBOX, event_handler) return combobox def crt_bitmap_combobox(self, choices, size=(-1, -1), event_handler=None): combobox = wx.combo.BitmapComboBox(self, size=size, style=wx.CB_READONLY) for item in choices: lang_code, lang_name = item _, country = lang_code.split('_') if country in flagart.catalog: flag_bmp = flagart.catalog[country].getBitmap() else: flag_bmp = flagart.catalog["BLANK"].getBitmap() combobox.Append(lang_name, flag_bmp) if event_handler is not None: combobox.Bind(wx.EVT_COMBOBOX, event_handler) return combobox def crt_spinctrl(self, spin_range=(0, 9999)): spinctrl = wx.SpinCtrl(self, size=self.SPINCTRL_SIZE) spinctrl.SetRange(*spin_range) return spinctrl def crt_statictext(self, label): return wx.StaticText(self, wx.ID_ANY, label) def crt_staticbox(self, label): return wx.StaticBox(self, wx.ID_ANY, label) def crt_checklistbox(self, choices, style=None): if style is None: checklistbox = wx.CheckListBox(self, choices=choices, size=self.CHECKLISTBOX_SIZE) else: checklistbox = wx.CheckListBox(self, choices=choices, style=style, size=self.CHECKLISTBOX_SIZE) return checklistbox def crt_listbox(self, choices, style=None): if style is None: listbox = wx.ListBox(self, choices=choices, size=self.LISTBOX_SIZE) else: listbox = wx.ListBox(self, choices=choices, style=style, size=self.LISTBOX_SIZE) return listbox class GeneralTab(TabPanel): # Lang code = _ LOCALE_NAMES = twodict([ ('ar_SA', 'Arabic'), ('cs_CZ', 'Czech'), ('en_US', 'English'), ('fr_FR', 'French'), ('it_IT', 'Italian'), ('ja_JP', 'Japanese'), ('ko_KR', 'Korean'), ('pt_BR', 'Portuguese'), ('ru_RU', 'Russian'), ('es_ES', 'Spanish') ]) OUTPUT_TEMPLATES = [ "Id", "Title", "Ext", "Uploader", "Resolution", "Autonumber", "", "View Count", "Like Count", "Dislike Count", "Comment Count", "Average Rating", "Age Limit", "Width", "Height", "Extractor", "", "Playlist", "Playlist Index", ] BUTTONS_SIZE = (30, -1) def __init__(self, *args, **kwargs): super(GeneralTab, self).__init__(*args, **kwargs) self.language_label = self.crt_statictext(_("Language")) self.language_combobox = self.crt_bitmap_combobox(list(self.LOCALE_NAMES.items()), event_handler=self._on_language) self.filename_format_label = self.crt_statictext(_("Filename format")) self.filename_format_combobox = self.crt_combobox(list(OUTPUT_FORMATS.values()), event_handler=self._on_filename) self.filename_custom_format = self.crt_textctrl() self.filename_custom_format_button = self.crt_button("...", self._on_format) self.filename_opts_label = self.crt_statictext(_("Filename options")) self.filename_ascii_checkbox = self.crt_checkbox(_("Restrict filenames to ASCII")) self.more_opts_label = self.crt_statictext(_("More options")) self.confirm_exit_checkbox = self.crt_checkbox(_("Confirm on exit")) self.confirm_deletion_checkbox = self.crt_checkbox(_("Confirm item deletion")) self.show_completion_popup_checkbox = self.crt_checkbox(_("Inform me on download completion")) self.shutdown_checkbox = self.crt_checkbox(_("Shutdown on download completion"), event_handler=self._on_shutdown) self.sudo_textctrl = self.crt_textctrl(wx.TE_PASSWORD) # Build the menu for the custom format button self.custom_format_menu = self._build_custom_format_menu() self._set_layout() if os.name == "nt": self.sudo_textctrl.Hide() self.sudo_textctrl.SetToolTip(wx.ToolTip(_("SUDO password"))) def _set_layout(self): main_sizer = wx.BoxSizer(wx.HORIZONTAL) vertical_sizer = wx.BoxSizer(wx.VERTICAL) vertical_sizer.Add(self.language_label) vertical_sizer.Add(self.language_combobox, flag=wx.EXPAND | wx.ALL, border=5) vertical_sizer.Add(self.filename_format_label, flag=wx.TOP, border=5) vertical_sizer.Add(self.filename_format_combobox, flag=wx.EXPAND | wx.ALL, border=5) custom_format_sizer = wx.BoxSizer(wx.HORIZONTAL) custom_format_sizer.Add(self.filename_custom_format, 1, wx.ALIGN_CENTER_VERTICAL) custom_format_sizer.AddSpacer((5, -1)) custom_format_sizer.Add(self.filename_custom_format_button) vertical_sizer.Add(custom_format_sizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) vertical_sizer.Add(self.filename_opts_label, flag=wx.TOP, border=5) vertical_sizer.Add(self.filename_ascii_checkbox, flag=wx.ALL, border=5) vertical_sizer.Add(self.more_opts_label, flag=wx.TOP, border=5) vertical_sizer.Add(self.confirm_exit_checkbox, flag=wx.ALL, border=5) vertical_sizer.Add(self.confirm_deletion_checkbox, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) vertical_sizer.Add(self.show_completion_popup_checkbox, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) shutdown_sizer = wx.BoxSizer(wx.HORIZONTAL) shutdown_sizer.Add(self.shutdown_checkbox) shutdown_sizer.AddSpacer((-1, -1), 1) shutdown_sizer.Add(self.sudo_textctrl, 1) vertical_sizer.Add(shutdown_sizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) main_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.ALL, border=5) self.SetSizer(main_sizer) def _build_custom_format_menu(self): menu = wx.Menu() for template in self.OUTPUT_TEMPLATES: if template: menu_item = menu.Append(wx.ID_ANY, template) menu.Bind(wx.EVT_MENU, self._on_template, menu_item) else: menu.AppendSeparator() return menu def _on_template(self, event): """Event handler for the wx.EVT_MENU of the custom_format_menu menu items.""" label = self.custom_format_menu.GetLabelText(event.GetId()) label = label.lower().replace(' ', '_') custom_format = self.filename_custom_format.GetValue() if label == "ext": prefix = '.' else: prefix = '-' if not custom_format or custom_format[-1] == os_sep: # If the custom format is empty or ends with path separator # remove the prefix prefix = '' template = "{0}%({1})s".format(prefix, label) self.filename_custom_format.SetValue(custom_format + template) def _on_format(self, event): """Event handler for the wx.EVT_BUTTON of the filename_custom_format_button.""" event_object_pos = event.EventObject.GetPosition() event_object_height = event.EventObject.GetSize()[1] event_object_pos = (event_object_pos[0], event_object_pos[1] + event_object_height) self.PopupMenu(self.custom_format_menu, event_object_pos) def _on_language(self, event): """Event handler for the wx.EVT_COMBOBOX of the language_combobox.""" wx.MessageBox(_("In order for the changes to take effect please restart {0}").format(__appname__), _("Restart"), wx.OK | wx.ICON_INFORMATION, self) def _on_filename(self, event): """Event handler for the wx.EVT_COMBOBOX of the filename_format_combobox.""" custom_selected = self.filename_format_combobox.GetValue() == OUTPUT_FORMATS[3] self.filename_custom_format.Enable(custom_selected) self.filename_custom_format_button.Enable(custom_selected) def _on_shutdown(self, event): """Event handler for the wx.EVT_CHECKBOX of the shutdown_checkbox.""" self.sudo_textctrl.Enable(self.shutdown_checkbox.GetValue()) def load_options(self): self.language_combobox.SetValue(self.LOCALE_NAMES.get(self.opt_manager.options["locale_name"], "English")) self.filename_format_combobox.SetValue(OUTPUT_FORMATS[self.opt_manager.options["output_format"]]) self.filename_custom_format.SetValue(self.opt_manager.options["output_template"]) self.filename_ascii_checkbox.SetValue(self.opt_manager.options["restrict_filenames"]) self.shutdown_checkbox.SetValue(self.opt_manager.options["shutdown"]) self.sudo_textctrl.SetValue(self.opt_manager.options["sudo_password"]) self.confirm_exit_checkbox.SetValue(self.opt_manager.options["confirm_exit"]) self.show_completion_popup_checkbox.SetValue(self.opt_manager.options["show_completion_popup"]) self.confirm_deletion_checkbox.SetValue(self.opt_manager.options["confirm_deletion"]) #REFACTOR Automatically call on the new methods #save_options #load_options #NOTE Maybe on init add callback? self._on_filename(None) self._on_shutdown(None) def save_options(self): self.opt_manager.options["locale_name"] = self.LOCALE_NAMES[self.language_combobox.GetValue()] self.opt_manager.options["output_format"] = OUTPUT_FORMATS[self.filename_format_combobox.GetValue()] self.opt_manager.options["output_template"] = self.filename_custom_format.GetValue() self.opt_manager.options["restrict_filenames"] = self.filename_ascii_checkbox.GetValue() self.opt_manager.options["shutdown"] = self.shutdown_checkbox.GetValue() self.opt_manager.options["sudo_password"] = self.sudo_textctrl.GetValue() self.opt_manager.options["confirm_exit"] = self.confirm_exit_checkbox.GetValue() self.opt_manager.options["show_completion_popup"] = self.show_completion_popup_checkbox.GetValue() self.opt_manager.options["confirm_deletion"] = self.confirm_deletion_checkbox.GetValue() class FormatsTab(TabPanel): AUDIO_QUALITY = twodict([("0", _("high")), ("5", _("mid")), ("9", _("low"))]) def __init__(self, *args, **kwargs): super(FormatsTab, self).__init__(*args, **kwargs) self.video_formats_label = self.crt_statictext(_("Video formats")) self.video_formats_checklistbox = self.crt_checklistbox(list(VIDEO_FORMATS.values())) self.audio_formats_label = self.crt_statictext(_("Audio formats")) self.audio_formats_checklistbox = self.crt_checklistbox(list(AUDIO_FORMATS.values())) self.post_proc_opts_label = self.crt_statictext(_("Post-Process options")) self.keep_video_checkbox = self.crt_checkbox(_("Keep original files")) self.extract_audio_checkbox = self.crt_checkbox(_("Extract audio from video file")) self.embed_thumbnail_checkbox = self.crt_checkbox(_("Embed thumbnail in audio file")) self.add_metadata_checkbox = self.crt_checkbox(_("Add metadata to file")) self.audio_quality_label = self.crt_statictext(_("Audio quality")) self.audio_quality_combobox = self.crt_combobox(list(self.AUDIO_QUALITY.values())) self._set_layout() def _set_layout(self): main_sizer = wx.BoxSizer(wx.HORIZONTAL) vertical_sizer = wx.BoxSizer(wx.VERTICAL) vertical_sizer.Add(self.video_formats_label) vertical_sizer.Add(self.video_formats_checklistbox, 1, wx.EXPAND | wx.ALL, border=5) vertical_sizer.Add(self.audio_formats_label, flag=wx.TOP, border=5) vertical_sizer.Add(self.audio_formats_checklistbox, 1, wx.EXPAND | wx.ALL, border=5) vertical_sizer.Add(self.post_proc_opts_label, flag=wx.TOP, border=5) vertical_sizer.Add(self.keep_video_checkbox, flag=wx.ALL, border=5) vertical_sizer.Add(self.extract_audio_checkbox, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) vertical_sizer.Add(self.embed_thumbnail_checkbox, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) vertical_sizer.Add(self.add_metadata_checkbox, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) audio_quality_sizer = wx.BoxSizer(wx.HORIZONTAL) audio_quality_sizer.Add(self.audio_quality_label, flag=wx.ALIGN_CENTER_VERTICAL) audio_quality_sizer.AddSpacer((20, -1)) audio_quality_sizer.Add(self.audio_quality_combobox) vertical_sizer.Add(audio_quality_sizer, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) main_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.ALL, border=5) self.SetSizer(main_sizer) def load_options(self): checked_video_formats = [VIDEO_FORMATS[vformat] for vformat in self.opt_manager.options["selected_video_formats"]] self.video_formats_checklistbox.SetCheckedStrings(checked_video_formats) checked_audio_formats = [AUDIO_FORMATS[aformat] for aformat in self.opt_manager.options["selected_audio_formats"]] self.audio_formats_checklistbox.SetCheckedStrings(checked_audio_formats) self.keep_video_checkbox.SetValue(self.opt_manager.options["keep_video"]) self.audio_quality_combobox.SetValue(self.AUDIO_QUALITY[self.opt_manager.options["audio_quality"]]) self.extract_audio_checkbox.SetValue(self.opt_manager.options["to_audio"]) self.embed_thumbnail_checkbox.SetValue(self.opt_manager.options["embed_thumbnail"]) self.add_metadata_checkbox.SetValue(self.opt_manager.options["add_metadata"]) def save_options(self): checked_video_formats = [VIDEO_FORMATS[vformat] for vformat in self.video_formats_checklistbox.GetCheckedStrings()] self.opt_manager.options["selected_video_formats"] = checked_video_formats checked_audio_formats = [AUDIO_FORMATS[aformat] for aformat in self.audio_formats_checklistbox.GetCheckedStrings()] self.opt_manager.options["selected_audio_formats"] = checked_audio_formats self.opt_manager.options["keep_video"] = self.keep_video_checkbox.GetValue() self.opt_manager.options["audio_quality"] = self.AUDIO_QUALITY[self.audio_quality_combobox.GetValue()] self.opt_manager.options["to_audio"] = self.extract_audio_checkbox.GetValue() self.opt_manager.options["embed_thumbnail"] = self.embed_thumbnail_checkbox.GetValue() self.opt_manager.options["add_metadata"] = self.add_metadata_checkbox.GetValue() class DownloadsTab(TabPanel): # Lang code = ISO 639-1 SUBS_LANG = twodict([ ("en", _("English")), ("fr", _("French")), ("de", _("German")), ("el", _("Greek")), ("he", _("Hebrew")), ("it", _("Italian")), ("pt", _("Portuguese")), ("ru", _("Russian")), ("es", _("Spanish")), ("sv", _("Swedish")), ("tr", _("Turkish")) ]) FILESIZES = twodict([ ("", "Bytes"), ("k", "Kilobytes"), ("m", "Megabytes"), ("g", "Gigabytes"), ("t", "Terabytes"), ("p", "Petabytes"), ("e", "Exabytes"), ("z", "Zettabytes"), ("y", "Yottabytes") ]) SUBS_CHOICES = [ _("None"), _("Automatic subtitles (YOUTUBE ONLY)"), _("All available subtitles"), _("Subtitles by language") ] def __init__(self, *args, **kwargs): super(DownloadsTab, self).__init__(*args, **kwargs) self.subtitles_label = self.crt_statictext(_("Subtitles")) self.subtitles_combobox = self.crt_combobox(self.SUBS_CHOICES, event_handler=self._on_subtitles) self.subtitles_lang_listbox = self.crt_listbox(list(self.SUBS_LANG.values())) self.subtitles_opts_label = self.crt_statictext(_("Subtitles options")) self.embed_subs_checkbox = self.crt_checkbox(_("Embed subtitles into video file (mp4 ONLY)")) self.playlist_box = self.crt_staticbox(_("Playlist")) self.playlist_start_label = self.crt_statictext(_("Start")) self.playlist_start_spinctrl = self.crt_spinctrl((1, 9999)) self.playlist_stop_label = self.crt_statictext(_("Stop")) self.playlist_stop_spinctrl = self.crt_spinctrl() self.playlist_max_label = self.crt_statictext(_("Max")) self.playlist_max_spinctrl = self.crt_spinctrl() self.filesize_box = self.crt_staticbox(_("Filesize")) self.filesize_max_label = self.crt_statictext(_("Max")) self.filesize_max_spinctrl = self.crt_spinctrl((0, 1024)) self.filesize_max_sizeunit_combobox = self.crt_combobox(list(self.FILESIZES.values())) self.filesize_min_label = self.crt_statictext(_("Min")) self.filesize_min_spinctrl = self.crt_spinctrl((0, 1024)) self.filesize_min_sizeunit_combobox = self.crt_combobox(list(self.FILESIZES.values())) self._set_layout() def _set_layout(self): main_sizer = wx.BoxSizer(wx.HORIZONTAL) vertical_sizer = wx.BoxSizer(wx.VERTICAL) vertical_sizer.Add(self.subtitles_label) vertical_sizer.Add(self.subtitles_combobox, flag=wx.EXPAND | wx.ALL, border=5) vertical_sizer.Add(self.subtitles_lang_listbox, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) vertical_sizer.Add(self.subtitles_opts_label, flag=wx.TOP, border=5) vertical_sizer.Add(self.embed_subs_checkbox, flag=wx.ALL, border=5) plist_and_fsize_sizer = wx.BoxSizer(wx.HORIZONTAL) plist_and_fsize_sizer.Add(self._build_playlist_sizer(), 1, wx.EXPAND) plist_and_fsize_sizer.AddSpacer((5, -1)) plist_and_fsize_sizer.Add(self._build_filesize_sizer(), 1, wx.EXPAND) vertical_sizer.Add(plist_and_fsize_sizer, 1, wx.EXPAND | wx.TOP, border=5) main_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.ALL, border=5) self.SetSizer(main_sizer) def _build_playlist_sizer(self): playlist_box_sizer = wx.StaticBoxSizer(self.playlist_box, wx.VERTICAL) playlist_box_sizer.AddSpacer((-1, 10)) border = wx.GridBagSizer(5, 40) border.Add(self.playlist_start_label, (0, 0), flag=wx.ALIGN_CENTER_VERTICAL) border.Add(self.playlist_start_spinctrl, (0, 1)) border.Add(self.playlist_stop_label, (1, 0), flag=wx.ALIGN_CENTER_VERTICAL) border.Add(self.playlist_stop_spinctrl, (1, 1)) border.Add(self.playlist_max_label, (2, 0), flag=wx.ALIGN_CENTER_VERTICAL) border.Add(self.playlist_max_spinctrl, (2, 1)) playlist_box_sizer.Add(border, flag=wx.ALIGN_CENTER) return playlist_box_sizer def _build_filesize_sizer(self): filesize_box_sizer = wx.StaticBoxSizer(self.filesize_box, wx.VERTICAL) border = wx.GridBagSizer(5, 20) border.Add(self.filesize_max_label, (0, 0), (1, 2), wx.ALIGN_CENTER_HORIZONTAL) border.Add(self.filesize_max_spinctrl, (1, 0)) border.Add(self.filesize_max_sizeunit_combobox, (1, 1)) border.Add(self.filesize_min_label, (2, 0), (1, 2), wx.ALIGN_CENTER_HORIZONTAL) border.Add(self.filesize_min_spinctrl, (3, 0)) border.Add(self.filesize_min_sizeunit_combobox, (3, 1)) filesize_box_sizer.Add(border, flag=wx.ALIGN_CENTER) return filesize_box_sizer def _on_subtitles(self, event): """Event handler for the wx.EVT_COMBOBOX of the subtitles_combobox.""" self.subtitles_lang_listbox.Enable(self.subtitles_combobox.GetValue() == self.SUBS_CHOICES[-1]) def load_options(self): #NOTE Find a better way to do this if self.opt_manager.options["write_subs"]: self.subtitles_combobox.SetValue(self.SUBS_CHOICES[3]) elif self.opt_manager.options["write_all_subs"]: self.subtitles_combobox.SetValue(self.SUBS_CHOICES[2]) elif self.opt_manager.options["write_auto_subs"]: self.subtitles_combobox.SetValue(self.SUBS_CHOICES[1]) else: self.subtitles_combobox.SetValue(self.SUBS_CHOICES[0]) self.subtitles_lang_listbox.SetStringSelection(self.SUBS_LANG[self.opt_manager.options["subs_lang"]]) self.embed_subs_checkbox.SetValue(self.opt_manager.options["embed_subs"]) self.playlist_start_spinctrl.SetValue(self.opt_manager.options["playlist_start"]) self.playlist_stop_spinctrl.SetValue(self.opt_manager.options["playlist_end"]) self.playlist_max_spinctrl.SetValue(self.opt_manager.options["max_downloads"]) self.filesize_min_spinctrl.SetValue(self.opt_manager.options["min_filesize"]) self.filesize_max_spinctrl.SetValue(self.opt_manager.options["max_filesize"]) self.filesize_min_sizeunit_combobox.SetValue(self.FILESIZES[self.opt_manager.options["min_filesize_unit"]]) self.filesize_max_sizeunit_combobox.SetValue(self.FILESIZES[self.opt_manager.options["max_filesize_unit"]]) self._on_subtitles(None) def save_options(self): subs_choice = self.SUBS_CHOICES.index(self.subtitles_combobox.GetValue()) if subs_choice == 1: self.opt_manager.options["write_subs"] = False self.opt_manager.options["write_all_subs"] = False self.opt_manager.options["write_auto_subs"] = True elif subs_choice == 2: self.opt_manager.options["write_subs"] = False self.opt_manager.options["write_all_subs"] = True self.opt_manager.options["write_auto_subs"] = False elif subs_choice == 3: self.opt_manager.options["write_subs"] = True self.opt_manager.options["write_all_subs"] = False self.opt_manager.options["write_auto_subs"] = False else: self.opt_manager.options["write_subs"] = False self.opt_manager.options["write_all_subs"] = False self.opt_manager.options["write_auto_subs"] = False self.opt_manager.options["subs_lang"] = self.SUBS_LANG[self.subtitles_lang_listbox.GetStringSelection()] self.opt_manager.options["embed_subs"] = self.embed_subs_checkbox.GetValue() self.opt_manager.options["playlist_start"] = self.playlist_start_spinctrl.GetValue() self.opt_manager.options["playlist_end"] = self.playlist_stop_spinctrl.GetValue() self.opt_manager.options["max_downloads"] = self.playlist_max_spinctrl.GetValue() self.opt_manager.options["min_filesize"] = self.filesize_min_spinctrl.GetValue() self.opt_manager.options["max_filesize"] = self.filesize_max_spinctrl.GetValue() self.opt_manager.options["min_filesize_unit"] = self.FILESIZES[self.filesize_min_sizeunit_combobox.GetValue()] self.opt_manager.options["max_filesize_unit"] = self.FILESIZES[self.filesize_max_sizeunit_combobox.GetValue()] class AdvancedTab(TabPanel): TEXTCTRL_SIZE = (300, -1) def __init__(self, *args, **kwargs): super(AdvancedTab, self).__init__(*args, **kwargs) self.retries_label = self.crt_statictext(_("Retries")) self.retries_spinctrl = self.crt_spinctrl((1, 999)) self.auth_label = self.crt_statictext(_("Authentication")) self.username_label = self.crt_statictext(_("Username")) self.username_textctrl = self.crt_textctrl() self.password_label = self.crt_statictext(_("Password")) self.password_textctrl = self.crt_textctrl(wx.TE_PASSWORD) self.video_pass_label = self.crt_statictext(_("Video password")) self.video_pass_textctrl = self.crt_textctrl(wx.TE_PASSWORD) self.network_label = self.crt_statictext(_("Network")) self.proxy_label = self.crt_statictext(_("Proxy")) self.proxy_textctrl = self.crt_textctrl() self.useragent_label = self.crt_statictext(_("User agent")) self.useragent_textctrl = self.crt_textctrl() self.referer_label = self.crt_statictext(_("Referer")) self.referer_textctrl = self.crt_textctrl() self.logging_label = self.crt_statictext(_("Logging")) self.enable_log_checkbox = self.crt_checkbox(_("Enable log"), self._on_enable_log) self.view_log_button = self.crt_button(_("View"), self._on_view) self.clear_log_button = self.crt_button(_("Clear"), self._on_clear) self._set_layout() if self.log_manager is None: self.view_log_button.Disable() self.clear_log_button.Disable() def _set_layout(self): main_sizer = wx.BoxSizer(wx.HORIZONTAL) vertical_sizer = wx.BoxSizer(wx.VERTICAL) # Set up retries box retries_sizer = wx.BoxSizer(wx.HORIZONTAL) retries_sizer.Add(self.retries_label, flag=wx.ALIGN_CENTER_VERTICAL) retries_sizer.AddSpacer((20, -1)) retries_sizer.Add(self.retries_spinctrl) vertical_sizer.Add(retries_sizer, flag=wx.ALIGN_RIGHT | wx.TOP | wx.RIGHT, border=5) # Set up authentication box vertical_sizer.Add(self.auth_label, flag=wx.TOP, border=10) auth_sizer = wx.GridBagSizer(5, -1) auth_sizer.Add(self.username_label, (0, 0), flag=wx.ALIGN_CENTER_VERTICAL) auth_sizer.Add(self.username_textctrl, (0, 2)) auth_sizer.Add(self.password_label, (1, 0), flag=wx.ALIGN_CENTER_VERTICAL) auth_sizer.Add(self.password_textctrl, (1, 2)) auth_sizer.Add(self.video_pass_label, (2, 0), flag=wx.ALIGN_CENTER_VERTICAL) auth_sizer.Add(self.video_pass_textctrl, (2, 2)) auth_sizer.AddGrowableCol(1) vertical_sizer.Add(auth_sizer, flag=wx.EXPAND | wx.ALL, border=5) # Set up network box vertical_sizer.Add(self.network_label, flag=wx.TOP, border=10) network_sizer = wx.GridBagSizer(5, -1) network_sizer.Add(self.proxy_label, (0, 0), flag=wx.ALIGN_CENTER_VERTICAL) network_sizer.Add(self.proxy_textctrl, (0, 2)) network_sizer.Add(self.useragent_label, (1, 0), flag=wx.ALIGN_CENTER_VERTICAL) network_sizer.Add(self.useragent_textctrl, (1, 2)) network_sizer.Add(self.referer_label, (2, 0), flag=wx.ALIGN_CENTER_VERTICAL) network_sizer.Add(self.referer_textctrl, (2, 2)) network_sizer.AddGrowableCol(1) vertical_sizer.Add(network_sizer, flag=wx.EXPAND | wx.ALL, border=5) # Set up logging box vertical_sizer.Add(self.logging_label, flag=wx.TOP, border=10) logging_sizer = wx.BoxSizer(wx.HORIZONTAL) logging_sizer.Add(self.enable_log_checkbox) logging_sizer.AddSpacer((-1, -1), 1) logging_sizer.Add(self.view_log_button) logging_sizer.AddSpacer((5, -1)) logging_sizer.Add(self.clear_log_button) vertical_sizer.Add(logging_sizer, flag=wx.EXPAND | wx.ALL, border=5) main_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.ALL, border=5) self.SetSizer(main_sizer) def _on_enable_log(self, event): """Event handler for the wx.EVT_CHECKBOX of the enable_log_checkbox.""" wx.MessageBox(_("In order for the changes to take effect please restart {0}").format(__appname__), _("Restart"), wx.OK | wx.ICON_INFORMATION, self) def _on_view(self, event): """Event handler for the wx.EVT_BUTTON of the view_log_button.""" log_window = LogGUI(self) log_window.load(self.log_manager.log_file) log_window.Show() def _on_clear(self, event): """Event handler for the wx.EVT_BUTTON of the clear_log_button.""" if self.log_manager is not None: self.log_manager.clear() def load_options(self): self.retries_spinctrl.SetValue(self.opt_manager.options["retries"]) self.username_textctrl.SetValue(self.opt_manager.options["username"]) self.password_textctrl.SetValue(self.opt_manager.options["password"]) self.video_pass_textctrl.SetValue(self.opt_manager.options["video_password"]) self.proxy_textctrl.SetValue(self.opt_manager.options["proxy"]) self.useragent_textctrl.SetValue(self.opt_manager.options["user_agent"]) self.referer_textctrl.SetValue(self.opt_manager.options["referer"]) self.enable_log_checkbox.SetValue(self.opt_manager.options["enable_log"]) def save_options(self): self.opt_manager.options["retries"] = self.retries_spinctrl.GetValue() self.opt_manager.options["username"] = self.username_textctrl.GetValue() self.opt_manager.options["password"] = self.password_textctrl.GetValue() self.opt_manager.options["video_password"] = self.video_pass_textctrl.GetValue() self.opt_manager.options["proxy"] = self.proxy_textctrl.GetValue() self.opt_manager.options["user_agent"] = self.useragent_textctrl.GetValue() self.opt_manager.options["referer"] = self.referer_textctrl.GetValue() self.opt_manager.options["enable_log"] = self.enable_log_checkbox.GetValue() class ExtraTab(TabPanel): def __init__(self, *args, **kwargs): super(ExtraTab, self).__init__(*args, **kwargs) self.cmdline_args_label = self.crt_statictext(_("Youtube-dl command line options (e.g. --help)")) self.cmdline_args_textctrl = self.crt_textctrl(wx.TE_MULTILINE | wx.TE_LINEWRAP) self.extra_opts_label = self.crt_statictext(_("Extra options")) self.youtube_dl_debug_checkbox = self.crt_checkbox(_("Debug youtube-dl")) self.ignore_errors_checkbox = self.crt_checkbox(_("Ignore errors")) self.ignore_config_checkbox = self.crt_checkbox(_("Ignore youtube-dl config")) self.no_mtime_checkbox = self.crt_checkbox(_("No mtime")) self.native_hls_checkbox = self.crt_checkbox(_("Prefer native HLS")) self._set_layout() def _set_layout(self): main_sizer = wx.BoxSizer(wx.HORIZONTAL) vertical_sizer = wx.BoxSizer(wx.VERTICAL) vertical_sizer.Add(self.cmdline_args_label) vertical_sizer.Add(self.cmdline_args_textctrl, 1, wx.EXPAND | wx.ALL, border=5) vertical_sizer.Add(self.extra_opts_label, flag=wx.TOP, border=5) extra_opts_sizer = wx.WrapSizer() extra_opts_sizer.Add(self.youtube_dl_debug_checkbox) extra_opts_sizer.AddSpacer((5, -1)) extra_opts_sizer.Add(self.ignore_errors_checkbox) extra_opts_sizer.AddSpacer((5, -1)) extra_opts_sizer.Add(self.ignore_config_checkbox) extra_opts_sizer.AddSpacer((5, -1)) extra_opts_sizer.Add(self.no_mtime_checkbox) extra_opts_sizer.AddSpacer((5, -1)) extra_opts_sizer.Add(self.native_hls_checkbox) vertical_sizer.Add(extra_opts_sizer, flag=wx.ALL, border=5) main_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.ALL, border=5) self.SetSizer(main_sizer) def load_options(self): self.cmdline_args_textctrl.SetValue(self.opt_manager.options["cmd_args"]) self.ignore_errors_checkbox.SetValue(self.opt_manager.options["ignore_errors"]) self.youtube_dl_debug_checkbox.SetValue(self.opt_manager.options["youtube_dl_debug"]) self.ignore_config_checkbox.SetValue(self.opt_manager.options["ignore_config"]) self.native_hls_checkbox.SetValue(self.opt_manager.options["native_hls"]) self.no_mtime_checkbox.SetValue(self.opt_manager.options["nomtime"]) def save_options(self): self.opt_manager.options["cmd_args"] = self.cmdline_args_textctrl.GetValue() self.opt_manager.options["ignore_errors"] = self.ignore_errors_checkbox.GetValue() self.opt_manager.options["youtube_dl_debug"] = self.youtube_dl_debug_checkbox.GetValue() self.opt_manager.options["ignore_config"] = self.ignore_config_checkbox.GetValue() self.opt_manager.options["native_hls"] = self.native_hls_checkbox.GetValue() self.opt_manager.options["nomtime"] = self.no_mtime_checkbox.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. """ # REFACTOR move it on widgets module TITLE = _("Log Viewer") FRAME_SIZE = (750, 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) ================================================ FILE: youtube_dl_gui/optionsmanager.py ================================================ #!/usr/bin/env python2 # -*- coding: utf-8 -*- """Youtubedlg module to handle settings. """ from __future__ import unicode_literals import os import json from .utils import ( os_path_expanduser, os_path_exists, encode_tuple, decode_tuple, check_path, get_default_lang ) from .formats import ( OUTPUT_FORMATS, FORMATS ) 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", "opus" & "flac". 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 (int): This option sets the downloaded file output template. See formats.OUTPUT_FORMATS for more info. 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. save_path_dirs (list): List that contains temporary save paths. selected_video_formats (list): List that contains the selected video formats to display on the main window. selected_audio_formats (list): List that contains the selected audio formats to display on the main window. selected_format (string): Current format selected on the main window. youtube_dl_debug (boolean): When True will pass '-v' flag to youtube-dl. ignore_config (boolean): When True will ignore youtube-dl config file options. confirm_exit (boolean): When True create popup to confirm exiting youtube-dl-gui. native_hls (boolean): When True youtube-dl will use the native HLS implementation. show_completion_popup (boolean): When True youtube-dl-gui will create a popup to inform the user for the download completion. confirm_deletion (boolean): When True ask user before item removal. nomtime (boolean): When True will not use the Last-modified header to set the file modification time. embed_thumbnail (boolean): When True will embed the thumbnail in the audio file as cover art. add_metadata (boolean): When True will write metadata to file. disable_update (boolean): When True the update process will be disabled. """ #REFACTOR Remove old options & check options validation self.options = { 'save_path': os_path_expanduser('~'), 'save_path_dirs': [ os_path_expanduser('~'), os.path.join(os_path_expanduser('~'), "Downloads"), os.path.join(os_path_expanduser('~'), "Desktop"), os.path.join(os_path_expanduser('~'), "Videos"), os.path.join(os_path_expanduser('~'), "Music"), ], 'video_format': '0', 'second_video_format': '0', 'to_audio': False, 'keep_video': False, 'audio_format': '', 'audio_quality': '5', 'restrict_filenames': False, 'output_format': 1, 'output_template': os.path.join('%(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': False, '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': True, 'workers_number': 3, 'locale_name': get_default_lang(), 'main_win_size': (740, 490), 'opts_win_size': (640, 490), 'selected_video_formats': ['webm', 'mp4'], 'selected_audio_formats': ['mp3', 'm4a', 'vorbis'], 'selected_format': '0', 'youtube_dl_debug': False, 'ignore_config': True, 'confirm_exit': True, 'native_hls': True, 'show_completion_popup': True, 'confirm_deletion': True, 'nomtime': False, 'embed_thumbnail': False, 'add_metadata': False, 'disable_update': False } # Set the youtubedl_path again if the disable_update option is set new_path = '/usr/bin' if self.options['disable_update'] and os.name != 'nt' and os_path_exists(new_path): self.options['youtubedl_path'] = new_path 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 (dict): 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', 'opus', 'flac', '') VALID_AUDIO_QUALITY = ('0', '5', '9') VALID_FILESIZE_UNIT = ('', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y') VALID_SUB_LANGUAGE = ('en', 'el', 'pt', 'fr', 'it', 'ru', 'es', 'de', 'he', 'sv', 'tr') 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': FORMATS.keys(), 'second_video_format': VALID_VIDEO_FORMAT, 'audio_format': VALID_AUDIO_FORMAT, 'audio_quality': VALID_AUDIO_QUALITY, 'output_format': OUTPUT_FORMATS.keys(), '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 ================================================ FILE: youtube_dl_gui/parsers.py ================================================ #!/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 (dict): 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', ''), OptionHolder('video_format', '-f', '0'), OptionHolder('subs_lang', '--sub-lang', '', ['write_subs']), OptionHolder('audio_quality', '--audio-quality', '5', ['to_audio']), OptionHolder('youtube_dl_debug', '-v', False), OptionHolder('ignore_config', '--ignore-config', False), OptionHolder('native_hls', '--hls-prefer-native', False), OptionHolder('nomtime', '--no-mtime', False), OptionHolder('embed_thumbnail', '--embed-thumbnail', False), OptionHolder('add_metadata', '--add-metadata', False) ] def parse(self, options_dictionary): """Parse optionsmanager.OptionsManager options. Parses the given options to youtube-dl command line arguments. Args: options_dictionary (dict): Dictionary with all the options. Returns: List of strings with all the youtube-dl command line options. """ # REFACTOR 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: #NOTE Special case should be removed if option.name == "to_audio": if options_dict["audio_format"] == "": value = options_dict[option.name] if value != option.default_value: options_list.append(option.flag) elif option.name == "audio_format": value = options_dict[option.name] if value != option.default_value: options_list.append("-x") options_list.append(option.flag) options_list.append(to_string(value)) #NOTE Temp fix # If current 'audio_quality' is not the default one ('5') # then append the audio quality flag and value to the # options list if options_dict["audio_quality"] != "5": options_list.append("--audio-quality") options_list.append(to_string(options_dict["audio_quality"])) elif option.name == "audio_quality": # If the '--audio-quality' is not already in the options list # from the above branch then follow the standard procedure. # We don't have to worry for the sequence in which the code # will be executed since the 'audio_quality' option is placed # after the 'audio_format' option in the self._ydl_options list if option.flag not in options_list: if option.check_requirements(options_dict): value = options_dict[option.name] if value != option.default_value: options_list.append(option.flag) options_list.append(to_string(value)) elif 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 # Indicates whether an item needs special handling special_case = False # Temp list to hold special items special_items = [] for item in options_dict["cmd_args"].split(): # Its a special case if its already a special case # or an item starts with double quotes special_case = (special_case or item[0] == "\"") if special_case: special_items.append(item) else: options_list.append(item) # If its a special case and we meet a double quote # at the end of the item, special case is over and # we need to join, filter and append our special items # to the options list if special_case and item[-1] == "\"": options_list.append(" ".join(special_items)[1:-1]) special_case = False special_items = [] 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 (dict): Copy of the original options dictionary. """ save_path = remove_shortcuts(options_dict['save_path']) if options_dict["output_format"] == 0: template = "%(id)s.%(ext)s" elif options_dict["output_format"] == 1: template = "%(title)s.%(ext)s" elif options_dict["output_format"] == 2: template = "%(title)s-%(id)s.%(ext)s" elif options_dict["output_format"] == 4: template = "%(title)s-%(height)sp.%(ext)s" elif options_dict["output_format"] == 5: template = "%(title)s-%(id)s-%(height)sp.%(ext)s" else: template = options_dict["output_template"] options_dict["save_path"] = os.path.join(save_path, template) 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 (dict): 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 (dict): 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'] ================================================ FILE: youtube_dl_gui/updatemanager.py ================================================ #!/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 = 10 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)) ================================================ FILE: youtube_dl_gui/utils.py ================================================ #!/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 json import math import locale import subprocess try: from twodict import TwoWayOrderedDict except ImportError as error: print error sys.exit(1) from .info import __appname__ from .version import __version__ _RANDOM_OBJECT = object() YOUTUBEDL_BIN = 'youtube-dl' if os.name == 'nt': YOUTUBEDL_BIN += '.exe' FILESIZE_METRICS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] KILO_SIZE = 1024.0 def get_encoding(): """Return system encoding. """ try: encoding = locale.getpreferredencoding() 'TEST'.encode(encoding) except: encoding = 'UTF-8' return encoding def convert_item(item, to_unicode=False): """Convert item between 'unicode' and 'str'. Args: item (-): Can be any python item. to_unicode (boolean): When True it will convert all the 'str' types to 'unicode'. When False it will convert all the 'unicode' types back to 'str'. """ if to_unicode and isinstance(item, str): # Convert str to unicode return item.decode(get_encoding(), 'ignore') if not to_unicode and isinstance(item, unicode): # Convert unicode to str return item.encode(get_encoding(), 'ignore') if hasattr(item, '__iter__'): # Handle iterables temp_list = [] for sub_item in item: if isinstance(item, dict): temp_list.append((convert_item(sub_item, to_unicode), convert_item(item[sub_item], to_unicode))) else: temp_list.append(convert_item(sub_item, to_unicode)) return type(item)(temp_list) return item def convert_on_bounds(func): """Decorator to convert string inputs & outputs. Covert string inputs & outputs between 'str' and 'unicode' at the application bounds using the preferred system encoding. It will convert all the string params (args, kwargs) to 'str' type and all the returned strings values back to 'unicode'. """ def wrapper(*args, **kwargs): returned_value = func(*convert_item(args), **convert_item(kwargs)) return convert_item(returned_value, True) return wrapper # See: https://github.com/MrS0m30n3/youtube-dl-gui/issues/57 # Patch os functions to convert between 'str' and 'unicode' on app bounds os_sep = unicode(os.sep) os_getenv = convert_on_bounds(os.getenv) os_makedirs = convert_on_bounds(os.makedirs) os_path_isdir = convert_on_bounds(os.path.isdir) os_path_exists = convert_on_bounds(os.path.exists) os_path_dirname = convert_on_bounds(os.path.dirname) os_path_abspath = convert_on_bounds(os.path.abspath) os_path_realpath = convert_on_bounds(os.path.realpath) os_path_expanduser = convert_on_bounds(os.path.expanduser) # Patch locale functions locale_getdefaultlocale = convert_on_bounds(locale.getdefaultlocale) # Patch Windows specific functions if os.name == 'nt': os_startfile = convert_on_bounds(os.startfile) def remove_file(filename): if os_path_exists(filename): os.remove(filename) return True return False def remove_shortcuts(path): """Return given path after removing the shortcuts. """ return path.replace('~', os_path_expanduser('~')) def absolute_path(filename): """Return absolute path to the given file. """ return os_path_dirname(os_path_realpath(os_path_abspath(filename))) def open_file(file_path): """Open file in file_path using the default OS application. Returns: True on success else False. """ file_path = remove_shortcuts(file_path) if not os_path_exists(file_path): return False if os.name == "nt": os_startfile(file_path) else: subprocess.call(("xdg-open", file_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_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 """ DIR_NAME = "locale" SEARCH_DIRS = [ os.path.join(absolute_path(sys.argv[0]), DIR_NAME), os.path.join(os_path_dirname(__file__), 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. """ ICON_NAME = "youtube-dl-gui.png" pixmaps_dir = get_pixmaps_dir() if pixmaps_dir is not None: icon_file = os.path.join(pixmaps_dir, ICON_NAME) if os_path_exists(icon_file): return icon_file return None def get_pixmaps_dir(): """Return absolute path to the pixmaps icons folder. Note: Paths we search: __main__ dir, library dir """ search_dirs = [ os.path.join(absolute_path(sys.argv[0]), "data"), os.path.join(os_path_dirname(__file__), "data") ] for directory in search_dirs: pixmaps_dir = os.path.join(directory, "pixmaps") if os_path_exists(pixmaps_dir): return pixmaps_dir return None def to_bytes(string): """Convert given youtube-dl size string to bytes.""" value = 0.0 for index, metric in enumerate(reversed(FILESIZE_METRICS)): if metric in string: value = float(string.split(metric)[0]) break exponent = index * (-1) + (len(FILESIZE_METRICS) - 1) return round(value * (KILO_SIZE ** exponent), 2) def format_bytes(bytes): """Format bytes to youtube-dl size output strings.""" if bytes == 0.0: exponent = 0 else: exponent = int(math.log(bytes, KILO_SIZE)) suffix = FILESIZE_METRICS[exponent] output_value = bytes / (KILO_SIZE ** exponent) return "%.2f%s" % (output_value, suffix) def build_command(options_list, url): """Build the youtube-dl command line string.""" def escape(option): """Wrap option with double quotes if it contains special symbols.""" special_symbols = [" ", "(", ")"] for symbol in special_symbols: if symbol in option: return "\"{}\"".format(option) return option # If option has special symbols wrap it with double quotes # Probably not the best solution since if the option already contains # double quotes it will be a mess, see issue #173 options = [escape(option) for option in options_list] # Always wrap the url with double quotes url = "\"{}\"".format(url) return " ".join([YOUTUBEDL_BIN] + options + [url]) def get_default_lang(): """Get default language using the 'locale' module.""" default_lang, _ = locale_getdefaultlocale() if not default_lang: default_lang = "en_US" return default_lang ================================================ FILE: youtube_dl_gui/version.py ================================================ # -*- coding: utf-8 -*- from __future__ import unicode_literals __version__ = '0.4' ================================================ FILE: youtube_dl_gui/widgets.py ================================================ #!/usr/bin/env python # -*- coding: UTF-8 -*- from __future__ import unicode_literals import sys try: import wx except ImportError as error: print error sys.exit(1) def crt_command_event(event_type, event_id=0): """Shortcut to create command events.""" return wx.CommandEvent(event_type.typeId, event_id) class ListBoxWithHeaders(wx.ListBox): """Custom ListBox object that supports 'headers'. Attributes: NAME (string): Default name for the name argument of the __init__. TEXT_PREFIX (string): Text to add before normal items in order to distinguish them (normal items) from headers. EVENTS (list): List with events to overwrite to avoid header selection. """ NAME = "listBoxWithHeaders" TEXT_PREFIX = " " EVENTS = [ wx.EVT_LEFT_DOWN, wx.EVT_LEFT_DCLICK, wx.EVT_RIGHT_DOWN, wx.EVT_RIGHT_DCLICK, wx.EVT_MIDDLE_DOWN, wx.EVT_MIDDLE_DCLICK ] def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, choices=[], style=0, validator=wx.DefaultValidator, name=NAME): super(ListBoxWithHeaders, self).__init__(parent, id, pos, size, [], style, validator, name) self.__headers = set() # Ignore all key events i'm bored to handle the header selection self.Bind(wx.EVT_KEY_DOWN, lambda event: None) # Make sure that a header is never selected self.Bind(wx.EVT_LISTBOX, self._on_listbox) for event in self.EVENTS: self.Bind(event, self._disable_header_selection) # Append the items in our own way in order to add the TEXT_PREFIX self.AppendItems(choices) def _disable_header_selection(self, event): """Stop event propagation if the selected item is a header.""" row = self.HitTest(event.GetPosition()) event_skip = True if row != wx.NOT_FOUND and self.GetString(row) in self.__headers: event_skip = False event.Skip(event_skip) def _on_listbox(self, event): """Make sure no header is selected.""" if event.GetString() in self.__headers: self.Deselect(event.GetSelection()) event.Skip() def _add_prefix(self, string): return self.TEXT_PREFIX + string def _remove_prefix(self, string): if string[:len(self.TEXT_PREFIX)] == self.TEXT_PREFIX: return string[len(self.TEXT_PREFIX):] return string # wx.ListBox methods def FindString(self, string): index = super(ListBoxWithHeaders, self).FindString(string) if index == wx.NOT_FOUND: # This time try with prefix index = super(ListBoxWithHeaders, self).FindString(self._add_prefix(string)) return index def GetStringSelection(self): return self._remove_prefix(super(ListBoxWithHeaders, self).GetStringSelection()) def GetString(self, index): if index < 0 or index >= self.GetCount(): # Return empty string based on the wx.ListBox docs # for some reason parent GetString does not handle # invalid indices return "" return self._remove_prefix(super(ListBoxWithHeaders, self).GetString(index)) def InsertItems(self, items, pos): items = [self._add_prefix(item) for item in items] super(ListBoxWithHeaders, self).InsertItems(items, pos) def SetSelection(self, index): if index == wx.NOT_FOUND: self.Deselect(self.GetSelection()) elif self.GetString(index) not in self.__headers: super(ListBoxWithHeaders, self).SetSelection(index) def SetString(self, index, string): old_string = self.GetString(index) if old_string in self.__headers and string != old_string: self.__headers.remove(old_string) self.__headers.add(string) super(ListBoxWithHeaders, self).SetString(index, string) def SetStringSelection(self, string): if string in self.__headers: return False self.SetSelection(self.FindString(string)) return True # wx.ItemContainer methods def Append(self, string): super(ListBoxWithHeaders, self).Append(self._add_prefix(string)) def AppendItems(self, strings): strings = [self._add_prefix(string) for string in strings] super(ListBoxWithHeaders, self).AppendItems(strings) def Clear(self): self.__headers.clear() super(ListBoxWithHeaders, self).Clear() def Delete(self, index): string = self.GetString(index) if string in self.__headers: self.__headers.remove(string) super(ListBoxWithHeaders, self).Delete(index) # Extra methods def add_header(self, header_string): self.__headers.add(header_string) super(ListBoxWithHeaders, self).Append(header_string) def add_item(self, item, with_prefix=True): if with_prefix: item = self._add_prefix(item) super(ListBoxWithHeaders, self).Append(item) def add_items(self, items, with_prefix=True): if with_prefix: items = [self._add_prefix(item) for item in items] super(ListBoxWithHeaders, self).AppendItems(items) class ListBoxPopup(wx.PopupTransientWindow): """ListBoxWithHeaders as a popup. This class uses the wx.PopupTransientWindow to create the popup and the API is based on the wx.combo.ComboPopup class. Attributes: EVENTS_TABLE (dict): Dictionary that contains all the events that this class emits. """ EVENTS_TABLE = { "EVT_COMBOBOX": crt_command_event(wx.EVT_COMBOBOX), "EVT_COMBOBOX_DROPDOWN" : crt_command_event(wx.EVT_COMBOBOX_DROPDOWN), "EVT_COMBOBOX_CLOSEUP": crt_command_event(wx.EVT_COMBOBOX_CLOSEUP) } def __init__(self, parent=None, flags=wx.BORDER_NONE): super(ListBoxPopup, self).__init__(parent, flags) self.__listbox = None def _on_motion(self, event): row = self.__listbox.HitTest(event.GetPosition()) if row != wx.NOT_FOUND: self.__listbox.SetSelection(row) if self.__listbox.IsSelected(row): self.curitem = row def _on_left_down(self, event): self.value = self.curitem self.Dismiss() # Send EVT_COMBOBOX to inform the parent for changes wx.PostEvent(self, self.EVENTS_TABLE["EVT_COMBOBOX"]) def Popup(self): super(ListBoxPopup, self).Popup() wx.PostEvent(self, self.EVENTS_TABLE["EVT_COMBOBOX_DROPDOWN"]) def OnDismiss(self): wx.PostEvent(self, self.EVENTS_TABLE["EVT_COMBOBOX_CLOSEUP"]) # wx.combo.ComboPopup methods def Init(self): self.value = self.curitem = -1 def Create(self, parent): self.__listbox = ListBoxWithHeaders(parent, style=wx.LB_SINGLE) self.__listbox.Bind(wx.EVT_MOTION, self._on_motion) self.__listbox.Bind(wx.EVT_LEFT_DOWN, self._on_left_down) sizer = wx.BoxSizer() sizer.Add(self.__listbox, 1, wx.EXPAND) self.SetSizer(sizer) return True def GetAdjustedSize(self, min_width, pref_height, max_height): width, height = self.GetBestSize() if width < min_width: width = min_width if pref_height != -1: height = pref_height * self.__listbox.GetCount() + 5 if height > max_height: height = max_height return wx.Size(width, height) def GetControl(self): return self.__listbox def GetStringValue(self): return self.__listbox.GetString(self.value) #def SetStringValue(self, string): #self.__listbox.SetStringSelection(string) class CustomComboBox(wx.Panel): """Custom combobox. Attributes: CB_READONLY (long): Read-only style. The only one supported from the wx.ComboBox styles. NAME (string): Default name for the name argument of the __init__. """ #NOTE wx.ComboBox does not support EVT_MOTION inside the popup #NOTE Tried with ComboCtrl but i was not able to draw the button CB_READONLY = wx.TE_READONLY NAME = "customComboBox" def __init__(self, parent, id=wx.ID_ANY, value="", pos=wx.DefaultPosition, size=wx.DefaultSize, choices=[], style=0, validator=wx.DefaultValidator, name=NAME): super(CustomComboBox, self).__init__(parent, id, pos, size, 0, name) assert style == self.CB_READONLY or style == 0 # Create components self.textctrl = wx.TextCtrl(self, wx.ID_ANY, style=style, validator=validator) tc_height = self.textctrl.GetSize()[1] self.button = wx.Button(self, wx.ID_ANY, "▾", size=(tc_height, tc_height)) # Create the ListBoxPopup in two steps self.listbox = ListBoxPopup(self) self.listbox.Init() self.listbox.Create(self.listbox) # Set layout sizer = wx.BoxSizer() sizer.Add(self.textctrl, 1, wx.ALIGN_CENTER_VERTICAL) sizer.Add(self.button) self.SetSizer(sizer) # Bind events self.button.Bind(wx.EVT_BUTTON, self._on_button) for event in ListBoxPopup.EVENTS_TABLE.values(): self.listbox.Bind(wx.PyEventBinder(event.GetEventType()), self._propagate) # Append items since the ListBoxPopup does not have the 'choices' arg self.listbox.GetControl().AppendItems(choices) self.SetStringSelection(value) def _propagate(self, event): if event.GetEventType() == wx.EVT_COMBOBOX.typeId: self.textctrl.SetValue(self.listbox.GetStringValue()) wx.PostEvent(self, event) def _on_button(self, event): self.Popup() def _calc_popup_position(self): tc_x_axis, tc_y_axis = self.textctrl.ClientToScreen((0, 0)) _, tc_height = self.textctrl.GetSize() return tc_x_axis, tc_y_axis + tc_height def _calc_popup_size(self): me_width, _ = self.GetSize() _, tc_height = self.textctrl.GetSize() _, screen_height = wx.DisplaySize() _, me_y_axis = self.GetScreenPosition() available_height = screen_height - (me_y_axis + tc_height) sug_width, sug_height = self.listbox.GetAdjustedSize(me_width, tc_height, available_height) return me_width, sug_height # wx.ComboBox methods def Dismiss(self): self.listbox.Dismiss() def FindString(self, string, caseSensitive=False): #TODO handle caseSensitive return self.listbox.GetControl().FindString(string) def GetCount(self): return self.listbox.GetControl().GetCount() def GetCurrentSelection(self): return self.GetSelection() def GetInsertionPoint(self): return self.textctrl.GetInsertionPoint() def GetSelection(self): return self.listbox.value def GetTextSelection(self): return self.textctrl.GetSelection() def GetString(self, index): return self.listbox.GetControl().GetString(index) def GetStringSelection(self): return self.listbox.GetStringValue() def IsListEmpty(self): return self.listbox.GetControl().GetCount() == 0 def IsTextEmpty(self): return not self.textctrl.GetValue() def Popup(self): self.listbox.SetPosition(self._calc_popup_position()) self.listbox.SetSize(self._calc_popup_size()) self.listbox.Popup() def SetSelection(self, index): self.listbox.GetControl().SetSelection(index) if self.listbox.GetControl().IsSelected(index): self.listbox.value = index self.textctrl.SetValue(self.listbox.GetStringValue()) def SetString(self, index, string): self.listbox.GetControl().SetString(index, string) def SetTextSelection(self, from_, to_): self.textctrl.SetSelection(from_, to_) def SetStringSelection(self, string): index = self.listbox.GetControl().FindString(string) self.listbox.GetControl().SetSelection(index) if index != wx.NOT_FOUND and self.listbox.GetControl().GetSelection() == index: self.listbox.value = index self.textctrl.SetValue(string) def SetValue(self, value): self.textctrl.SetValue(value) # wx.ItemContainer methods def Clear(self): self.textctrl.Clear() self.listbox.GetControl().Clear() def Append(self, item): self.listbox.GetControl().Append(item) def AppendItems(self, items): self.listbox.GetControl().AppendItems(items) def Delete(self, index): self.listbox.GetControl().Delete(index) # wx.TextEntry methods def GetValue(self): return self.textctrl.GetValue() # ListBoxWithHeaders methods def add_header(self, header): self.listbox.GetControl().add_header(header) def add_item(self, item, with_prefix=True): self.listbox.GetControl().add_item(item, with_prefix) def add_items(self, items, with_prefix=True): self.listbox.GetControl().add_items(items, with_prefix)