Compare commits
245 Commits
Author | SHA1 | Date | |
---|---|---|---|
7d340bb7eb | |||
fb2395ce67 | |||
2386401633 | |||
aab1230c07 | |||
516e20c3b8 | |||
062bbcc842 | |||
f5ecaefe8c | |||
3b17f4b7b0 | |||
8aa55b0aa4 | |||
2613803031 | |||
b286e526cc | |||
a1b29005bb | |||
6eaa0f50a1 | |||
0ea41cea89 | |||
5c9169fadd | |||
817df4219b | |||
70273b2023 | |||
9bd3b7f448 | |||
0fef6427ff | |||
32fb52f941 | |||
ef36948ab8 | |||
236539f337 | |||
af16329c72 | |||
ebb400672f | |||
9bb85688bb | |||
da4433894f | |||
b60e62a56d | |||
74d9b5b1a4 | |||
14d311f29d | |||
441277a472 | |||
b0f241b2b1 | |||
7d64515bff | |||
cc1216f7fe | |||
226a17b4fe | |||
1cd7d2ad39 | |||
c5d103dc8d | |||
1a5fe83eee | |||
ab264d4100 | |||
de28e80327 | |||
ea01e13930 | |||
2ef50b73c4 | |||
18fd1610bf | |||
b498dfb91e | |||
5f20f2ea91 | |||
0e63c3e148 | |||
ee1e13deac | |||
1fd75dfa93 | |||
52dd577b6c | |||
7cb6a10db4 | |||
a09079ba43 | |||
c4694ac4b1 | |||
a068ea2342 | |||
ba39db0e46 | |||
e321cdf259 | |||
b9707aaf18 | |||
4f7b9c5b60 | |||
162ce4491b | |||
b36504a493 | |||
fe878b227d | |||
3fe5d98f66 | |||
eb61d55d20 | |||
346f6e15fe | |||
52be306691 | |||
282b840c02 | |||
d300713f94 | |||
b8c55af897 | |||
643f9c5613 | |||
475e9fd573 | |||
f6b08cb046 | |||
c1931f22bc | |||
12c3da2886 | |||
79a7de3940 | |||
91db712833 | |||
de86808eb4 | |||
d7f7e508a3 | |||
55eb7244a3 | |||
2aa39da0d8 | |||
e4356d58e0 | |||
cc8ea272b9 | |||
360f4a7ce4 | |||
2d329ac8e9 | |||
0d1172dd9e | |||
ff17f36781 | |||
7087edc1a5 | |||
76d62eaca2 | |||
1d236d6e0f | |||
90b155c04a | |||
fb4b0b835e | |||
dfdd3785ef | |||
8d92b01fa4 | |||
e9626b977e | |||
68798328d2 | |||
908eb64481 | |||
cac07c0864 | |||
27d593b512 | |||
0ead659156 | |||
c66c924321 | |||
e985f97811 | |||
34bee5ad50 | |||
d62c4e226d | |||
36eb2b2537 | |||
98184a9abc | |||
4a625b7a29 | |||
241a553fce | |||
da0bb3a6aa | |||
afffd9ba15 | |||
4d4e0bf283 | |||
6429e80964 | |||
2579224d80 | |||
0e73d55c06 | |||
28a1eb7ee9 | |||
5ccee02391 | |||
cf14992283 | |||
8e29595b08 | |||
b3158c8813 | |||
84698c29bd | |||
153b001930 | |||
6e1b6232a4 | |||
41f18eb8dd | |||
6f820779f5 | |||
68c1cd7718 | |||
39059725f6 | |||
4c10890554 | |||
877d588f8f | |||
1698cc59c3 | |||
351783aa53 | |||
932f945b1f | |||
033b7f2c6d | |||
01d85cdbf6 | |||
87065716b0 | |||
c1f5244b7d | |||
e89d02eeeb | |||
66c6e62293 | |||
92239613b2 | |||
2adbd2f2d7 | |||
07158f5ffd | |||
158391330c | |||
3874b34a77 | |||
83abe3376b | |||
e85acd6c20 | |||
2410af484c | |||
5b9a28344e | |||
0c0c89debe | |||
5f8d3e51de | |||
87e3169a19 | |||
3d7432f7b7 | |||
b0c0cdbdba | |||
4cb07bd2f0 | |||
ce2435dfb4 | |||
5c0d98fc9f | |||
893b075f06 | |||
d7759c75a0 | |||
18f1a15a81 | |||
ad2189039e | |||
57a23868e9 | |||
2a834559ec | |||
9481d2868f | |||
fd791fe181 | |||
1477522681 | |||
81d0c48a8b | |||
8fe6302942 | |||
0f062a2fde | |||
ca6e6b23bf | |||
ff8a7492e8 | |||
aa2c46c618 | |||
d6e75f8604 | |||
abd83553b7 | |||
4c2d696ee6 | |||
884bcfadca | |||
e342003de5 | |||
b9c763c4f4 | |||
9ef726e762 | |||
d1ec62e41d | |||
fa3ac19651 | |||
49d6d4093c | |||
a15f35822e | |||
3190e90974 | |||
7e2c6d6430 | |||
c9b85f31f7 | |||
841df99d61 | |||
fdea954083 | |||
444c132843 | |||
6e86359698 | |||
3f2fc9c9de | |||
0d5cf787fc | |||
f4872a1ecd | |||
2d8b7b2ed6 | |||
503dead2aa | |||
19b7248074 | |||
b4c8a41757 | |||
91acce2df9 | |||
3b7df1230c | |||
5e6749cf84 | |||
a94aaf4ce3 | |||
c7ba0a86c1 | |||
7abdf00529 | |||
b42c7d402f | |||
bff6b3d6c3 | |||
3e284d5a8b | |||
a690f116d2 | |||
693a9f474e | |||
5512b605e1 | |||
1051b81983 | |||
f0f23c9b05 | |||
12a4ee65c3 | |||
96af01279a | |||
0cc6f97f01 | |||
fef5c218b0 | |||
c518d0ea08 | |||
a2caaa6449 | |||
0a102fecc2 | |||
f9f1a9c864 | |||
10570c23b6 | |||
42446107f7 | |||
7d9067a611 | |||
b9302000f6 | |||
cfd6a92c35 | |||
f1f460bf0a | |||
561660b700 | |||
bcd3b7b36e | |||
332365d53b | |||
0768ecc24f | |||
6aeffd57bb | |||
dae1df79ad | |||
c06df2092e | |||
bb32d79dd7 | |||
27e422888b | |||
b1f239fe82 | |||
ff8c8ac22b | |||
ea04cf2eb1 | |||
861fdb56b5 | |||
e2d73ec446 | |||
b4a0060756 | |||
66f6f32916 | |||
01f639b809 | |||
10d60e3aa2 | |||
16d99aaaac | |||
625931f40b | |||
6a188b7ec7 | |||
adc1317401 | |||
207a7110ef | |||
69452b69bd | |||
084d3b8b10 | |||
eedf897de0 | |||
9ac26bc818 |
187
.gitlab-ci.yml
187
.gitlab-ci.yml
@ -1,106 +1,121 @@
|
|||||||
# This CI configuration file is used to build all available versions of MusicDL. It's intended to launch a new version when a tag is pushed to master.
|
|
||||||
# In order to work, A Gitlab Runner with Windows Server 2016 Standard with the following packages is used:
|
|
||||||
# * Latest python for both 2.7 and 3.x branches.
|
|
||||||
# * Py2exe for Python 2.7
|
|
||||||
# * Microsoft Visual C++ 2015 redistributable files
|
|
||||||
# * Nsis
|
|
||||||
|
|
||||||
# Declare some variables dependent on the operating system where the runner is installed.
|
|
||||||
# This CI file assumes we install everything in C:\ (Python 2.7, 3.7 and Nsis).
|
|
||||||
variables:
|
|
||||||
PYTHON3: "C:\\python37\\python.exe"
|
|
||||||
PYINSTALLER: "C:\\python37\\scripts\\pyinstaller.exe"
|
|
||||||
NSIS: "C:\\nsis\\makensis.exe"
|
|
||||||
|
|
||||||
### Stage list
|
|
||||||
# Build: This will be the main stage generating stuff in the dist folder. Jobs present in this stage will run py2exe or pyinstaller files accordingly.
|
|
||||||
# pack: Jobs in this stage will take the dist folder and zip it or generate an exe file.
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
- build
|
- generate_docs
|
||||||
- pack
|
- build
|
||||||
|
- versions
|
||||||
|
- upload
|
||||||
|
|
||||||
# Python 3 tests
|
program64:
|
||||||
test_py3:
|
interruptible: true
|
||||||
stage: test
|
|
||||||
tags:
|
tags:
|
||||||
|
- windows
|
||||||
- windows10
|
- windows10
|
||||||
before_script:
|
|
||||||
- '%PYTHON3% -v'
|
|
||||||
- '%PYTHON3% -m pip install --upgrade pip'
|
|
||||||
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
|
|
||||||
script:
|
|
||||||
- cd src
|
|
||||||
- '%PYTHON3% -m coverage run run_tests.py'
|
|
||||||
- '%PYTHON3% -m coverage report --omit="test*"'
|
|
||||||
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
|
|
||||||
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- tags
|
|
||||||
- schedule_pipelines
|
|
||||||
|
|
||||||
# Python 3 version. During this job, the dist folder, containing all files to distribute, will be generated
|
|
||||||
# and passed to build_zip and build_setup jobs.
|
|
||||||
build_py3:
|
|
||||||
stage: build
|
stage: build
|
||||||
tags:
|
variables:
|
||||||
- windows10
|
PYTHON: "C:\\python310\\python.exe"
|
||||||
# Update stuff before building versions
|
|
||||||
before_script:
|
before_script:
|
||||||
- '%PYTHON3% -v'
|
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||||
- '%PYTHON3% -m pip install --upgrade pip'
|
- echo ${time}
|
||||||
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
|
- echo "started by ${GITLAB_USER_NAME}"
|
||||||
|
- '&$env:PYTHON -V'
|
||||||
|
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||||
|
- '&$env:PYTHON -m venv env'
|
||||||
|
- 'env\Scripts\Activate.ps1'
|
||||||
|
- 'python -m pip install --upgrade -r requirements.txt'
|
||||||
script:
|
script:
|
||||||
- cd src
|
- cd src
|
||||||
- '%PYINSTALLER% main.spec'
|
- 'python write_version_data.py'
|
||||||
# Build this automatically only when tags are pushed to master or when a pipeline has been scheduled by Gitlab.
|
- 'python setup.py build'
|
||||||
only:
|
- cd ..
|
||||||
- tags
|
- 'mkdir build'
|
||||||
- schedule_pipelines
|
|
||||||
# Make the dist folder available to other jobs.
|
|
||||||
# It will expire in 30 mins as we won't need the dist folder after the pipeline is completed.
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- src\\dist
|
|
||||||
expire_in: 30 mins
|
|
||||||
|
|
||||||
# This job takes the src\\dist folder generated in build_py3 and creates a zip file, which will be uploaded to the repository's artifacts.
|
|
||||||
zip_py3:
|
|
||||||
stage: pack
|
|
||||||
tags:
|
|
||||||
- windows10
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
- schedule_pipelines
|
|
||||||
dependencies:
|
|
||||||
- build_py3
|
|
||||||
script:
|
|
||||||
- cd scripts
|
- cd scripts
|
||||||
- '%PYTHON3% prepare_zipversion.py'
|
- 'python prepare_zipversion.py'
|
||||||
- cd ..
|
- cd ..
|
||||||
- move src\music_dl.zip music_dl.zip
|
- move src\music_dl.zip build\music_dl_x64.zip
|
||||||
# No expiry date as there will be only releases in the artifacts.
|
- 'move src/dist build/program64'
|
||||||
|
- 'move src/installer.nsi build'
|
||||||
|
only:
|
||||||
|
- schedules
|
||||||
|
- master
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- music_dl.zip
|
- build
|
||||||
|
expire_in: 1 day
|
||||||
|
|
||||||
# This job takes the src\\dist generated in build_py3 and creates a setup installer file.
|
program32:
|
||||||
build_setup:
|
interruptible: true
|
||||||
stage: pack
|
|
||||||
tags:
|
tags:
|
||||||
|
- windows
|
||||||
- windows10
|
- windows10
|
||||||
only:
|
stage: build
|
||||||
- tags
|
variables:
|
||||||
- schedule_pipelines
|
PYTHON: "C:\\python310-32\\python.exe"
|
||||||
dependencies:
|
before_script:
|
||||||
- build_py3
|
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||||
|
- echo ${time}
|
||||||
|
- echo "started by ${GITLAB_USER_NAME}"
|
||||||
|
- '&$env:PYTHON -V'
|
||||||
|
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||||
|
- '&$env:PYTHON -m venv env'
|
||||||
|
- 'env\Scripts\Activate.ps1'
|
||||||
|
- 'python -m pip install https://github.com/josephsl/wxpy32whl/raw/main/wxPython-4.2.0-cp310-cp310-win32.whl'
|
||||||
|
- 'python -m pip install --upgrade -r requirements.txt'
|
||||||
script:
|
script:
|
||||||
- cd src
|
- cd src
|
||||||
- '%NSIS% installer.nsi'
|
- 'python write_version_data.py'
|
||||||
|
- 'python setup.py build'
|
||||||
- cd ..
|
- cd ..
|
||||||
- move src\music_dl* .
|
- 'mkdir build'
|
||||||
|
- cd scripts
|
||||||
|
- 'python prepare_zipversion.py'
|
||||||
|
- cd ..
|
||||||
|
- move src\music_dl.zip build\music_dl_x86.zip
|
||||||
|
- 'move src/dist build/program32'
|
||||||
|
only:
|
||||||
|
- schedules
|
||||||
|
- master
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- "music_dl_*"
|
- build
|
||||||
name: music_dl
|
expire_in: 1 day
|
||||||
|
|
||||||
|
generate_versions:
|
||||||
|
stage: versions
|
||||||
|
tags:
|
||||||
|
- windows
|
||||||
|
- windows10
|
||||||
|
variables:
|
||||||
|
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
|
||||||
|
before_script:
|
||||||
|
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||||
|
- echo ${time}
|
||||||
|
- echo "started by ${GITLAB_USER_NAME}"
|
||||||
|
script:
|
||||||
|
- mkdir artifacts
|
||||||
|
- 'cd build'
|
||||||
|
- '&$env:NSIS installer.nsi'
|
||||||
|
- move *.exe ../artifacts
|
||||||
|
- move *.zip ../artifacts
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
- master
|
||||||
|
- schedules
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts
|
||||||
|
expire_in: 1 day
|
||||||
|
|
||||||
|
upload:
|
||||||
|
image:
|
||||||
|
name: amazon/aws-cli
|
||||||
|
entrypoint: [""]
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
- master
|
||||||
|
interruptible: true
|
||||||
|
stage: upload
|
||||||
|
script:
|
||||||
|
- aws --version
|
||||||
|
- aws --endpoint-url https://s3.us-west-001.backblazeb2.com s3 cp artifacts s3://$S3_BUCKET/music_dl/ --recursive
|
26
README.md
26
README.md
@ -12,26 +12,32 @@ MusicDL is an app for downloading music directly from services like Youtube, zay
|
|||||||
|
|
||||||
See the requirements.txt, located in the root of this repository. Additionally, take into account the following.
|
See the requirements.txt, located in the root of this repository. Additionally, take into account the following.
|
||||||
|
|
||||||
* In case you want to create your own distributable version with Python 2, you'll need py2exe.
|
|
||||||
|
|
||||||
## running
|
## running
|
||||||
|
|
||||||
Run the file main.py, located in the src directory.
|
Run the file main.py, located in the src directory.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
### Python 3
|
I have provided a setup.py file for cx_freeze, so you should be able to do something like:
|
||||||
|
|
||||||
I have provided a main.spec file for pyinstaller, so you should be able to do something like:
|
> python setup.py build
|
||||||
|
|
||||||
> C:\python3\scripts\pyinstaller.exe main.spec
|
|
||||||
|
|
||||||
And start building. Check the dist folder for results.
|
And start building. Check the dist folder for results.
|
||||||
|
|
||||||
### Python 2
|
## Updating translation catalog
|
||||||
|
|
||||||
If you are using Python 2.x and want to build MusicDL, there is a setup.py file made for pyinstaller aswell. Just run it the usual way:
|
Every time there are new strings in the application a translations catalog update must be performed with the following commands in the src directory:
|
||||||
|
|
||||||
> C:\python2\python.exe setup.py py2exe
|
> python setup.py extract_messages -o musicdl.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "Manuel Cortez" --input-dirs .
|
||||||
|
> python setup.py update_catalog --input-file musicdl.pot --domain musicdl --output-dir locales --ignore-obsolete true
|
||||||
|
|
||||||
And You will get a distributable version of MusicDL.
|
And after updating translations they should be compiled with:
|
||||||
|
|
||||||
|
> python setup.py compile_catalog --statistics -d locales --domain musicdl
|
||||||
|
|
||||||
|
## Adding new translations
|
||||||
|
|
||||||
|
The procedure for adding new translations is also easy, thanks to the following command. Just replace xx for the new locale name to add:
|
||||||
|
|
||||||
|
> python setup.py extract_messages -o musicdl.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "Copyright (C) 2019, 2020 Manuel Cortez" --input-dirs .
|
||||||
|
> python setup.py init_catalog --domain musicdl --input-file musicdl.pot -d locales --locale xx
|
24
changes.md
24
changes.md
@ -1,5 +1,29 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
## Changes on January
|
||||||
|
|
||||||
|
* Application improvements:
|
||||||
|
* Updated VLC components to V3.0.11.
|
||||||
|
* Updated SSL/TLS plugin in vlc which should make youtube playback less buggy.
|
||||||
|
* MusicDL now builds with Python V 3.9.1.
|
||||||
|
* zaycev.net:
|
||||||
|
* Disabled service by default as it will not work outside Russia.
|
||||||
|
|
||||||
|
## Version 0.7
|
||||||
|
|
||||||
|
* Application improvements:
|
||||||
|
* MusicDL no longer adds invalid characters when attempting to download a file.
|
||||||
|
* Tidal:
|
||||||
|
* Added a new search mode for the service to retrieve the top rated tracks of a provided artist. The sintax is top://artist.
|
||||||
|
* In the settings dialog, you can control wether Albums, compilations and singles will be added when searching by artist (by using artist://...).
|
||||||
|
* When searching by artists, results that belong to an album will be numbered.
|
||||||
|
* Downloads will be tagged with title, album, artist and track number provided by tidal.
|
||||||
|
* It is possible to download an original version in high and low quality. Before, those versions were encoded to mp3 from an m4a file. Now the M4a file can be retrieved by using the checkbox in the tidal settings page.
|
||||||
|
* YouTube:
|
||||||
|
* Fixed search algorithm for Youtube videos.
|
||||||
|
* Updated Youtube-Dl to version 2020.6.16.1
|
||||||
|
* re-added VK module. By default, this module searches up to 50 results but you can increase it up to 200 if needed from the services settings.
|
||||||
|
|
||||||
## Version 0.6
|
## Version 0.6
|
||||||
|
|
||||||
* Added a settings dialog for the application, from this dialog you will be able to find some general settings, available for MusicDL, and service's settings. Every service defines certain specific settings.
|
* Added a settings dialog for the application, from this dialog you will be able to find some general settings, available for MusicDL, and service's settings. Every service defines certain specific settings.
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
coverage
|
wxpython
|
||||||
wxpython==4.0.3
|
|
||||||
requests
|
requests
|
||||||
bs4
|
bs4
|
||||||
pypubsub
|
pypubsub
|
||||||
python-vlc
|
yt-dlp
|
||||||
google-api-python-client
|
python-mpv
|
||||||
youtube-dl
|
cx_freeze
|
||||||
pyinstaller
|
|
||||||
isodate
|
|
||||||
configobj
|
configobj
|
||||||
winpaths
|
winpaths
|
||||||
tidalapi
|
|
||||||
mutagen
|
mutagen
|
||||||
|
babel
|
||||||
|
tidalapi
|
||||||
|
git+https://github.com/accessibleapps/platform_utils
|
||||||
|
21
scripts/generate_update_file.py
Normal file
21
scripts/generate_update_file.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
print("Generating update files for Socializer...")# Determine if we are going to write stable or alpha update file.
|
||||||
|
version = datetime.datetime.now().strftime("%Y.%m.%d")
|
||||||
|
version_type = "latest"
|
||||||
|
print("Version detected: %s" % (version_type,))
|
||||||
|
|
||||||
|
# Read update description and URL'S
|
||||||
|
description = os.environ.get("CI_COMMIT_MESSAGE")
|
||||||
|
urls = dict(Windows32="https://files.mcvsoftware.com/music_dl/latest/music_dl_x86.zip", Windows64="https://files.mcvsoftware.com/music_dl/latest/music_dl_x64.zip")
|
||||||
|
|
||||||
|
# build the main dict object
|
||||||
|
data = dict(current_version=version, description=description, downloads=urls)
|
||||||
|
print("Generating file with the following arguments: %r" % (data,))
|
||||||
|
updatefile = "latest.json"
|
||||||
|
f = open(updatefile, "w")
|
||||||
|
json.dump(data, f, ensure_ascii=False)
|
||||||
|
f.close()
|
@ -1,407 +1,19 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# Translations template for musicDL.
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
# Copyright (C) 2020 Manuel Cortez
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# This file is distributed under the same license as the musicDL project.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||||
#
|
#
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: musicDL 2020.7.23\n"
|
||||||
"POT-Creation-Date: 2019-06-24 13:04+Hora de verano central (Mexico)\n"
|
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
|
||||||
|
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=CHARSET\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: ENCODING\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: Babel 2.9.0\n"
|
||||||
|
|
||||||
|
|
||||||
#: ../src\application.py:9
|
|
||||||
msgid " Is an application that will allow you to download music from popular sites such as youtube, zaycev.net."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\application.py:14
|
|
||||||
msgid "Manuel Cortez (Spanish)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\application.py:14
|
|
||||||
msgid "Valeria K (Russian)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\configuration.py:10 ../src\wxUI\mainWindow.py:15
|
|
||||||
msgid "Settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:31
|
|
||||||
msgid "Ready"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:47
|
|
||||||
msgid "Showing {0} results."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:51
|
|
||||||
msgid "Shuffle on"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:107
|
|
||||||
msgid "Searching {0}... "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:141
|
|
||||||
#: ../src\controller\mainController.py:161 ../src\wxUI\mainWindow.py:18
|
|
||||||
#: ../src\wxUI\mainWindow.py:68
|
|
||||||
msgid "Play"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:144
|
|
||||||
#: ../src\controller\mainController.py:156
|
|
||||||
msgid "Pause"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:243
|
|
||||||
msgid "File downloaded: {0}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:263
|
|
||||||
msgid "No results found. "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:70
|
|
||||||
msgid "Error playing {0}. {1}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:76
|
|
||||||
msgid "Playing {0}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:146 ../src\utils.py:58
|
|
||||||
msgid "Downloading {0}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:151 ../src\utils.py:69
|
|
||||||
msgid "Downloading {0} ({1}%)."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:164
|
|
||||||
msgid "There was an error while trying to access the file you have requested."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:164 ../src\issueReporter\wx_ui.py:94
|
|
||||||
#: ../src\issueReporter\wx_ui.py:97
|
|
||||||
msgid "Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:102
|
|
||||||
msgid "Tidal"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:106
|
|
||||||
msgid "Lossless"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:106
|
|
||||||
msgid "Low"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:106 ../src\extractors\tidal.py:141
|
|
||||||
msgid "High"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:118 ../src\extractors\youtube.py:112
|
|
||||||
msgid "Enable this service"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:122
|
|
||||||
msgid "Tidal username or email address"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:130
|
|
||||||
msgid "Password"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:137
|
|
||||||
msgid "You can subscribe for a tidal account here"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:140
|
|
||||||
msgid "Audio quality"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\youtube.py:106
|
|
||||||
msgid "Youtube Settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\youtube.py:116
|
|
||||||
msgid "Max results per page"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\youtube.py:123
|
|
||||||
msgid "Enable transcode when downloading"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\zaycev.py:52
|
|
||||||
msgid "zaycev.net"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\extractors\zaycev.py:58
|
|
||||||
msgid "Enable this service (works only in the Russian Federation)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:26 ../src\wxUI\mainWindow.py:31
|
|
||||||
msgid "Report an error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:30
|
|
||||||
msgid "Briefly describe what happened. You will be able to thoroughly explain it later"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:40
|
|
||||||
msgid "First Name"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:50
|
|
||||||
msgid "Last Name"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:60
|
|
||||||
msgid "Email address (Will not be public)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:70
|
|
||||||
msgid "Here, you can describe the bug in detail"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:80
|
|
||||||
msgid "I know that the {0} bug system will get my email address to contact me and fix the bug quickly"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:83
|
|
||||||
msgid "Send report"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:85
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:94
|
|
||||||
msgid "You must fill out the following fields: first name, last name, email address and issue information."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:97
|
|
||||||
msgid "You need to mark the checkbox to provide us your email address to contact you if it is necessary."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:100
|
|
||||||
msgid "Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:100
|
|
||||||
msgid "reported"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:104
|
|
||||||
msgid "Error while reporting"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:104
|
|
||||||
msgid "Something unexpected occurred while trying to report the bug. Please, try again later"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:108
|
|
||||||
msgid "Please wait while your report is being send."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:108
|
|
||||||
msgid "Sending report..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\test\test_i18n.py:21
|
|
||||||
msgid "This is a string with no special characters."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\test\test_i18n.py:24
|
|
||||||
msgid "\320\237\321\200\320\270\320\262\320\265\321\202 \320\262\321\201\320\265\320\274"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:27
|
|
||||||
msgid "%d day, "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:29
|
|
||||||
msgid "%d days, "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:31
|
|
||||||
msgid "%d hour, "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:33
|
|
||||||
msgid "%d hours, "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:35
|
|
||||||
msgid "%d minute, "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:37
|
|
||||||
msgid "%d minutes, "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:39
|
|
||||||
msgid "%s second"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:41
|
|
||||||
msgid "%s seconds"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:10
|
|
||||||
msgid "New version for %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:10
|
|
||||||
msgid ""
|
|
||||||
"There's a new %s version available. Would you like to download it now?\n"
|
|
||||||
"\n"
|
|
||||||
" %s version: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Changes:\n"
|
|
||||||
"%s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:17
|
|
||||||
msgid "Download in Progress"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:17
|
|
||||||
msgid "Downloading the new version..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:27
|
|
||||||
msgid "Updating... %s of %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:30
|
|
||||||
msgid "Done!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:30
|
|
||||||
msgid "The update has been downloaded and installed successfully. Press OK to continue."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:9
|
|
||||||
msgid "Output device"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:27
|
|
||||||
msgid "General"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:31
|
|
||||||
msgid "Services"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:34
|
|
||||||
msgid "Save"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:36
|
|
||||||
msgid "Close"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:16
|
|
||||||
msgid "Application"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:19 ../src\wxUI\mainWindow.py:69
|
|
||||||
msgid "Stop"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:20 ../src\wxUI\mainWindow.py:67
|
|
||||||
msgid "Previous"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:21 ../src\wxUI\mainWindow.py:70
|
|
||||||
msgid "Next"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:22
|
|
||||||
msgid "Shuffle"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:23
|
|
||||||
msgid "Volume down"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:24
|
|
||||||
msgid "Volume up"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:25
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:27
|
|
||||||
msgid "About {0}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:28
|
|
||||||
msgid "Check for updates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:29
|
|
||||||
msgid "What's new in this version?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:30
|
|
||||||
msgid "Visit website"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:32
|
|
||||||
msgid "Player"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:33
|
|
||||||
msgid "Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:43
|
|
||||||
msgid "search"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:48
|
|
||||||
msgid "Search in"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:51
|
|
||||||
msgid "Search"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:55
|
|
||||||
msgid "Results"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:61
|
|
||||||
msgid "Position"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:64
|
|
||||||
msgid "Volume"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:114
|
|
||||||
msgid "Audio Files(*.mp3)|*.mp3"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:114
|
|
||||||
msgid "Save this file"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:8
|
|
||||||
msgid "Play/Pause"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:9
|
|
||||||
msgid "Download"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
|
@ -6,10 +6,7 @@ import sys
|
|||||||
def create_archive():
|
def create_archive():
|
||||||
os.chdir("..\\src")
|
os.chdir("..\\src")
|
||||||
print("Creating zip archive...")
|
print("Creating zip archive...")
|
||||||
if sys.version[0] == "3":
|
folder = "dist"
|
||||||
folder = "dist/main"
|
|
||||||
else:
|
|
||||||
folder = "dist"
|
|
||||||
shutil.make_archive("music_dl", "zip", folder)
|
shutil.make_archive("music_dl", "zip", folder)
|
||||||
# if os.path.exists("dist"):
|
# if os.path.exists("dist"):
|
||||||
# shutil.rmtree("dist")
|
# shutil.rmtree("dist")
|
||||||
|
80
scripts/upload.py
Normal file
80
scripts/upload.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
"""
|
||||||
|
Important note: for this script to work, the following conditions should be met:
|
||||||
|
* There must be ftp server data, via environment variables (FTP_SERVER, FTP_USERNAME and FTP_PASSWORD) or via arguments to the script call (in the prior order). Connection to this server is done via default ftp port via TLS.
|
||||||
|
* this code assumes it's going to connect to an FTP via TLS.
|
||||||
|
* If the version to upload is alpha, there's not need of an extra variable. Otherwise, CI_COMMIT_TAG should point to a version as vx.x, where v is a literal and x are numbers, example may be v0.18, v0.25, v0.3. This variable should be set in the environment.
|
||||||
|
* Inside the ftp server, the following directory structure will be expected: manuelcortez.net/static/files/music_dl. The script will create the <version> folder or alpha if needed.
|
||||||
|
* The script will upload all .exe, .zip and .json files located in the root directory from where it was called. The json files are uploaded to manuelcortez.net/static/files/music_dl/update and other files are going to manuelcortez.net/static/files/music_dl/<version>.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
import ftplib
|
||||||
|
|
||||||
|
transferred=0
|
||||||
|
|
||||||
|
class MyFTP_TLS(ftplib.FTP_TLS):
|
||||||
|
"""Explicit FTPS, with shared TLS session"""
|
||||||
|
def ntransfercmd(self, cmd, rest=None):
|
||||||
|
conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
|
||||||
|
if self._prot_p:
|
||||||
|
conn = self.context.wrap_socket(conn,
|
||||||
|
server_hostname=self.host,
|
||||||
|
session=self.sock.session) # this is the fix
|
||||||
|
return conn, size
|
||||||
|
|
||||||
|
def convert_bytes(n):
|
||||||
|
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
|
||||||
|
if n >= P:
|
||||||
|
return '%.2fPb' % (float(n) / T)
|
||||||
|
elif n >= T:
|
||||||
|
return '%.2fTb' % (float(n) / T)
|
||||||
|
elif n >= G:
|
||||||
|
return '%.2fGb' % (float(n) / G)
|
||||||
|
elif n >= M:
|
||||||
|
return '%.2fMb' % (float(n) / M)
|
||||||
|
elif n >= K:
|
||||||
|
return '%.2fKb' % (float(n) / K)
|
||||||
|
else:
|
||||||
|
return '%d' % n
|
||||||
|
|
||||||
|
def callback(progress):
|
||||||
|
global transferred
|
||||||
|
transferred = transferred+len(progress)
|
||||||
|
print("Uploaded {}".format(convert_bytes(transferred),))
|
||||||
|
|
||||||
|
ftp_server = os.environ.get("FTP_SERVER") or sys.argv[1]
|
||||||
|
ftp_username = os.environ.get("FTP_USERNAME") or sys.argv[2]
|
||||||
|
ftp_password = os.environ.get("FTP_PASSWORD") or sys.argv[3]
|
||||||
|
version = os.environ.get("CI_COMMIT_TAG") or "latest"
|
||||||
|
version = version.replace("v", "")
|
||||||
|
|
||||||
|
print("Uploading files to the Socializer server...")
|
||||||
|
connection = ftplib.FTP(ftp_server)
|
||||||
|
print("Connected to FTP server {}".format(ftp_server,))
|
||||||
|
connection.login(user=ftp_username, passwd=ftp_password)
|
||||||
|
#connection.prot_p()
|
||||||
|
print("Logged in successfully")
|
||||||
|
connection.cwd("music_dl")
|
||||||
|
if version not in connection.nlst():
|
||||||
|
print("Creating version directory {} because does not exists...".format(version,))
|
||||||
|
connection.mkd(version)
|
||||||
|
|
||||||
|
if "update" not in connection.nlst():
|
||||||
|
print("Creating update info directory because does not exists...")
|
||||||
|
connection.mkd("update")
|
||||||
|
connection.cwd(version)
|
||||||
|
print("Moved into version directory")
|
||||||
|
files = glob.glob("*.zip")+glob.glob("*.exe")+glob.glob("*.json")
|
||||||
|
print("These files will be uploaded into the version folder: {}".format(files,))
|
||||||
|
for file in files:
|
||||||
|
transferred = 0
|
||||||
|
print("Uploading {}".format(file,))
|
||||||
|
with open(file, "rb") as f:
|
||||||
|
if file.endswith("json"):
|
||||||
|
connection.storbinary('STOR ../update/%s' % file, f, callback=callback, blocksize=1024*1024)
|
||||||
|
else:
|
||||||
|
connection.storbinary('STOR %s' % file, f, callback=callback, blocksize=1024*1024)
|
||||||
|
print("Upload completed. exiting...")
|
||||||
|
connection.quit()
|
@ -1,14 +1,24 @@
|
|||||||
[main]
|
[main]
|
||||||
volume = integer(default=50)
|
volume = integer(default=50)
|
||||||
language = string(default="system")
|
language = string(default="system")
|
||||||
output_device = string(default="")
|
output_device = string(default="Default")
|
||||||
|
|
||||||
[services]
|
[services]
|
||||||
[[tidal]]
|
[[tidal]]
|
||||||
enabled = boolean(default=True)
|
enabled = boolean(default=True)
|
||||||
username = string(default="")
|
session_id=string(default="")
|
||||||
password = string(default="")
|
token_type=string(default="")
|
||||||
|
access_token=string(default="")
|
||||||
|
refresh_token=string(default="")
|
||||||
quality=string(default="high")
|
quality=string(default="high")
|
||||||
|
avoid_transcoding = boolean(default=False)
|
||||||
|
include_albums = boolean(default=True)
|
||||||
|
include_compilations = boolean(default=True)
|
||||||
|
include_singles = boolean(default=True)
|
||||||
|
|
||||||
|
[[vk]]
|
||||||
|
enabled = boolean(default=True)
|
||||||
|
max_results = integer(default=20)
|
||||||
|
|
||||||
[[youtube]]
|
[[youtube]]
|
||||||
enabled = boolean(default=True)
|
enabled = boolean(default=True)
|
||||||
@ -16,4 +26,4 @@ max_results = integer(default=20)
|
|||||||
transcode = boolean(default=True)
|
transcode = boolean(default=True)
|
||||||
|
|
||||||
[[zaycev]]
|
[[zaycev]]
|
||||||
enabled = boolean(default=True)
|
enabled = boolean(default=False)
|
@ -1,17 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
|
||||||
python_version = int(sys.version[0])
|
|
||||||
name = "MusicDL"
|
name = "MusicDL"
|
||||||
version = "0.6"
|
author = "MCV Software"
|
||||||
author = "Manuel Cortéz"
|
authorEmail = "info@mcvsoftware.com"
|
||||||
authorEmail = "manuel@manuelcortez.net"
|
copyright = "Copyright (C) 2019-2022, MCV Software"
|
||||||
copyright = "Copyright (C) 2019, Manuel Cortez"
|
|
||||||
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
|
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
|
||||||
url = "https://manuelcortez.net/music_dl"
|
url = "https://mcvsoftware.com/music_dl"
|
||||||
update_url = "https://manuelcortez.net/music_dl/update"
|
|
||||||
# The short name will be used for detecting translation files. See languageHandler for more details.
|
# The short name will be used for detecting translation files. See languageHandler for more details.
|
||||||
short_name = "musicdl"
|
short_name = "musicdl"
|
||||||
translators = [_(u"Manuel Cortez (Spanish)"), _("Valeria K (Russian)"), ]
|
translators = [_(u"Manuel Cortez (Spanish)")]
|
||||||
bts_name = "music_dl"
|
bts_name = "music_dl"
|
||||||
bts_access_token = "fe3j2ijirvevv9"
|
bts_access_token = "fe3j2ijirvevv9"
|
||||||
bts_url = "https://issues.manuelcortez.net"
|
bts_url = "https://issues.manuelcortez.net"
|
||||||
|
update_url = "https://files.mcvsoftware.com/music_dl/update/latest.json"
|
||||||
|
version = "2020.07.23"
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
if sys.version[0] == "3":
|
if sys.version[0] == "3":
|
||||||
raise ImportError()
|
raise ImportError()
|
||||||
import os
|
import os
|
||||||
import paths
|
import paths
|
||||||
|
|
||||||
def get():
|
def get():
|
||||||
return os.path.join(paths.app_path(), "cacerts.txt")
|
return os.path.join(paths.app_path(), "cacerts.txt")
|
||||||
|
@ -12,7 +12,6 @@ MAINSPEC = "app-configuration.defaults"
|
|||||||
|
|
||||||
app = None
|
app = None
|
||||||
def setup ():
|
def setup ():
|
||||||
global app
|
global app
|
||||||
log.debug("Loading global app settings...")
|
log.debug("Loading global app settings...")
|
||||||
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))
|
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))
|
||||||
|
|
||||||
|
@ -8,66 +8,66 @@ class ConfigLoadError(Exception): pass
|
|||||||
def load_config(config_path, configspec_path=None, *args, **kwargs):
|
def load_config(config_path, configspec_path=None, *args, **kwargs):
|
||||||
# if os.path.exists(config_path):
|
# if os.path.exists(config_path):
|
||||||
# clean_config(config_path)
|
# clean_config(config_path)
|
||||||
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
|
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
|
||||||
try:
|
try:
|
||||||
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
|
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
|
||||||
except ParseError:
|
except ParseError:
|
||||||
raise ConfigLoadError("Unable to load %r" % config_path)
|
raise ConfigLoadError("Unable to load %r" % config_path)
|
||||||
validator = Validator()
|
validator = Validator()
|
||||||
validated = config.validate(validator, copy=True)
|
validated = config.validate(validator, copy=True)
|
||||||
if validated == True:
|
if validated == True:
|
||||||
config.write()
|
config.write()
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def is_blank(arg):
|
def is_blank(arg):
|
||||||
"Check if a line is blank."
|
"Check if a line is blank."
|
||||||
for c in arg:
|
for c in arg:
|
||||||
if c not in string.whitespace:
|
if c not in string.whitespace:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
def get_keys(path):
|
def get_keys(path):
|
||||||
"Gets the keys of a configobj config file."
|
"Gets the keys of a configobj config file."
|
||||||
res=[]
|
res=[]
|
||||||
fin=open(path)
|
fin=open(path)
|
||||||
for line in fin:
|
for line in fin:
|
||||||
if not is_blank(line):
|
if not is_blank(line):
|
||||||
res.append(line[0:line.find('=')].strip())
|
res.append(line[0:line.find('=')].strip())
|
||||||
fin.close()
|
fin.close()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def hist(keys):
|
def hist(keys):
|
||||||
"Generates a histogram of an iterable."
|
"Generates a histogram of an iterable."
|
||||||
res={}
|
res={}
|
||||||
for k in keys:
|
for k in keys:
|
||||||
res[k]=res.setdefault(k,0)+1
|
res[k]=res.setdefault(k,0)+1
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def find_problems(hist):
|
def find_problems(hist):
|
||||||
"Takes a histogram and returns a list of items occurring more than once."
|
"Takes a histogram and returns a list of items occurring more than once."
|
||||||
res=[]
|
res=[]
|
||||||
for k,v in hist.items():
|
for k,v in hist.items():
|
||||||
if v>1:
|
if v>1:
|
||||||
res.append(k)
|
res.append(k)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def clean_config(path):
|
def clean_config(path):
|
||||||
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
|
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
|
||||||
orig=[]
|
orig=[]
|
||||||
cleaned=[]
|
cleaned=[]
|
||||||
fin=open(path)
|
fin=open(path)
|
||||||
for line in fin:
|
for line in fin:
|
||||||
orig.append(line)
|
orig.append(line)
|
||||||
fin.close()
|
fin.close()
|
||||||
for p in find_problems(hist(get_keys(path))):
|
for p in find_problems(hist(get_keys(path))):
|
||||||
for o in orig:
|
for o in orig:
|
||||||
o.strip()
|
o.strip()
|
||||||
if p not in o:
|
if p not in o:
|
||||||
cleaned.append(o)
|
cleaned.append(o)
|
||||||
if len(cleaned) != 0:
|
if len(cleaned) != 0:
|
||||||
cam=open(path,'w')
|
cam=open(path,'w')
|
||||||
for c in cleaned:
|
for c in cleaned:
|
||||||
cam.write(c)
|
cam.write(c)
|
||||||
cam.close()
|
cam.close()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -1,52 +1,44 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import config
|
import config
|
||||||
from utils import get_extractors
|
from utils import get_services
|
||||||
from wxUI.configuration import configurationDialog
|
from wxUI.configuration import configurationDialog
|
||||||
from . import player
|
from . import player
|
||||||
|
|
||||||
class configuration(object):
|
class configuration(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.view = configurationDialog(_("Settings"))
|
self.view = configurationDialog(_("Settings"))
|
||||||
self.create_config()
|
self.create_config()
|
||||||
self.view.get_response()
|
self.view.get_response()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def create_config(self):
|
def create_config(self):
|
||||||
self.output_devices = player.player.get_output_devices()
|
self.output_devices = player.player.get_output_devices()
|
||||||
self.view.create_general(output_devices=[i["name"] for i in self.output_devices])
|
self.view.create_general(output_devices=[i for i in self.output_devices])
|
||||||
current_output_device = config.app["main"]["output_device"]
|
current_output_device = config.app["main"]["output_device"]
|
||||||
for i in self.output_devices:
|
for i in self.output_devices:
|
||||||
# here we must compare against the str version of the vlc's device identifier.
|
# here we must compare against the str version of the vlc's device identifier.
|
||||||
if str(i["id"]) == current_output_device:
|
if i == current_output_device:
|
||||||
self.view.set_value("general", "output_device", i["name"])
|
self.view.set_value("general", "output_device", i)
|
||||||
break
|
break
|
||||||
self.view.realize()
|
self.view.realize()
|
||||||
extractors = get_extractors(import_all=True)
|
extractors = get_services(import_all=True)
|
||||||
for i in extractors:
|
for i in extractors:
|
||||||
if hasattr(i, "settings"):
|
if hasattr(i, "settings"):
|
||||||
panel = getattr(i, "settings")(self.view.notebook)
|
panel = getattr(i, "settings")(self.view.notebook)
|
||||||
self.view.notebook.InsertSubPage(1, panel, panel.name)
|
self.view.notebook.InsertSubPage(1, panel, panel.name)
|
||||||
panel.load()
|
panel.load()
|
||||||
if hasattr(panel, "on_enabled"):
|
if hasattr(panel, "on_enabled"):
|
||||||
panel.on_enabled()
|
panel.on_enabled()
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
selected_output_device = self.view.get_value("general", "output_device")
|
selected_output_device = self.view.get_value("general", "output_device")
|
||||||
selected_device_id = None
|
if config.app["main"]["output_device"] != selected_output_device:
|
||||||
for i in self.output_devices:
|
config.app["main"]["output_device"] = selected_output_device
|
||||||
# Vlc returns everything as bytes object whereas WX works with string objects, so I need to convert the wx returned string to bytes before
|
player.player.set_output_device(config.app["main"]["output_device"])
|
||||||
# Otherwise the comparison will be false.
|
for i in range(0, self.view.notebook.GetPageCount()):
|
||||||
# toDo: Check if utf-8 would be enough or we'd have to use the fylesystem encode for handling this.
|
page = self.view.notebook.GetPage(i)
|
||||||
if i["name"] == bytes(selected_output_device, "utf-8"):
|
if hasattr(page, "save"):
|
||||||
selected_device_id = i["id"]
|
page.save()
|
||||||
break
|
config.app.write()
|
||||||
if config.app["main"]["output_device"] != selected_device_id:
|
|
||||||
config.app["main"]["output_device"] = selected_device_id
|
|
||||||
player.player.set_output_device(config.app["main"]["output_device"])
|
|
||||||
for i in range(0, self.view.notebook.GetPageCount()):
|
|
||||||
page = self.view.notebook.GetPage(i)
|
|
||||||
if hasattr(page, "save"):
|
|
||||||
page.save()
|
|
||||||
config.app.write()
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
""" main controller for MusicDL"""
|
""" main controller for MusicDL"""
|
||||||
from __future__ import unicode_literals # at top of module
|
|
||||||
import types
|
import types
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import wx
|
import wx
|
||||||
@ -13,259 +12,262 @@ from pubsub import pub
|
|||||||
from issueReporter import issueReporter
|
from issueReporter import issueReporter
|
||||||
from wxUI import mainWindow, menus
|
from wxUI import mainWindow, menus
|
||||||
from update import updater
|
from update import updater
|
||||||
from utils import get_extractors
|
from utils import get_services
|
||||||
from . import player, configuration
|
from . import player, configuration
|
||||||
|
|
||||||
log = logging.getLogger("controller.main")
|
log = logging.getLogger("controller.main")
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Controller, self).__init__()
|
super(Controller, self).__init__()
|
||||||
log.debug("Starting main controller...")
|
log.debug("Starting main controller...")
|
||||||
# Setting up the player object
|
# Setting up the player object
|
||||||
player.setup()
|
player.setup()
|
||||||
# Get main window
|
# Get main window
|
||||||
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_extractors()])
|
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_services()])
|
||||||
log.debug("Main window created")
|
log.debug("Main window created")
|
||||||
self.window.change_status(_(u"Ready"))
|
self.window.change_status(_(u"Ready"))
|
||||||
# Here we will save results for searches as song objects.
|
# Here we will save results for searches as song objects.
|
||||||
self.results = []
|
self.results = []
|
||||||
self.connect_events()
|
self.connect_events()
|
||||||
self.timer = wx.Timer(self.window)
|
self.timer = wx.Timer(self.window)
|
||||||
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
|
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
|
||||||
self.timer.Start(75)
|
self.timer.Start(75)
|
||||||
self.window.vol_slider.SetValue(player.player.volume)
|
self.window.vol_slider.SetValue(player.player.volume)
|
||||||
# Shows window.
|
# Shows window.
|
||||||
utils.call_threaded(updater.do_update)
|
utils.call_threaded(updater.do_update)
|
||||||
log.debug("Music DL is ready")
|
log.debug("Music DL is ready")
|
||||||
self.window.Show()
|
self.window.Show()
|
||||||
|
|
||||||
def get_status_info(self):
|
def get_status_info(self):
|
||||||
""" Formatting string for status bar messages """
|
""" Formatting string for status bar messages """
|
||||||
if len(self.results) > 0:
|
if len(self.results) > 0:
|
||||||
results = _(u"Showing {0} results.").format(len(self.results))
|
results = _(u"Showing {0} results.").format(len(self.results))
|
||||||
else:
|
else:
|
||||||
results = u""
|
results = u""
|
||||||
if player.player.shuffle:
|
if player.player.shuffle:
|
||||||
shuffle = _(u"Shuffle on")
|
shuffle = _(u"Shuffle on")
|
||||||
else:
|
else:
|
||||||
shuffle = u""
|
shuffle = u""
|
||||||
final = u"{0} {1}".format(results, shuffle)
|
final = u"{0} {1}".format(results, shuffle)
|
||||||
return final
|
return final
|
||||||
|
|
||||||
def connect_events(self):
|
def connect_events(self):
|
||||||
""" connects all widgets to their corresponding events."""
|
""" connects all widgets to their corresponding events."""
|
||||||
log.debug("Binding events...")
|
log.debug("Binding events...")
|
||||||
widgetUtils.connect_event(self.window.search, widgetUtils.BUTTON_PRESSED, self.on_search)
|
widgetUtils.connect_event(self.window.search, widgetUtils.BUTTON_PRESSED, self.on_search)
|
||||||
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
|
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
|
||||||
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
|
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_down, menuitem=self.window.player_volume_down)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_down, menuitem=self.window.player_volume_down)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_up, menuitem=self.window.player_volume_up)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_up, menuitem=self.window.player_volume_up)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_mute, menuitem=self.window.player_mute)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_mute, menuitem=self.window.player_mute)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_shuffle, menuitem=self.window.player_shuffle)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_shuffle, menuitem=self.window.player_shuffle)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_check_for_updates, menuitem=self.window.check_for_updates)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_check_for_updates, menuitem=self.window.check_for_updates)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_changelog, menuitem=self.window.changelog)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_changelog, menuitem=self.window.changelog)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_website, menuitem=self.window.website)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_website, menuitem=self.window.website)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report)
|
||||||
widgetUtils.connect_event(self.window.previous, widgetUtils.BUTTON_PRESSED, self.on_previous)
|
widgetUtils.connect_event(self.window.previous, widgetUtils.BUTTON_PRESSED, self.on_previous)
|
||||||
widgetUtils.connect_event(self.window.play, widgetUtils.BUTTON_PRESSED, self.on_play_pause)
|
widgetUtils.connect_event(self.window.play, widgetUtils.BUTTON_PRESSED, self.on_play_pause)
|
||||||
widgetUtils.connect_event(self.window.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
|
widgetUtils.connect_event(self.window.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
|
||||||
widgetUtils.connect_event(self.window.next, widgetUtils.BUTTON_PRESSED, self.on_next)
|
widgetUtils.connect_event(self.window.next, widgetUtils.BUTTON_PRESSED, self.on_next)
|
||||||
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_set_volume, self.window.vol_slider)
|
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_set_volume, self.window.vol_slider)
|
||||||
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_set_volume, self.window.vol_slider)
|
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_set_volume, self.window.vol_slider)
|
||||||
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_time_change, self.window.time_slider)
|
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_time_change, self.window.time_slider)
|
||||||
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_time_change, self.window.time_slider)
|
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_time_change, self.window.time_slider)
|
||||||
self.window.list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_play)
|
self.window.list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_play)
|
||||||
self.window.list.Bind(wx.EVT_CONTEXT_MENU, self.on_context)
|
self.window.list.Bind(wx.EVT_CONTEXT_MENU, self.on_context)
|
||||||
self.window.Bind(wx.EVT_CLOSE, self.on_close)
|
self.window.Bind(wx.EVT_CLOSE, self.on_close)
|
||||||
pub.subscribe(self.change_status, "change_status")
|
pub.subscribe(self.change_status, "change_status")
|
||||||
pub.subscribe(self.on_download_finished, "download_finished")
|
pub.subscribe(self.on_download_finished, "download_finished")
|
||||||
pub.subscribe(self.on_notify, "notify")
|
pub.subscribe(self.on_notify, "notify")
|
||||||
pub.subscribe(self.on_update_progress, "update-progress")
|
pub.subscribe(self.on_update_progress, "update-progress")
|
||||||
|
|
||||||
# Event functions. These functions will call other functions in a thread and are bound to widget events.
|
# Event functions. These functions will call other functions in a thread and are bound to widget events.
|
||||||
|
|
||||||
def on_update_progress(self, value):
|
def on_update_progress(self, value):
|
||||||
wx.CallAfter(self.window.progressbar.SetValue, value)
|
wx.CallAfter(self.window.progressbar.SetValue, value)
|
||||||
|
|
||||||
def on_settings(self, *args, **kwargs):
|
def on_settings(self, *args, **kwargs):
|
||||||
settings = configuration.configuration()
|
settings = configuration.configuration()
|
||||||
self.reload_extractors()
|
self.reload_extractors()
|
||||||
|
|
||||||
def on_search(self, *args, **kwargs):
|
def on_search(self, *args, **kwargs):
|
||||||
text = self.window.get_text()
|
text = self.window.get_text()
|
||||||
if text == "":
|
if text == "":
|
||||||
return
|
return
|
||||||
extractor = self.window.extractor.GetValue()
|
extractor = self.window.extractor.GetValue()
|
||||||
self.change_status(_(u"Searching {0}... ").format(text))
|
self.change_status(_(u"Searching {0}... ").format(text))
|
||||||
utils.call_threaded(self.search, text=text, extractor=extractor)
|
utils.call_threaded(self.search, text=text, extractor=extractor)
|
||||||
|
|
||||||
def on_activated(self, *args, **kwargs):
|
def on_activated(self, *args, **kwargs):
|
||||||
self.on_play()
|
self.on_play()
|
||||||
|
|
||||||
def on_keypress(self, ev):
|
def on_keypress(self, ev):
|
||||||
if ev.GetKeyCode() == wx.WXK_RETURN:
|
if ev.GetKeyCode() == wx.WXK_RETURN:
|
||||||
return self.on_play()
|
return self.on_play()
|
||||||
elif ev.GetKeyCode() == wx.WXK_SPACE:
|
elif ev.GetKeyCode() == wx.WXK_SPACE:
|
||||||
return self.on_play_pause()
|
return self.on_play_pause()
|
||||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
|
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
|
||||||
position = player.player.player.get_time()
|
position = player.player.player.get_position()
|
||||||
if position > 5000:
|
if position > 5000:
|
||||||
player.player.player.set_time(position-5000)
|
player.player.player.set_position(position-5000)
|
||||||
else:
|
else:
|
||||||
player.player.player.set_time(0)
|
player.player.player.set_position(0)
|
||||||
return
|
return
|
||||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
|
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
|
||||||
position = player.player.player.get_time()
|
position = player.player.player.get_position()
|
||||||
player.player.player.set_time(position+5000)
|
player.player.player.set_position(position+5000)
|
||||||
return
|
return
|
||||||
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
|
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
|
||||||
return self.on_volume_up()
|
return self.on_volume_up()
|
||||||
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
|
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
|
||||||
return self.on_volume_down()
|
return self.on_volume_down()
|
||||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
|
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
|
||||||
return self.on_previous()
|
return self.on_previous()
|
||||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
|
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
|
||||||
return self.on_next()
|
return self.on_next()
|
||||||
ev.Skip()
|
ev.Skip()
|
||||||
|
|
||||||
def on_play_pause(self, *args, **kwargs):
|
def on_play_pause(self, *args, **kwargs):
|
||||||
if player.player.player.is_playing() == 1:
|
if player.player.player.playback_time != None and player.player.player.pause == False:
|
||||||
self.window.play.SetLabel(_(u"Play"))
|
self.window.play.SetLabel(_("Play"))
|
||||||
return player.player.pause()
|
return player.player.pause()
|
||||||
else:
|
else:
|
||||||
self.window.play.SetLabel(_(u"Pause"))
|
self.window.play.SetLabel(_("Pause"))
|
||||||
return player.player.player.play()
|
player.player.player.pause = False
|
||||||
|
|
||||||
def on_next(self, *args, **kwargs):
|
def on_next(self, *args, **kwargs):
|
||||||
return utils.call_threaded(player.player.next)
|
return utils.call_threaded(player.player.next)
|
||||||
|
|
||||||
def on_previous(self, *args, **kwargs):
|
def on_previous(self, *args, **kwargs):
|
||||||
return utils.call_threaded(player.player.previous)
|
return utils.call_threaded(player.player.previous)
|
||||||
|
|
||||||
def on_play(self, *args, **kwargs):
|
def on_play(self, *args, **kwargs):
|
||||||
items = self.results[::]
|
items = self.results[::]
|
||||||
playing_item = self.window.get_item()
|
playing_item = self.window.get_item()
|
||||||
self.window.play.SetLabel(_(u"Pause"))
|
self.window.play.SetLabel(_(u"Pause"))
|
||||||
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
|
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
|
||||||
|
|
||||||
def on_stop(self, *args, **kwargs):
|
def on_stop(self, *args, **kwargs):
|
||||||
player.player.stop()
|
player.player.stop()
|
||||||
self.window.play.SetLabel(_(u"Play"))
|
self.window.play.SetLabel(_(u"Play"))
|
||||||
|
|
||||||
def on_volume_down(self, *args, **kwargs):
|
def on_volume_down(self, *args, **kwargs):
|
||||||
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
|
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
|
||||||
self.on_set_volume()
|
self.on_set_volume()
|
||||||
|
|
||||||
def on_volume_up(self, *args, **kwargs):
|
def on_volume_up(self, *args, **kwargs):
|
||||||
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
|
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
|
||||||
self.on_set_volume()
|
self.on_set_volume()
|
||||||
|
|
||||||
def on_mute(self, *args, **kwargs):
|
def on_mute(self, *args, **kwargs):
|
||||||
self.window.vol_slider.SetValue(0)
|
self.window.vol_slider.SetValue(0)
|
||||||
self.on_set_volume()
|
self.on_set_volume()
|
||||||
|
|
||||||
def on_shuffle(self, *args, **kwargs):
|
def on_shuffle(self, *args, **kwargs):
|
||||||
player.player.shuffle = self.window.player_shuffle.IsChecked()
|
player.player.shuffle = self.window.player_shuffle.IsChecked()
|
||||||
|
|
||||||
def on_context(self, *args, **kwargs):
|
def on_context(self, *args, **kwargs):
|
||||||
item = self.window.get_item()
|
item = self.window.get_item()
|
||||||
if item == -1:
|
if item == -1:
|
||||||
return wx.Bell()
|
return wx.Bell()
|
||||||
menu = menus.contextMenu()
|
menu = menus.contextMenu()
|
||||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
|
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
|
||||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
|
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
|
||||||
self.window.PopupMenu(menu, wx.GetMousePosition())
|
self.window.PopupMenu(menu, wx.GetMousePosition())
|
||||||
menu.Destroy()
|
menu.Destroy()
|
||||||
|
|
||||||
def on_download(self, *args, **kwargs):
|
def on_download(self, *args, **kwargs):
|
||||||
item = self.results[self.window.get_item()]
|
item = self.results[self.window.get_item()]
|
||||||
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
|
if item.download_url == "":
|
||||||
f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format())
|
item.get_download_url()
|
||||||
if item.download_url == "":
|
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
|
||||||
item.get_download_url()
|
f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format())
|
||||||
path = self.window.get_destination_path(f)
|
path = self.window.get_destination_path(utils.safe_filename(f))
|
||||||
if path != None:
|
if path != None:
|
||||||
log.debug("User has requested the following path: {0}".format(path,))
|
log.debug("User has requested the following path: {0}".format(path,))
|
||||||
if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder
|
if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder
|
||||||
utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format())
|
utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format(), metadata=item.get_metadata())
|
||||||
else:
|
else:
|
||||||
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
|
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
|
||||||
utils.call_threaded(utils.download_file, item.download_url, path)
|
utils.call_threaded(utils.download_file, item.download_url, path, metadata=item.get_metadata())
|
||||||
|
|
||||||
def on_set_volume(self, *args, **kwargs):
|
def on_set_volume(self, *args, **kwargs):
|
||||||
volume = self.window.vol_slider.GetValue()
|
volume = self.window.vol_slider.GetValue()
|
||||||
player.player.volume = volume
|
player.player.volume = volume
|
||||||
|
|
||||||
def on_time_change(self, event, *args, **kwargs):
|
def on_time_change(self, event, *args, **kwargs):
|
||||||
p = event.GetPosition()
|
p = event.GetPosition()
|
||||||
player.player.player.set_position(p/100.0)
|
if player.player.player != None:
|
||||||
event.Skip()
|
progress = int((player.player.player.get_length()/100)*p)
|
||||||
|
player.player.player.set_position(progress)
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def on_timer(self, *args, **kwargs):
|
def on_timer(self, *args, **kwargs):
|
||||||
if not self.window.time_slider.HasFocus():
|
if not self.window.time_slider.HasFocus():
|
||||||
progress = player.player.player.get_position()*100
|
if player.player.player.playback_time != None:
|
||||||
self.window.time_slider.SetValue(progress)
|
progress = (player.player.player.playback_time/(player.player.player.playback_time+player.player.player.playtime_remaining))*100
|
||||||
|
self.window.time_slider.SetValue(int(progress))
|
||||||
|
|
||||||
def on_close(self, event):
|
def on_close(self, event):
|
||||||
log.debug("Exiting...")
|
log.debug("Exiting...")
|
||||||
self.timer.Stop()
|
self.timer.Stop()
|
||||||
pub.unsubscribe(self.on_download_finished, "download_finished")
|
pub.unsubscribe(self.on_download_finished, "download_finished")
|
||||||
config.app.write()
|
config.app.write()
|
||||||
event.Skip()
|
event.Skip()
|
||||||
widgetUtils.exit_application()
|
widgetUtils.exit_application()
|
||||||
|
|
||||||
def change_status(self, status):
|
def change_status(self, status):
|
||||||
""" Function used for changing the status bar from outside the main controller module."""
|
""" Function used for changing the status bar from outside the main controller module."""
|
||||||
self.window.change_status(u"{0} {1}".format(status, self.get_status_info()))
|
self.window.change_status(u"{0} {1}".format(status, self.get_status_info()))
|
||||||
|
|
||||||
def on_visit_website(self, *args, **kwargs):
|
def on_visit_website(self, *args, **kwargs):
|
||||||
webbrowser.open_new_tab(application.url)
|
webbrowser.open_new_tab(application.url)
|
||||||
|
|
||||||
def on_report_error(self, *args, **kwargs):
|
def on_report_error(self, *args, **kwargs):
|
||||||
r = issueReporter.reportBug()
|
r = issueReporter.reportBug()
|
||||||
|
|
||||||
def on_visit_changelog(self, *args, **kwargs):
|
def on_visit_changelog(self, *args, **kwargs):
|
||||||
webbrowser.open_new_tab(application.url+"/news")
|
webbrowser.open_new_tab(application.url+"/news")
|
||||||
|
|
||||||
def on_check_for_updates(self, *args, **kwargs):
|
def on_check_for_updates(self, *args, **kwargs):
|
||||||
utils.call_threaded(updater.do_update)
|
utils.call_threaded(updater.do_update)
|
||||||
|
|
||||||
def on_download_finished(self, file):
|
def on_download_finished(self, file):
|
||||||
title = "MusicDL"
|
title = "MusicDL"
|
||||||
msg = _(u"File downloaded: {0}").format(file,)
|
msg = _(u"File downloaded: {0}").format(file,)
|
||||||
self.window.notify(title, msg)
|
self.window.notify(title, msg)
|
||||||
|
|
||||||
def on_notify(self, title, message):
|
def on_notify(self, title, message):
|
||||||
self.window.notify(title, message)
|
self.window.notify(title, message)
|
||||||
|
|
||||||
# real functions. These functions really are doing the work.
|
# real functions. These functions really are doing the work.
|
||||||
def search(self, text, extractor, *args, **kwargs):
|
def search(self, text, extractor, *args, **kwargs):
|
||||||
extractors = get_extractors()
|
extractors = get_services()
|
||||||
for i in extractors:
|
for i in extractors:
|
||||||
if extractor == i.interface.name:
|
if extractor == i.interface.name:
|
||||||
self.extractor = i.interface()
|
self.extractor = i.interface()
|
||||||
break
|
break
|
||||||
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
|
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
|
||||||
wx.CallAfter(self.window.list.Clear)
|
wx.CallAfter(self.window.list.Clear)
|
||||||
self.extractor.search(text)
|
self.extractor.search(text)
|
||||||
self.results = self.extractor.results
|
self.results = self.extractor.results
|
||||||
for i in self.results:
|
for i in self.results:
|
||||||
wx.CallAfter(self.window.list.Append, i.format_track())
|
wx.CallAfter(self.window.list.Append, i.format_track())
|
||||||
if len(self.results) == 0:
|
if len(self.results) == 0:
|
||||||
wx.CallAfter(self.change_status, _(u"No results found. "))
|
wx.CallAfter(self.change_status, _(u"No results found. "))
|
||||||
else:
|
else:
|
||||||
wx.CallAfter(self.change_status, u"")
|
wx.CallAfter(self.change_status, u"")
|
||||||
wx.CallAfter(self.window.list.SetFocus)
|
wx.CallAfter(self.window.list.SetFocus)
|
||||||
|
|
||||||
def reload_extractors(self):
|
def reload_extractors(self):
|
||||||
extractors = [i.interface.name for i in get_extractors()]
|
extractors = [i.interface.name for i in get_services()]
|
||||||
self.window.extractor.SetItems(extractors)
|
self.window.extractor.SetItems(extractors)
|
||||||
self.window.extractor.SetValue(extractors[0])
|
self.window.extractor.SetValue(extractors[0])
|
||||||
|
@ -1,169 +1,112 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals # at top of module
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import vlc
|
|
||||||
import logging
|
import logging
|
||||||
import config
|
import config
|
||||||
import time
|
import time
|
||||||
|
import mpv
|
||||||
from pubsub import pub
|
from pubsub import pub
|
||||||
from utils import call_threaded
|
|
||||||
|
|
||||||
player = None
|
player = None
|
||||||
log = logging.getLogger("controller.player")
|
log = logging.getLogger("controller.player")
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
global player
|
global player
|
||||||
if player == None:
|
if player == None:
|
||||||
player = audioPlayer()
|
player = audioPlayer()
|
||||||
|
|
||||||
class audioPlayer(object):
|
class audioPlayer(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.is_playing = False
|
self.is_playing = False
|
||||||
self.vol = config.app["main"]["volume"]
|
self.vol = config.app["main"]["volume"]
|
||||||
self.is_working = False
|
self.is_working = False
|
||||||
self.queue = []
|
self.queue = []
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
self.queue_pos = 0
|
self.queue_pos = 0
|
||||||
self.shuffle = False
|
self.shuffle = False
|
||||||
self.instance = vlc.Instance()
|
self.player = mpv.MPV()
|
||||||
self.player = self.instance.media_player_new()
|
|
||||||
log.debug("Media player instantiated.")
|
|
||||||
self.event_manager = self.player.event_manager()
|
|
||||||
self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback)
|
|
||||||
self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
|
|
||||||
log.debug("Bound media playback events.")
|
|
||||||
# configure output device
|
|
||||||
self.set_output_device(config.app["main"]["output_device"])
|
|
||||||
|
|
||||||
def get_output_devices(self):
|
# Fires at the end of every file and attempts to play the next one.
|
||||||
""" Retrieve enabled output devices so we can switch or use those later. """
|
@self.player.event_callback('end-file')
|
||||||
log.debug("Retrieving output devices...")
|
def handle_end_idle(event):
|
||||||
devices = []
|
if event.as_dict()["reason"] == b"aborted" or event.as_dict()["reason"] == b"stop":
|
||||||
mods = self.player.audio_output_device_enum()
|
return
|
||||||
if mods:
|
log.debug("Reached end of file stream.")
|
||||||
mod = mods
|
if len(self.queue) > 1:
|
||||||
while mod:
|
log.debug("Requesting next item...")
|
||||||
mod = mod.contents
|
self.next()
|
||||||
devices.append(dict(id=mod.device, name=mod.description))
|
|
||||||
mod = mod.next
|
|
||||||
vlc.libvlc_audio_output_device_list_release(mods)
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def set_output_device(self, device_id):
|
def get_output_devices(self):
|
||||||
""" Set Output device to be ued in LibVLC"""
|
""" Retrieve enabled output devices so we can switch or use those later. """
|
||||||
log.debug("Setting output audio device to {device}...".format(device=device_id,))
|
return None
|
||||||
self.player.audio_output_device_set(None, device_id)
|
|
||||||
|
|
||||||
def play(self, item):
|
def set_output_device(self, device_name):
|
||||||
self.stopped = True
|
""" Set Output device to be used in LibVLC"""
|
||||||
if self.is_working == False:
|
log.debug("Setting output audio device to {device}...".format(device=device_name,))
|
||||||
self.is_working = True
|
# config.app["main"]["output_device"] = "Default"
|
||||||
if item.download_url == "":
|
|
||||||
item.get_download_url()
|
|
||||||
log.debug("playing {0}...".format(item.download_url,))
|
|
||||||
self.stream_new = self.instance.media_new(item.download_url)
|
|
||||||
self.player.set_media(self.stream_new)
|
|
||||||
if self.player.play() == -1:
|
|
||||||
log.debug("Error when playing the file {0}".format(item.title,))
|
|
||||||
pub.sendMessage("change_status", status=_("Error playing {0}. {1}.").format(item.title, e.description))
|
|
||||||
self.stopped = True
|
|
||||||
self.is_working = False
|
|
||||||
self.next()
|
|
||||||
return
|
|
||||||
self.player.audio_set_volume(self.vol)
|
|
||||||
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
|
|
||||||
self.stopped = False
|
|
||||||
self.is_working = False
|
|
||||||
|
|
||||||
def next(self):
|
def play(self, item):
|
||||||
if len(self.queue) > 0:
|
self.stopped = True
|
||||||
if self.shuffle:
|
if self.is_working == False:
|
||||||
self.queue_pos = random.randint(0, len(self.queue)-1)
|
self.is_working = True
|
||||||
else:
|
if item.download_url == "":
|
||||||
if self.queue_pos < len(self.queue)-1:
|
item.get_download_url()
|
||||||
self.queue_pos += 1
|
log.debug("playing {0}...".format(item.download_url,))
|
||||||
else:
|
self.player.play(item.download_url)
|
||||||
self.queue_pos = 0
|
self.player.volume = self.vol
|
||||||
self.play(self.queue[self.queue_pos])
|
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
|
||||||
|
self.stopped = False
|
||||||
|
self.is_working = False
|
||||||
|
|
||||||
def previous(self):
|
def next(self):
|
||||||
if len(self.queue) > 0:
|
if len(self.queue) > 0:
|
||||||
if self.shuffle:
|
if self.shuffle:
|
||||||
self.queue_pos = random.randint(0, len(self.queue)-1)
|
self.queue_pos = random.randint(0, len(self.queue)-1)
|
||||||
else:
|
else:
|
||||||
if self.queue_pos > 0:
|
if self.queue_pos < len(self.queue)-1:
|
||||||
self.queue_pos -= 1
|
self.queue_pos += 1
|
||||||
else:
|
else:
|
||||||
self.queue_pos = len(self.queue)-1
|
self.queue_pos = 0
|
||||||
self.play(self.queue[self.queue_pos])
|
self.play(self.queue[self.queue_pos])
|
||||||
|
|
||||||
def stop(self):
|
def previous(self):
|
||||||
self.player.stop()
|
if len(self.queue) > 0:
|
||||||
self.stopped = True
|
if self.shuffle:
|
||||||
|
self.queue_pos = random.randint(0, len(self.queue)-1)
|
||||||
|
else:
|
||||||
|
if self.queue_pos > 0:
|
||||||
|
self.queue_pos -= 1
|
||||||
|
else:
|
||||||
|
self.queue_pos = len(self.queue)-1
|
||||||
|
self.play(self.queue[self.queue_pos])
|
||||||
|
|
||||||
def pause(self):
|
def stop(self):
|
||||||
self.player.pause()
|
self.player.stop()
|
||||||
if self.stopped == True:
|
self.stopped = True
|
||||||
self.stopped = False
|
|
||||||
else:
|
|
||||||
self.stopped = True
|
|
||||||
|
|
||||||
@property
|
def pause(self):
|
||||||
def volume(self):
|
self.player.pause = True
|
||||||
return self.vol
|
if self.stopped == True:
|
||||||
|
self.stopped = False
|
||||||
|
else:
|
||||||
|
self.stopped = True
|
||||||
|
|
||||||
@volume.setter
|
@property
|
||||||
def volume(self, vol):
|
def volume(self):
|
||||||
if vol <= 100 and vol >= 0:
|
return self.vol
|
||||||
config.app["main"]["volume"] = vol
|
|
||||||
self.vol = vol
|
|
||||||
self.player.audio_set_volume(self.vol)
|
|
||||||
|
|
||||||
def play_all(self, list_of_items, playing=0, shuffle=False):
|
@volume.setter
|
||||||
if list_of_items != self.queue:
|
def volume(self, vol):
|
||||||
self.queue = list_of_items
|
if vol <= 100 and vol >= 0:
|
||||||
self.shuffle = shuffle
|
config.app["main"]["volume"] = vol
|
||||||
self.queue_pos = playing
|
self.vol = vol
|
||||||
self.play(self.queue[self.queue_pos])
|
if self.player != None:
|
||||||
|
self.player.volume = self.vol
|
||||||
|
|
||||||
def end_callback(self, event, *args, **kwargs):
|
def play_all(self, list_of_items, playing=0, shuffle=False):
|
||||||
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4
|
if list_of_items != self.queue:
|
||||||
call_threaded(self.next)
|
self.queue = list_of_items
|
||||||
|
self.shuffle = shuffle
|
||||||
def transcode_audio(self, item, path, _format="mp3", bitrate=320):
|
self.queue_pos = playing
|
||||||
""" Converts given item to mp3. This method will be available when needed automatically."""
|
self.play(self.queue[self.queue_pos])
|
||||||
if item.download_url == "":
|
|
||||||
item.get_download_url()
|
|
||||||
log.debug("Download started: filename={0}, url={1}".format(path, item.download_url))
|
|
||||||
temporary_filename = "chunk_{0}".format(random.randint(0,2000000))
|
|
||||||
temporary_path = os.path.join(os.path.dirname(path), temporary_filename)
|
|
||||||
# Let's get a new VLC instance for transcoding this file.
|
|
||||||
transcoding_instance = vlc.Instance(*["--sout=#transcode{acodec=%s,ab=%d}:file{mux=raw,dst=\"%s\"}"% (_format, bitrate, temporary_path,)])
|
|
||||||
transcoder = transcoding_instance.media_player_new()
|
|
||||||
transcoder.set_mrl(item.download_url)
|
|
||||||
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(item.title,))
|
|
||||||
media = transcoder.get_media()
|
|
||||||
transcoder.play()
|
|
||||||
while True:
|
|
||||||
state = media.get_state()
|
|
||||||
pub.sendMessage("change_status", status=_("Downloading {0} ({1}%).").format(item.title, int(transcoder.get_position()*100)))
|
|
||||||
pub.sendMessage("update-progress", value=int(transcoder.get_position()*100))
|
|
||||||
if str(state) == 'State.Ended':
|
|
||||||
break
|
|
||||||
elif str(state) == 'state.error':
|
|
||||||
os.remove(temporary_path)
|
|
||||||
break
|
|
||||||
transcoder.release()
|
|
||||||
os.rename(temporary_path, path)
|
|
||||||
log.debug("Download finished sucsessfully.")
|
|
||||||
pub.sendMessage("download_finished", file=os.path.basename(path))
|
|
||||||
|
|
||||||
def playback_error(self, event):
|
|
||||||
pub.sendMessage("notify", title=_("Error"), message=_("There was an error while trying to access the file you have requested."))
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached)
|
|
||||||
if hasattr(self, "event_manager"):
|
|
||||||
self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
|
|
@ -1,70 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
""" Base components useful for all other extractors. """
|
|
||||||
import logging
|
|
||||||
import wx
|
|
||||||
import config
|
|
||||||
log = logging.getLogger("extractors.config")
|
|
||||||
|
|
||||||
class baseInterface(object):
|
|
||||||
name = "base"
|
|
||||||
enabled = False
|
|
||||||
needs_transcode = False
|
|
||||||
results = []
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(baseInterface, self).__init__()
|
|
||||||
log.debug("started extraction service for {0}".format(self.name,))
|
|
||||||
|
|
||||||
def search(self, text, *args, **kwargs):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get_download_url(self, url):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def format_track(self, item):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get_file_format(self):
|
|
||||||
return "mp3"
|
|
||||||
|
|
||||||
def transcoder_enabled(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
class song(object):
|
|
||||||
""" Represents a song in all services. Data will be filled by the service itself"""
|
|
||||||
|
|
||||||
def __init__(self, extractor):
|
|
||||||
self.extractor = extractor
|
|
||||||
self.bitrate = 0
|
|
||||||
self.title = ""
|
|
||||||
self.artist = ""
|
|
||||||
self.duration = ""
|
|
||||||
self.size = 0
|
|
||||||
self.url = ""
|
|
||||||
self.download_url = ""
|
|
||||||
self.info = None
|
|
||||||
|
|
||||||
def format_track(self):
|
|
||||||
return self.extractor.format_track(self)
|
|
||||||
|
|
||||||
def get_download_url(self):
|
|
||||||
self.download_url = self.extractor.get_download_url(self.url)
|
|
||||||
|
|
||||||
class baseSettings(wx.Panel):
|
|
||||||
config_section = "base"
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(baseSettings, self).__init__(*args, **kwargs)
|
|
||||||
self.map = []
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
for i in self.map:
|
|
||||||
config.app["services"][self.config_section][i[0]] = i[1].GetValue()
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
for i in self.map:
|
|
||||||
if i[0] in config.app["services"][self.config_section]:
|
|
||||||
i[1].SetValue(config.app["services"][self.config_section][i[0]])
|
|
||||||
else:
|
|
||||||
log.error("No key available: {key} on extractor {extractor}".format(key=i[0], extractor=self.config_section))
|
|
@ -1,168 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import logging
|
|
||||||
import webbrowser
|
|
||||||
import wx
|
|
||||||
import tidalapi
|
|
||||||
import config
|
|
||||||
from update.utils import seconds_to_string
|
|
||||||
from .import base
|
|
||||||
|
|
||||||
log = logging.getLogger("extractors.tidal.com")
|
|
||||||
|
|
||||||
class interface(base.baseInterface):
|
|
||||||
name = "tidal"
|
|
||||||
enabled = config.app["services"]["tidal"].get("enabled")
|
|
||||||
# This should not be enabled if credentials are not in config.
|
|
||||||
if config.app["services"]["tidal"]["username"] == "" or config.app["services"]["tidal"]["password"] == "":
|
|
||||||
enabled = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(interface, self).__init__()
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
# Assign quality or switch to high if not specified/not found.
|
|
||||||
if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]):
|
|
||||||
quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"])
|
|
||||||
else:
|
|
||||||
quality = tidalapi.Quality.high
|
|
||||||
_config = tidalapi.Config(quality=quality)
|
|
||||||
username = config.app["services"]["tidal"]["username"]
|
|
||||||
password = config.app["services"]["tidal"]["password"]
|
|
||||||
log.debug("Using quality: %s" % (quality,))
|
|
||||||
self.session = tidalapi.Session(config=_config)
|
|
||||||
self.session.login(username=username, password=password)
|
|
||||||
|
|
||||||
def get_file_format(self):
|
|
||||||
if config.app["services"]["tidal"]["quality"] == "lossless":
|
|
||||||
self.file_extension = "flac"
|
|
||||||
else:
|
|
||||||
self.file_extension = "mp3"
|
|
||||||
return self.file_extension
|
|
||||||
|
|
||||||
def transcoder_enabled(self):
|
|
||||||
if config.app["services"]["tidal"]["quality"] == "lossless":
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def search(self, text, page=1):
|
|
||||||
if text == "" or text == None:
|
|
||||||
raise ValueError("Text must be passed and should not be blank.")
|
|
||||||
log.debug("Retrieving data from Tidal...")
|
|
||||||
fieldtypes = ["artist", "album", "playlist"]
|
|
||||||
field = "track"
|
|
||||||
for i in fieldtypes:
|
|
||||||
if text.startswith(i+"://"):
|
|
||||||
field = i
|
|
||||||
text = text.replace(i+"://", "")
|
|
||||||
log.debug("Searching for %s..." % (field))
|
|
||||||
search_response = self.session.search(value=text, field=field)
|
|
||||||
self.results = []
|
|
||||||
if field == "track":
|
|
||||||
data = search_response.tracks
|
|
||||||
elif field == "artist":
|
|
||||||
data = []
|
|
||||||
artist = search_response.artists[0].id
|
|
||||||
albums = self.session.get_artist_albums(artist)
|
|
||||||
for album in albums:
|
|
||||||
tracks = self.session.get_album_tracks(album.id)
|
|
||||||
for track in tracks:
|
|
||||||
data.append(track)
|
|
||||||
compilations = self.session.get_artist_albums_other(artist)
|
|
||||||
for album in compilations:
|
|
||||||
tracks = self.session.get_album_tracks(album.id)
|
|
||||||
for track in tracks:
|
|
||||||
data.append(track)
|
|
||||||
singles = self.session.get_artist_albums_ep_singles(artist)
|
|
||||||
for album in singles:
|
|
||||||
tracks = self.session.get_album_tracks(album.id)
|
|
||||||
for track in tracks:
|
|
||||||
data.append(track)
|
|
||||||
for search_result in data:
|
|
||||||
s = base.song(self)
|
|
||||||
s.title = search_result.name
|
|
||||||
s.artist = search_result.artist.name
|
|
||||||
s.duration = seconds_to_string(search_result.duration)
|
|
||||||
s.url = search_result.id
|
|
||||||
s.info = search_result
|
|
||||||
self.results.append(s)
|
|
||||||
log.debug("{0} results found.".format(len(self.results)))
|
|
||||||
|
|
||||||
def get_download_url(self, url):
|
|
||||||
url = self.session.get_media_url(url)
|
|
||||||
if url.startswith("https://") or url.startswith("http://") == False:
|
|
||||||
url = "rtmp://"+url
|
|
||||||
return url
|
|
||||||
|
|
||||||
def format_track(self, item):
|
|
||||||
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
|
|
||||||
|
|
||||||
class settings(base.baseSettings):
|
|
||||||
name = _("Tidal")
|
|
||||||
config_section = "tidal"
|
|
||||||
|
|
||||||
def get_quality_list(self):
|
|
||||||
results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless"))
|
|
||||||
return results
|
|
||||||
|
|
||||||
def get_quality_value(self, *args, **kwargs):
|
|
||||||
q = self.get_quality_list()
|
|
||||||
for i in q.keys():
|
|
||||||
if q.get(i) == self.quality.GetStringSelection():
|
|
||||||
return i
|
|
||||||
|
|
||||||
def set_quality_value(self, value, *args, **kwargs):
|
|
||||||
q = self.get_quality_list()
|
|
||||||
for i in q.keys():
|
|
||||||
if i == value:
|
|
||||||
self.quality.SetStringSelection(q.get(i))
|
|
||||||
break
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(settings, self).__init__(parent=parent)
|
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
||||||
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
|
|
||||||
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
|
|
||||||
self.map.append(("enabled", self.enabled))
|
|
||||||
sizer.Add(self.enabled, 0, wx.ALL, 5)
|
|
||||||
username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address"))
|
|
||||||
self.username = wx.TextCtrl(self, wx.NewId())
|
|
||||||
usernamebox = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
usernamebox.Add(username, 0, wx.ALL, 5)
|
|
||||||
usernamebox.Add(self.username, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(usernamebox, 0, wx.ALL, 5)
|
|
||||||
self.map.append(("username", self.username))
|
|
||||||
|
|
||||||
password = wx.StaticText(self, wx.NewId(), _("Password"))
|
|
||||||
self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD)
|
|
||||||
passwordbox = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
passwordbox.Add(password, 0, wx.ALL, 5)
|
|
||||||
passwordbox.Add(self.password, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(passwordbox, 0, wx.ALL, 5)
|
|
||||||
self.map.append(("password", self.password))
|
|
||||||
self.get_account = wx.Button(self, wx.NewId(), _("You can subscribe for a tidal account here"))
|
|
||||||
self.get_account.Bind(wx.EVT_BUTTON, self.on_get_account)
|
|
||||||
sizer.Add(self.get_account, 0, wx.ALL, 5)
|
|
||||||
quality = wx.StaticText(self, wx.NewId(), _("Audio quality"))
|
|
||||||
self.quality = wx.ComboBox(self, wx.NewId(), choices=[i for i in self.get_quality_list().values()], value=_("High"), style=wx.CB_READONLY)
|
|
||||||
qualitybox = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
qualitybox.Add(quality, 0, wx.ALL, 5)
|
|
||||||
qualitybox.Add(self.quality, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(qualitybox, 0, wx.ALL, 5)
|
|
||||||
# Monkeypatch for getting the right quality value here.
|
|
||||||
self.quality.GetValue = self.get_quality_value
|
|
||||||
self.quality.SetValue = self.set_quality_value
|
|
||||||
self.map.append(("quality", self.quality))
|
|
||||||
self.SetSizer(sizer)
|
|
||||||
|
|
||||||
def on_enabled(self, *args, **kwargs):
|
|
||||||
for i in self.map:
|
|
||||||
if i[1] != self.enabled:
|
|
||||||
if self.enabled.GetValue() == True:
|
|
||||||
i[1].Enable(True)
|
|
||||||
else:
|
|
||||||
i[1].Enable(False)
|
|
||||||
|
|
||||||
def on_get_account(self, *args, **kwargs):
|
|
||||||
webbrowser.open_new_tab("https://tidal.com")
|
|
@ -1,134 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import isodate
|
|
||||||
import youtube_dl
|
|
||||||
import logging
|
|
||||||
import wx
|
|
||||||
import config
|
|
||||||
from googleapiclient.discovery import build
|
|
||||||
from googleapiclient.errors import HttpError
|
|
||||||
from update.utils import seconds_to_string
|
|
||||||
from .import base
|
|
||||||
|
|
||||||
DEVELOPER_KEY = "AIzaSyCU_hvZJEjLlAGAnlscquKEkE8l0lVOfn0"
|
|
||||||
YOUTUBE_API_SERVICE_NAME = "youtube"
|
|
||||||
YOUTUBE_API_VERSION = "v3"
|
|
||||||
|
|
||||||
log = logging.getLogger("extractors.youtube.com")
|
|
||||||
|
|
||||||
class interface(base.baseInterface):
|
|
||||||
name = "YouTube"
|
|
||||||
enabled = config.app["services"]["youtube"].get("enabled")
|
|
||||||
|
|
||||||
def search(self, text, page=1):
|
|
||||||
if text == "" or text == None:
|
|
||||||
raise ValueError("Text must be passed and should not be blank.")
|
|
||||||
if text.startswith("https") or text.startswith("http"):
|
|
||||||
return self.search_from_url(text)
|
|
||||||
type = "video"
|
|
||||||
max_results = config.app["services"]["youtube"]["max_results"]
|
|
||||||
log.debug("Retrieving data from Youtube...")
|
|
||||||
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)
|
|
||||||
search_response = youtube.search().list(q=text, part="id,snippet", maxResults=max_results, type=type).execute()
|
|
||||||
self.results = []
|
|
||||||
ids = []
|
|
||||||
for search_result in search_response.get("items", []):
|
|
||||||
if search_result["id"]["kind"] == "youtube#video":
|
|
||||||
s = base.song(self)
|
|
||||||
s.title = search_result["snippet"]["title"]
|
|
||||||
ids.append(search_result["id"]["videoId"])
|
|
||||||
s.url = "https://www.youtube.com/watch?v="+search_result["id"]["videoId"]
|
|
||||||
self.results.append(s)
|
|
||||||
ssr = youtube.videos().list(id=",".join(ids), part="contentDetails", maxResults=1).execute()
|
|
||||||
for i in range(len(self.results)):
|
|
||||||
self.results[i].duration = seconds_to_string(isodate.parse_duration(ssr["items"][i]["contentDetails"]["duration"]).total_seconds())
|
|
||||||
log.debug("{0} results found.".format(len(self.results)))
|
|
||||||
|
|
||||||
def search_from_url(self, url):
|
|
||||||
log.debug("Getting download URL for {0}".format(url,))
|
|
||||||
if "playlist?list=" in url:
|
|
||||||
return self.search_from_playlist(url)
|
|
||||||
ydl = youtube_dl.YoutubeDL({'quiet': True, 'no_warnings': True, 'logger': log, 'prefer-free-formats': True, 'format': 'bestaudio', 'outtmpl': u'%(id)s%(ext)s'})
|
|
||||||
with ydl:
|
|
||||||
result = ydl.extract_info(url, download=False)
|
|
||||||
if 'entries' in result:
|
|
||||||
videos = result['entries']
|
|
||||||
else:
|
|
||||||
videos = [result]
|
|
||||||
for video in videos:
|
|
||||||
s = baseFile.song(self)
|
|
||||||
s.title = video["title"]
|
|
||||||
s.url = video["webpage_url"] # Cannot use direct URL here cause Youtube URLS expire after a minute.
|
|
||||||
s.duration = seconds_to_string(video["duration"])
|
|
||||||
self.results.append(s)
|
|
||||||
log.debug("{0} results found.".format(len(self.results)))
|
|
||||||
|
|
||||||
def search_from_playlist(self, url):
|
|
||||||
id = url.split("=")[1]
|
|
||||||
max_results = 50
|
|
||||||
log.debug("Retrieving data from Youtube...")
|
|
||||||
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)
|
|
||||||
search_response = youtube.playlistItems().list(playlistId=id, part="id, status, snippet", maxResults=max_results).execute()
|
|
||||||
self.results = []
|
|
||||||
ids = []
|
|
||||||
for search_result in search_response.get("items", []):
|
|
||||||
if search_result["status"]["privacyStatus"] != "public":
|
|
||||||
continue
|
|
||||||
s = baseFile.song(self)
|
|
||||||
s.title = search_result["snippet"]["title"]
|
|
||||||
ids.append(search_result["snippet"]["resourceId"]["videoId"])
|
|
||||||
s.url = "https://www.youtube.com/watch?v="+search_result["snippet"]["resourceId"]["videoId"]
|
|
||||||
self.results.append(s)
|
|
||||||
ssr = youtube.videos().list(id=",".join(ids), part="contentDetails", maxResults=50).execute()
|
|
||||||
for i in range(len(self.results)):
|
|
||||||
self.results[i].duration = seconds_to_string(isodate.parse_duration(ssr["items"][i]["contentDetails"]["duration"]).total_seconds())
|
|
||||||
log.debug("{0} results found.".format(len(self.results)))
|
|
||||||
|
|
||||||
def get_download_url(self, url):
|
|
||||||
log.debug("Getting download URL for {0}".format(url,))
|
|
||||||
ydl = youtube_dl.YoutubeDL({'quiet': True, 'no_warnings': True, 'logger': log, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'})
|
|
||||||
with ydl:
|
|
||||||
result = ydl.extract_info(url, download=False)
|
|
||||||
if 'entries' in result:
|
|
||||||
video = result['entries'][0]
|
|
||||||
else:
|
|
||||||
video = result
|
|
||||||
# From here we should extract the first format so it will contain audio only.
|
|
||||||
log.debug("Download URL: {0}".format(video["formats"][0]["url"],))
|
|
||||||
return video["formats"][0]["url"]
|
|
||||||
|
|
||||||
def format_track(self, item):
|
|
||||||
return "{0} {1}".format(item.title, item.duration)
|
|
||||||
|
|
||||||
def transcoder_enabled(self):
|
|
||||||
return config.app["services"]["youtube"]["transcode"]
|
|
||||||
|
|
||||||
class settings(base.baseSettings):
|
|
||||||
name = _("Youtube Settings")
|
|
||||||
config_section = "youtube"
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(settings, self).__init__(parent=parent)
|
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
||||||
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
|
|
||||||
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
|
|
||||||
self.map.append(("enabled", self.enabled))
|
|
||||||
sizer.Add(self.enabled, 0, wx.ALL, 5)
|
|
||||||
max_results_label = wx.StaticText(self, wx.NewId(), _("Max results per page"))
|
|
||||||
self.max_results = wx.SpinCtrl(self, wx.NewId())
|
|
||||||
self.max_results.SetRange(1, 50)
|
|
||||||
max_results_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
max_results_sizer.Add(max_results_label, 0, wx.ALL, 5)
|
|
||||||
max_results_sizer.Add(self.max_results, 0, wx.ALL, 5)
|
|
||||||
self.map.append(("max_results", self.max_results))
|
|
||||||
# self.transcode = wx.CheckBox(self, wx.NewId(), _("Enable transcode when downloading"))
|
|
||||||
# self.map.append(("transcode", self.transcode))
|
|
||||||
# sizer.Add(self.transcode, 0, wx.ALL, 5)
|
|
||||||
self.SetSizer(sizer)
|
|
||||||
|
|
||||||
def on_enabled(self, *args, **kwargs):
|
|
||||||
for i in self.map:
|
|
||||||
if i[1] != self.enabled:
|
|
||||||
if self.enabled.GetValue() == True:
|
|
||||||
i[1].Enable(True)
|
|
||||||
else:
|
|
||||||
i[1].Enable(False)
|
|
@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
import logging
|
|
||||||
import wx
|
|
||||||
import config
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from . import base
|
|
||||||
|
|
||||||
log = logging.getLogger("extractors.zaycev.net")
|
|
||||||
|
|
||||||
class interface(base.baseInterface):
|
|
||||||
name = "zaycev.net"
|
|
||||||
enabled = config.app["services"]["zaycev"].get("enabled")
|
|
||||||
|
|
||||||
def search(self, text, page=1):
|
|
||||||
if text == "" or text == None:
|
|
||||||
raise ValueError("Text must be passed and should not be blank.")
|
|
||||||
site = "http://zaycev.net/search.html?query_search=%s" % (text,)
|
|
||||||
log.debug("Retrieving data from {0}...".format(site,))
|
|
||||||
r = requests.get(site)
|
|
||||||
soup = BeautifulSoup(r.text, 'html.parser')
|
|
||||||
search_results = soup.find_all("div", {"class": "musicset-track__title track-geo__title"})
|
|
||||||
self.results = []
|
|
||||||
for i in search_results:
|
|
||||||
# The easiest method to get artist and song names is to fetch links. There are only two links per result here.
|
|
||||||
data = i.find_all("a")
|
|
||||||
# from here, data[0] contains artist info and data[1] contains info of the retrieved song.
|
|
||||||
s = base.song(self)
|
|
||||||
s.title = data[1].text
|
|
||||||
s.artist = data[0].text
|
|
||||||
s.url = "http://zaycev.net%s" % (data[1].attrs["href"])
|
|
||||||
# s.duration = self.hd[i]["duration"]
|
|
||||||
# s.size = self.hd[i]["size"]
|
|
||||||
# s.bitrate = self.hd[i]["bitrate"]
|
|
||||||
self.results.append(s)
|
|
||||||
log.debug("{0} results found.".format(len(self.results)))
|
|
||||||
|
|
||||||
def get_download_url(self, url):
|
|
||||||
log.debug("Getting download URL for {0}".format(url,))
|
|
||||||
soups = BeautifulSoup(requests.get(url).text, 'html.parser')
|
|
||||||
data = json.loads(requests.get('http://zaycev.net' + soups.find('div', {'class':"musicset-track"}).get('data-url')).text)
|
|
||||||
log.debug("Download URL: {0}".format(data["url"]))
|
|
||||||
return data["url"]
|
|
||||||
|
|
||||||
def format_track(self, item):
|
|
||||||
return "{0}. {1}. {2}".format(item.title, item.duration, item.size)
|
|
||||||
|
|
||||||
class settings(base.baseSettings):
|
|
||||||
name = _("zaycev.net")
|
|
||||||
config_section = "zaycev"
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(settings, self).__init__(parent=parent)
|
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
||||||
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service (works only in the Russian Federation)"))
|
|
||||||
self.map.append(("enabled", self.enabled))
|
|
||||||
sizer.Add(self.enabled, 0, wx.ALL, 5)
|
|
||||||
self.SetSizer(sizer)
|
|
@ -4,4 +4,4 @@ from . import fix_requests
|
|||||||
from .import fix_winpaths
|
from .import fix_winpaths
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
fix_requests.fix()
|
fix_requests.fix()
|
||||||
|
@ -7,6 +7,6 @@ import paths
|
|||||||
log = logging.getLogger("fixes.fix_requests")
|
log = logging.getLogger("fixes.fix_requests")
|
||||||
|
|
||||||
def fix():
|
def fix():
|
||||||
log.debug("Applying fix for requests...")
|
log.debug("Applying fix for requests...")
|
||||||
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "cacerts.txt")
|
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "cacerts.txt")
|
||||||
log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"],))
|
log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"],))
|
||||||
|
@ -4,9 +4,9 @@ import winpaths
|
|||||||
from ctypes import wintypes
|
from ctypes import wintypes
|
||||||
|
|
||||||
def _get_path_buf(csidl):
|
def _get_path_buf(csidl):
|
||||||
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
|
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
|
||||||
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
|
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
|
||||||
return path_buf.value
|
return path_buf.value
|
||||||
|
|
||||||
def fix():
|
def fix():
|
||||||
winpaths._get_path_buf = _get_path_buf
|
winpaths._get_path_buf = _get_path_buf
|
||||||
|
14
src/i18n.py
14
src/i18n.py
@ -9,10 +9,10 @@ import paths
|
|||||||
log = logging.getLogger("i18n")
|
log = logging.getLogger("i18n")
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
lang = locale.getdefaultlocale()[0]
|
lang = locale.getdefaultlocale()[0]
|
||||||
os.environ["lang"] = lang
|
os.environ["lang"] = lang
|
||||||
log.debug("System detected language: {0}".format(lang,))
|
log.debug("System detected language: {0}".format(lang,))
|
||||||
if sys.version[0] == "3":
|
if sys.version[0] == "3":
|
||||||
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
|
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
|
||||||
else:
|
else:
|
||||||
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)
|
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
!include "MUI2.nsh"
|
!include "MUI2.nsh"
|
||||||
!include "LogicLib.nsh"
|
!include "LogicLib.nsh"
|
||||||
|
!include "x64.nsh"
|
||||||
Unicode true
|
Unicode true
|
||||||
CRCCheck on
|
CRCCheck on
|
||||||
ManifestSupportedOS all
|
ManifestSupportedOS all
|
||||||
XPStyle on
|
XPStyle on
|
||||||
Name "MusicDL"
|
Name "MusicDL"
|
||||||
OutFile "music_dl_0.6_setup.exe"
|
OutFile "music_dl_setup.exe"
|
||||||
InstallDir "$PROGRAMFILES\musicDL"
|
InstallDir "$PROGRAMFILES\musicDL"
|
||||||
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "InstallLocation"
|
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "InstallLocation"
|
||||||
RequestExecutionLevel admin
|
RequestExecutionLevel admin
|
||||||
@ -13,18 +14,18 @@ SetCompress auto
|
|||||||
SetCompressor /solid lzma
|
SetCompressor /solid lzma
|
||||||
SetDatablockOptimize on
|
SetDatablockOptimize on
|
||||||
VIAddVersionKey ProductName "MusicDL"
|
VIAddVersionKey ProductName "MusicDL"
|
||||||
VIAddVersionKey LegalCopyright "Copyright 2019 Manuel Cortez."
|
VIAddVersionKey LegalCopyright "Copyright 2019 - 2022 MCV Software."
|
||||||
VIAddVersionKey ProductVersion "0.6"
|
VIAddVersionKey ProductVersion "0.7"
|
||||||
VIAddVersionKey FileVersion "0.6"
|
VIAddVersionKey FileVersion "0.7"
|
||||||
VIProductVersion "0.6.0.0"
|
VIProductVersion "0.7.0.0"
|
||||||
VIFileVersion "0.6.0.0"
|
VIFileVersion "0.7.0.0"
|
||||||
!insertmacro MUI_PAGE_WELCOME
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
!insertmacro MUI_PAGE_DIRECTORY
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
var StartMenuFolder
|
var StartMenuFolder
|
||||||
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
|
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
|
||||||
!insertmacro MUI_PAGE_INSTFILES
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
!define MUI_FINISHPAGE_LINK "Visit MusicDL website"
|
!define MUI_FINISHPAGE_LINK "Visit MusicDL website"
|
||||||
!define MUI_FINISHPAGE_LINK_LOCATION "https://manuelcortez.net/music_dl"
|
!define MUI_FINISHPAGE_LINK_LOCATION "https://mcvsoftware.com/music_dl"
|
||||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\musicDL.exe"
|
!define MUI_FINISHPAGE_RUN "$INSTDIR\musicDL.exe"
|
||||||
!insertmacro MUI_PAGE_FINISH
|
!insertmacro MUI_PAGE_FINISH
|
||||||
!insertmacro MUI_UNPAGE_CONFIRM
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
@ -36,7 +37,11 @@ var StartMenuFolder
|
|||||||
Section
|
Section
|
||||||
SetShellVarContext All
|
SetShellVarContext All
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
File /r dist\main\*
|
${If} ${RunningX64}
|
||||||
|
File /r program64\*
|
||||||
|
${Else}
|
||||||
|
File /r program32\*
|
||||||
|
${EndIf}
|
||||||
CreateShortCut "$DESKTOP\musicDL.lnk" "$INSTDIR\musicDL.exe"
|
CreateShortCut "$DESKTOP\musicDL.lnk" "$INSTDIR\musicDL.exe"
|
||||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
|
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
|
||||||
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
|
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
|
||||||
@ -49,7 +54,7 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "
|
|||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "DisplayVersion" "0.6"
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "DisplayVersion" "0.7"
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "URLInfoAbout" "https://manuelcortez.net/music_dl"
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "URLInfoAbout" "https://manuelcortez.net/music_dl"
|
||||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMajor" 0
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMajor" 0
|
||||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMinor" 1
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMinor" 1
|
||||||
@ -65,5 +70,8 @@ Delete "$DESKTOP\MusicDL.lnk"
|
|||||||
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
|
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
|
||||||
SectionEnd
|
SectionEnd
|
||||||
Function .onInit
|
Function .onInit
|
||||||
|
${If} ${RunningX64}
|
||||||
|
StrCpy $instdir "$programfiles64\musicDL"
|
||||||
|
${EndIf}
|
||||||
!insertmacro MUI_LANGDLL_DISPLAY
|
!insertmacro MUI_LANGDLL_DISPLAY
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
@ -27,37 +27,37 @@ from utils import call_threaded
|
|||||||
from . import wx_ui
|
from . import wx_ui
|
||||||
|
|
||||||
class reportBug(object):
|
class reportBug(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.dialog = wx_ui.reportBugDialog()
|
self.dialog = wx_ui.reportBugDialog()
|
||||||
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
|
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
|
||||||
self.dialog.get_response()
|
self.dialog.get_response()
|
||||||
|
|
||||||
def do_report(self, *args, **kwargs):
|
def do_report(self, *args, **kwargs):
|
||||||
r = requests.post(*args, **kwargs)
|
r = requests.post(*args, **kwargs)
|
||||||
if r.status_code > 300:
|
if r.status_code > 300:
|
||||||
wx.CallAfter(self.dialog.error)
|
wx.CallAfter(self.dialog.error)
|
||||||
wx.CallAfter(self.dialog.progress.Destroy)
|
wx.CallAfter(self.dialog.progress.Destroy)
|
||||||
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
|
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
|
||||||
|
|
||||||
def send(self, *args, **kwargs):
|
def send(self, *args, **kwargs):
|
||||||
if self.dialog.get("summary") == "" or self.dialog.get("description") == "" or self.dialog.get("first_name") == "" or self.dialog.get("last_name") == "":
|
if self.dialog.get("summary") == "" or self.dialog.get("description") == "" or self.dialog.get("first_name") == "" or self.dialog.get("last_name") == "":
|
||||||
self.dialog.no_filled()
|
self.dialog.no_filled()
|
||||||
return
|
return
|
||||||
if self.dialog.get("agree") == False:
|
if self.dialog.get("agree") == False:
|
||||||
self.dialog.no_checkbox()
|
self.dialog.no_checkbox()
|
||||||
return
|
return
|
||||||
title = self.dialog.get("summary")
|
title = self.dialog.get("summary")
|
||||||
body = self.dialog.get("description")
|
body = self.dialog.get("description")
|
||||||
issue_type = "issue" # for now just have issue
|
issue_type = "issue" # for now just have issue
|
||||||
app_type = storage.app_type
|
app_type = storage.app_type
|
||||||
app_version = application.version
|
app_version = application.version
|
||||||
reporter_name = "{first_name} {last_name}".format(first_name=self.dialog.get("first_name"), last_name=self.dialog.get("last_name"))
|
reporter_name = "{first_name} {last_name}".format(first_name=self.dialog.get("first_name"), last_name=self.dialog.get("last_name"))
|
||||||
reporter_contact_type = "email" # For now just email is supported in the issue reporter
|
reporter_contact_type = "email" # For now just email is supported in the issue reporter
|
||||||
reporter_contact_handle = self.dialog.get("email")
|
reporter_contact_handle = self.dialog.get("email")
|
||||||
operating_system = platform.platform()
|
operating_system = platform.platform()
|
||||||
json = dict(title=title, issue_type=issue_type, body=body, operating_system=operating_system, app_type=app_type, app_version=app_version, reporter_name=reporter_name, reporter_contact_handle=reporter_contact_handle, reporter_contact_type=reporter_contact_type)
|
json = dict(title=title, issue_type=issue_type, body=body, operating_system=operating_system, app_type=app_type, app_version=app_version, reporter_name=reporter_name, reporter_contact_handle=reporter_contact_handle, reporter_contact_type=reporter_contact_type)
|
||||||
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
|
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
|
||||||
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
|
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
|
||||||
call_threaded(self.do_report, url, json=json, auth=auth)
|
call_threaded(self.do_report, url, json=json, auth=auth)
|
||||||
self.dialog.show_progress()
|
self.dialog.show_progress()
|
||||||
self.dialog.EndModal(wx.ID_OK)
|
self.dialog.EndModal(wx.ID_OK)
|
||||||
|
@ -21,89 +21,89 @@ import widgetUtils
|
|||||||
import application
|
import application
|
||||||
|
|
||||||
class reportBugDialog(widgetUtils.BaseDialog):
|
class reportBugDialog(widgetUtils.BaseDialog):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
|
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
|
||||||
self.SetTitle(_(u"Report an error"))
|
self.SetTitle(_(u"Report an error"))
|
||||||
panel = wx.Panel(self)
|
panel = wx.Panel(self)
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
|
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
|
||||||
self.summary = wx.TextCtrl(panel, -1)
|
self.summary = wx.TextCtrl(panel, -1)
|
||||||
dc = wx.WindowDC(self.summary)
|
dc = wx.WindowDC(self.summary)
|
||||||
dc.SetFont(self.summary.GetFont())
|
dc.SetFont(self.summary.GetFont())
|
||||||
self.summary.SetSize(dc.GetTextExtent("a"*80))
|
self.summary.SetSize(dc.GetTextExtent("a"*80))
|
||||||
summaryB = wx.BoxSizer(wx.HORIZONTAL)
|
summaryB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
|
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
|
||||||
summaryB.Add(self.summary, 0, wx.ALL, 5)
|
summaryB.Add(self.summary, 0, wx.ALL, 5)
|
||||||
sizer.Add(summaryB, 0, wx.ALL, 5)
|
sizer.Add(summaryB, 0, wx.ALL, 5)
|
||||||
|
|
||||||
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
|
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
|
||||||
self.first_name = wx.TextCtrl(panel, -1)
|
self.first_name = wx.TextCtrl(panel, -1)
|
||||||
dc = wx.WindowDC(self.first_name)
|
dc = wx.WindowDC(self.first_name)
|
||||||
dc.SetFont(self.first_name.GetFont())
|
dc.SetFont(self.first_name.GetFont())
|
||||||
self.first_name.SetSize(dc.GetTextExtent("a"*40))
|
self.first_name.SetSize(dc.GetTextExtent("a"*40))
|
||||||
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
|
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
|
||||||
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
|
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
|
||||||
sizer.Add(first_nameB, 0, wx.ALL, 5)
|
sizer.Add(first_nameB, 0, wx.ALL, 5)
|
||||||
|
|
||||||
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
|
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
|
||||||
self.last_name = wx.TextCtrl(panel, -1)
|
self.last_name = wx.TextCtrl(panel, -1)
|
||||||
dc = wx.WindowDC(self.last_name)
|
dc = wx.WindowDC(self.last_name)
|
||||||
dc.SetFont(self.last_name.GetFont())
|
dc.SetFont(self.last_name.GetFont())
|
||||||
self.last_name.SetSize(dc.GetTextExtent("a"*40))
|
self.last_name.SetSize(dc.GetTextExtent("a"*40))
|
||||||
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
|
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
|
||||||
last_nameB.Add(self.last_name, 0, wx.ALL, 5)
|
last_nameB.Add(self.last_name, 0, wx.ALL, 5)
|
||||||
sizer.Add(last_nameB, 0, wx.ALL, 5)
|
sizer.Add(last_nameB, 0, wx.ALL, 5)
|
||||||
|
|
||||||
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
|
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
|
||||||
self.email = wx.TextCtrl(panel, -1)
|
self.email = wx.TextCtrl(panel, -1)
|
||||||
dc = wx.WindowDC(self.email)
|
dc = wx.WindowDC(self.email)
|
||||||
dc.SetFont(self.email.GetFont())
|
dc.SetFont(self.email.GetFont())
|
||||||
self.email.SetSize(dc.GetTextExtent("a"*30))
|
self.email.SetSize(dc.GetTextExtent("a"*30))
|
||||||
emailB = wx.BoxSizer(wx.HORIZONTAL)
|
emailB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
emailB.Add(emailLabel, 0, wx.ALL, 5)
|
emailB.Add(emailLabel, 0, wx.ALL, 5)
|
||||||
emailB.Add(self.email, 0, wx.ALL, 5)
|
emailB.Add(self.email, 0, wx.ALL, 5)
|
||||||
sizer.Add(emailB, 0, wx.ALL, 5)
|
sizer.Add(emailB, 0, wx.ALL, 5)
|
||||||
|
|
||||||
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
|
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
|
||||||
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
|
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
|
||||||
dc = wx.WindowDC(self.description)
|
dc = wx.WindowDC(self.description)
|
||||||
dc.SetFont(self.description.GetFont())
|
dc.SetFont(self.description.GetFont())
|
||||||
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
|
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
|
||||||
self.description.SetSize((x, y))
|
self.description.SetSize((x, y))
|
||||||
descBox = wx.BoxSizer(wx.HORIZONTAL)
|
descBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
|
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
|
||||||
descBox.Add(self.description, 0, wx.ALL, 5)
|
descBox.Add(self.description, 0, wx.ALL, 5)
|
||||||
sizer.Add(descBox, 0, wx.ALL, 5)
|
sizer.Add(descBox, 0, wx.ALL, 5)
|
||||||
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my email address to contact me and fix the bug quickly").format(application.name,))
|
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my email address to contact me and fix the bug quickly").format(application.name,))
|
||||||
self.agree.SetValue(False)
|
self.agree.SetValue(False)
|
||||||
sizer.Add(self.agree, 0, wx.ALL, 5)
|
sizer.Add(self.agree, 0, wx.ALL, 5)
|
||||||
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
|
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
|
||||||
self.ok.SetDefault()
|
self.ok.SetDefault()
|
||||||
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
|
||||||
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
btnBox.Add(self.ok, 0, wx.ALL, 5)
|
btnBox.Add(self.ok, 0, wx.ALL, 5)
|
||||||
btnBox.Add(cancel, 0, wx.ALL, 5)
|
btnBox.Add(cancel, 0, wx.ALL, 5)
|
||||||
sizer.Add(btnBox, 0, wx.ALL, 5)
|
sizer.Add(btnBox, 0, wx.ALL, 5)
|
||||||
panel.SetSizer(sizer)
|
panel.SetSizer(sizer)
|
||||||
self.SetClientSize(sizer.CalcMin())
|
self.SetClientSize(sizer.CalcMin())
|
||||||
|
|
||||||
def no_filled(self):
|
def no_filled(self):
|
||||||
wx.MessageDialog(self, _(u"You must fill out the following fields: first name, last name, email address and issue information."), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
|
wx.MessageDialog(self, _(u"You must fill out the following fields: first name, last name, email address and issue information."), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
|
||||||
|
|
||||||
def no_checkbox(self):
|
def no_checkbox(self):
|
||||||
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your email address to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your email address to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
|
||||||
def success(self, id):
|
def success(self, id):
|
||||||
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
|
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
|
||||||
self.Destroy()
|
self.Destroy()
|
||||||
|
|
||||||
def error(self):
|
def error(self):
|
||||||
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
|
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
|
||||||
self.Destroy()
|
self.Destroy()
|
||||||
|
|
||||||
def show_progress(self):
|
def show_progress(self):
|
||||||
self.progress = wx.ProgressDialog(title=_(u"Sending report..."), message=_(u"Please wait while your report is being send."), maximum=100, parent=self)
|
self.progress = wx.ProgressDialog(title=_(u"Sending report..."), message=_(u"Please wait while your report is being send."), maximum=100, parent=self)
|
||||||
self.progress.ShowModal()
|
self.progress.ShowModal()
|
||||||
|
BIN
src/libvlc.dll
BIN
src/libvlc.dll
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,452 +1,20 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
# Copyright (C) 2019 ORGANIZATION
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: 2019-06-24 13:05-0500\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"PO-Revision-Date: 2019-06-24 13:18-0500\n"
|
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||||
|
"PO-Revision-Date: 2020-10-02 15:10+0000\n"
|
||||||
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
|
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
|
||||||
"Language-Team: \n"
|
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
|
"Language-Team: Spanish "
|
||||||
|
"<http://translations.manuelcortez.net/projects/musicdl/interface/es/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: Babel 2.9.0\n"
|
||||||
"X-Generator: Poedit 2.0.1\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
|
|
||||||
#: ../src\application.py:9
|
|
||||||
msgid ""
|
|
||||||
" Is an application that will allow you to download music from popular sites "
|
|
||||||
"such as youtube, zaycev.net."
|
|
||||||
msgstr ""
|
|
||||||
" Es una aplicación que te permite descargar música de sitios populares como "
|
|
||||||
"YouTube y zaycev.net."
|
|
||||||
|
|
||||||
#: ../src\application.py:14
|
|
||||||
msgid "Manuel Cortez (Spanish)"
|
|
||||||
msgstr "Manuel Cortez (Español)"
|
|
||||||
|
|
||||||
#: ../src\application.py:14
|
|
||||||
msgid "Valeria K (Russian)"
|
|
||||||
msgstr "Valeria K (Ruso)"
|
|
||||||
|
|
||||||
#: ../src\controller\configuration.py:10 ../src\wxUI\mainWindow.py:15
|
|
||||||
msgid "Settings"
|
|
||||||
msgstr "Preferencias"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:31
|
|
||||||
msgid "Ready"
|
|
||||||
msgstr "Listo"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:47
|
|
||||||
msgid "Showing {0} results."
|
|
||||||
msgstr "Mostrando {0} resultados"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:51
|
|
||||||
msgid "Shuffle on"
|
|
||||||
msgstr "Modo aleatorio activo"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:107
|
|
||||||
msgid "Searching {0}... "
|
|
||||||
msgstr "Buscando {0}..."
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:141
|
|
||||||
#: ../src\controller\mainController.py:161 ../src\wxUI\mainWindow.py:18
|
|
||||||
#: ../src\wxUI\mainWindow.py:68
|
|
||||||
msgid "Play"
|
|
||||||
msgstr "Reproducir"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:144
|
|
||||||
#: ../src\controller\mainController.py:156
|
|
||||||
msgid "Pause"
|
|
||||||
msgstr "pausa"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:243
|
|
||||||
msgid "File downloaded: {0}"
|
|
||||||
msgstr "Archivo descargado: {0}"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:263
|
|
||||||
msgid "No results found. "
|
|
||||||
msgstr "No se han encontrado resultados."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:70
|
|
||||||
msgid "Error playing {0}. {1}."
|
|
||||||
msgstr "Error reproduciendo {0}. {1}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:76
|
|
||||||
msgid "Playing {0}."
|
|
||||||
msgstr "Reproduciendo {0}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:146 ../src\utils.py:58
|
|
||||||
msgid "Downloading {0}."
|
|
||||||
msgstr "Descargando {0}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:151 ../src\utils.py:69
|
|
||||||
msgid "Downloading {0} ({1}%)."
|
|
||||||
msgstr "Descargando {0} ({1}%)."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:164
|
|
||||||
msgid "There was an error while trying to access the file you have requested."
|
|
||||||
msgstr "Ocurrió un error al intentar acceder al fichero solicitado."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:164 ../src\issueReporter\wx_ui.py:94
|
|
||||||
#: ../src\issueReporter\wx_ui.py:97
|
|
||||||
msgid "Error"
|
|
||||||
msgstr "Error"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:102
|
|
||||||
msgid "Tidal"
|
|
||||||
msgstr "Tidal"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:106
|
|
||||||
msgid "Lossless"
|
|
||||||
msgstr "Calidad más alta (FLAC)"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:106
|
|
||||||
msgid "Low"
|
|
||||||
msgstr "Baja"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:106 ../src\extractors\tidal.py:141
|
|
||||||
msgid "High"
|
|
||||||
msgstr "Alta"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:118 ../src\extractors\youtube.py:112
|
|
||||||
msgid "Enable this service"
|
|
||||||
msgstr "Activar servicio"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:122
|
|
||||||
msgid "Tidal username or email address"
|
|
||||||
msgstr "Correo o nombre de usuario de Tidal"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:130
|
|
||||||
msgid "Password"
|
|
||||||
msgstr "Contraseña"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:137
|
|
||||||
msgid "You can subscribe for a tidal account here"
|
|
||||||
msgstr "Puedes obtener tu cuenta de Tidal aquí"
|
|
||||||
|
|
||||||
#: ../src\extractors\tidal.py:140
|
|
||||||
msgid "Audio quality"
|
|
||||||
msgstr "Calidad de audio"
|
|
||||||
|
|
||||||
#: ../src\extractors\youtube.py:106
|
|
||||||
msgid "Youtube Settings"
|
|
||||||
msgstr "Opciones de Youtube"
|
|
||||||
|
|
||||||
#: ../src\extractors\youtube.py:116
|
|
||||||
msgid "Max results per page"
|
|
||||||
msgstr "Resultados máximos a cargar por cada búsqueda"
|
|
||||||
|
|
||||||
#: ../src\extractors\youtube.py:123
|
|
||||||
msgid "Enable transcode when downloading"
|
|
||||||
msgstr "Activar transcodificación al descargar"
|
|
||||||
|
|
||||||
#: ../src\extractors\zaycev.py:52
|
|
||||||
msgid "zaycev.net"
|
|
||||||
msgstr "zaycev.net"
|
|
||||||
|
|
||||||
#: ../src\extractors\zaycev.py:58
|
|
||||||
msgid "Enable this service (works only in the Russian Federation)"
|
|
||||||
msgstr "Activar servicio (funciona solo en la federación de Rusia)"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:26 ../src\wxUI\mainWindow.py:31
|
|
||||||
msgid "Report an error"
|
|
||||||
msgstr "Reportar un error"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:30
|
|
||||||
msgid ""
|
|
||||||
"Briefly describe what happened. You will be able to thoroughly explain it "
|
|
||||||
"later"
|
|
||||||
msgstr ""
|
|
||||||
"Describe brevemente lo que ha ocurrido. Más adelante podrás añadir más "
|
|
||||||
"detalles."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:40
|
|
||||||
msgid "First Name"
|
|
||||||
msgstr "Nombre (s)"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:50
|
|
||||||
msgid "Last Name"
|
|
||||||
msgstr "Apellido (s)"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:60
|
|
||||||
msgid "Email address (Will not be public)"
|
|
||||||
msgstr "Dirección de correo electrónico (No será publicada)"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:70
|
|
||||||
msgid "Here, you can describe the bug in detail"
|
|
||||||
msgstr "Aquí puedes describir el problema con más detalles."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:80
|
|
||||||
msgid ""
|
|
||||||
"I know that the {0} bug system will get my email address to contact me and "
|
|
||||||
"fix the bug quickly"
|
|
||||||
msgstr ""
|
|
||||||
"Estoy enterado que el sistema de reporte de errores de {0} podrá utilizar mi "
|
|
||||||
"correo electrónico para contactarme en caso de necesitar más detalles para "
|
|
||||||
"arreglar el problema rápidamente."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:83
|
|
||||||
msgid "Send report"
|
|
||||||
msgstr "Enviar reporte"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:85
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr "Cancelar"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:94
|
|
||||||
msgid ""
|
|
||||||
"You must fill out the following fields: first name, last name, email address "
|
|
||||||
"and issue information."
|
|
||||||
msgstr ""
|
|
||||||
"Debes llenar los siguientes campos: Nombre, apellido, dirección de correo "
|
|
||||||
"electrónico y la información del problema."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:97
|
|
||||||
msgid ""
|
|
||||||
"You need to mark the checkbox to provide us your email address to contact "
|
|
||||||
"you if it is necessary."
|
|
||||||
msgstr ""
|
|
||||||
"Debes marcar la casilla para proporcionarnos tu correo electrónico y poder "
|
|
||||||
"contactarte si es necesario."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:100
|
|
||||||
msgid ""
|
|
||||||
"Thanks for reporting this bug! In future versions, you may be able to find "
|
|
||||||
"it in the changes list. You have received an email with more information "
|
|
||||||
"regarding your report. You've reported the bug number %i"
|
|
||||||
msgstr ""
|
|
||||||
"¡Gracias por reportar este error! Esperamos que puedas encontrar este "
|
|
||||||
"problema resuelto en la lista de cambios de una versión futura. Has recibido "
|
|
||||||
"un correo electrónico con más información sobre tu reporte. Has reportado el "
|
|
||||||
"error número %i"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:100
|
|
||||||
msgid "reported"
|
|
||||||
msgstr "Reportado"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:104
|
|
||||||
msgid "Error while reporting"
|
|
||||||
msgstr "Error al intentar reportar"
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:104
|
|
||||||
msgid ""
|
|
||||||
"Something unexpected occurred while trying to report the bug. Please, try "
|
|
||||||
"again later"
|
|
||||||
msgstr ""
|
|
||||||
"Algo inesperado ha ocurrido al intentar realizar el reporte de error. Por "
|
|
||||||
"favor, inténtalo nuevamente más tarde."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:108
|
|
||||||
msgid "Please wait while your report is being send."
|
|
||||||
msgstr "Por favor, espera mientras tu reporte es enviado."
|
|
||||||
|
|
||||||
#: ../src\issueReporter\wx_ui.py:108
|
|
||||||
msgid "Sending report..."
|
|
||||||
msgstr "Enviando reporte..."
|
|
||||||
|
|
||||||
#: ../src\test\test_i18n.py:21
|
|
||||||
msgid "This is a string with no special characters."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\test\test_i18n.py:24
|
|
||||||
msgid ""
|
|
||||||
"\\320\\237\\321\\200\\320\\270\\320\\262\\320\\265\\321\\202 "
|
|
||||||
"\\320\\262\\321\\201\\320\\265\\320\\274"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:27
|
|
||||||
msgid "%d day, "
|
|
||||||
msgstr "%d día, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:29
|
|
||||||
msgid "%d days, "
|
|
||||||
msgstr "%d días, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:31
|
|
||||||
msgid "%d hour, "
|
|
||||||
msgstr "%d hora, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:33
|
|
||||||
msgid "%d hours, "
|
|
||||||
msgstr "%d horas, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:35
|
|
||||||
msgid "%d minute, "
|
|
||||||
msgstr "%d minuto, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:37
|
|
||||||
msgid "%d minutes, "
|
|
||||||
msgstr "%d minutos, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:39
|
|
||||||
msgid "%s second"
|
|
||||||
msgstr "%s segundo"
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:41
|
|
||||||
msgid "%s seconds"
|
|
||||||
msgstr "%s segundos"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:10
|
|
||||||
msgid "New version for %s"
|
|
||||||
msgstr "Nueva versión de %s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:10
|
|
||||||
msgid ""
|
|
||||||
"There's a new %s version available. Would you like to download it now?\n"
|
|
||||||
"\n"
|
|
||||||
" %s version: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Changes:\n"
|
|
||||||
"%s"
|
|
||||||
msgstr ""
|
|
||||||
"Hay una nueva versión de %s disponible. ¿Te gustaría descargarla ahora?\n"
|
|
||||||
"\n"
|
|
||||||
" %s versión: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Novedades:\n"
|
|
||||||
"%s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:17
|
|
||||||
msgid "Download in Progress"
|
|
||||||
msgstr "Descarga en progreso"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:17
|
|
||||||
msgid "Downloading the new version..."
|
|
||||||
msgstr "Descargando la nueva versión..."
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:27
|
|
||||||
msgid "Updating... %s of %s"
|
|
||||||
msgstr "Actualizando... %s de %s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:30
|
|
||||||
msgid "Done!"
|
|
||||||
msgstr "¡Hecho!"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:30
|
|
||||||
msgid ""
|
|
||||||
"The update has been downloaded and installed successfully. Press OK to "
|
|
||||||
"continue."
|
|
||||||
msgstr ""
|
|
||||||
"La actualización ha sido descargada e instalada satisfactoriamente. Pulsa "
|
|
||||||
"aceptar para continuar."
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:9
|
|
||||||
msgid "Output device"
|
|
||||||
msgstr "Dispositivo de salida"
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:27
|
|
||||||
msgid "General"
|
|
||||||
msgstr "General"
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:31
|
|
||||||
msgid "Services"
|
|
||||||
msgstr "Servicios"
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:34
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Guardar"
|
|
||||||
|
|
||||||
#: ../src\wxUI\configuration.py:36
|
|
||||||
msgid "Close"
|
|
||||||
msgstr "Cerrar"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:16
|
|
||||||
msgid "Application"
|
|
||||||
msgstr "Aplicación"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:19 ../src\wxUI\mainWindow.py:69
|
|
||||||
msgid "Stop"
|
|
||||||
msgstr "Detener"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:20 ../src\wxUI\mainWindow.py:67
|
|
||||||
msgid "Previous"
|
|
||||||
msgstr "Anterior"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:21 ../src\wxUI\mainWindow.py:70
|
|
||||||
msgid "Next"
|
|
||||||
msgstr "Siguiente"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:22
|
|
||||||
msgid "Shuffle"
|
|
||||||
msgstr "Aleatorio"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:23
|
|
||||||
msgid "Volume down"
|
|
||||||
msgstr "Bajar volumen"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:24
|
|
||||||
msgid "Volume up"
|
|
||||||
msgstr "Subir volumen"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:25
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr "Silenciar"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:27
|
|
||||||
msgid "About {0}"
|
|
||||||
msgstr "Sobre {0}"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:28
|
|
||||||
msgid "Check for updates"
|
|
||||||
msgstr "Comprobar actualizaciones"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:29
|
|
||||||
msgid "What's new in this version?"
|
|
||||||
msgstr "¿qué hay de nuevo?"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:30
|
|
||||||
msgid "Visit website"
|
|
||||||
msgstr "Visitar sitio web"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:32
|
|
||||||
msgid "Player"
|
|
||||||
msgstr "Reproductor"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:33
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "Ayuda"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:43
|
|
||||||
msgid "search"
|
|
||||||
msgstr "Buscar"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:48
|
|
||||||
msgid "Search in"
|
|
||||||
msgstr "Buscar en"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:51
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Buscar"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:55
|
|
||||||
msgid "Results"
|
|
||||||
msgstr "Resultados"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:61
|
|
||||||
msgid "Position"
|
|
||||||
msgstr "Posición"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:64
|
|
||||||
msgid "Volume"
|
|
||||||
msgstr "Volumen"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:114
|
|
||||||
msgid "Audio Files(*.mp3)|*.mp3"
|
|
||||||
msgstr "Archivos de audio (*.mp3)|*.mp3"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:114
|
|
||||||
msgid "Save this file"
|
|
||||||
msgstr "Guardar archivo"
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:8
|
|
||||||
msgid "Play/Pause"
|
|
||||||
msgstr "Reproducir/pausar"
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:9
|
|
||||||
msgid "Download"
|
|
||||||
msgstr "Descargar"
|
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -1,254 +1,20 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
# Copyright (C) 2018 ORGANIZATION
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: 2018-03-03 09:38+Hora estándar central (México)\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"PO-Revision-Date: 2018-03-12 01:35+0400\n"
|
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"PO-Revision-Date: 2020-07-30 05:13-0500\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"Language-Team: ru <LL@li.org>\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||||
|
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: Babel 2.9.0\n"
|
||||||
"X-Generator: Poedit 1.5.7\n"
|
|
||||||
|
|
||||||
#: ../src\application.py:7
|
|
||||||
msgid ""
|
|
||||||
" Is an application that will allow you to download music from popular sites "
|
|
||||||
"such as youtube, zaycev.net."
|
|
||||||
msgstr ""
|
|
||||||
" Это приложение, позволяющее скачивать музыку с таких популярных сайтов как "
|
|
||||||
"Youtube, zaycev.net."
|
|
||||||
|
|
||||||
#: ../src\application.py:12
|
|
||||||
msgid "Manuel Cortez (Spanish)"
|
|
||||||
msgstr "Manuel Cortez (Испанский)"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:27
|
|
||||||
msgid "Ready"
|
|
||||||
msgstr "Готово"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:42
|
|
||||||
msgid "Showing {0} results."
|
|
||||||
msgstr "Показано {0} результатов."
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:46
|
|
||||||
msgid "Shuffle on"
|
|
||||||
msgstr "Случайный порядок включён"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:118
|
|
||||||
#: ../src\controller\mainController.py:138 ../src\wxUI\mainWindow.py:13
|
|
||||||
#: ../src\wxUI\mainWindow.py:62
|
|
||||||
msgid "Play"
|
|
||||||
msgstr "Воспроизвести"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:121
|
|
||||||
#: ../src\controller\mainController.py:133
|
|
||||||
msgid "Pause"
|
|
||||||
msgstr "Приостановить"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:213
|
|
||||||
msgid "File downloaded: {0}"
|
|
||||||
msgstr "Файл загружен: {0}"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:236
|
|
||||||
msgid "Searching {0}... "
|
|
||||||
msgstr "Поиск {0}... "
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:242
|
|
||||||
msgid "No results found. "
|
|
||||||
msgstr "Нет результатов."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:43
|
|
||||||
msgid "Error playing {0}. {1}."
|
|
||||||
msgstr "Ошибка воспроизведения {0}. {1}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:49
|
|
||||||
msgid "Playing {0}."
|
|
||||||
msgstr "Сейчас играет {0}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:117 ../src\utils.py:53
|
|
||||||
msgid "Downloading {0}."
|
|
||||||
msgstr "Сейчас загружается {0}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:122 ../src\utils.py:63
|
|
||||||
msgid "Downloading {0} ({1}%)."
|
|
||||||
msgstr "Загрузка {0} ({1}%)."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:133
|
|
||||||
msgid "Error"
|
|
||||||
msgstr "Ошибка"
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:133
|
|
||||||
msgid "There was an error while trying to access the file you have requested."
|
|
||||||
msgstr "Произошла ошибка при попытке открыть запрашиваемый файл."
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:27
|
|
||||||
msgid "%d day, "
|
|
||||||
msgstr "%d день, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:29
|
|
||||||
msgid "%d days, "
|
|
||||||
msgstr "%d дней, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:31
|
|
||||||
msgid "%d hour, "
|
|
||||||
msgstr "%d час, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:33
|
|
||||||
msgid "%d hours, "
|
|
||||||
msgstr "%d часов, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:35
|
|
||||||
msgid "%d minute, "
|
|
||||||
msgstr "%d минута, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:37
|
|
||||||
msgid "%d minutes, "
|
|
||||||
msgstr "%d минут, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:39
|
|
||||||
msgid "%s second"
|
|
||||||
msgstr "%s секунда"
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:41
|
|
||||||
msgid "%s seconds"
|
|
||||||
msgstr "%s секунд"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:9
|
|
||||||
msgid "New version for %s"
|
|
||||||
msgstr "Новая версия %s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:9
|
|
||||||
msgid ""
|
|
||||||
"There's a new %s version available. Would you like to download it now?\n"
|
|
||||||
"\n"
|
|
||||||
" %s version: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Changes:\n"
|
|
||||||
"%s"
|
|
||||||
msgstr ""
|
|
||||||
"Доступна новая версия %s. Желаете скачать её?\n"
|
|
||||||
"\n"
|
|
||||||
" %s версия: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Изменения:\n"
|
|
||||||
"%s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:16
|
|
||||||
msgid "Download in Progress"
|
|
||||||
msgstr "Процесс скачивания"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:16
|
|
||||||
msgid "Downloading the new version..."
|
|
||||||
msgstr "Скачивание новой версии..."
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:26
|
|
||||||
msgid "Updating... %s of %s"
|
|
||||||
msgstr "Обновление... %s из %s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:29
|
|
||||||
msgid "Done!"
|
|
||||||
msgstr "Готово!"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:29
|
|
||||||
msgid ""
|
|
||||||
"The update has been downloaded and installed successfully. Press OK to "
|
|
||||||
"continue."
|
|
||||||
msgstr ""
|
|
||||||
"Обновление было успешно загружено и установлено. Нажмите ОК для продолжения."
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:14 ../src\wxUI\mainWindow.py:63
|
|
||||||
msgid "Stop"
|
|
||||||
msgstr "Остановить"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:15 ../src\wxUI\mainWindow.py:61
|
|
||||||
msgid "Previous"
|
|
||||||
msgstr "Предыдущая композиция"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:16 ../src\wxUI\mainWindow.py:64
|
|
||||||
msgid "Next"
|
|
||||||
msgstr "Следующая композиция"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:17
|
|
||||||
msgid "Shuffle"
|
|
||||||
msgstr "Перемешать"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:18
|
|
||||||
msgid "Volume down"
|
|
||||||
msgstr "Уменьшить громкость"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:19
|
|
||||||
msgid "Volume up"
|
|
||||||
msgstr "Увеличить громкость"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:20
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr "Выключить звук"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:22
|
|
||||||
msgid "About {0}"
|
|
||||||
msgstr "О {0}"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:23
|
|
||||||
msgid "Check for updates"
|
|
||||||
msgstr "Проверить на наличие обновлений"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:24
|
|
||||||
msgid "What's new in this version?"
|
|
||||||
msgstr "Что нового в этой версии?"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:25
|
|
||||||
msgid "Visit website"
|
|
||||||
msgstr "Посетить вебсайт"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:26
|
|
||||||
msgid "Player"
|
|
||||||
msgstr "Плеер"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:27
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "Помощь"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:37
|
|
||||||
msgid "search"
|
|
||||||
msgstr "Поиск"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:42
|
|
||||||
msgid "Search in"
|
|
||||||
msgstr "Искать с помощью"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:45
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Поиск"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:49
|
|
||||||
msgid "Results"
|
|
||||||
msgstr "Результаты"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:55
|
|
||||||
msgid "Position"
|
|
||||||
msgstr "Позиция"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:58
|
|
||||||
msgid "Volume"
|
|
||||||
msgstr "Громкость"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:100
|
|
||||||
msgid "Audio Files(*.mp3)|*.mp3"
|
|
||||||
msgstr "Аудио Файлы(*.mp3)|*.mp3"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:100
|
|
||||||
msgid "Save this file"
|
|
||||||
msgstr "Сохранить этот файл"
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:7
|
|
||||||
msgid "Play/Pause"
|
|
||||||
msgstr "Воспроизвести/Приостановить"
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:9
|
|
||||||
msgid "Download"
|
|
||||||
msgstr "Скачать"
|
|
||||||
|
30
src/main.py
30
src/main.py
@ -1,9 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals # at top of module
|
|
||||||
# this is the first fix we have to import just before the paths module would.
|
|
||||||
# it changes a call from wintypes to ctypes.
|
|
||||||
from fixes import fix_winpaths
|
|
||||||
fix_winpaths.fix()
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import storage
|
import storage
|
||||||
@ -13,19 +8,16 @@ storage.setup()
|
|||||||
# Let's import config module here as it is dependent on storage being setup.
|
# Let's import config module here as it is dependent on storage being setup.
|
||||||
import config
|
import config
|
||||||
logging.basicConfig(handlers=[logging.FileHandler(os.path.join(storage.data_directory, "info.log"), "w", "utf-8")], level=logging.DEBUG)
|
logging.basicConfig(handlers=[logging.FileHandler(os.path.join(storage.data_directory, "info.log"), "w", "utf-8")], level=logging.DEBUG)
|
||||||
# Let's mute the google discovery_cache logger as we won't use it and we'll avoid some tracebacks.
|
|
||||||
glog = logging.getLogger("googleapiclient.discovery_cache")
|
|
||||||
glog.setLevel(logging.CRITICAL)
|
|
||||||
# Let's capture all exceptions raised in our log file (especially useful for pyinstaller builds).
|
# Let's capture all exceptions raised in our log file (especially useful for pyinstaller builds).
|
||||||
sys.excepthook = lambda x, y, z: logging.critical(''.join(traceback.format_exception(x, y, z)))
|
sys.excepthook = lambda x, y, z: logging.critical(''.join(traceback.format_exception(x, y, z)))
|
||||||
log = logging.getLogger("main")
|
log = logging.getLogger("main")
|
||||||
log.debug("Logger initialized. Saving debug to {0}".format(storage.data_directory,))
|
log.debug("Logger initialized. Saving debug to {0}".format(storage.data_directory,))
|
||||||
log.debug("Using Python version {0}".format(sys.version,))
|
log.debug("Using Python version {0}".format(sys.version,))
|
||||||
if sys.version[0] == "2":
|
if sys.version[0] == "2":
|
||||||
if hasattr(sys, "frozen"):
|
if hasattr(sys, "frozen"):
|
||||||
log.debug("Applying fixes for Python 2 frozen executables.")
|
log.debug("Applying fixes for Python 2 frozen executables.")
|
||||||
import fixes
|
import fixes
|
||||||
fixes.setup()
|
fixes.setup()
|
||||||
import i18n
|
import i18n
|
||||||
i18n.setup()
|
i18n.setup()
|
||||||
config.setup()
|
config.setup()
|
||||||
@ -34,12 +26,12 @@ import widgetUtils
|
|||||||
import paths
|
import paths
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
log.debug("Starting music-dl %s" % (application.version,))
|
log.debug("Starting music-dl %s" % (application.version,))
|
||||||
log.debug("Application path is %s" % (paths.app_path(),))
|
log.debug("Application path is %s" % (paths.app_path(),))
|
||||||
from controller import mainController
|
from controller import mainController
|
||||||
app = widgetUtils.mainLoopObject()
|
app = widgetUtils.mainLoopObject()
|
||||||
log.debug("Created Application mainloop object")
|
log.debug("Created Application mainloop object")
|
||||||
r = mainController.Controller()
|
r = mainController.Controller()
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
# -*- mode: python -*-
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
a = Analysis(['main.py'],
|
|
||||||
pathex=['.'],
|
|
||||||
binaries=[("plugins", "plugins"),
|
|
||||||
("locales", "locales"),
|
|
||||||
("libvlc.dll", "."),
|
|
||||||
("libvlccore.dll", "."),
|
|
||||||
("bootstrap.exe", "."),
|
|
||||||
("app-configuration.defaults", ".")],
|
|
||||||
datas=[],
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data,
|
|
||||||
cipher=block_cipher)
|
|
||||||
exe = EXE(pyz,
|
|
||||||
a.scripts,
|
|
||||||
exclude_binaries=True,
|
|
||||||
name='musicDL',
|
|
||||||
debug=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False )
|
|
||||||
coll = COLLECT(exe,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
name='main')
|
|
158
src/paths.py
158
src/paths.py
@ -1,116 +1,70 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
import inspect
|
import sys
|
||||||
import platform
|
import platform
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import glob
|
||||||
import sys
|
from platform_utils import paths as paths_
|
||||||
import string
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
def app_data_path(app_name=None):
|
mode = "portable"
|
||||||
"""Cross-platform method for determining where to put application data."""
|
directory = None
|
||||||
"""Requires the name of the application"""
|
fsencoding = sys.getfilesystemencoding()
|
||||||
plat = platform.system()
|
|
||||||
if plat == 'Windows':
|
|
||||||
import winpaths
|
|
||||||
path = winpaths.get_appdata()
|
|
||||||
elif plat == 'Darwin':
|
|
||||||
path = os.path.join(os.path.expanduser('~'), 'Library', 'Application Support')
|
|
||||||
elif plat == 'Linux':
|
|
||||||
path = os.path.expanduser('~')
|
|
||||||
app_name = '.%s' % app_name.replace(' ', '_')
|
|
||||||
return os.path.join(path, app_name)
|
|
||||||
|
|
||||||
def prepare_app_data_path(app_name):
|
if len(glob.glob("Uninstall.exe")) > 0: # installed copy
|
||||||
"""Creates the application's data directory, given its name."""
|
mode= "installed"
|
||||||
dir = app_data_path(app_name)
|
|
||||||
return ensure_path(dir)
|
|
||||||
|
|
||||||
def embedded_data_path():
|
|
||||||
if platform.system() == 'Darwin' and is_frozen():
|
|
||||||
return os.path.abspath(os.path.join(executable_directory(), '..', 'Resources'))
|
|
||||||
return app_path()
|
|
||||||
|
|
||||||
def is_frozen():
|
|
||||||
"""Return a bool indicating if application is compressed"""
|
|
||||||
import imp
|
|
||||||
return hasattr(sys, 'frozen') or imp.is_frozen("__main__")
|
|
||||||
|
|
||||||
def get_executable():
|
|
||||||
"""Returns the full executable path/name if frozen, or the full path/name of the main module if not."""
|
|
||||||
if is_frozen():
|
|
||||||
if platform.system() != 'Darwin':
|
|
||||||
return sys.executable
|
|
||||||
#On darwin, sys.executable points to python. We want the full path to the exe we ran.
|
|
||||||
exedir = os.path.abspath(os.path.dirname(sys.executable))
|
|
||||||
items = os.listdir(exedir)
|
|
||||||
items.remove('python')
|
|
||||||
return os.path.join(exedir, items[0])
|
|
||||||
#Not frozen
|
|
||||||
try:
|
|
||||||
import __main__
|
|
||||||
return os.path.abspath(__main__.__file__)
|
|
||||||
except AttributeError:
|
|
||||||
return sys.argv[0]
|
|
||||||
|
|
||||||
def get_module(level=2):
|
|
||||||
"""Hacky method for deriving the caller of this function's module."""
|
|
||||||
return inspect.getmodule(inspect.stack()[level][0]).__file__
|
|
||||||
|
|
||||||
def executable_directory():
|
|
||||||
"""Always determine the directory of the executable, even when run with py2exe or otherwise frozen"""
|
|
||||||
executable = get_executable()
|
|
||||||
path = os.path.abspath(os.path.dirname(executable))
|
|
||||||
return path
|
|
||||||
|
|
||||||
def app_path():
|
def app_path():
|
||||||
"""Return the root of the application's directory"""
|
return paths_.app_path()
|
||||||
path = executable_directory()
|
|
||||||
if is_frozen() and platform.system() == 'Darwin':
|
|
||||||
path = os.path.abspath(os.path.join(path, '..', '..'))
|
|
||||||
return path
|
|
||||||
|
|
||||||
def module_path(level=2):
|
def config_path():
|
||||||
return os.path.abspath(os.path.dirname(get_module(level)))
|
global mode, directory
|
||||||
|
if mode == "portable":
|
||||||
|
if directory != None: path = os.path.join(directory, "config")
|
||||||
|
elif directory == None: path = os.path.join(app_path(), "config")
|
||||||
|
elif mode == "installed":
|
||||||
|
path = os.path.join(data_path(), "config")
|
||||||
|
if not os.path.exists(path):
|
||||||
|
# log.debug("%s path does not exist, creating..." % (path,))
|
||||||
|
os.mkdir(path)
|
||||||
|
return path
|
||||||
|
|
||||||
def documents_path():
|
def logs_path():
|
||||||
"""On windows, returns the path to My Documents. On OSX, returns the user's Documents folder. For anything else, returns the user's home directory."""
|
global mode, directory
|
||||||
plat = platform.system()
|
if mode == "portable":
|
||||||
if plat == 'Windows':
|
if directory != None: path = os.path.join(directory, "logs")
|
||||||
import winpaths
|
elif directory == None: path = os.path.join(app_path(), "logs")
|
||||||
path = winpaths.get_my_documents()
|
elif mode == "installed":
|
||||||
elif plat == 'Darwin':
|
path = os.path.join(data_path(), "logs")
|
||||||
path = os.path.join(os.path.expanduser('~'), 'Documents')
|
if not os.path.exists(path):
|
||||||
else:
|
# log.debug("%s path does not exist, creating..." % (path,))
|
||||||
path = os.path.expanduser('~')
|
os.mkdir(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def safe_filename(filename):
|
def data_path(app_name='socializer'):
|
||||||
"""Given a filename, returns a safe version with no characters that would not work on different platforms."""
|
if platform.system() == "Windows":
|
||||||
SAFE_FILE_CHARS = "'-_.()[]{}!@#$%^&+=`~ "
|
data_path = os.path.join(os.getenv("AppData"), app_name)
|
||||||
filename = unicode(filename)
|
else:
|
||||||
new_filename = ''.join(c for c in filename if c in SAFE_FILE_CHARS or c.isalnum())
|
data_path = os.path.join(os.environ['HOME'], ".%s" % app_name)
|
||||||
#Windows doesn't like directory names ending in space, macs consider filenames beginning with a dot as hidden, and windows removes dots at the ends of filenames.
|
if not os.path.exists(data_path):
|
||||||
return new_filename.strip(' .')
|
os.mkdir(data_path)
|
||||||
|
return data_path
|
||||||
|
|
||||||
def ensure_path(path):
|
def locale_path():
|
||||||
if not os.path.exists(path):
|
return os.path.join(app_path(), "locales")
|
||||||
os.makedirs(path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def start_file(path):
|
def sound_path():
|
||||||
if platform.system() == 'Windows':
|
return os.path.join(app_path(), "sounds")
|
||||||
os.startfile(path)
|
|
||||||
else:
|
|
||||||
subprocess.Popen(['open', path])
|
|
||||||
|
|
||||||
def get_applications_path():
|
def com_path():
|
||||||
"""Return the directory where applications are commonly installed on the system."""
|
global mode, directory
|
||||||
plat = platform.system()
|
if mode == "portable":
|
||||||
if plat == 'Windows':
|
if directory != None: path = os.path.join(directory, "com_cache")
|
||||||
import winpaths
|
elif directory == None: path = os.path.join(app_path(), "com_cache")
|
||||||
return winpaths.get_program_files()
|
elif mode == "installed":
|
||||||
elif plat == 'Darwin':
|
path = os.path.join(data_path(), "com_cache")
|
||||||
return '/Applications'
|
if not os.path.exists(path):
|
||||||
|
# log.debug("%s path does not exist, creating..." % (path,))
|
||||||
|
os.mkdir(path)
|
||||||
|
return path
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user