Compare commits
285 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 | |||
a65d6a82c0 | |||
659e436dc4 | |||
ab0fc159f1 | |||
18e90b7502 | |||
c8e83d2011 | |||
4254f444db | |||
7756d71b32 | |||
bb97d017b5 | |||
64d076ce44 | |||
d33205e84e | |||
bb411e7bbc | |||
a80bfd53c1 | |||
75131a6fe6 | |||
d480e06ee3 | |||
1fcdd51358 | |||
b254a4eb1b | |||
93b066804b | |||
edc46ee824 | |||
7786a31c2c | |||
ffa02088ad | |||
22b3b31895 | |||
2e67a1ae63 | |||
52265c4f3e | |||
b105dd649d | |||
ad5569f26f | |||
b090d7f896 | |||
0447974029 | |||
26f2da1e6d | |||
a8d6fa84b4 | |||
93d21868e6 | |||
a04dd9c11b | |||
daf1610054 | |||
76b06090e6 | |||
f080977e23 | |||
cb5b0707bb | |||
4e5941cdf4 | |||
6a16a66b5e | |||
dd23ce6adf | |||
99d02c97f0 | |||
d0491d8dd0 |
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:
|
||||
- test
|
||||
- build
|
||||
- pack
|
||||
- test
|
||||
- generate_docs
|
||||
- build
|
||||
- versions
|
||||
- upload
|
||||
|
||||
# Python 3 tests
|
||||
test_py3:
|
||||
stage: test
|
||||
program64:
|
||||
interruptible: true
|
||||
tags:
|
||||
- windows
|
||||
- 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
|
||||
tags:
|
||||
- windows10
|
||||
# Update stuff before building versions
|
||||
variables:
|
||||
PYTHON: "C:\\python310\\python.exe"
|
||||
before_script:
|
||||
- '%PYTHON3% -v'
|
||||
- '%PYTHON3% -m pip install --upgrade pip'
|
||||
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
|
||||
- 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 --upgrade -r requirements.txt'
|
||||
script:
|
||||
- cd src
|
||||
- '%PYINSTALLER% main.spec'
|
||||
# Build this automatically only when tags are pushed to master or when a pipeline has been scheduled by Gitlab.
|
||||
only:
|
||||
- tags
|
||||
- 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:
|
||||
- 'python write_version_data.py'
|
||||
- 'python setup.py build'
|
||||
- cd ..
|
||||
- 'mkdir build'
|
||||
- cd scripts
|
||||
- '%PYTHON3% prepare_zipversion.py'
|
||||
- 'python prepare_zipversion.py'
|
||||
- cd ..
|
||||
- move src\music_dl.zip music_dl.zip
|
||||
# No expiry date as there will be only releases in the artifacts.
|
||||
- move src\music_dl.zip build\music_dl_x64.zip
|
||||
- 'move src/dist build/program64'
|
||||
- 'move src/installer.nsi build'
|
||||
only:
|
||||
- schedules
|
||||
- master
|
||||
artifacts:
|
||||
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.
|
||||
build_setup:
|
||||
stage: pack
|
||||
program32:
|
||||
interruptible: true
|
||||
tags:
|
||||
- windows
|
||||
- windows10
|
||||
only:
|
||||
- tags
|
||||
- schedule_pipelines
|
||||
dependencies:
|
||||
- build_py3
|
||||
stage: build
|
||||
variables:
|
||||
PYTHON: "C:\\python310-32\\python.exe"
|
||||
before_script:
|
||||
- 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:
|
||||
- cd src
|
||||
- '%NSIS% installer.nsi'
|
||||
- 'python write_version_data.py'
|
||||
- 'python setup.py build'
|
||||
- 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:
|
||||
paths:
|
||||
- "music_dl_*"
|
||||
name: music_dl
|
||||
- build
|
||||
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.
|
||||
|
||||
* In case you want to create your own distributable version with Python 2, you'll need py2exe.
|
||||
|
||||
## running
|
||||
|
||||
Run the file main.py, located in the src directory.
|
||||
|
||||
## 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:
|
||||
|
||||
> C:\python3\scripts\pyinstaller.exe main.spec
|
||||
> python setup.py build
|
||||
|
||||
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
|
46
changes.md
46
changes.md
@ -1,5 +1,49 @@
|
||||
## 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
|
||||
|
||||
* 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.
|
||||
* When searching in any service, the search should be performed without freezing the application window.
|
||||
* When transcoding to mp3, the default bitrate now will be 320 KBPS instead of 192.
|
||||
* When downloading, besides the status bar, there is a progress bar which will be updated with the results for the current download.
|
||||
* From the settings dialog, it is possible to switch between all available output devices in the machine, so MusicDL can output audio to a different device than the default in windows.
|
||||
* Added a new and experimental extractor for supporting tidal.
|
||||
* Take into account that this extractor requires you to have a paid account on tidal. Depending in the account level, you will be able to play and download music in high quality or lossless audio. MusicDL will handle both. Lossless audio will be downloaded as flac files, and high quality audio will be downloaded as transcoded 320 KBPS mp3.
|
||||
* There is a new search mode supported in this service. You can retrieve all work for a certain artist by using the protocol artist://, plus the name of the artist you want to retrieve. For example, artist://The beatles will retrieve everything made by the beatles available in the service. The search results will be grouped by albums, compilations and singles, in this order. Depending in the amount of results to display, this may take a long time.
|
||||
* Due to recent problems with mail.ru and unavailable content in most cases, the service has been removed from MusicDL.
|
||||
* YouTube:
|
||||
* Fixed a long standing issue with playback of some elements, due to Youtube sending encrypted versions of these videos. Now playback should be better.
|
||||
* Updated YoutubeDL to version 2019.6.7
|
||||
* Now it is possible to load 50 items for searches as opposed to the previous 20 items limit. This setting can be controlled in the service's preferences
|
||||
* zaycev.net:
|
||||
* Fixed extractor for searching and playing music in zaycev.net.
|
||||
* Unfortunately, it seems this service works only in the russian Federation and some other CIS countries due to copyright reasons.
|
||||
* Updated Spanish translations.
|
||||
|
||||
## Version 0.4
|
||||
|
||||
* Fixed an error when creating a directory located in %appdata%, when using MusicDL as an installed version. MusicDL should be able to work normally again.
|
||||
@ -7,7 +51,7 @@
|
||||
* MusicDL will no longer set volume at 50% when it starts. It will save the volume in a settings file, so it will remember volume settings across restarts.
|
||||
* Added an option in the help menu to report an issue. You can use this feature for sending reports of problems you have encountered while using the application. You will need to provide your email address, though it will not be public anywhere. Your email address will be used only for contacting you if necessary.
|
||||
* changes in Youtube module:
|
||||
* Updated YoutubeDL to version 2018.10.05
|
||||
* Updated YoutubeDL to latest version.
|
||||
|
||||
## Version 0.3
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
coverage
|
||||
wxpython==4.0.3
|
||||
wxpython
|
||||
requests
|
||||
bs4
|
||||
pypubsub
|
||||
python-vlc
|
||||
google-api-python-client
|
||||
youtube-dl
|
||||
pyinstaller
|
||||
isodate
|
||||
yt-dlp
|
||||
python-mpv
|
||||
cx_freeze
|
||||
configobj
|
||||
winpaths
|
||||
winpaths
|
||||
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,242 +1,19 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
# Translations template for musicDL.
|
||||
# Copyright (C) 2020 Manuel Cortez
|
||||
# This file is distributed under the same license as the musicDL project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-03-03 09:38+Hora estándar central (México)\n"
|
||||
"Project-Id-Version: musicDL 2020.7.23\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Generated-By: pygettext.py 1.5\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 ""
|
||||
|
||||
#: ../src\application.py:12
|
||||
msgid "Manuel Cortez (Spanish)"
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\mainController.py:27
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\mainController.py:42
|
||||
msgid "Showing {0} results."
|
||||
msgstr ""
|
||||
|
||||
#: ../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 ""
|
||||
|
||||
#: ../src\controller\mainController.py:236
|
||||
msgid "Searching {0}... "
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\mainController.py:242
|
||||
msgid "No results found. "
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\player.py:43
|
||||
msgid "Error playing {0}. {1}."
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\player.py:49
|
||||
msgid "Playing {0}."
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\player.py:117 ../src\utils.py:53
|
||||
msgid "Downloading {0}."
|
||||
msgstr ""
|
||||
|
||||
#: ../src\controller\player.py:122 ../src\utils.py:63
|
||||
msgid "Downloading {0} ({1}%)."
|
||||
msgstr ""
|
||||
|
||||
#: ../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 ""
|
||||
|
||||
#: ../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:9
|
||||
msgid "New version for %s"
|
||||
msgstr ""
|
||||
|
||||
#: ../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 ""
|
||||
|
||||
#: ../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 ""
|
||||
|
||||
#: ../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 ""
|
||||
|
||||
#: ../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 ""
|
||||
|
||||
#: ../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 ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.9.0\n"
|
||||
|
||||
|
@ -6,10 +6,7 @@ import sys
|
||||
def create_archive():
|
||||
os.chdir("..\\src")
|
||||
print("Creating zip archive...")
|
||||
if sys.version[0] == "3":
|
||||
folder = "dist/main"
|
||||
else:
|
||||
folder = "dist"
|
||||
folder = "dist"
|
||||
shutil.make_archive("music_dl", "zip", folder)
|
||||
# if os.path.exists("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,2 +1,29 @@
|
||||
[main]
|
||||
volume = integer(default=50)
|
||||
language = string(default="system")
|
||||
output_device = string(default="Default")
|
||||
|
||||
[services]
|
||||
[[tidal]]
|
||||
enabled = boolean(default=True)
|
||||
session_id=string(default="")
|
||||
token_type=string(default="")
|
||||
access_token=string(default="")
|
||||
refresh_token=string(default="")
|
||||
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]]
|
||||
enabled = boolean(default=True)
|
||||
max_results = integer(default=20)
|
||||
transcode = boolean(default=True)
|
||||
|
||||
[[zaycev]]
|
||||
enabled = boolean(default=False)
|
@ -1,17 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
python_version = int(sys.version[0])
|
||||
name = "MusicDL"
|
||||
version = "0.5"
|
||||
author = "Manuel Cortéz"
|
||||
authorEmail = "manuel@manuelcortez.net"
|
||||
copyright = "Copyright (C) 2019, Manuel Cortez"
|
||||
author = "MCV Software"
|
||||
authorEmail = "info@mcvsoftware.com"
|
||||
copyright = "Copyright (C) 2019-2022, MCV Software"
|
||||
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"
|
||||
update_url = "https://manuelcortez.net/music_dl/update"
|
||||
url = "https://mcvsoftware.com/music_dl"
|
||||
# The short name will be used for detecting translation files. See languageHandler for more details.
|
||||
short_name = "musicdl"
|
||||
translators = [_(u"Manuel Cortez (Spanish)"), _("Valeria K (Russian)"), ]
|
||||
translators = [_(u"Manuel Cortez (Spanish)")]
|
||||
bts_name = "music_dl"
|
||||
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 -*-
|
||||
import sys
|
||||
if sys.version[0] == "3":
|
||||
raise ImportError()
|
||||
raise ImportError()
|
||||
import os
|
||||
import paths
|
||||
|
||||
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
|
||||
def setup ():
|
||||
global app
|
||||
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))
|
||||
|
||||
global app
|
||||
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))
|
||||
|
@ -6,68 +6,68 @@ import string
|
||||
class ConfigLoadError(Exception): pass
|
||||
|
||||
def load_config(config_path, configspec_path=None, *args, **kwargs):
|
||||
if os.path.exists(config_path):
|
||||
clean_config(config_path)
|
||||
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
|
||||
try:
|
||||
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
|
||||
except ParseError:
|
||||
raise ConfigLoadError("Unable to load %r" % config_path)
|
||||
validator = Validator()
|
||||
validated = config.validate(validator, copy=True)
|
||||
if validated == True:
|
||||
config.write()
|
||||
return config
|
||||
# if os.path.exists(config_path):
|
||||
# clean_config(config_path)
|
||||
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
|
||||
try:
|
||||
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
|
||||
except ParseError:
|
||||
raise ConfigLoadError("Unable to load %r" % config_path)
|
||||
validator = Validator()
|
||||
validated = config.validate(validator, copy=True)
|
||||
if validated == True:
|
||||
config.write()
|
||||
return config
|
||||
|
||||
def is_blank(arg):
|
||||
"Check if a line is blank."
|
||||
for c in arg:
|
||||
if c not in string.whitespace:
|
||||
return False
|
||||
return True
|
||||
"Check if a line is blank."
|
||||
for c in arg:
|
||||
if c not in string.whitespace:
|
||||
return False
|
||||
return True
|
||||
def get_keys(path):
|
||||
"Gets the keys of a configobj config file."
|
||||
res=[]
|
||||
fin=open(path)
|
||||
for line in fin:
|
||||
if not is_blank(line):
|
||||
res.append(line[0:line.find('=')].strip())
|
||||
fin.close()
|
||||
return res
|
||||
"Gets the keys of a configobj config file."
|
||||
res=[]
|
||||
fin=open(path)
|
||||
for line in fin:
|
||||
if not is_blank(line):
|
||||
res.append(line[0:line.find('=')].strip())
|
||||
fin.close()
|
||||
return res
|
||||
|
||||
def hist(keys):
|
||||
"Generates a histogram of an iterable."
|
||||
res={}
|
||||
for k in keys:
|
||||
res[k]=res.setdefault(k,0)+1
|
||||
return res
|
||||
"Generates a histogram of an iterable."
|
||||
res={}
|
||||
for k in keys:
|
||||
res[k]=res.setdefault(k,0)+1
|
||||
return res
|
||||
|
||||
def find_problems(hist):
|
||||
"Takes a histogram and returns a list of items occurring more than once."
|
||||
res=[]
|
||||
for k,v in hist.items():
|
||||
if v>1:
|
||||
res.append(k)
|
||||
return res
|
||||
"Takes a histogram and returns a list of items occurring more than once."
|
||||
res=[]
|
||||
for k,v in hist.items():
|
||||
if v>1:
|
||||
res.append(k)
|
||||
return res
|
||||
|
||||
def clean_config(path):
|
||||
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
|
||||
orig=[]
|
||||
cleaned=[]
|
||||
fin=open(path)
|
||||
for line in fin:
|
||||
orig.append(line)
|
||||
fin.close()
|
||||
for p in find_problems(hist(get_keys(path))):
|
||||
for o in orig:
|
||||
o.strip()
|
||||
if p not in o:
|
||||
cleaned.append(o)
|
||||
if len(cleaned) != 0:
|
||||
cam=open(path,'w')
|
||||
for c in cleaned:
|
||||
cam.write(c)
|
||||
cam.close()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
|
||||
orig=[]
|
||||
cleaned=[]
|
||||
fin=open(path)
|
||||
for line in fin:
|
||||
orig.append(line)
|
||||
fin.close()
|
||||
for p in find_problems(hist(get_keys(path))):
|
||||
for o in orig:
|
||||
o.strip()
|
||||
if p not in o:
|
||||
cleaned.append(o)
|
||||
if len(cleaned) != 0:
|
||||
cam=open(path,'w')
|
||||
for c in cleaned:
|
||||
cam.write(c)
|
||||
cam.close()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
44
src/controller/configuration.py
Normal file
44
src/controller/configuration.py
Normal file
@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import config
|
||||
from utils import get_services
|
||||
from wxUI.configuration import configurationDialog
|
||||
from . import player
|
||||
|
||||
class configuration(object):
|
||||
|
||||
def __init__(self):
|
||||
self.view = configurationDialog(_("Settings"))
|
||||
self.create_config()
|
||||
self.view.get_response()
|
||||
self.save()
|
||||
|
||||
def create_config(self):
|
||||
self.output_devices = player.player.get_output_devices()
|
||||
self.view.create_general(output_devices=[i for i in self.output_devices])
|
||||
current_output_device = config.app["main"]["output_device"]
|
||||
for i in self.output_devices:
|
||||
# here we must compare against the str version of the vlc's device identifier.
|
||||
if i == current_output_device:
|
||||
self.view.set_value("general", "output_device", i)
|
||||
break
|
||||
self.view.realize()
|
||||
extractors = get_services(import_all=True)
|
||||
for i in extractors:
|
||||
if hasattr(i, "settings"):
|
||||
panel = getattr(i, "settings")(self.view.notebook)
|
||||
self.view.notebook.InsertSubPage(1, panel, panel.name)
|
||||
panel.load()
|
||||
if hasattr(panel, "on_enabled"):
|
||||
panel.on_enabled()
|
||||
|
||||
|
||||
def save(self):
|
||||
selected_output_device = self.view.get_value("general", "output_device")
|
||||
if config.app["main"]["output_device"] != selected_output_device:
|
||||
config.app["main"]["output_device"] = selected_output_device
|
||||
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 -*-
|
||||
""" main controller for MusicDL"""
|
||||
from __future__ import unicode_literals # at top of module
|
||||
import types
|
||||
import webbrowser
|
||||
import wx
|
||||
@ -13,250 +12,262 @@ from pubsub import pub
|
||||
from issueReporter import issueReporter
|
||||
from wxUI import mainWindow, menus
|
||||
from update import updater
|
||||
from . import player
|
||||
from utils import get_services
|
||||
from . import player, configuration
|
||||
|
||||
log = logging.getLogger("controller.main")
|
||||
|
||||
def get_extractors():
|
||||
""" Function for importing everything wich is located in the extractors package and has a class named interface."""
|
||||
import extractors
|
||||
module_type = types.ModuleType
|
||||
classes = [m.interface for m in extractors.__dict__.values() if type(m) == module_type and hasattr(m, 'interface')]
|
||||
return sorted(classes, key=lambda c: c.name)
|
||||
|
||||
class Controller(object):
|
||||
|
||||
def __init__(self):
|
||||
super(Controller, self).__init__()
|
||||
log.debug("Starting main controller...")
|
||||
# Setting up the player object
|
||||
player.setup()
|
||||
# Get main window
|
||||
self.window = mainWindow.mainWindow()
|
||||
log.debug("Main window created")
|
||||
self.window.change_status(_(u"Ready"))
|
||||
# Here we will save results for searches as song objects.
|
||||
self.results = []
|
||||
self.connect_events()
|
||||
self.timer = wx.Timer(self.window)
|
||||
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
|
||||
self.timer.Start(75)
|
||||
self.window.vol_slider.SetValue(player.player.volume)
|
||||
# Shows window.
|
||||
utils.call_threaded(updater.do_update)
|
||||
log.debug("Music DL is ready")
|
||||
self.window.Show()
|
||||
def __init__(self):
|
||||
super(Controller, self).__init__()
|
||||
log.debug("Starting main controller...")
|
||||
# Setting up the player object
|
||||
player.setup()
|
||||
# Get main window
|
||||
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_services()])
|
||||
log.debug("Main window created")
|
||||
self.window.change_status(_(u"Ready"))
|
||||
# Here we will save results for searches as song objects.
|
||||
self.results = []
|
||||
self.connect_events()
|
||||
self.timer = wx.Timer(self.window)
|
||||
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
|
||||
self.timer.Start(75)
|
||||
self.window.vol_slider.SetValue(player.player.volume)
|
||||
# Shows window.
|
||||
utils.call_threaded(updater.do_update)
|
||||
log.debug("Music DL is ready")
|
||||
self.window.Show()
|
||||
|
||||
def get_status_info(self):
|
||||
""" Formatting string for status bar messages """
|
||||
if len(self.results) > 0:
|
||||
results = _(u"Showing {0} results.").format(len(self.results))
|
||||
else:
|
||||
results = u""
|
||||
if player.player.shuffle:
|
||||
shuffle = _(u"Shuffle on")
|
||||
else:
|
||||
shuffle = u""
|
||||
final = u"{0} {1}".format(results, shuffle)
|
||||
return final
|
||||
def get_status_info(self):
|
||||
""" Formatting string for status bar messages """
|
||||
if len(self.results) > 0:
|
||||
results = _(u"Showing {0} results.").format(len(self.results))
|
||||
else:
|
||||
results = u""
|
||||
if player.player.shuffle:
|
||||
shuffle = _(u"Shuffle on")
|
||||
else:
|
||||
shuffle = u""
|
||||
final = u"{0} {1}".format(results, shuffle)
|
||||
return final
|
||||
|
||||
def connect_events(self):
|
||||
""" connects all widgets to their corresponding events."""
|
||||
log.debug("Binding events...")
|
||||
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.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_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_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_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_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.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_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.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.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
|
||||
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_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_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_CONTEXT_MENU, self.on_context)
|
||||
self.window.Bind(wx.EVT_CLOSE, self.on_close)
|
||||
pub.subscribe(self.change_status, "change_status")
|
||||
pub.subscribe(self.on_download_finished, "download_finished")
|
||||
pub.subscribe(self.on_notify, "notify")
|
||||
def connect_events(self):
|
||||
""" connects all widgets to their corresponding events."""
|
||||
log.debug("Binding events...")
|
||||
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.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_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_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_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_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.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_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_report_error, menuitem=self.window.report)
|
||||
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.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
|
||||
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_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_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_CONTEXT_MENU, self.on_context)
|
||||
self.window.Bind(wx.EVT_CLOSE, self.on_close)
|
||||
pub.subscribe(self.change_status, "change_status")
|
||||
pub.subscribe(self.on_download_finished, "download_finished")
|
||||
pub.subscribe(self.on_notify, "notify")
|
||||
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.
|
||||
def on_search(self, *args, **kwargs):
|
||||
utils.call_threaded(self.search)
|
||||
# Event functions. These functions will call other functions in a thread and are bound to widget events.
|
||||
|
||||
def on_activated(self, *args, **kwargs):
|
||||
self.on_play()
|
||||
def on_update_progress(self, value):
|
||||
wx.CallAfter(self.window.progressbar.SetValue, value)
|
||||
|
||||
def on_keypress(self, ev):
|
||||
if ev.GetKeyCode() == wx.WXK_RETURN:
|
||||
return self.on_play()
|
||||
elif ev.GetKeyCode() == wx.WXK_SPACE:
|
||||
return self.on_play_pause()
|
||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
|
||||
position = player.player.player.get_time()
|
||||
if position > 5000:
|
||||
player.player.player.set_time(position-5000)
|
||||
else:
|
||||
player.player.player.set_time(0)
|
||||
return
|
||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
|
||||
position = player.player.player.get_time()
|
||||
player.player.player.set_time(position+5000)
|
||||
return
|
||||
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
|
||||
return self.on_volume_up()
|
||||
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
|
||||
return self.on_volume_down()
|
||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
|
||||
return self.on_previous()
|
||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
|
||||
return self.on_next()
|
||||
ev.Skip()
|
||||
def on_settings(self, *args, **kwargs):
|
||||
settings = configuration.configuration()
|
||||
self.reload_extractors()
|
||||
|
||||
def on_play_pause(self, *args, **kwargs):
|
||||
if player.player.player.is_playing() == 1:
|
||||
self.window.play.SetLabel(_(u"Play"))
|
||||
return player.player.pause()
|
||||
else:
|
||||
self.window.play.SetLabel(_(u"Pause"))
|
||||
return player.player.player.play()
|
||||
def on_search(self, *args, **kwargs):
|
||||
text = self.window.get_text()
|
||||
if text == "":
|
||||
return
|
||||
extractor = self.window.extractor.GetValue()
|
||||
self.change_status(_(u"Searching {0}... ").format(text))
|
||||
utils.call_threaded(self.search, text=text, extractor=extractor)
|
||||
|
||||
def on_next(self, *args, **kwargs):
|
||||
return utils.call_threaded(player.player.next)
|
||||
def on_activated(self, *args, **kwargs):
|
||||
self.on_play()
|
||||
|
||||
def on_previous(self, *args, **kwargs):
|
||||
return utils.call_threaded(player.player.previous)
|
||||
def on_keypress(self, ev):
|
||||
if ev.GetKeyCode() == wx.WXK_RETURN:
|
||||
return self.on_play()
|
||||
elif ev.GetKeyCode() == wx.WXK_SPACE:
|
||||
return self.on_play_pause()
|
||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
|
||||
position = player.player.player.get_position()
|
||||
if position > 5000:
|
||||
player.player.player.set_position(position-5000)
|
||||
else:
|
||||
player.player.player.set_position(0)
|
||||
return
|
||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
|
||||
position = player.player.player.get_position()
|
||||
player.player.player.set_position(position+5000)
|
||||
return
|
||||
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
|
||||
return self.on_volume_up()
|
||||
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
|
||||
return self.on_volume_down()
|
||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
|
||||
return self.on_previous()
|
||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
|
||||
return self.on_next()
|
||||
ev.Skip()
|
||||
|
||||
def on_play(self, *args, **kwargs):
|
||||
items = self.results[::]
|
||||
playing_item = self.window.get_item()
|
||||
self.window.play.SetLabel(_(u"Pause"))
|
||||
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
|
||||
def on_play_pause(self, *args, **kwargs):
|
||||
if player.player.player.playback_time != None and player.player.player.pause == False:
|
||||
self.window.play.SetLabel(_("Play"))
|
||||
return player.player.pause()
|
||||
else:
|
||||
self.window.play.SetLabel(_("Pause"))
|
||||
player.player.player.pause = False
|
||||
|
||||
def on_stop(self, *args, **kwargs):
|
||||
player.player.stop()
|
||||
self.window.play.SetLabel(_(u"Play"))
|
||||
def on_next(self, *args, **kwargs):
|
||||
return utils.call_threaded(player.player.next)
|
||||
|
||||
def on_volume_down(self, *args, **kwargs):
|
||||
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
|
||||
self.on_set_volume()
|
||||
def on_previous(self, *args, **kwargs):
|
||||
return utils.call_threaded(player.player.previous)
|
||||
|
||||
def on_volume_up(self, *args, **kwargs):
|
||||
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
|
||||
self.on_set_volume()
|
||||
def on_play(self, *args, **kwargs):
|
||||
items = self.results[::]
|
||||
playing_item = self.window.get_item()
|
||||
self.window.play.SetLabel(_(u"Pause"))
|
||||
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
|
||||
|
||||
def on_mute(self, *args, **kwargs):
|
||||
self.window.vol_slider.SetValue(0)
|
||||
self.on_set_volume()
|
||||
def on_stop(self, *args, **kwargs):
|
||||
player.player.stop()
|
||||
self.window.play.SetLabel(_(u"Play"))
|
||||
|
||||
def on_shuffle(self, *args, **kwargs):
|
||||
player.player.shuffle = self.window.player_shuffle.IsChecked()
|
||||
def on_volume_down(self, *args, **kwargs):
|
||||
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
|
||||
self.on_set_volume()
|
||||
|
||||
def on_context(self, *args, **kwargs):
|
||||
item = self.window.get_item()
|
||||
if item == -1:
|
||||
return wx.Bell()
|
||||
menu = menus.contextMenu()
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
|
||||
self.window.PopupMenu(menu, wx.GetMousePosition())
|
||||
menu.Destroy()
|
||||
def on_volume_up(self, *args, **kwargs):
|
||||
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
|
||||
self.on_set_volume()
|
||||
|
||||
def on_download(self, *args, **kwargs):
|
||||
item = self.results[self.window.get_item()]
|
||||
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
|
||||
f = "{0}.mp3".format(item.format_track())
|
||||
if item.download_url == "":
|
||||
item.get_download_url()
|
||||
path = self.window.get_destination_path(f)
|
||||
if path != None:
|
||||
log.debug("User has requested the following path: {0}".format(path,))
|
||||
if self.extractor.needs_transcode == True: # Send download to vlc based transcoder
|
||||
utils.call_threaded(player.player.transcode_audio, item, path)
|
||||
else:
|
||||
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
|
||||
utils.call_threaded(utils.download_file, item.download_url, path)
|
||||
def on_mute(self, *args, **kwargs):
|
||||
self.window.vol_slider.SetValue(0)
|
||||
self.on_set_volume()
|
||||
|
||||
def on_set_volume(self, *args, **kwargs):
|
||||
volume = self.window.vol_slider.GetValue()
|
||||
player.player.volume = volume
|
||||
def on_shuffle(self, *args, **kwargs):
|
||||
player.player.shuffle = self.window.player_shuffle.IsChecked()
|
||||
|
||||
def on_time_change(self, event, *args, **kwargs):
|
||||
p = event.GetPosition()
|
||||
player.player.player.set_position(p/100.0)
|
||||
event.Skip()
|
||||
def on_context(self, *args, **kwargs):
|
||||
item = self.window.get_item()
|
||||
if item == -1:
|
||||
return wx.Bell()
|
||||
menu = menus.contextMenu()
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
|
||||
self.window.PopupMenu(menu, wx.GetMousePosition())
|
||||
menu.Destroy()
|
||||
|
||||
def on_timer(self, *args, **kwargs):
|
||||
if not self.window.time_slider.HasFocus():
|
||||
progress = player.player.player.get_position()*100
|
||||
self.window.time_slider.SetValue(progress)
|
||||
def on_download(self, *args, **kwargs):
|
||||
item = self.results[self.window.get_item()]
|
||||
if item.download_url == "":
|
||||
item.get_download_url()
|
||||
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
|
||||
f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format())
|
||||
path = self.window.get_destination_path(utils.safe_filename(f))
|
||||
if path != None:
|
||||
log.debug("User has requested the following path: {0}".format(path,))
|
||||
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(), metadata=item.get_metadata())
|
||||
else:
|
||||
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
|
||||
utils.call_threaded(utils.download_file, item.download_url, path, metadata=item.get_metadata())
|
||||
|
||||
def on_close(self, event):
|
||||
log.debug("Exiting...")
|
||||
self.timer.Stop()
|
||||
pub.unsubscribe(self.on_download_finished, "download_finished")
|
||||
config.app.write()
|
||||
event.Skip()
|
||||
widgetUtils.exit_application()
|
||||
def on_set_volume(self, *args, **kwargs):
|
||||
volume = self.window.vol_slider.GetValue()
|
||||
player.player.volume = volume
|
||||
|
||||
def change_status(self, status):
|
||||
""" 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()))
|
||||
def on_time_change(self, event, *args, **kwargs):
|
||||
p = event.GetPosition()
|
||||
if player.player.player != None:
|
||||
progress = int((player.player.player.get_length()/100)*p)
|
||||
player.player.player.set_position(progress)
|
||||
event.Skip()
|
||||
|
||||
def on_visit_website(self, *args, **kwargs):
|
||||
webbrowser.open_new_tab(application.url)
|
||||
def on_timer(self, *args, **kwargs):
|
||||
if not self.window.time_slider.HasFocus():
|
||||
if player.player.player.playback_time != None:
|
||||
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_report_error(self, *args, **kwargs):
|
||||
r = issueReporter.reportBug()
|
||||
def on_close(self, event):
|
||||
log.debug("Exiting...")
|
||||
self.timer.Stop()
|
||||
pub.unsubscribe(self.on_download_finished, "download_finished")
|
||||
config.app.write()
|
||||
event.Skip()
|
||||
widgetUtils.exit_application()
|
||||
|
||||
def on_visit_changelog(self, *args, **kwargs):
|
||||
webbrowser.open_new_tab(application.url+"/news")
|
||||
def change_status(self, status):
|
||||
""" 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()))
|
||||
|
||||
def on_check_for_updates(self, *args, **kwargs):
|
||||
utils.call_threaded(updater.do_update)
|
||||
def on_visit_website(self, *args, **kwargs):
|
||||
webbrowser.open_new_tab(application.url)
|
||||
|
||||
def on_download_finished(self, file):
|
||||
title = "MusicDL"
|
||||
msg = _(u"File downloaded: {0}").format(file,)
|
||||
self.window.notify(title, msg)
|
||||
def on_report_error(self, *args, **kwargs):
|
||||
r = issueReporter.reportBug()
|
||||
|
||||
def on_notify(self, title, message):
|
||||
self.window.notify(title, message)
|
||||
def on_visit_changelog(self, *args, **kwargs):
|
||||
webbrowser.open_new_tab(application.url+"/news")
|
||||
|
||||
# real functions. These functions really are doing the work.
|
||||
def search(self, *args, **kwargs):
|
||||
text = self.window.get_text()
|
||||
if text == "":
|
||||
return
|
||||
extractor = self.window.extractor.GetValue()
|
||||
self.change_status(_(u"Searching {0}... ").format(text))
|
||||
extractors = get_extractors()
|
||||
for i in extractors:
|
||||
if extractor == i.name:
|
||||
self.extractor = i()
|
||||
break
|
||||
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
|
||||
self.window.list.Clear()
|
||||
self.extractor.search(text)
|
||||
self.results = self.extractor.results
|
||||
for i in self.results:
|
||||
self.window.list.Append(i.format_track())
|
||||
if len(self.results) == 0:
|
||||
self.change_status(_(u"No results found. "))
|
||||
else:
|
||||
self.change_status(u"")
|
||||
wx.CallAfter(self.window.list.SetFocus)
|
||||
def on_check_for_updates(self, *args, **kwargs):
|
||||
utils.call_threaded(updater.do_update)
|
||||
|
||||
def on_download_finished(self, file):
|
||||
title = "MusicDL"
|
||||
msg = _(u"File downloaded: {0}").format(file,)
|
||||
self.window.notify(title, msg)
|
||||
|
||||
def on_notify(self, title, message):
|
||||
self.window.notify(title, message)
|
||||
|
||||
# real functions. These functions really are doing the work.
|
||||
def search(self, text, extractor, *args, **kwargs):
|
||||
extractors = get_services()
|
||||
for i in extractors:
|
||||
if extractor == i.interface.name:
|
||||
self.extractor = i.interface()
|
||||
break
|
||||
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
|
||||
wx.CallAfter(self.window.list.Clear)
|
||||
self.extractor.search(text)
|
||||
self.results = self.extractor.results
|
||||
for i in self.results:
|
||||
wx.CallAfter(self.window.list.Append, i.format_track())
|
||||
if len(self.results) == 0:
|
||||
wx.CallAfter(self.change_status, _(u"No results found. "))
|
||||
else:
|
||||
wx.CallAfter(self.change_status, u"")
|
||||
wx.CallAfter(self.window.list.SetFocus)
|
||||
|
||||
def reload_extractors(self):
|
||||
extractors = [i.interface.name for i in get_services()]
|
||||
self.window.extractor.SetItems(extractors)
|
||||
self.window.extractor.SetValue(extractors[0])
|
||||
|
@ -1,145 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals # at top of module
|
||||
import os
|
||||
import random
|
||||
import vlc
|
||||
import logging
|
||||
import config
|
||||
import time
|
||||
import mpv
|
||||
from pubsub import pub
|
||||
from utils import call_threaded
|
||||
|
||||
player = None
|
||||
log = logging.getLogger("controller.player")
|
||||
|
||||
def setup():
|
||||
global player
|
||||
if player == None:
|
||||
player = audioPlayer()
|
||||
global player
|
||||
if player == None:
|
||||
player = audioPlayer()
|
||||
|
||||
class audioPlayer(object):
|
||||
|
||||
def __init__(self):
|
||||
self.is_playing = False
|
||||
self.vol = config.app["main"]["volume"]
|
||||
self.is_working = False
|
||||
self.queue = []
|
||||
self.stopped = True
|
||||
self.queue_pos = 0
|
||||
self.shuffle = False
|
||||
self.instance = vlc.Instance()
|
||||
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.")
|
||||
def __init__(self):
|
||||
self.is_playing = False
|
||||
self.vol = config.app["main"]["volume"]
|
||||
self.is_working = False
|
||||
self.queue = []
|
||||
self.stopped = True
|
||||
self.queue_pos = 0
|
||||
self.shuffle = False
|
||||
self.player = mpv.MPV()
|
||||
|
||||
def play(self, item):
|
||||
self.stopped = True
|
||||
if self.is_working == False:
|
||||
self.is_working = True
|
||||
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
|
||||
# Fires at the end of every file and attempts to play the next one.
|
||||
@self.player.event_callback('end-file')
|
||||
def handle_end_idle(event):
|
||||
if event.as_dict()["reason"] == b"aborted" or event.as_dict()["reason"] == b"stop":
|
||||
return
|
||||
log.debug("Reached end of file stream.")
|
||||
if len(self.queue) > 1:
|
||||
log.debug("Requesting next item...")
|
||||
self.next()
|
||||
|
||||
def next(self):
|
||||
if len(self.queue) > 0:
|
||||
if self.shuffle:
|
||||
self.queue_pos = random.randint(0, len(self.queue)-1)
|
||||
else:
|
||||
if self.queue_pos < len(self.queue)-1:
|
||||
self.queue_pos += 1
|
||||
else:
|
||||
self.queue_pos = 0
|
||||
self.play(self.queue[self.queue_pos])
|
||||
def get_output_devices(self):
|
||||
""" Retrieve enabled output devices so we can switch or use those later. """
|
||||
return None
|
||||
|
||||
def previous(self):
|
||||
if len(self.queue) > 0:
|
||||
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 set_output_device(self, device_name):
|
||||
""" Set Output device to be used in LibVLC"""
|
||||
log.debug("Setting output audio device to {device}...".format(device=device_name,))
|
||||
# config.app["main"]["output_device"] = "Default"
|
||||
|
||||
def stop(self):
|
||||
self.player.stop()
|
||||
self.stopped = True
|
||||
def play(self, item):
|
||||
self.stopped = True
|
||||
if self.is_working == False:
|
||||
self.is_working = True
|
||||
if item.download_url == "":
|
||||
item.get_download_url()
|
||||
log.debug("playing {0}...".format(item.download_url,))
|
||||
self.player.play(item.download_url)
|
||||
self.player.volume = self.vol
|
||||
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
|
||||
self.stopped = False
|
||||
self.is_working = False
|
||||
|
||||
def pause(self):
|
||||
self.player.pause()
|
||||
if self.stopped == True:
|
||||
self.stopped = False
|
||||
else:
|
||||
self.stopped = True
|
||||
def next(self):
|
||||
if len(self.queue) > 0:
|
||||
if self.shuffle:
|
||||
self.queue_pos = random.randint(0, len(self.queue)-1)
|
||||
else:
|
||||
if self.queue_pos < len(self.queue)-1:
|
||||
self.queue_pos += 1
|
||||
else:
|
||||
self.queue_pos = 0
|
||||
self.play(self.queue[self.queue_pos])
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
return self.vol
|
||||
def previous(self):
|
||||
if len(self.queue) > 0:
|
||||
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])
|
||||
|
||||
@volume.setter
|
||||
def volume(self, vol):
|
||||
if vol <= 100 and vol >= 0:
|
||||
config.app["main"]["volume"] = vol
|
||||
self.vol = vol
|
||||
self.player.audio_set_volume(self.vol)
|
||||
def stop(self):
|
||||
self.player.stop()
|
||||
self.stopped = True
|
||||
|
||||
def play_all(self, list_of_items, playing=0, shuffle=False):
|
||||
if list_of_items != self.queue:
|
||||
self.queue = list_of_items
|
||||
self.shuffle = shuffle
|
||||
self.queue_pos = playing
|
||||
self.play(self.queue[self.queue_pos])
|
||||
def pause(self):
|
||||
self.player.pause = True
|
||||
if self.stopped == True:
|
||||
self.stopped = False
|
||||
else:
|
||||
self.stopped = True
|
||||
|
||||
def end_callback(self, event, *args, **kwargs):
|
||||
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4
|
||||
call_threaded(self.next)
|
||||
@property
|
||||
def volume(self):
|
||||
return self.vol
|
||||
|
||||
def transcode_audio(self, item, path):
|
||||
""" Converts given item to mp3. This method will be available when needed automatically."""
|
||||
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=mp3,ab=192}:file{mux=raw,dst=\"%s\"}"% (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)))
|
||||
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))
|
||||
@volume.setter
|
||||
def volume(self, vol):
|
||||
if vol <= 100 and vol >= 0:
|
||||
config.app["main"]["volume"] = vol
|
||||
self.vol = vol
|
||||
if self.player != None:
|
||||
self.player.volume = self.vol
|
||||
|
||||
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)
|
||||
self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
|
||||
def play_all(self, list_of_items, playing=0, shuffle=False):
|
||||
if list_of_items != self.queue:
|
||||
self.queue = list_of_items
|
||||
self.shuffle = shuffle
|
||||
self.queue_pos = playing
|
||||
self.play(self.queue[self.queue_pos])
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from __future__ import unicode_literals # at top of module
|
||||
|
||||
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 = ""
|
||||
|
||||
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)
|
@ -1,56 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from __future__ import unicode_literals # at top of module
|
||||
try:
|
||||
import urllib.parse as urlparse
|
||||
except ImportError:
|
||||
import urllib as urlparse
|
||||
import requests
|
||||
import youtube_dl
|
||||
import logging
|
||||
from bs4 import BeautifulSoup
|
||||
from . import baseFile
|
||||
|
||||
log = logging.getLogger("extractors.mail.ru")
|
||||
|
||||
class interface(object):
|
||||
name = "mail.ru"
|
||||
|
||||
def __init__(self):
|
||||
self.results = []
|
||||
self.needs_transcode = False
|
||||
log.debug("Started extraction service for mail.ru music")
|
||||
|
||||
def search(self, text, page=1):
|
||||
if text == "" or text == None:
|
||||
raise ValueError("Text must be passed and should not be blank.")
|
||||
site = 'https://my.mail.ru/music/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": "songs-table__row__col songs-table__row__col--title title songs-table__row__col--title-hq-similar resize"})
|
||||
self.results = []
|
||||
for search in search_results:
|
||||
data = search.find_all("a")
|
||||
s = baseFile.song(self)
|
||||
s.title = data[0].text.replace("\n", "").replace("\t", "")
|
||||
# s.artist = data[1].text.replace("\n", "").replace("\t", "")
|
||||
# print(data)
|
||||
s.url = u"https://my.mail.ru"+urlparse.quote(data[0].__dict__["attrs"]["href"].encode("utf-8"))
|
||||
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,))
|
||||
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
|
||||
log.debug("Download URL: {0}".format(video["url"],))
|
||||
return video["url"]
|
||||
|
||||
def format_track(self, item):
|
||||
return item.title
|
@ -1,102 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals # at top of module
|
||||
import isodate
|
||||
import youtube_dl
|
||||
import logging
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
from .import baseFile
|
||||
from update.utils import seconds_to_string
|
||||
|
||||
DEVELOPER_KEY = "AIzaSyCU_hvZJEjLlAGAnlscquKEkE8l0lVOfn0"
|
||||
YOUTUBE_API_SERVICE_NAME = "youtube"
|
||||
YOUTUBE_API_VERSION = "v3"
|
||||
|
||||
log = logging.getLogger("extractors.youtube.com")
|
||||
|
||||
class interface(object):
|
||||
name = "youtube"
|
||||
|
||||
def __init__(self):
|
||||
self.results = []
|
||||
self.needs_transcode = True
|
||||
log.debug("started extraction service for {0}".format(self.name,))
|
||||
|
||||
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 = 20
|
||||
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 = baseFile.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, 'format': 'bestaudio/best', '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
|
||||
log.debug("Download URL: {0}".format(video["url"],))
|
||||
return video["url"]
|
||||
|
||||
def format_track(self, item):
|
||||
return "{0} {1}".format(item.title, item.duration)
|
@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from __future__ import unicode_literals # at top of module
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
import logging
|
||||
from bs4 import BeautifulSoup
|
||||
from . import baseFile
|
||||
|
||||
log = logging.getLogger("extractors.zaycev.net")
|
||||
|
||||
class interface(object):
|
||||
name = "zaycev.net"
|
||||
|
||||
def __init__(self):
|
||||
self.results = []
|
||||
self.needs_transcode = False
|
||||
log.debug("Started extraction service for zaycev.net")
|
||||
|
||||
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 = baseFile.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)
|
@ -4,4 +4,4 @@ from . import fix_requests
|
||||
from .import fix_winpaths
|
||||
|
||||
def setup():
|
||||
fix_requests.fix()
|
||||
fix_requests.fix()
|
||||
|
@ -7,6 +7,6 @@ import paths
|
||||
log = logging.getLogger("fixes.fix_requests")
|
||||
|
||||
def fix():
|
||||
log.debug("Applying fix for requests...")
|
||||
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("Applying fix for requests...")
|
||||
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"],))
|
||||
|
@ -4,9 +4,9 @@ import winpaths
|
||||
from ctypes import wintypes
|
||||
|
||||
def _get_path_buf(csidl):
|
||||
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
|
||||
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
|
||||
return path_buf.value
|
||||
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
|
||||
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
|
||||
return path_buf.value
|
||||
|
||||
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")
|
||||
|
||||
def setup():
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
os.environ["lang"] = lang
|
||||
log.debug("System detected language: {0}".format(lang,))
|
||||
if sys.version[0] == "3":
|
||||
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
|
||||
else:
|
||||
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
os.environ["lang"] = lang
|
||||
log.debug("System detected language: {0}".format(lang,))
|
||||
if sys.version[0] == "3":
|
||||
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
|
||||
else:
|
||||
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)
|
||||
|
@ -1,11 +1,12 @@
|
||||
!include "MUI2.nsh"
|
||||
!include "LogicLib.nsh"
|
||||
!include "x64.nsh"
|
||||
Unicode true
|
||||
CRCCheck on
|
||||
ManifestSupportedOS all
|
||||
XPStyle on
|
||||
Name "MusicDL"
|
||||
OutFile "music_dl_0.5_setup.exe"
|
||||
OutFile "music_dl_setup.exe"
|
||||
InstallDir "$PROGRAMFILES\musicDL"
|
||||
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "InstallLocation"
|
||||
RequestExecutionLevel admin
|
||||
@ -13,18 +14,18 @@ SetCompress auto
|
||||
SetCompressor /solid lzma
|
||||
SetDatablockOptimize on
|
||||
VIAddVersionKey ProductName "MusicDL"
|
||||
VIAddVersionKey LegalCopyright "Copyright 2019 Manuel Cortez."
|
||||
VIAddVersionKey ProductVersion "0.5"
|
||||
VIAddVersionKey FileVersion "0.5"
|
||||
VIProductVersion "0.5.0.0"
|
||||
VIFileVersion "0.5.0.0"
|
||||
VIAddVersionKey LegalCopyright "Copyright 2019 - 2022 MCV Software."
|
||||
VIAddVersionKey ProductVersion "0.7"
|
||||
VIAddVersionKey FileVersion "0.7"
|
||||
VIProductVersion "0.7.0.0"
|
||||
VIFileVersion "0.7.0.0"
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
var StartMenuFolder
|
||||
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!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"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
@ -36,7 +37,11 @@ var StartMenuFolder
|
||||
Section
|
||||
SetShellVarContext All
|
||||
SetOutPath "$INSTDIR"
|
||||
File /r dist\main\*
|
||||
${If} ${RunningX64}
|
||||
File /r program64\*
|
||||
${Else}
|
||||
File /r program32\*
|
||||
${EndIf}
|
||||
CreateShortCut "$DESKTOP\musicDL.lnk" "$INSTDIR\musicDL.exe"
|
||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
|
||||
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" "InstallLocation" $INSTDIR
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "DisplayVersion" "0.5"
|
||||
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"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMajor" 0
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMinor" 1
|
||||
@ -65,5 +70,8 @@ Delete "$DESKTOP\MusicDL.lnk"
|
||||
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
|
||||
SectionEnd
|
||||
Function .onInit
|
||||
${If} ${RunningX64}
|
||||
StrCpy $instdir "$programfiles64\musicDL"
|
||||
${EndIf}
|
||||
!insertmacro MUI_LANGDLL_DISPLAY
|
||||
FunctionEnd
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
############################################################
|
||||
# Copyright (c) 2018 Manuel Cortez <manuel@manuelcortez.net>
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
@ -27,37 +27,37 @@ from utils import call_threaded
|
||||
from . import wx_ui
|
||||
|
||||
class reportBug(object):
|
||||
def __init__(self):
|
||||
self.dialog = wx_ui.reportBugDialog()
|
||||
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
|
||||
self.dialog.get_response()
|
||||
def __init__(self):
|
||||
self.dialog = wx_ui.reportBugDialog()
|
||||
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
|
||||
self.dialog.get_response()
|
||||
|
||||
def do_report(self, *args, **kwargs):
|
||||
r = requests.post(*args, **kwargs)
|
||||
if r.status_code > 300:
|
||||
wx.CallAfter(self.dialog.error)
|
||||
wx.CallAfter(self.dialog.progress.Destroy)
|
||||
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
|
||||
def do_report(self, *args, **kwargs):
|
||||
r = requests.post(*args, **kwargs)
|
||||
if r.status_code > 300:
|
||||
wx.CallAfter(self.dialog.error)
|
||||
wx.CallAfter(self.dialog.progress.Destroy)
|
||||
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
|
||||
|
||||
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") == "":
|
||||
self.dialog.no_filled()
|
||||
return
|
||||
if self.dialog.get("agree") == False:
|
||||
self.dialog.no_checkbox()
|
||||
return
|
||||
title = self.dialog.get("summary")
|
||||
body = self.dialog.get("description")
|
||||
issue_type = "issue" # for now just have issue
|
||||
app_type = storage.app_type
|
||||
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_contact_type = "email" # For now just email is supported in the issue reporter
|
||||
reporter_contact_handle = self.dialog.get("email")
|
||||
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)
|
||||
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
|
||||
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
|
||||
call_threaded(self.do_report, url, json=json, auth=auth)
|
||||
self.dialog.show_progress()
|
||||
self.dialog.EndModal(wx.ID_OK)
|
||||
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") == "":
|
||||
self.dialog.no_filled()
|
||||
return
|
||||
if self.dialog.get("agree") == False:
|
||||
self.dialog.no_checkbox()
|
||||
return
|
||||
title = self.dialog.get("summary")
|
||||
body = self.dialog.get("description")
|
||||
issue_type = "issue" # for now just have issue
|
||||
app_type = storage.app_type
|
||||
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_contact_type = "email" # For now just email is supported in the issue reporter
|
||||
reporter_contact_handle = self.dialog.get("email")
|
||||
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)
|
||||
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
|
||||
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
|
||||
call_threaded(self.do_report, url, json=json, auth=auth)
|
||||
self.dialog.show_progress()
|
||||
self.dialog.EndModal(wx.ID_OK)
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
############################################################
|
||||
# Copyright (c) 2018 Manuel cortez <manuel@manuelcortez.net>
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
@ -21,89 +21,89 @@ import widgetUtils
|
||||
import application
|
||||
|
||||
class reportBugDialog(widgetUtils.BaseDialog):
|
||||
def __init__(self):
|
||||
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
|
||||
self.SetTitle(_(u"Report an error"))
|
||||
panel = wx.Panel(self)
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
def __init__(self):
|
||||
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
|
||||
self.SetTitle(_(u"Report an error"))
|
||||
panel = wx.Panel(self)
|
||||
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)
|
||||
self.summary = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.summary)
|
||||
dc.SetFont(self.summary.GetFont())
|
||||
self.summary.SetSize(dc.GetTextExtent("a"*80))
|
||||
summaryB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
|
||||
summaryB.Add(self.summary, 0, wx.ALL, 5)
|
||||
sizer.Add(summaryB, 0, wx.ALL, 5)
|
||||
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)
|
||||
dc = wx.WindowDC(self.summary)
|
||||
dc.SetFont(self.summary.GetFont())
|
||||
self.summary.SetSize(dc.GetTextExtent("a"*80))
|
||||
summaryB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
|
||||
summaryB.Add(self.summary, 0, wx.ALL, 5)
|
||||
sizer.Add(summaryB, 0, wx.ALL, 5)
|
||||
|
||||
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
|
||||
self.first_name = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.first_name)
|
||||
dc.SetFont(self.first_name.GetFont())
|
||||
self.first_name.SetSize(dc.GetTextExtent("a"*40))
|
||||
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
|
||||
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
|
||||
sizer.Add(first_nameB, 0, wx.ALL, 5)
|
||||
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
|
||||
self.first_name = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.first_name)
|
||||
dc.SetFont(self.first_name.GetFont())
|
||||
self.first_name.SetSize(dc.GetTextExtent("a"*40))
|
||||
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
|
||||
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
|
||||
sizer.Add(first_nameB, 0, wx.ALL, 5)
|
||||
|
||||
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
|
||||
self.last_name = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.last_name)
|
||||
dc.SetFont(self.last_name.GetFont())
|
||||
self.last_name.SetSize(dc.GetTextExtent("a"*40))
|
||||
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
|
||||
last_nameB.Add(self.last_name, 0, wx.ALL, 5)
|
||||
sizer.Add(last_nameB, 0, wx.ALL, 5)
|
||||
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
|
||||
self.last_name = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.last_name)
|
||||
dc.SetFont(self.last_name.GetFont())
|
||||
self.last_name.SetSize(dc.GetTextExtent("a"*40))
|
||||
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
|
||||
last_nameB.Add(self.last_name, 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)
|
||||
self.email = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.email)
|
||||
dc.SetFont(self.email.GetFont())
|
||||
self.email.SetSize(dc.GetTextExtent("a"*30))
|
||||
emailB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
emailB.Add(emailLabel, 0, wx.ALL, 5)
|
||||
emailB.Add(self.email, 0, wx.ALL, 5)
|
||||
sizer.Add(emailB, 0, wx.ALL, 5)
|
||||
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
|
||||
self.email = wx.TextCtrl(panel, -1)
|
||||
dc = wx.WindowDC(self.email)
|
||||
dc.SetFont(self.email.GetFont())
|
||||
self.email.SetSize(dc.GetTextExtent("a"*30))
|
||||
emailB = wx.BoxSizer(wx.HORIZONTAL)
|
||||
emailB.Add(emailLabel, 0, wx.ALL, 5)
|
||||
emailB.Add(self.email, 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)
|
||||
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
|
||||
dc = wx.WindowDC(self.description)
|
||||
dc.SetFont(self.description.GetFont())
|
||||
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
|
||||
self.description.SetSize((x, y))
|
||||
descBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
|
||||
descBox.Add(self.description, 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.SetValue(False)
|
||||
sizer.Add(self.agree, 0, wx.ALL, 5)
|
||||
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
|
||||
self.ok.SetDefault()
|
||||
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
|
||||
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
btnBox.Add(self.ok, 0, wx.ALL, 5)
|
||||
btnBox.Add(cancel, 0, wx.ALL, 5)
|
||||
sizer.Add(btnBox, 0, wx.ALL, 5)
|
||||
panel.SetSizer(sizer)
|
||||
self.SetClientSize(sizer.CalcMin())
|
||||
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)
|
||||
dc = wx.WindowDC(self.description)
|
||||
dc.SetFont(self.description.GetFont())
|
||||
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
|
||||
self.description.SetSize((x, y))
|
||||
descBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
|
||||
descBox.Add(self.description, 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.SetValue(False)
|
||||
sizer.Add(self.agree, 0, wx.ALL, 5)
|
||||
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
|
||||
self.ok.SetDefault()
|
||||
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
|
||||
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
btnBox.Add(self.ok, 0, wx.ALL, 5)
|
||||
btnBox.Add(cancel, 0, wx.ALL, 5)
|
||||
sizer.Add(btnBox, 0, wx.ALL, 5)
|
||||
panel.SetSizer(sizer)
|
||||
self.SetClientSize(sizer.CalcMin())
|
||||
|
||||
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()
|
||||
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()
|
||||
|
||||
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()
|
||||
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()
|
||||
|
||||
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()
|
||||
self.Destroy()
|
||||
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()
|
||||
self.Destroy()
|
||||
|
||||
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()
|
||||
self.Destroy()
|
||||
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()
|
||||
self.Destroy()
|
||||
|
||||
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.ShowModal()
|
||||
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.ShowModal()
|
||||
|
BIN
src/libvlc.dll
BIN
src/libvlc.dll
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,237 +1,20 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
# Copyright (C) 2019 ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: 2018-02-28 15:02-0600\n"
|
||||
"PO-Revision-Date: 2018-03-17 16:25-0600\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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"
|
||||
"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"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
"X-Generator: Poedit 2.0.2\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Generated-By: Babel 2.9.0\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 ""
|
||||
" Es una aplicación que te permite descargar música de sitios populares como "
|
||||
"YouTube y zaycev.net."
|
||||
|
||||
#: ../src\application.py:12
|
||||
msgid "Manuel Cortez (Spanish)"
|
||||
msgstr "Manuel Cortez (Español)"
|
||||
|
||||
#: ../src\controller\mainController.py:27
|
||||
msgid "Ready"
|
||||
msgstr "Listo"
|
||||
|
||||
#: ../src\controller\mainController.py:42
|
||||
msgid "Showing {0} results."
|
||||
msgstr "Mostrando {0} resultados"
|
||||
|
||||
#: ../src\controller\mainController.py:46
|
||||
msgid "Shuffle on"
|
||||
msgstr "Modo aleatorio activo"
|
||||
|
||||
#: ../src\controller\mainController.py:105
|
||||
#: ../src\controller\mainController.py:125 ../src\wxUI\mainWindow.py:13
|
||||
#: ../src\wxUI\mainWindow.py:61
|
||||
msgid "Play"
|
||||
msgstr "Reproducir"
|
||||
|
||||
#: ../src\controller\mainController.py:108
|
||||
#: ../src\controller\mainController.py:120
|
||||
msgid "Pause"
|
||||
msgstr "pausa"
|
||||
|
||||
#: ../src\controller\mainController.py:206
|
||||
msgid "Searching {0}... "
|
||||
msgstr "Buscando {0}..."
|
||||
|
||||
#: ../src\controller\player.py:42
|
||||
msgid "Error playing {0}. {1}."
|
||||
msgstr "Error reproduciendo {0}. {1}."
|
||||
|
||||
#: ../src\controller\player.py:48
|
||||
msgid "Playing {0}."
|
||||
msgstr "Reproduciendo {0}."
|
||||
|
||||
#: ../src\controller\player.py:116 ../src\utils.py:53
|
||||
msgid "Downloading {0}."
|
||||
msgstr "Descargando {0}."
|
||||
|
||||
#: ../src\controller\player.py:121 ../src\utils.py:63
|
||||
msgid "Downloading {0} ({1}%)."
|
||||
msgstr "Descargando {0} ({1}%)."
|
||||
|
||||
#: ../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:9
|
||||
msgid "New version for %s"
|
||||
msgstr "Nueva versión de %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 ""
|
||||
"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:16
|
||||
msgid "Download in Progress"
|
||||
msgstr "Descarga en progreso"
|
||||
|
||||
#: ../src\update\wxUpdater.py:16
|
||||
msgid "Downloading the new version..."
|
||||
msgstr "Descargando la nueva versión..."
|
||||
|
||||
#: ../src\update\wxUpdater.py:26
|
||||
msgid "Updating... %s of %s"
|
||||
msgstr "Actualizando... %s de %s"
|
||||
|
||||
#: ../src\update\wxUpdater.py:29
|
||||
msgid "Done!"
|
||||
msgstr "¡Hecho!"
|
||||
|
||||
#: ../src\update\wxUpdater.py:29
|
||||
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\mainWindow.py:14 ../src\wxUI\mainWindow.py:62
|
||||
msgid "Stop"
|
||||
msgstr "Detener"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:15 ../src\wxUI\mainWindow.py:60
|
||||
msgid "Previous"
|
||||
msgstr "Anterior"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:16 ../src\wxUI\mainWindow.py:63
|
||||
msgid "Next"
|
||||
msgstr "Siguiente"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:17
|
||||
msgid "Shuffle"
|
||||
msgstr "Aleatorio"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:18
|
||||
msgid "Volume down"
|
||||
msgstr "Bajar volumen"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:19
|
||||
msgid "Volume up"
|
||||
msgstr "Subir volumen"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:20
|
||||
msgid "Mute"
|
||||
msgstr "Silenciar"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:22
|
||||
msgid "About {0}"
|
||||
msgstr "Sobre {0}"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:23
|
||||
msgid "Check for updates"
|
||||
msgstr "Comprobar actualizaciones"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:24
|
||||
msgid "Visit website"
|
||||
msgstr "Visitar sitio web"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:25
|
||||
msgid "Player"
|
||||
msgstr "Reproductor"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:26
|
||||
msgid "Help"
|
||||
msgstr "Ayuda"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:36
|
||||
msgid "search"
|
||||
msgstr "Buscar"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:41
|
||||
msgid "Search in"
|
||||
msgstr "Buscar en"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:44
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:48
|
||||
msgid "Results"
|
||||
msgstr "Resultados"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:54
|
||||
msgid "Position"
|
||||
msgstr "Posición"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:57
|
||||
msgid "Volume"
|
||||
msgstr "Volumen"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:99
|
||||
msgid "Audio Files(*.mp3)|*.mp3"
|
||||
msgstr "Archivos de audio (*.mp3)|*.mp3"
|
||||
|
||||
#: ../src\wxUI\mainWindow.py:99
|
||||
msgid "Save this file"
|
||||
msgstr "Guardar archivo"
|
||||
|
||||
#: ../src\wxUI\menus.py:7
|
||||
msgid "Play/Pause"
|
||||
msgstr "Reproducir/pausar"
|
||||
|
||||
#: ../src\wxUI\menus.py:9
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
|
Binary file not shown.
@ -1,254 +1,20 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
# Copyright (C) 2018 ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"POT-Creation-Date: 2018-03-03 09:38+Hora estándar central (México)\n"
|
||||
"PO-Revision-Date: 2018-03-12 01:35+0400\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||
"PO-Revision-Date: 2020-07-30 05:13-0500\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"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
"Generated-By: Babel 2.9.0\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 "Скачать"
|
||||
|
32
src/main.py
32
src/main.py
@ -1,9 +1,4 @@
|
||||
# -*- 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 logging
|
||||
import storage
|
||||
@ -12,20 +7,17 @@ import sys
|
||||
storage.setup()
|
||||
# Let's import config module here as it is dependent on storage being setup.
|
||||
import config
|
||||
logging.basicConfig(filename=os.path.join(storage.data_directory, "info.log"), level=logging.DEBUG, filemode="w")
|
||||
# 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)
|
||||
logging.basicConfig(handlers=[logging.FileHandler(os.path.join(storage.data_directory, "info.log"), "w", "utf-8")], level=logging.DEBUG)
|
||||
# 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)))
|
||||
log = logging.getLogger("main")
|
||||
log.debug("Logger initialized. Saving debug to {0}".format(storage.data_directory,))
|
||||
log.debug("Using Python version {0}".format(sys.version,))
|
||||
if sys.version[0] == "2":
|
||||
if hasattr(sys, "frozen"):
|
||||
log.debug("Applying fixes for Python 2 frozen executables.")
|
||||
import fixes
|
||||
fixes.setup()
|
||||
if hasattr(sys, "frozen"):
|
||||
log.debug("Applying fixes for Python 2 frozen executables.")
|
||||
import fixes
|
||||
fixes.setup()
|
||||
import i18n
|
||||
i18n.setup()
|
||||
config.setup()
|
||||
@ -34,12 +26,12 @@ import widgetUtils
|
||||
import paths
|
||||
|
||||
def setup():
|
||||
log.debug("Starting music-dl %s" % (application.version,))
|
||||
log.debug("Application path is %s" % (paths.app_path(),))
|
||||
from controller import mainController
|
||||
app = widgetUtils.mainLoopObject()
|
||||
log.debug("Created Application mainloop object")
|
||||
r = mainController.Controller()
|
||||
app.run()
|
||||
log.debug("Starting music-dl %s" % (application.version,))
|
||||
log.debug("Application path is %s" % (paths.app_path(),))
|
||||
from controller import mainController
|
||||
app = widgetUtils.mainLoopObject()
|
||||
log.debug("Created Application mainloop object")
|
||||
r = mainController.Controller()
|
||||
app.run()
|
||||
|
||||
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 -*-
|
||||
|
||||
import inspect
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
import platform
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import string
|
||||
import unicodedata
|
||||
import glob
|
||||
from platform_utils import paths as paths_
|
||||
|
||||
from functools import wraps
|
||||
|
||||
def app_data_path(app_name=None):
|
||||
"""Cross-platform method for determining where to put application data."""
|
||||
"""Requires the name of the application"""
|
||||
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)
|
||||
mode = "portable"
|
||||
directory = None
|
||||
fsencoding = sys.getfilesystemencoding()
|
||||
|
||||
def prepare_app_data_path(app_name):
|
||||
"""Creates the application's data directory, given its name."""
|
||||
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
|
||||
if len(glob.glob("Uninstall.exe")) > 0: # installed copy
|
||||
mode= "installed"
|
||||
|
||||
def app_path():
|
||||
"""Return the root of the application's directory"""
|
||||
path = executable_directory()
|
||||
if is_frozen() and platform.system() == 'Darwin':
|
||||
path = os.path.abspath(os.path.join(path, '..', '..'))
|
||||
return path
|
||||
return paths_.app_path()
|
||||
|
||||
def module_path(level=2):
|
||||
return os.path.abspath(os.path.dirname(get_module(level)))
|
||||
def config_path():
|
||||
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():
|
||||
"""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."""
|
||||
plat = platform.system()
|
||||
if plat == 'Windows':
|
||||
import winpaths
|
||||
path = winpaths.get_my_documents()
|
||||
elif plat == 'Darwin':
|
||||
path = os.path.join(os.path.expanduser('~'), 'Documents')
|
||||
else:
|
||||
path = os.path.expanduser('~')
|
||||
return path
|
||||
def logs_path():
|
||||
global mode, directory
|
||||
if mode == "portable":
|
||||
if directory != None: path = os.path.join(directory, "logs")
|
||||
elif directory == None: path = os.path.join(app_path(), "logs")
|
||||
elif mode == "installed":
|
||||
path = os.path.join(data_path(), "logs")
|
||||
if not os.path.exists(path):
|
||||
# log.debug("%s path does not exist, creating..." % (path,))
|
||||
os.mkdir(path)
|
||||
return path
|
||||
|
||||
def safe_filename(filename):
|
||||
"""Given a filename, returns a safe version with no characters that would not work on different platforms."""
|
||||
SAFE_FILE_CHARS = "'-_.()[]{}!@#$%^&+=`~ "
|
||||
filename = unicode(filename)
|
||||
new_filename = ''.join(c for c in filename if c in SAFE_FILE_CHARS or c.isalnum())
|
||||
#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.
|
||||
return new_filename.strip(' .')
|
||||
def data_path(app_name='socializer'):
|
||||
if platform.system() == "Windows":
|
||||
data_path = os.path.join(os.getenv("AppData"), app_name)
|
||||
else:
|
||||
data_path = os.path.join(os.environ['HOME'], ".%s" % app_name)
|
||||
if not os.path.exists(data_path):
|
||||
os.mkdir(data_path)
|
||||
return data_path
|
||||
|
||||
def ensure_path(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
return path
|
||||
def locale_path():
|
||||
return os.path.join(app_path(), "locales")
|
||||
|
||||
def start_file(path):
|
||||
if platform.system() == 'Windows':
|
||||
os.startfile(path)
|
||||
else:
|
||||
subprocess.Popen(['open', path])
|
||||
def sound_path():
|
||||
return os.path.join(app_path(), "sounds")
|
||||
|
||||
def get_applications_path():
|
||||
"""Return the directory where applications are commonly installed on the system."""
|
||||
plat = platform.system()
|
||||
if plat == 'Windows':
|
||||
import winpaths
|
||||
return winpaths.get_program_files()
|
||||
elif plat == 'Darwin':
|
||||
return '/Applications'
|
||||
def com_path():
|
||||
global mode, directory
|
||||
if mode == "portable":
|
||||
if directory != None: path = os.path.join(directory, "com_cache")
|
||||
elif directory == None: path = os.path.join(app_path(), "com_cache")
|
||||
elif mode == "installed":
|
||||
path = os.path.join(data_path(), "com_cache")
|
||||
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.
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