Showing preview only (438K chars total). Download the full file or copy to clipboard to get everything.
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 ?
---
<!--Enter description of your issue, suggested solution and other information below. Please make sure the description is worded well enough to be understood-->
================================================
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/<lang>/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 <ytubedlg@gmail.com>
Henrique Pereira <ikkibr@gmail.com>
Fironet <dolphinssmile@hotmail.com>
Max Bruckner <max@maxbruckner.de>
Sergey M․ <dstftw@gmail.com>
Marcin Nowicki <pr0d1r2@gmail.com>
dnlsrl <dnlsrl.kaiser@gmail.com>
David Wales <daviewales@gmail.com>
Nikita Bystrov <arttsesoft@gmail.com>
nodiscc <nodiscc@gmail.com>
Leo Wzukw <leowzukw@users.noreply.github.com>
todool <tobie.doolaard@gmail.com>
Yousuf 'Jay' Philips <me@philipz.com>
memnoth <yisooan@gmail.com>
Peter Stevenson <2e0pgs@gmail.com>
Swyter <swyterzone@gmail.com>
Sebastian Wagner <sebix@sebix.at>
saad snosi <fixall.dz@gmail.com>
cleitonme <cleitonme@gmail.com>
# 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 <http://unlicense.org/>
================================================
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
================================================
[](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

## 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 <ytubedlg@gmail.com>
# Last-Revision: 2017-04-17
# Script to bump the version and automatically update related files
#
# Usage: ./bump_version.sh <new-version>
PACKAGE="youtube_dl_gui"
FILES=`cat <<EOF
$PACKAGE/version.py
.github/ISSUE_TEMPLATE.md
README.md
youtube-dl-gui.1
EOF`
# Update version string on given file
# $1 = current version
# $2 = new version
# $3 = file
function update_version {
echo "Updating file: $3"
sed -i "s/$1/$2/g" $3
}
# Returns 'true' if given version is less or equal to the current version
# $1 = version to check
# $2 = current version
function version_le {
smallest_version=`echo -e "$1\n$2" | sort -V | head -n1`
[ "$1" = "$smallest_version" ]
}
if [ $# -ne 1 ]; then
echo "Usage ./bump_version.sh <new-version>"
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 <ytubedlg@gmail.com>
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 <ytubedlg@gmail.com>
Last-Revision: 2017-01-30
Script to add support for a new language
Usage : ./new-locale.py <language_code>
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} <language_code>".format(sys.argv[0]))
print("Example : {0} en_US".format(sys.argv[0]))
================================================
FILE: devscripts/update-authors.sh
================================================
#!/bin/bash
# Author: Sotiris Papadopoulos <ytubedlg@gmail.com>
# 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 <ytubedlg@gmail.com>
# 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 `<ISO 639-1>_<ISO 3166-1 alpha-2>` (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/<LANG_CODE>/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 <LANGUAGE_CODE_HERE>`
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 <LANGUAGE_CODE_HERE>`
#### 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 <LANGUAGE_CODE_HERE>`
* 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 <LANGUAGE_CODE_HERE>`
### 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 <package director>
$ 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(<URL STRING>, ['-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': <item_row>, data_to_send*})
('receive', {'index': <item_row>, '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 <http://unlicense.org/>'''
================================================
FILE: youtube_dl_gui/locale/ar_SA/LC_MESSAGES/youtube_dl_gui.po
================================================
# Youtube-dlG localization file.
# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.
# snosi <fixall.dz@gmail.com>, 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 <fixall.dz@gmail.com>\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 <ytubedlg@gmail.com>, 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 <pavel-rehak@email.cz>\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 <ytubedlg@gmail.com>, 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 <ytubedlg@gmail.com>\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 <ytubedlg@gmail.com>, 2015.
# Ismael Ferreras Morezuelas <swyterzone+youtubedlgui@gmail.com>, 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 <swyterzone+youtubedlgui@gmail.com>\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
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
SYMBOL INDEX (546 symbols across 20 files)
FILE: devscripts/check-translation.py
function parse (line 42) | def parse():
class UTC_Offset_Timezone (line 56) | class UTC_Offset_Timezone(tzinfo):
method __init__ (line 60) | def __init__(self, offset_string):
method utcoffset (line 63) | def utcoffset(self, dt):
method dst (line 66) | def dst(self, dt):
method parse_offset (line 70) | def parse_offset(offset_string):
function parse_date (line 87) | def parse_date(date_string):
function my_print (line 115) | def my_print(msg, char="*", value=None, exit=False):
function perror (line 125) | def perror(msg, value=None):
function pwarn (line 128) | def pwarn(msg, value=None, exit=False):
function pinfo (line 131) | def pinfo(msg):
function main (line 137) | def main(args):
FILE: devscripts/new-locale.py
function error (line 26) | def error(msg):
function output (line 31) | def output(msg):
function manage_directory (line 35) | def manage_directory():
function main (line 41) | def main(lang_code):
FILE: setup.py
function on_windows (line 89) | def on_windows():
class BuildBin (line 94) | class BuildBin(cmd.Command):
method initialize_options (line 99) | def initialize_options(self):
method finalize_options (line 102) | def finalize_options(self):
method run (line 105) | def run(self):
class BuildTranslations (line 113) | class BuildTranslations(cmd.Command):
method initialize_options (line 118) | def initialize_options(self):
method finalize_options (line 122) | def finalize_options(self):
method run (line 130) | def run(self):
class Build (line 142) | class Build(build):
method initialize_options (line 153) | def initialize_options(self):
method run (line 157) | def run(self):
method __disable_updates (line 163) | def __disable_updates(self):
function linux_setup (line 196) | def linux_setup():
function windows_setup (line 238) | def windows_setup():
FILE: tests/test_ditem.py
class TestItemInit (line 22) | class TestItemInit(unittest.TestCase):
method test_init (line 26) | def test_init(self):
class TestGetFiles (line 56) | class TestGetFiles(unittest.TestCase):
method setUp (line 60) | def setUp(self):
method test_get_files (line 63) | def test_get_files(self):
method test_get_files_no_data (line 72) | def test_get_files_no_data(self):
class TestItemComparison (line 76) | class TestItemComparison(unittest.TestCase):
method test_equal_true (line 80) | def test_equal_true(self):
method test_equal_false (line 86) | def test_equal_false(self):
class TestSetItemStage (line 98) | class TestSetItemStage(unittest.TestCase):
method setUp (line 102) | def setUp(self):
method test_set_stage_valid (line 105) | def test_set_stage_valid(self):
method test_set_stage_invalid (line 126) | def test_set_stage_invalid(self):
class TestUpdateStats (line 137) | class TestUpdateStats(unittest.TestCase):
method setUp (line 141) | def setUp(self):
method test_update_stats (line 144) | def test_update_stats(self):
method test_update_stats_invalid_input (line 330) | def test_update_stats_invalid_input(self):
method test_update_stats_empty_strings (line 333) | def test_update_stats_empty_strings(self):
method test_update_stats_not_string (line 357) | def test_update_stats_not_string(self):
class TestDownloadItemPrivate (line 365) | class TestDownloadItemPrivate(unittest.TestCase):
method test_set_stage (line 369) | def test_set_stage(self):
method test_calc_post_proc_size (line 388) | def test_calc_post_proc_size(self):
class TestReset (line 435) | class TestReset(unittest.TestCase):
method setUp (line 439) | def setUp(self):
method test_reset_completed_stage (line 442) | def test_reset_completed_stage(self):
method test_reset_error_stage (line 480) | def test_reset_error_stage(self):
method test_reset_paused_stage (line 518) | def test_reset_paused_stage(self):
method test_reset_active_stage (line 526) | def test_reset_active_stage(self):
function main (line 545) | def main():
FILE: tests/test_dlist.py
class TestInit (line 23) | class TestInit(unittest.TestCase):
method test_init (line 27) | def test_init(self):
method test_init_empty (line 34) | def test_init_empty(self):
method test_init_invalid_args (line 39) | def test_init_invalid_args(self):
class TestInsert (line 45) | class TestInsert(unittest.TestCase):
method test_insert (line 49) | def test_insert(self):
class TestRemove (line 59) | class TestRemove(unittest.TestCase):
method setUp (line 63) | def setUp(self):
method test_remove (line 67) | def test_remove(self):
method test_remove_not_exist (line 73) | def test_remove_not_exist(self):
method test_remove_active (line 76) | def test_remove_active(self):
class TestFetchNext (line 84) | class TestFetchNext(unittest.TestCase):
method test_fetch_next (line 88) | def test_fetch_next(self):
method test_fetch_next_empty_list (line 109) | def test_fetch_next_empty_list(self):
class TestMoveUp (line 114) | class TestMoveUp(unittest.TestCase):
method setUp (line 118) | def setUp(self):
method test_move_up (line 122) | def test_move_up(self):
method test_move_up_already_on_top (line 126) | def test_move_up_already_on_top(self):
method test_move_up_not_exist (line 130) | def test_move_up_not_exist(self):
class TestMoveDown (line 134) | class TestMoveDown(unittest.TestCase):
method setUp (line 138) | def setUp(self):
method test_move_down (line 142) | def test_move_down(self):
method test_move_down_already_on_bottom (line 146) | def test_move_down_already_on_bottom(self):
method test_move_down_not_exist (line 150) | def test_move_down_not_exist(self):
class TestGetItem (line 154) | class TestGetItem(unittest.TestCase):
method test_get_item (line 158) | def test_get_item(self):
method test_get_item_not_exist (line 165) | def test_get_item_not_exist(self):
class TestGetLength (line 170) | class TestGetLength(unittest.TestCase):
method test_get_length (line 174) | def test_get_length(self):
method test_get_length_empty_list (line 178) | def test_get_length_empty_list(self):
class TestHasItem (line 183) | class TestHasItem(unittest.TestCase):
method setUp (line 187) | def setUp(self):
method test_has_item_true (line 191) | def test_has_item_true(self):
method test_has_item_false (line 194) | def test_has_item_false(self):
class TestGetItems (line 198) | class TestGetItems(unittest.TestCase):
method test_get_items (line 202) | def test_get_items(self):
method test_get_items_empty_list (line 208) | def test_get_items_empty_list(self):
class TestClear (line 213) | class TestClear(unittest.TestCase):
method test_clear (line 217) | def test_clear(self):
class TestChangeStage (line 225) | class TestChangeStage(unittest.TestCase):
method setUp (line 229) | def setUp(self):
method test_change_stage (line 233) | def test_change_stage(self):
method test_change_stage_id_not_exist (line 237) | def test_change_stage_id_not_exist(self):
class TestIndex (line 241) | class TestIndex(unittest.TestCase):
method setUp (line 245) | def setUp(self):
method test_index (line 249) | def test_index(self):
method test_index_not_exist (line 252) | def test_index_not_exist(self):
class TestSynchronizeDecorator (line 256) | class TestSynchronizeDecorator(unittest.TestCase):
method test_synchronize (line 258) | def test_synchronize(self):
function main (line 271) | def main():
FILE: tests/test_parsers.py
class TestParse (line 22) | class TestParse(unittest.TestCase):
method setUp (line 26) | def setUp(self):
method check_options_parse (line 39) | def check_options_parse(self, expected_options):
method test_parse_to_audio_requirement_bug (line 44) | def test_parse_to_audio_requirement_bug(self):
method test_parse_cmd_args_with_quotes (line 69) | def test_parse_cmd_args_with_quotes(self):
function main (line 136) | def main():
FILE: tests/test_utils.py
class TestToBytes (line 24) | class TestToBytes(unittest.TestCase):
method test_to_bytes_bytes (line 28) | def test_to_bytes_bytes(self):
method test_to_bytes_kilobytes (line 32) | def test_to_bytes_kilobytes(self):
method test_to_bytes_megabytes (line 36) | def test_to_bytes_megabytes(self):
method test_to_bytes_gigabytes (line 40) | def test_to_bytes_gigabytes(self):
method test_to_bytes_terabytes (line 44) | def test_to_bytes_terabytes(self):
class TestFormatBytes (line 48) | class TestFormatBytes(unittest.TestCase):
method test_format_bytes_bytes (line 52) | def test_format_bytes_bytes(self):
method test_format_bytes_kilobytes (line 55) | def test_format_bytes_kilobytes(self):
method test_format_bytes_megabytes (line 58) | def test_format_bytes_megabytes(self):
method test_format_bytes_gigabytes (line 61) | def test_format_bytes_gigabytes(self):
method test_format_bytes_terabytes (line 64) | def test_format_bytes_terabytes(self):
class TestBuildCommand (line 68) | class TestBuildCommand(unittest.TestCase):
method setUp (line 72) | def setUp(self):
method run_tests (line 79) | def run_tests(self, ydl_bin, tmpl):
method test_build_command_with_spaces_linux (line 94) | def test_build_command_with_spaces_linux(self):
method test_build_command_without_spaces_linux (line 99) | def test_build_command_without_spaces_linux(self):
method test_build_command_with_spaces_windows (line 104) | def test_build_command_with_spaces_windows(self):
method test_build_command_without_spaces_windows (line 109) | def test_build_command_without_spaces_windows(self):
class TestConvertItem (line 115) | class TestConvertItem(unittest.TestCase):
method setUp (line 119) | def setUp(self):
method check_iter (line 129) | def check_iter(self, iterable, iter_type, is_unicode):
method test_convert_item_unicode_str (line 142) | def test_convert_item_unicode_str(self):
method test_convert_item_unicode_unicode (line 145) | def test_convert_item_unicode_unicode(self):
method test_convert_item_str_unicode (line 148) | def test_convert_item_str_unicode(self):
method test_convert_item_str_str (line 151) | def test_convert_item_str_str(self):
method test_convert_item_list_empty (line 154) | def test_convert_item_list_empty(self):
method test_convert_item_dict_empty (line 157) | def test_convert_item_dict_empty(self):
method test_convert_item_list_unicode_str (line 160) | def test_convert_item_list_unicode_str(self):
method test_convert_item_list_str_unicode (line 163) | def test_convert_item_list_str_unicode(self):
method test_convert_item_tuple_unicode_str (line 166) | def test_convert_item_tuple_unicode_str(self):
method test_convert_item_tuple_str_unicode (line 169) | def test_convert_item_tuple_str_unicode(self):
method test_convert_item_dict_unicode_str (line 172) | def test_convert_item_dict_unicode_str(self):
method test_convert_item_dict_str_unicode (line 175) | def test_convert_item_dict_str_unicode(self):
class TestGetDefaultLang (line 179) | class TestGetDefaultLang(unittest.TestCase):
method run_tests (line 184) | def run_tests(self, ret_value, result, mock_getdefaultlocale):
method test_get_default_lang (line 198) | def test_get_default_lang(self):
method test_get_default_lang_none (line 201) | def test_get_default_lang_none(self):
method test_get_default_lang_empty (line 204) | def test_get_default_lang_empty(self):
function main (line 208) | def main():
FILE: tests/test_widgets.py
class TestListBoxWithHeaders (line 30) | class TestListBoxWithHeaders(unittest.TestCase):
method setUp (line 34) | def setUp(self):
method tearDown (line 43) | def tearDown(self):
method test_find_string_header_found (line 46) | def test_find_string_header_found(self):
method test_find_string_header_not_found (line 49) | def test_find_string_header_not_found(self):
method test_find_string_item_found (line 52) | def test_find_string_item_found(self):
method test_find_string_item_not_found (line 55) | def test_find_string_item_not_found(self):
method test_get_string_header (line 58) | def test_get_string_header(self):
method test_get_string_item (line 61) | def test_get_string_item(self):
method test_get_string_item_not_found (line 64) | def test_get_string_item_not_found(self):
method test_get_string_item_negative_index (line 67) | def test_get_string_item_negative_index(self):
method test_insert_items (line 70) | def test_insert_items(self):
method test_set_selection_header (line 80) | def test_set_selection_header(self):
method test_set_selection_item_valid_index (line 84) | def test_set_selection_item_valid_index(self):
method test_set_selection_item_invalid_index (line 88) | def test_set_selection_item_invalid_index(self):
method test_set_string_item (line 95) | def test_set_string_item(self):
method test_set_string_header (line 99) | def test_set_string_header(self):
method test_set_string_selection_header (line 107) | def test_set_string_selection_header(self):
method test_set_string_selection_item (line 111) | def test_set_string_selection_item(self):
method test_get_string_selection (line 115) | def test_get_string_selection(self):
method test_get_string_selection_empty (line 119) | def test_get_string_selection_empty(self):
method test_append (line 124) | def test_append(self):
method test_append_items (line 128) | def test_append_items(self):
method test_clear (line 133) | def test_clear(self):
method test_delete (line 137) | def test_delete(self):
method test_add_header (line 147) | def test_add_header(self):
method test_add_item_with_prefix (line 153) | def test_add_item_with_prefix(self, mock_append):
method test_add_item_without_prefix (line 158) | def test_add_item_without_prefix(self, mock_append):
method test_add_items_with_prefix (line 163) | def test_add_items_with_prefix(self, mock_append):
method test_add_items_without_prefix (line 168) | def test_add_items_without_prefix(self, mock_append):
class TestCustomComboBox (line 173) | class TestCustomComboBox(unittest.TestCase):
method setUp (line 177) | def setUp(self):
method tearDown (line 187) | def tearDown(self):
method test_init (line 190) | def test_init(self):
method test_is_list_empty_false (line 201) | def test_is_list_empty_false(self):
method test_is_list_empty_true (line 204) | def test_is_list_empty_true(self):
method test_is_text_empty_false (line 208) | def test_is_text_empty_false(self):
method test_is_text_empty_true (line 212) | def test_is_text_empty_true(self):
method test_set_selection_item (line 215) | def test_set_selection_item(self):
method test_set_selection_header (line 220) | def test_set_selection_header(self):
method test_set_string_selection_item (line 225) | def test_set_string_selection_item(self):
method test_set_string_selection_header (line 230) | def test_set_string_selection_header(self):
method test_set_string_selection_invalid_string (line 235) | def test_set_string_selection_invalid_string(self):
method test_clear (line 242) | def test_clear(self):
method test_append (line 249) | def test_append(self):
method test_append_items (line 253) | def test_append_items(self):
method test_delete (line 257) | def test_delete(self):
method test_get_value (line 263) | def test_get_value(self):
function main (line 268) | def main():
FILE: youtube_dl_gui/__init__.py
function main (line 82) | def main():
FILE: youtube_dl_gui/downloaders.py
class PipeReader (line 27) | class PipeReader(Thread):
method __init__ (line 47) | def __init__(self, queue):
method run (line 55) | def run(self):
method attach_filedescriptor (line 74) | def attach_filedescriptor(self, filedesc):
method join (line 78) | def join(self, timeout=None):
class YoutubeDLDownloader (line 83) | class YoutubeDLDownloader(object):
method __init__ (line 129) | def __init__(self, youtubedl_path, data_hook=None, log_data=None):
method download (line 140) | def download(self, url, options):
method stop (line 207) | def stop(self):
method close (line 226) | def close(self):
method _set_returncode (line 230) | def _set_returncode(self, code):
method _is_warning (line 236) | def _is_warning(self, stderr):
method _last_data_hook (line 239) | def _last_data_hook(self):
method _extract_info (line 264) | def _extract_info(self, data):
method _log (line 286) | def _log(self, data):
method _hook_data (line 291) | def _hook_data(self, data):
method _proc_is_alive (line 296) | def _proc_is_alive(self):
method _get_cmd (line 303) | def _get_cmd(self, url, options):
method _create_process (line 321) | def _create_process(self, cmd):
function extract_data (line 358) | def extract_data(stdout):
FILE: youtube_dl_gui/downloadmanager.py
function synchronized (line 56) | def synchronized(lock):
class DownloadItem (line 67) | class DownloadItem(object):
method __init__ (line 95) | def __init__(self, url, options):
method stage (line 103) | def stage(self):
method stage (line 107) | def stage(self, value):
method reset (line 124) | def reset(self):
method get_files (line 151) | def get_files(self):
method update_stats (line 161) | def update_stats(self, stats_dict):
method _set_stage (line 212) | def _set_stage(self, status):
method __eq__ (line 222) | def __eq__(self, other):
class DownloadList (line 227) | class DownloadList(object):
method __init__ (line 236) | def __init__(self, items=None):
method clear (line 247) | def clear(self):
method insert (line 253) | def insert(self, item):
method remove (line 259) | def remove(self, object_id):
method fetch_next (line 277) | def fetch_next(self):
method move_up (line 293) | def move_up(self, object_id):
method move_down (line 304) | def move_down(self, object_id):
method get_item (line 315) | def get_item(self, object_id):
method has_item (line 320) | def has_item(self, object_id):
method get_items (line 325) | def get_items(self):
method change_stage (line 330) | def change_stage(self, object_id, new_stage):
method index (line 335) | def index(self, object_id):
method __len__ (line 342) | def __len__(self):
method _swap (line 345) | def _swap(self, index1, index2):
class DownloadManager (line 349) | class DownloadManager(Thread):
method __init__ (line 369) | def __init__(self, parent, download_list, opt_manager, log_manager=None):
method successful (line 388) | def successful(self):
method time_it_took (line 393) | def time_it_took(self):
method run (line 398) | def run(self):
method active (line 434) | def active(self):
method stop_downloads (line 450) | def stop_downloads(self):
method add_url (line 462) | def add_url(self, url):
method send_to_worker (line 474) | def send_to_worker(self, data):
method _talk_to_gui (line 489) | def _talk_to_gui(self, data):
method _check_youtubedl (line 507) | def _check_youtubedl(self):
method _get_worker (line 514) | def _get_worker(self):
method _jobs_done (line 521) | def _jobs_done(self):
method _youtubedl_path (line 529) | def _youtubedl_path(self):
class Worker (line 536) | class Worker(Thread):
method __init__ (line 564) | def __init__(self, opt_manager, youtubedl, log_manager=None, log_lock=...
method run (line 596) | def run(self):
method download (line 621) | def download(self, url, options, object_id):
method stop_download (line 635) | def stop_download(self):
method close (line 639) | def close(self):
method available (line 644) | def available(self):
method has_index (line 648) | def has_index(self, index):
method update_data (line 652) | def update_data(self, data):
method successful (line 662) | def successful(self):
method _reset (line 666) | def _reset(self):
method _log_data (line 671) | def _log_data(self, data):
method _data_hook (line 686) | def _data_hook(self, data):
method _talk_to_gui (line 721) | def _talk_to_gui(self, signal, data):
FILE: youtube_dl_gui/formats.py
function reload_strings (line 87) | def reload_strings():
FILE: youtube_dl_gui/logmanager.py
class LogManager (line 18) | class LogManager(object):
method __init__ (line 41) | def __init__(self, config_path, add_time=False):
method log_size (line 49) | def log_size(self):
method clear (line 56) | def clear(self):
method log (line 60) | def log(self, data):
method _write (line 70) | def _write(self, data, mode):
method _init_log (line 90) | def _init_log(self):
method _auto_clear_log (line 95) | def _auto_clear_log(self):
FILE: youtube_dl_gui/mainframe.py
class MainFrame (line 67) | class MainFrame(wx.Frame):
method __init__ (line 166) | def __init__(self, opt_manager, log_manager, parent=None):
method _create_menu_item (line 323) | def _create_menu_item(self, items):
method _on_statuslist_right_click (line 334) | def _on_statuslist_right_click(self, event):
method _on_reenter (line 343) | def _on_reenter(self, event):
method reset (line 363) | def reset(self):
method _on_open_dest (line 368) | def _on_open_dest(self, event):
method _on_open_path (line 378) | def _on_open_path(self, event):
method _on_geturl (line 381) | def _on_geturl(self, event):
method _on_getcmd (line 397) | def _on_getcmd(self, event):
method _on_timer (line 413) | def _on_timer(self, event):
method _update_pause_button (line 445) | def _update_pause_button(self, event):
method _update_videoformat_combobox (line 466) | def _update_videoformat_combobox(self):
method _update_videoformat (line 496) | def _update_videoformat(self, event):
method _update_savepath (line 509) | def _update_savepath(self, event):
method _on_delete (line 512) | def _on_delete(self, event):
method _on_play (line 566) | def _on_play(self, event):
method _on_arrow_up (line 581) | def _on_arrow_up(self, event):
method _on_arrow_down (line 600) | def _on_arrow_down(self, event):
method _on_reload (line 619) | def _on_reload(self, event):
method _on_pause (line 644) | def _on_pause(self, event):
method _on_start (line 665) | def _on_start(self, event):
method _on_savepath (line 676) | def _on_savepath(self, event):
method _on_add (line 688) | def _on_add(self, event):
method _on_settings (line 708) | def _on_settings(self, event):
method _on_viewlog (line 714) | def _on_viewlog(self, event):
method _on_about (line 724) | def _on_about(self, event):
method _set_publisher (line 739) | def _set_publisher(self, handler, topic):
method _create_statictext (line 753) | def _create_statictext(self, label):
method _create_bitmap_button (line 756) | def _create_bitmap_button(self, icon, size=(-1, -1), handler=None):
method _create_static_bitmap (line 764) | def _create_static_bitmap(self, icon, event_handler=None):
method _create_textctrl (line 772) | def _create_textctrl(self, style=None, event_handler=None):
method _create_popup (line 794) | def _create_popup(self, text, title, style):
method _set_layout (line 797) | def _set_layout(self):
method _update_youtubedl (line 846) | def _update_youtubedl(self):
method _status_bar_write (line 859) | def _status_bar_write(self, msg):
method _reset_widgets (line 863) | def _reset_widgets(self):
method _print_stats (line 869) | def _print_stats(self):
method _after_download (line 882) | def _after_download(self):
method _download_worker_handler (line 907) | def _download_worker_handler(self, msg):
method _download_manager_handler (line 924) | def _download_manager_handler(self, msg):
method _update_handler (line 953) | def _update_handler(self, msg):
method _get_urls (line 974) | def _get_urls(self):
method _start_download (line 978) | def _start_download(self):
method _paste_from_clipboard (line 992) | def _paste_from_clipboard(self):
method _on_urllist_edit (line 1014) | def _on_urllist_edit(self, event):
method _on_update (line 1029) | def _on_update(self, event):
method _on_options (line 1046) | def _on_options(self, event):
method _on_close (line 1056) | def _on_close(self, event):
method close (line 1075) | def close(self):
class ListCtrl (line 1095) | class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
method __init__ (line 1104) | def __init__(self, columns, *args, **kwargs):
method remove_row (line 1112) | def remove_row(self, row_number):
method move_item_up (line 1116) | def move_item_up(self, row_number):
method move_item_down (line 1119) | def move_item_down(self, row_number):
method _move_item (line 1122) | def _move_item(self, cur_row, new_row):
method has_url (line 1133) | def has_url(self, url):
method bind_item (line 1142) | def bind_item(self, download_item):
method _update_from_item (line 1151) | def _update_from_item(self, row, download_item):
method clear (line 1167) | def clear(self):
method is_empty (line 1174) | def is_empty(self):
method get_selected (line 1178) | def get_selected(self):
method get_all_selected (line 1181) | def get_all_selected(self):
method deselect_all (line 1184) | def deselect_all(self):
method get_next_selected (line 1188) | def get_next_selected(self, start=-1, reverse=False):
method _set_columns (line 1207) | def _set_columns(self):
class ExtComboBox (line 1225) | class ExtComboBox(wx.ComboBox):
method __init__ (line 1227) | def __init__(self, parent, max_items=-1, *args, **kwargs):
method Append (line 1233) | def Append(self, new_value):
method SetValue (line 1240) | def SetValue(self, new_value):
method LoadMultiple (line 1246) | def LoadMultiple(self, items_list):
class DoubleStageButton (line 1251) | class DoubleStageButton(wx.Button):
method __init__ (line 1253) | def __init__(self, parent, labels, bitmaps, bitmap_pos=wx.TOP, *args, ...
method _set_layout (line 1267) | def _set_layout(self):
method change_stage (line 1273) | def change_stage(self):
method set_stage (line 1277) | def set_stage(self, new_stage):
class ButtonsChoiceDialog (line 1284) | class ButtonsChoiceDialog(wx.Dialog):
method __init__ (line 1293) | def __init__(self, parent, choices, message, *args, **kwargs):
method _on_close (line 1361) | def _on_close(self, event):
class ButtonsGroup (line 1365) | class ButtonsGroup(object):
method __init__ (line 1370) | def __init__(self, buttons_list=None, squared=False):
method set_size (line 1378) | def set_size(self, size):
method create_sizer (line 1403) | def create_sizer(self, orient=wx.HORIZONTAL, space=-1):
method bind_event (line 1414) | def bind_event(self, event, event_handler):
method disable_all (line 1418) | def disable_all(self):
method enable_all (line 1422) | def enable_all(self):
method add (line 1426) | def add(self, button):
class ShutdownDialog (line 1430) | class ShutdownDialog(wx.Dialog):
method __init__ (line 1441) | def __init__(self, parent, timeout, message, *args, **kwargs):
method _get_message (line 1487) | def _get_message(self):
method _on_timer (line 1490) | def _on_timer(self, event):
method Destroy (line 1497) | def Destroy(self):
FILE: youtube_dl_gui/optionsframe.py
class OptionsFrame (line 32) | class OptionsFrame(wx.Frame):
method __init__ (line 45) | def __init__(self, parent):
method _set_layout (line 93) | def _set_layout(self):
method _on_close (line 110) | def _on_close(self, event):
method _on_reset (line 117) | def _on_reset(self, event):
method reset (line 122) | def reset(self):
method load_all_options (line 127) | def load_all_options(self):
method save_all_options (line 132) | def save_all_options(self):
method Show (line 137) | def Show(self, *args, **kwargs):
class TabPanel (line 146) | class TabPanel(wx.Panel):
method __init__ (line 173) | def __init__(self, parent, notebook):
method crt_button (line 186) | def crt_button(self, label, event_handler=None):
method crt_checkbox (line 194) | def crt_checkbox(self, label, event_handler=None):
method crt_textctrl (line 202) | def crt_textctrl(self, style=None):
method crt_combobox (line 210) | def crt_combobox(self, choices, size=(-1, -1), event_handler=None):
method crt_bitmap_combobox (line 218) | def crt_bitmap_combobox(self, choices, size=(-1, -1), event_handler=No...
method crt_spinctrl (line 238) | def crt_spinctrl(self, spin_range=(0, 9999)):
method crt_statictext (line 244) | def crt_statictext(self, label):
method crt_staticbox (line 247) | def crt_staticbox(self, label):
method crt_checklistbox (line 250) | def crt_checklistbox(self, choices, style=None):
method crt_listbox (line 258) | def crt_listbox(self, choices, style=None):
class GeneralTab (line 267) | class GeneralTab(TabPanel):
method __init__ (line 307) | def __init__(self, *args, **kwargs):
method _set_layout (line 339) | def _set_layout(self):
method _build_custom_format_menu (line 374) | def _build_custom_format_menu(self):
method _on_template (line 386) | def _on_template(self, event):
method _on_format (line 406) | def _on_format(self, event):
method _on_language (line 413) | def _on_language(self, event):
method _on_filename (line 420) | def _on_filename(self, event):
method _on_shutdown (line 427) | def _on_shutdown(self, event):
method load_options (line 431) | def load_options(self):
method save_options (line 449) | def save_options(self):
class FormatsTab (line 461) | class FormatsTab(TabPanel):
method __init__ (line 465) | def __init__(self, *args, **kwargs):
method _set_layout (line 485) | def _set_layout(self):
method load_options (line 511) | def load_options(self):
method save_options (line 522) | def save_options(self):
class DownloadsTab (line 534) | class DownloadsTab(TabPanel):
method __init__ (line 570) | def __init__(self, *args, **kwargs):
method _set_layout (line 600) | def _set_layout(self):
method _build_playlist_sizer (line 621) | def _build_playlist_sizer(self):
method _build_filesize_sizer (line 640) | def _build_filesize_sizer(self):
method _on_subtitles (line 659) | def _on_subtitles(self, event):
method load_options (line 663) | def load_options(self):
method save_options (line 686) | def save_options(self):
class AdvancedTab (line 716) | class AdvancedTab(TabPanel):
method __init__ (line 720) | def __init__(self, *args, **kwargs):
method _set_layout (line 756) | def _set_layout(self):
method _on_enable_log (line 814) | def _on_enable_log(self, event):
method _on_view (line 821) | def _on_view(self, event):
method _on_clear (line 827) | def _on_clear(self, event):
method load_options (line 832) | def load_options(self):
method save_options (line 842) | def save_options(self):
class ExtraTab (line 853) | class ExtraTab(TabPanel):
method __init__ (line 855) | def __init__(self, *args, **kwargs):
method _set_layout (line 871) | def _set_layout(self):
method load_options (line 896) | def load_options(self):
method save_options (line 904) | def save_options(self):
class LogGUI (line 913) | class LogGUI(wx.Frame):
method __init__ (line 931) | def __init__(self, parent=None):
method load (line 945) | def load(self, filename):
FILE: youtube_dl_gui/optionsmanager.py
class OptionsManager (line 26) | class OptionsManager(object):
method __init__ (line 55) | def __init__(self, config_path):
method load_default (line 62) | def load_default(self):
method load_from_file (line 312) | def load_from_file(self):
method save_to_file (line 326) | def save_to_file(self):
method _settings_are_valid (line 337) | def _settings_are_valid(self, settings_dictionary):
method _get_options (line 405) | def _get_options(self):
FILE: youtube_dl_gui/parsers.py
class OptionHolder (line 16) | class OptionHolder(object):
method __init__ (line 39) | def __init__(self, name, flag, default_value, requirements=None):
method is_boolean (line 45) | def is_boolean(self):
method check_requirements (line 49) | def check_requirements(self, options_dict):
class OptionsParser (line 65) | class OptionsParser(object):
method __init__ (line 74) | def __init__(self):
method parse (line 112) | def parse(self, options_dictionary):
method _build_savepath (line 215) | def _build_savepath(self, options_dict):
method _build_videoformat (line 242) | def _build_videoformat(self, options_dict):
method _build_filesizes (line 255) | def _build_filesizes(self, options_dict):
FILE: youtube_dl_gui/updatemanager.py
class UpdateThread (line 30) | class UpdateThread(Thread):
method __init__ (line 51) | def __init__(self, download_path, quiet=False):
method run (line 57) | def run(self):
method _talk_to_gui (line 78) | def _talk_to_gui(self, signal, data=None):
FILE: youtube_dl_gui/utils.py
function get_encoding (line 45) | def get_encoding():
function convert_item (line 56) | def convert_item(item, to_unicode=False):
function convert_on_bounds (line 90) | def convert_on_bounds(func):
function remove_file (line 126) | def remove_file(filename):
function remove_shortcuts (line 133) | def remove_shortcuts(path):
function absolute_path (line 138) | def absolute_path(filename):
function open_file (line 143) | def open_file(file_path):
function encode_tuple (line 163) | def encode_tuple(tuple_to_encode):
function decode_tuple (line 168) | def decode_tuple(encoded_tuple):
function check_path (line 174) | def check_path(path):
function get_config_path (line 180) | def get_config_path():
function shutdown_sys (line 196) | def shutdown_sys(password=None):
function to_string (line 239) | def to_string(data):
function get_time (line 245) | def get_time(seconds):
function get_locale_file (line 266) | def get_locale_file():
function get_icon_file (line 292) | def get_icon_file():
function get_pixmaps_dir (line 312) | def get_pixmaps_dir():
function to_bytes (line 333) | def to_bytes(string):
function format_bytes (line 347) | def format_bytes(bytes):
function build_command (line 360) | def build_command(options_list, url):
function get_default_lang (line 384) | def get_default_lang():
FILE: youtube_dl_gui/widgets.py
function crt_command_event (line 15) | def crt_command_event(event_type, event_id=0):
class ListBoxWithHeaders (line 20) | class ListBoxWithHeaders(wx.ListBox):
method __init__ (line 47) | def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
method _disable_header_selection (line 63) | def _disable_header_selection(self, event):
method _on_listbox (line 73) | def _on_listbox(self, event):
method _add_prefix (line 79) | def _add_prefix(self, string):
method _remove_prefix (line 82) | def _remove_prefix(self, string):
method FindString (line 89) | def FindString(self, string):
method GetStringSelection (line 98) | def GetStringSelection(self):
method GetString (line 101) | def GetString(self, index):
method InsertItems (line 110) | def InsertItems(self, items, pos):
method SetSelection (line 114) | def SetSelection(self, index):
method SetString (line 120) | def SetString(self, index, string):
method SetStringSelection (line 129) | def SetStringSelection(self, string):
method Append (line 138) | def Append(self, string):
method AppendItems (line 141) | def AppendItems(self, strings):
method Clear (line 145) | def Clear(self):
method Delete (line 149) | def Delete(self, index):
method add_header (line 159) | def add_header(self, header_string):
method add_item (line 163) | def add_item(self, item, with_prefix=True):
method add_items (line 169) | def add_items(self, items, with_prefix=True):
class ListBoxPopup (line 176) | class ListBoxPopup(wx.PopupTransientWindow):
method __init__ (line 195) | def __init__(self, parent=None, flags=wx.BORDER_NONE):
method _on_motion (line 199) | def _on_motion(self, event):
method _on_left_down (line 208) | def _on_left_down(self, event):
method Popup (line 215) | def Popup(self):
method OnDismiss (line 219) | def OnDismiss(self):
method Init (line 224) | def Init(self):
method Create (line 227) | def Create(self, parent):
method GetAdjustedSize (line 238) | def GetAdjustedSize(self, min_width, pref_height, max_height):
method GetControl (line 252) | def GetControl(self):
method GetStringValue (line 255) | def GetStringValue(self):
class CustomComboBox (line 262) | class CustomComboBox(wx.Panel):
method __init__ (line 280) | def __init__(self, parent, id=wx.ID_ANY, value="", pos=wx.DefaultPosit...
method _propagate (line 313) | def _propagate(self, event):
method _on_button (line 319) | def _on_button(self, event):
method _calc_popup_position (line 322) | def _calc_popup_position(self):
method _calc_popup_size (line 328) | def _calc_popup_size(self):
method Dismiss (line 342) | def Dismiss(self):
method FindString (line 345) | def FindString(self, string, caseSensitive=False):
method GetCount (line 349) | def GetCount(self):
method GetCurrentSelection (line 352) | def GetCurrentSelection(self):
method GetInsertionPoint (line 355) | def GetInsertionPoint(self):
method GetSelection (line 358) | def GetSelection(self):
method GetTextSelection (line 361) | def GetTextSelection(self):
method GetString (line 364) | def GetString(self, index):
method GetStringSelection (line 367) | def GetStringSelection(self):
method IsListEmpty (line 370) | def IsListEmpty(self):
method IsTextEmpty (line 373) | def IsTextEmpty(self):
method Popup (line 376) | def Popup(self):
method SetSelection (line 382) | def SetSelection(self, index):
method SetString (line 388) | def SetString(self, index, string):
method SetTextSelection (line 391) | def SetTextSelection(self, from_, to_):
method SetStringSelection (line 394) | def SetStringSelection(self, string):
method SetValue (line 402) | def SetValue(self, value):
method Clear (line 407) | def Clear(self):
method Append (line 411) | def Append(self, item):
method AppendItems (line 414) | def AppendItems(self, items):
method Delete (line 417) | def Delete(self, index):
method GetValue (line 422) | def GetValue(self):
method add_header (line 427) | def add_header(self, header):
method add_item (line 430) | def add_item(self, item, with_prefix=True):
method add_items (line 433) | def add_items(self, items, with_prefix=True):
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (458K chars).
[
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 2730,
"preview": "## Please follow the guide below\n\n- You will be asked some questions and requested to provide some information, please r"
},
{
"path": ".gitignore",
"chars": 34,
"preview": "MANIFEST\n*.pyc\n*.mo\n\ndist/\nbuild/\n"
},
{
"path": ".tx/config",
"chars": 244,
"preview": "[main]\nhost = https://www.transifex.com\n\n[youtube-dl-gui.resources]\nfile_filter = youtube_dl_gui/locale/<lang>/LC_MESSAG"
},
{
"path": "AUTHORS",
"chars": 728,
"preview": "# Authors ordered by first contribution.\n\nMrS0m30n3 <ytubedlg@gmail.com>\nHenrique Pereira <ikkibr@gmail.com>\nFironet <do"
},
{
"path": "ChangeLog",
"chars": 630,
"preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
},
{
"path": "LICENSE",
"chars": 1212,
"preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
},
{
"path": "MANIFEST.in",
"chars": 375,
"preview": "include TODO\ninclude AUTHORS\ninclude LICENSE\ninclude README.md\ninclude MANIFEST.in\ninclude ChangeLog\ninclude youtube-dl-"
},
{
"path": "README.md",
"chars": 2400,
"preview": "[](https://mrs0m30n3.github.io/youtube-dl-gui/donate.html"
},
{
"path": "TODO",
"chars": 1295,
"preview": "Release 0.4.1\n=============\n* Intergrity check youtube-dl bin\n* Non-Windows shutdown using D-Bus instead of 'shutdown'\n*"
},
{
"path": "devscripts/bump-version.sh",
"chars": 1231,
"preview": "#!/bin/bash\n\n# Author: Sotiris Papadopoulos <ytubedlg@gmail.com>\n# Last-Revision: 2017-04-17\n# Script to bump the versio"
},
{
"path": "devscripts/check-translation.py",
"chars": 10019,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nAuthor: Sotiris Papadopoulos <ytubedlg@gmail.com>\nLast-Revision: 2017"
},
{
"path": "devscripts/new-locale.py",
"chars": 1539,
"preview": "#!/usr/bin/env python\n\n\"\"\"\nAuthor: Sotiris Papadopoulos <ytubedlg@gmail.com>\nLast-Revision: 2017-01-30\n\nScript to add su"
},
{
"path": "devscripts/update-authors.sh",
"chars": 545,
"preview": "#!/bin/bash\n\n# Author: Sotiris Papadopoulos <ytubedlg@gmail.com>\n# Last-Revision: 2017-04-17\n# Script to update the AUTH"
},
{
"path": "devscripts/update-locales.sh",
"chars": 1064,
"preview": "#!/bin/bash\n\n# Author: Sotiris Papadopoulos <ytubedlg@gmail.com>\n# Last-Revision: 2017-01-30\n# Script to update all loca"
},
{
"path": "docs/faqs.md",
"chars": 5196,
"preview": "# Frequently Asked Questions\n\n* **How can I make sure I'm getting the best quality possible?**:\nDon't force any output f"
},
{
"path": "docs/localization_howto.md",
"chars": 7397,
"preview": "# Localization Guide - [Transifex](https://www.transifex.com/youtube-dl-gui/public/)\r\n\r\n## 🔴 DISCLAIMER\r\n**By se"
},
{
"path": "setup.py",
"chars": 8159,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtube-dlg setup file.\n\nExamples:\n Windows::\n\n python setu"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_ditem.py",
"chars": 19123,
"preview": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\n\"\"\"Contains test cases for the DownloadItem object.\"\"\"\n\nfrom __future__ i"
},
{
"path": "tests/test_dlist.py",
"chars": 7733,
"preview": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\n\"\"\"Contains test cases for the DownloadList object.\"\"\"\n\nfrom __future__ i"
},
{
"path": "tests/test_parsers.py",
"chars": 4798,
"preview": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\n\"\"\"Contains test cases for the parsers module.\"\"\"\n\nfrom __future__ import"
},
{
"path": "tests/test_utils.py",
"chars": 6653,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"Contains test cases for the utils.py module.\"\"\"\n\nfrom __future__ impor"
},
{
"path": "tests/test_widgets.py",
"chars": 9065,
"preview": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\n\"\"\"Contains test cases for the widgets.py module.\"\"\"\n\nfrom __future__ imp"
},
{
"path": "youtube-dl-gui.1",
"chars": 863,
"preview": ".\\\" [program name] [section] [date YYYY-MM-DD] [version] [empty]\n.TH YOUTUBE\\-DL\\-GUI 1 \"2018-01-13\" \"Version 0.4\" \"\"\n\n."
},
{
"path": "youtube_dl_gui/__init__.py",
"chars": 2099,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg __init__ file.\n\nResponsible on how the package looks from "
},
{
"path": "youtube_dl_gui/__main__.py",
"chars": 991,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg __main__ file.\n\n__main__ file is a python 'executable' fil"
},
{
"path": "youtube_dl_gui/data/pixmaps/icons-license",
"chars": 133,
"preview": "icons from: https://www.iconfinder.com/iconsets/google-material-design-icons\nlicense: https://creativecommons.org/licens"
},
{
"path": "youtube_dl_gui/downloaders.py",
"chars": 17033,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Python module to download videos.\n\nThis module contains the actual do"
},
{
"path": "youtube_dl_gui/downloadmanager.py",
"chars": 23814,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module for managing the download process.\n\nThis module is "
},
{
"path": "youtube_dl_gui/formats.py",
"chars": 4784,
"preview": "# -*- coding: UTF-8 -*-\n\nimport gettext\n\nfrom .utils import TwoWayOrderedDict as tdict\n\n\nOUTPUT_FORMATS = tdict([\n (0"
},
{
"path": "youtube_dl_gui/info.py",
"chars": 1806,
"preview": "# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module that holds package information.\n\nNote:\n All those info could be stored "
},
{
"path": "youtube_dl_gui/locale/ar_SA/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14097,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n# snosi <fixall.dz@gma"
},
{
"path": "youtube_dl_gui/locale/cs_CZ/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14286,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/en_US/LC_MESSAGES/youtube_dl_gui.po",
"chars": 11792,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/es_ES/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14992,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n# Ismael Ferreras More"
},
{
"path": "youtube_dl_gui/locale/fr_FR/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14501,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/it_IT/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14502,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/ja_JP/LC_MESSAGES/youtube_dl_gui.po",
"chars": 13050,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/ko_KR/LC_MESSAGES/youtube_dl_gui.po",
"chars": 12968,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/pt_BR/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14348,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n#\nmsgid \"\"\nmsgstr \"\"\n\""
},
{
"path": "youtube_dl_gui/locale/ru_RU/LC_MESSAGES/youtube_dl_gui.po",
"chars": 14696,
"preview": "# Youtube-dlG localization file.\n# FIRST AUTHOR: Sotiris Papadopoulos <ytubedlg@gmail.com>, 2015.\n# Nikita «Arttse» Byst"
},
{
"path": "youtube_dl_gui/logmanager.py",
"chars": 2609,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module responsible for handling the log stuff. \"\"\"\n\nfrom _"
},
{
"path": "youtube_dl_gui/mainframe.py",
"chars": 52499,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module responsible for the main app window. \"\"\"\n\nfrom __fu"
},
{
"path": "youtube_dl_gui/optionsframe.py",
"chars": 38900,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module responsible for the options window. \"\"\"\n\nfrom __fut"
},
{
"path": "youtube_dl_gui/optionsmanager.py",
"chars": 15625,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module to handle settings. \"\"\"\n\nfrom __future__ import uni"
},
{
"path": "youtube_dl_gui/parsers.py",
"chars": 10560,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module responsible for parsing the options. \"\"\"\n\nfrom __fu"
},
{
"path": "youtube_dl_gui/updatemanager.py",
"chars": 2838,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module to update youtube-dl binary.\n\nAttributes:\n UPDAT"
},
{
"path": "youtube_dl_gui/utils.py",
"chars": 9816,
"preview": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\n\"\"\"Youtubedlg module that contains util functions.\n\nAttributes:\n _RAN"
},
{
"path": "youtube_dl_gui/version.py",
"chars": 86,
"preview": "# -*- coding: utf-8 -*-\n\nfrom __future__ import unicode_literals\n\n__version__ = '0.4'\n"
},
{
"path": "youtube_dl_gui/widgets.py",
"chars": 13178,
"preview": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nfrom __future__ import unicode_literals\n\nimport sys\n\ntry:\n import wx\ne"
}
]
About this extraction
This page contains the full source code of the MrS0m30n3/youtube-dl-gui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (419.0 KB), approximately 109.4k tokens, and a symbol index with 546 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.