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 |
183
.gitlab-ci.yml
183
.gitlab-ci.yml
@ -1,106 +1,121 @@
|
|||||||
# This CI configuration file is used to build all available versions of MusicDL. It's intended to launch a new version when a tag is pushed to master.
|
|
||||||
# In order to work, A Gitlab Runner with Windows Server 2016 Standard with the following packages is used:
|
|
||||||
# * Latest python for both 2.7 and 3.x branches.
|
|
||||||
# * Py2exe for Python 2.7
|
|
||||||
# * Microsoft Visual C++ 2015 redistributable files
|
|
||||||
# * Nsis
|
|
||||||
|
|
||||||
# Declare some variables dependent on the operating system where the runner is installed.
|
|
||||||
# This CI file assumes we install everything in C:\ (Python 2.7, 3.7 and Nsis).
|
|
||||||
variables:
|
|
||||||
PYTHON3: "C:\\python37\\python.exe"
|
|
||||||
PYINSTALLER: "C:\\python37\\scripts\\pyinstaller.exe"
|
|
||||||
NSIS: "C:\\nsis\\makensis.exe"
|
|
||||||
|
|
||||||
### Stage list
|
|
||||||
# Build: This will be the main stage generating stuff in the dist folder. Jobs present in this stage will run py2exe or pyinstaller files accordingly.
|
|
||||||
# pack: Jobs in this stage will take the dist folder and zip it or generate an exe file.
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
|
- generate_docs
|
||||||
- build
|
- build
|
||||||
- pack
|
- versions
|
||||||
|
- upload
|
||||||
|
|
||||||
# Python 3 tests
|
program64:
|
||||||
test_py3:
|
interruptible: true
|
||||||
stage: test
|
|
||||||
tags:
|
tags:
|
||||||
|
- windows
|
||||||
- windows10
|
- windows10
|
||||||
before_script:
|
|
||||||
- '%PYTHON3% -v'
|
|
||||||
- '%PYTHON3% -m pip install --upgrade pip'
|
|
||||||
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
|
|
||||||
script:
|
|
||||||
- cd src
|
|
||||||
- '%PYTHON3% -m coverage run run_tests.py'
|
|
||||||
- '%PYTHON3% -m coverage report --omit="test*"'
|
|
||||||
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
|
|
||||||
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- tags
|
|
||||||
- schedule_pipelines
|
|
||||||
|
|
||||||
# Python 3 version. During this job, the dist folder, containing all files to distribute, will be generated
|
|
||||||
# and passed to build_zip and build_setup jobs.
|
|
||||||
build_py3:
|
|
||||||
stage: build
|
stage: build
|
||||||
tags:
|
variables:
|
||||||
- windows10
|
PYTHON: "C:\\python310\\python.exe"
|
||||||
# Update stuff before building versions
|
|
||||||
before_script:
|
before_script:
|
||||||
- '%PYTHON3% -v'
|
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||||
- '%PYTHON3% -m pip install --upgrade pip'
|
- echo ${time}
|
||||||
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
|
- echo "started by ${GITLAB_USER_NAME}"
|
||||||
|
- '&$env:PYTHON -V'
|
||||||
|
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||||
|
- '&$env:PYTHON -m venv env'
|
||||||
|
- 'env\Scripts\Activate.ps1'
|
||||||
|
- 'python -m pip install --upgrade -r requirements.txt'
|
||||||
script:
|
script:
|
||||||
- cd src
|
- cd src
|
||||||
- '%PYINSTALLER% main.spec'
|
- 'python write_version_data.py'
|
||||||
# Build this automatically only when tags are pushed to master or when a pipeline has been scheduled by Gitlab.
|
- 'python setup.py build'
|
||||||
only:
|
- cd ..
|
||||||
- tags
|
- 'mkdir build'
|
||||||
- schedule_pipelines
|
|
||||||
# Make the dist folder available to other jobs.
|
|
||||||
# It will expire in 30 mins as we won't need the dist folder after the pipeline is completed.
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- src\\dist
|
|
||||||
expire_in: 30 mins
|
|
||||||
|
|
||||||
# This job takes the src\\dist folder generated in build_py3 and creates a zip file, which will be uploaded to the repository's artifacts.
|
|
||||||
zip_py3:
|
|
||||||
stage: pack
|
|
||||||
tags:
|
|
||||||
- windows10
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
- schedule_pipelines
|
|
||||||
dependencies:
|
|
||||||
- build_py3
|
|
||||||
script:
|
|
||||||
- cd scripts
|
- cd scripts
|
||||||
- '%PYTHON3% prepare_zipversion.py'
|
- 'python prepare_zipversion.py'
|
||||||
- cd ..
|
- cd ..
|
||||||
- move src\music_dl.zip music_dl.zip
|
- move src\music_dl.zip build\music_dl_x64.zip
|
||||||
# No expiry date as there will be only releases in the artifacts.
|
- 'move src/dist build/program64'
|
||||||
|
- 'move src/installer.nsi build'
|
||||||
|
only:
|
||||||
|
- schedules
|
||||||
|
- master
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- music_dl.zip
|
- build
|
||||||
|
expire_in: 1 day
|
||||||
|
|
||||||
# This job takes the src\\dist generated in build_py3 and creates a setup installer file.
|
program32:
|
||||||
build_setup:
|
interruptible: true
|
||||||
stage: pack
|
|
||||||
tags:
|
tags:
|
||||||
|
- windows
|
||||||
- windows10
|
- windows10
|
||||||
only:
|
stage: build
|
||||||
- tags
|
variables:
|
||||||
- schedule_pipelines
|
PYTHON: "C:\\python310-32\\python.exe"
|
||||||
dependencies:
|
before_script:
|
||||||
- build_py3
|
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||||
|
- echo ${time}
|
||||||
|
- echo "started by ${GITLAB_USER_NAME}"
|
||||||
|
- '&$env:PYTHON -V'
|
||||||
|
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||||
|
- '&$env:PYTHON -m venv env'
|
||||||
|
- 'env\Scripts\Activate.ps1'
|
||||||
|
- 'python -m pip install https://github.com/josephsl/wxpy32whl/raw/main/wxPython-4.2.0-cp310-cp310-win32.whl'
|
||||||
|
- 'python -m pip install --upgrade -r requirements.txt'
|
||||||
script:
|
script:
|
||||||
- cd src
|
- cd src
|
||||||
- '%NSIS% installer.nsi'
|
- 'python write_version_data.py'
|
||||||
|
- 'python setup.py build'
|
||||||
- cd ..
|
- cd ..
|
||||||
- move src\music_dl* .
|
- 'mkdir build'
|
||||||
|
- cd scripts
|
||||||
|
- 'python prepare_zipversion.py'
|
||||||
|
- cd ..
|
||||||
|
- move src\music_dl.zip build\music_dl_x86.zip
|
||||||
|
- 'move src/dist build/program32'
|
||||||
|
only:
|
||||||
|
- schedules
|
||||||
|
- master
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- "music_dl_*"
|
- build
|
||||||
name: music_dl
|
expire_in: 1 day
|
||||||
|
|
||||||
|
generate_versions:
|
||||||
|
stage: versions
|
||||||
|
tags:
|
||||||
|
- windows
|
||||||
|
- windows10
|
||||||
|
variables:
|
||||||
|
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
|
||||||
|
before_script:
|
||||||
|
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||||
|
- echo ${time}
|
||||||
|
- echo "started by ${GITLAB_USER_NAME}"
|
||||||
|
script:
|
||||||
|
- mkdir artifacts
|
||||||
|
- 'cd build'
|
||||||
|
- '&$env:NSIS installer.nsi'
|
||||||
|
- move *.exe ../artifacts
|
||||||
|
- move *.zip ../artifacts
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
- master
|
||||||
|
- schedules
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts
|
||||||
|
expire_in: 1 day
|
||||||
|
|
||||||
|
upload:
|
||||||
|
image:
|
||||||
|
name: amazon/aws-cli
|
||||||
|
entrypoint: [""]
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
- master
|
||||||
|
interruptible: true
|
||||||
|
stage: upload
|
||||||
|
script:
|
||||||
|
- aws --version
|
||||||
|
- aws --endpoint-url https://s3.us-west-001.backblazeb2.com s3 cp artifacts s3://$S3_BUCKET/music_dl/ --recursive
|
26
README.md
26
README.md
@ -12,26 +12,32 @@ MusicDL is an app for downloading music directly from services like Youtube, zay
|
|||||||
|
|
||||||
See the requirements.txt, located in the root of this repository. Additionally, take into account the following.
|
See the requirements.txt, located in the root of this repository. Additionally, take into account the following.
|
||||||
|
|
||||||
* In case you want to create your own distributable version with Python 2, you'll need py2exe.
|
|
||||||
|
|
||||||
## running
|
## running
|
||||||
|
|
||||||
Run the file main.py, located in the src directory.
|
Run the file main.py, located in the src directory.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
### Python 3
|
I have provided a setup.py file for cx_freeze, so you should be able to do something like:
|
||||||
|
|
||||||
I have provided a main.spec file for pyinstaller, so you should be able to do something like:
|
> python setup.py build
|
||||||
|
|
||||||
> C:\python3\scripts\pyinstaller.exe main.spec
|
|
||||||
|
|
||||||
And start building. Check the dist folder for results.
|
And start building. Check the dist folder for results.
|
||||||
|
|
||||||
### Python 2
|
## Updating translation catalog
|
||||||
|
|
||||||
If you are using Python 2.x and want to build MusicDL, there is a setup.py file made for pyinstaller aswell. Just run it the usual way:
|
Every time there are new strings in the application a translations catalog update must be performed with the following commands in the src directory:
|
||||||
|
|
||||||
> C:\python2\python.exe setup.py py2exe
|
> python setup.py extract_messages -o musicdl.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "Manuel Cortez" --input-dirs .
|
||||||
|
> python setup.py update_catalog --input-file musicdl.pot --domain musicdl --output-dir locales --ignore-obsolete true
|
||||||
|
|
||||||
And You will get a distributable version of MusicDL.
|
And after updating translations they should be compiled with:
|
||||||
|
|
||||||
|
> python setup.py compile_catalog --statistics -d locales --domain musicdl
|
||||||
|
|
||||||
|
## Adding new translations
|
||||||
|
|
||||||
|
The procedure for adding new translations is also easy, thanks to the following command. Just replace xx for the new locale name to add:
|
||||||
|
|
||||||
|
> python setup.py extract_messages -o musicdl.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "Copyright (C) 2019, 2020 Manuel Cortez" --input-dirs .
|
||||||
|
> python setup.py init_catalog --domain musicdl --input-file musicdl.pot -d locales --locale xx
|
46
changes.md
46
changes.md
@ -1,5 +1,49 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
## Changes on January
|
||||||
|
|
||||||
|
* Application improvements:
|
||||||
|
* Updated VLC components to V3.0.11.
|
||||||
|
* Updated SSL/TLS plugin in vlc which should make youtube playback less buggy.
|
||||||
|
* MusicDL now builds with Python V 3.9.1.
|
||||||
|
* zaycev.net:
|
||||||
|
* Disabled service by default as it will not work outside Russia.
|
||||||
|
|
||||||
|
## Version 0.7
|
||||||
|
|
||||||
|
* Application improvements:
|
||||||
|
* MusicDL no longer adds invalid characters when attempting to download a file.
|
||||||
|
* Tidal:
|
||||||
|
* Added a new search mode for the service to retrieve the top rated tracks of a provided artist. The sintax is top://artist.
|
||||||
|
* In the settings dialog, you can control wether Albums, compilations and singles will be added when searching by artist (by using artist://...).
|
||||||
|
* When searching by artists, results that belong to an album will be numbered.
|
||||||
|
* Downloads will be tagged with title, album, artist and track number provided by tidal.
|
||||||
|
* It is possible to download an original version in high and low quality. Before, those versions were encoded to mp3 from an m4a file. Now the M4a file can be retrieved by using the checkbox in the tidal settings page.
|
||||||
|
* YouTube:
|
||||||
|
* Fixed search algorithm for Youtube videos.
|
||||||
|
* Updated Youtube-Dl to version 2020.6.16.1
|
||||||
|
* re-added VK module. By default, this module searches up to 50 results but you can increase it up to 200 if needed from the services settings.
|
||||||
|
|
||||||
|
## Version 0.6
|
||||||
|
|
||||||
|
* 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
|
## 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.
|
* 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.
|
* 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.
|
* 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:
|
* changes in Youtube module:
|
||||||
* Updated YoutubeDL to version 2018.10.05
|
* Updated YoutubeDL to latest version.
|
||||||
|
|
||||||
## Version 0.3
|
## Version 0.3
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
coverage
|
wxpython
|
||||||
wxpython==4.0.3
|
|
||||||
requests
|
requests
|
||||||
bs4
|
bs4
|
||||||
pypubsub
|
pypubsub
|
||||||
python-vlc
|
yt-dlp
|
||||||
google-api-python-client
|
python-mpv
|
||||||
youtube-dl
|
cx_freeze
|
||||||
pyinstaller
|
|
||||||
isodate
|
|
||||||
configobj
|
configobj
|
||||||
winpaths
|
winpaths
|
||||||
|
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.
|
# Translations template for musicDL.
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
# Copyright (C) 2020 Manuel Cortez
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# This file is distributed under the same license as the musicDL project.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||||
#
|
#
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: musicDL 2020.7.23\n"
|
||||||
"POT-Creation-Date: 2018-03-03 09:38+Hora estándar central (México)\n"
|
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
|
||||||
|
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=CHARSET\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: ENCODING\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: Babel 2.9.0\n"
|
||||||
|
|
||||||
|
|
||||||
#: ../src\application.py: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 ""
|
|
||||||
|
|
||||||
|
@ -6,9 +6,6 @@ import sys
|
|||||||
def create_archive():
|
def create_archive():
|
||||||
os.chdir("..\\src")
|
os.chdir("..\\src")
|
||||||
print("Creating zip archive...")
|
print("Creating zip archive...")
|
||||||
if sys.version[0] == "3":
|
|
||||||
folder = "dist/main"
|
|
||||||
else:
|
|
||||||
folder = "dist"
|
folder = "dist"
|
||||||
shutil.make_archive("music_dl", "zip", folder)
|
shutil.make_archive("music_dl", "zip", folder)
|
||||||
# if os.path.exists("dist"):
|
# if os.path.exists("dist"):
|
||||||
|
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]
|
[main]
|
||||||
volume = integer(default=50)
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
|
||||||
python_version = int(sys.version[0])
|
|
||||||
name = "MusicDL"
|
name = "MusicDL"
|
||||||
version = "0.5"
|
author = "MCV Software"
|
||||||
author = "Manuel Cortéz"
|
authorEmail = "info@mcvsoftware.com"
|
||||||
authorEmail = "manuel@manuelcortez.net"
|
copyright = "Copyright (C) 2019-2022, MCV Software"
|
||||||
copyright = "Copyright (C) 2019, Manuel Cortez"
|
|
||||||
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
|
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
|
||||||
url = "https://manuelcortez.net/music_dl"
|
url = "https://mcvsoftware.com/music_dl"
|
||||||
update_url = "https://manuelcortez.net/music_dl/update"
|
|
||||||
# The short name will be used for detecting translation files. See languageHandler for more details.
|
# The short name will be used for detecting translation files. See languageHandler for more details.
|
||||||
short_name = "musicdl"
|
short_name = "musicdl"
|
||||||
translators = [_(u"Manuel Cortez (Spanish)"), _("Valeria K (Russian)"), ]
|
translators = [_(u"Manuel Cortez (Spanish)")]
|
||||||
bts_name = "music_dl"
|
bts_name = "music_dl"
|
||||||
bts_access_token = "fe3j2ijirvevv9"
|
bts_access_token = "fe3j2ijirvevv9"
|
||||||
bts_url = "https://issues.manuelcortez.net"
|
bts_url = "https://issues.manuelcortez.net"
|
||||||
|
update_url = "https://files.mcvsoftware.com/music_dl/update/latest.json"
|
||||||
|
version = "2020.07.23"
|
||||||
|
@ -15,4 +15,3 @@ def setup ():
|
|||||||
global app
|
global app
|
||||||
log.debug("Loading global app settings...")
|
log.debug("Loading global app settings...")
|
||||||
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))
|
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))
|
||||||
|
|
@ -6,8 +6,8 @@ import string
|
|||||||
class ConfigLoadError(Exception): pass
|
class ConfigLoadError(Exception): pass
|
||||||
|
|
||||||
def load_config(config_path, configspec_path=None, *args, **kwargs):
|
def load_config(config_path, configspec_path=None, *args, **kwargs):
|
||||||
if os.path.exists(config_path):
|
# if os.path.exists(config_path):
|
||||||
clean_config(config_path)
|
# clean_config(config_path)
|
||||||
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
|
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
|
||||||
try:
|
try:
|
||||||
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
|
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
|
||||||
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
""" main controller for MusicDL"""
|
""" main controller for MusicDL"""
|
||||||
from __future__ import unicode_literals # at top of module
|
|
||||||
import types
|
import types
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import wx
|
import wx
|
||||||
@ -13,17 +12,11 @@ from pubsub import pub
|
|||||||
from issueReporter import issueReporter
|
from issueReporter import issueReporter
|
||||||
from wxUI import mainWindow, menus
|
from wxUI import mainWindow, menus
|
||||||
from update import updater
|
from update import updater
|
||||||
from . import player
|
from utils import get_services
|
||||||
|
from . import player, configuration
|
||||||
|
|
||||||
log = logging.getLogger("controller.main")
|
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):
|
class Controller(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -32,7 +25,7 @@ class Controller(object):
|
|||||||
# Setting up the player object
|
# Setting up the player object
|
||||||
player.setup()
|
player.setup()
|
||||||
# Get main window
|
# Get main window
|
||||||
self.window = mainWindow.mainWindow()
|
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_services()])
|
||||||
log.debug("Main window created")
|
log.debug("Main window created")
|
||||||
self.window.change_status(_(u"Ready"))
|
self.window.change_status(_(u"Ready"))
|
||||||
# Here we will save results for searches as song objects.
|
# Here we will save results for searches as song objects.
|
||||||
@ -67,6 +60,7 @@ class Controller(object):
|
|||||||
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
|
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
|
||||||
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
|
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
|
||||||
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
|
||||||
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
|
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
|
||||||
@ -93,10 +87,24 @@ class Controller(object):
|
|||||||
pub.subscribe(self.change_status, "change_status")
|
pub.subscribe(self.change_status, "change_status")
|
||||||
pub.subscribe(self.on_download_finished, "download_finished")
|
pub.subscribe(self.on_download_finished, "download_finished")
|
||||||
pub.subscribe(self.on_notify, "notify")
|
pub.subscribe(self.on_notify, "notify")
|
||||||
|
pub.subscribe(self.on_update_progress, "update-progress")
|
||||||
|
|
||||||
# Event functions. These functions will call other functions in a thread and are bound to widget events.
|
# Event functions. These functions will call other functions in a thread and are bound to widget events.
|
||||||
|
|
||||||
|
def on_update_progress(self, value):
|
||||||
|
wx.CallAfter(self.window.progressbar.SetValue, value)
|
||||||
|
|
||||||
|
def on_settings(self, *args, **kwargs):
|
||||||
|
settings = configuration.configuration()
|
||||||
|
self.reload_extractors()
|
||||||
|
|
||||||
def on_search(self, *args, **kwargs):
|
def on_search(self, *args, **kwargs):
|
||||||
utils.call_threaded(self.search)
|
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_activated(self, *args, **kwargs):
|
def on_activated(self, *args, **kwargs):
|
||||||
self.on_play()
|
self.on_play()
|
||||||
@ -107,15 +115,15 @@ class Controller(object):
|
|||||||
elif ev.GetKeyCode() == wx.WXK_SPACE:
|
elif ev.GetKeyCode() == wx.WXK_SPACE:
|
||||||
return self.on_play_pause()
|
return self.on_play_pause()
|
||||||
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
|
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
|
||||||
position = player.player.player.get_time()
|
position = player.player.player.get_position()
|
||||||
if position > 5000:
|
if position > 5000:
|
||||||
player.player.player.set_time(position-5000)
|
player.player.player.set_position(position-5000)
|
||||||
else:
|
else:
|
||||||
player.player.player.set_time(0)
|
player.player.player.set_position(0)
|
||||||
return
|
return
|
||||||
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
|
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
|
||||||
position = player.player.player.get_time()
|
position = player.player.player.get_position()
|
||||||
player.player.player.set_time(position+5000)
|
player.player.player.set_position(position+5000)
|
||||||
return
|
return
|
||||||
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
|
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
|
||||||
return self.on_volume_up()
|
return self.on_volume_up()
|
||||||
@ -128,12 +136,12 @@ class Controller(object):
|
|||||||
ev.Skip()
|
ev.Skip()
|
||||||
|
|
||||||
def on_play_pause(self, *args, **kwargs):
|
def on_play_pause(self, *args, **kwargs):
|
||||||
if player.player.player.is_playing() == 1:
|
if player.player.player.playback_time != None and player.player.player.pause == False:
|
||||||
self.window.play.SetLabel(_(u"Play"))
|
self.window.play.SetLabel(_("Play"))
|
||||||
return player.player.pause()
|
return player.player.pause()
|
||||||
else:
|
else:
|
||||||
self.window.play.SetLabel(_(u"Pause"))
|
self.window.play.SetLabel(_("Pause"))
|
||||||
return player.player.player.play()
|
player.player.player.pause = False
|
||||||
|
|
||||||
def on_next(self, *args, **kwargs):
|
def on_next(self, *args, **kwargs):
|
||||||
return utils.call_threaded(player.player.next)
|
return utils.call_threaded(player.player.next)
|
||||||
@ -178,18 +186,18 @@ class Controller(object):
|
|||||||
|
|
||||||
def on_download(self, *args, **kwargs):
|
def on_download(self, *args, **kwargs):
|
||||||
item = self.results[self.window.get_item()]
|
item = self.results[self.window.get_item()]
|
||||||
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
|
|
||||||
f = "{0}.mp3".format(item.format_track())
|
|
||||||
if item.download_url == "":
|
if item.download_url == "":
|
||||||
item.get_download_url()
|
item.get_download_url()
|
||||||
path = self.window.get_destination_path(f)
|
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:
|
if path != None:
|
||||||
log.debug("User has requested the following path: {0}".format(path,))
|
log.debug("User has requested the following path: {0}".format(path,))
|
||||||
if self.extractor.needs_transcode == True: # Send download to vlc based transcoder
|
if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder
|
||||||
utils.call_threaded(player.player.transcode_audio, item, path)
|
utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format(), metadata=item.get_metadata())
|
||||||
else:
|
else:
|
||||||
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
|
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
|
||||||
utils.call_threaded(utils.download_file, item.download_url, path)
|
utils.call_threaded(utils.download_file, item.download_url, path, metadata=item.get_metadata())
|
||||||
|
|
||||||
def on_set_volume(self, *args, **kwargs):
|
def on_set_volume(self, *args, **kwargs):
|
||||||
volume = self.window.vol_slider.GetValue()
|
volume = self.window.vol_slider.GetValue()
|
||||||
@ -197,13 +205,16 @@ class Controller(object):
|
|||||||
|
|
||||||
def on_time_change(self, event, *args, **kwargs):
|
def on_time_change(self, event, *args, **kwargs):
|
||||||
p = event.GetPosition()
|
p = event.GetPosition()
|
||||||
player.player.player.set_position(p/100.0)
|
if player.player.player != None:
|
||||||
|
progress = int((player.player.player.get_length()/100)*p)
|
||||||
|
player.player.player.set_position(progress)
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
def on_timer(self, *args, **kwargs):
|
def on_timer(self, *args, **kwargs):
|
||||||
if not self.window.time_slider.HasFocus():
|
if not self.window.time_slider.HasFocus():
|
||||||
progress = player.player.player.get_position()*100
|
if player.player.player.playback_time != None:
|
||||||
self.window.time_slider.SetValue(progress)
|
progress = (player.player.player.playback_time/(player.player.player.playback_time+player.player.player.playtime_remaining))*100
|
||||||
|
self.window.time_slider.SetValue(int(progress))
|
||||||
|
|
||||||
def on_close(self, event):
|
def on_close(self, event):
|
||||||
log.debug("Exiting...")
|
log.debug("Exiting...")
|
||||||
@ -238,25 +249,25 @@ class Controller(object):
|
|||||||
self.window.notify(title, message)
|
self.window.notify(title, message)
|
||||||
|
|
||||||
# real functions. These functions really are doing the work.
|
# real functions. These functions really are doing the work.
|
||||||
def search(self, *args, **kwargs):
|
def search(self, text, extractor, *args, **kwargs):
|
||||||
text = self.window.get_text()
|
extractors = get_services()
|
||||||
if text == "":
|
|
||||||
return
|
|
||||||
extractor = self.window.extractor.GetValue()
|
|
||||||
self.change_status(_(u"Searching {0}... ").format(text))
|
|
||||||
extractors = get_extractors()
|
|
||||||
for i in extractors:
|
for i in extractors:
|
||||||
if extractor == i.name:
|
if extractor == i.interface.name:
|
||||||
self.extractor = i()
|
self.extractor = i.interface()
|
||||||
break
|
break
|
||||||
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
|
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
|
||||||
self.window.list.Clear()
|
wx.CallAfter(self.window.list.Clear)
|
||||||
self.extractor.search(text)
|
self.extractor.search(text)
|
||||||
self.results = self.extractor.results
|
self.results = self.extractor.results
|
||||||
for i in self.results:
|
for i in self.results:
|
||||||
self.window.list.Append(i.format_track())
|
wx.CallAfter(self.window.list.Append, i.format_track())
|
||||||
if len(self.results) == 0:
|
if len(self.results) == 0:
|
||||||
self.change_status(_(u"No results found. "))
|
wx.CallAfter(self.change_status, _(u"No results found. "))
|
||||||
else:
|
else:
|
||||||
self.change_status(u"")
|
wx.CallAfter(self.change_status, u"")
|
||||||
wx.CallAfter(self.window.list.SetFocus)
|
wx.CallAfter(self.window.list.SetFocus)
|
||||||
|
|
||||||
|
def reload_extractors(self):
|
||||||
|
extractors = [i.interface.name for i in get_services()]
|
||||||
|
self.window.extractor.SetItems(extractors)
|
||||||
|
self.window.extractor.SetValue(extractors[0])
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals # at top of module
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import vlc
|
|
||||||
import logging
|
import logging
|
||||||
import config
|
import config
|
||||||
|
import time
|
||||||
|
import mpv
|
||||||
from pubsub import pub
|
from pubsub import pub
|
||||||
from utils import call_threaded
|
|
||||||
|
|
||||||
player = None
|
player = None
|
||||||
log = logging.getLogger("controller.player")
|
log = logging.getLogger("controller.player")
|
||||||
@ -26,13 +25,26 @@ class audioPlayer(object):
|
|||||||
self.stopped = True
|
self.stopped = True
|
||||||
self.queue_pos = 0
|
self.queue_pos = 0
|
||||||
self.shuffle = False
|
self.shuffle = False
|
||||||
self.instance = vlc.Instance()
|
self.player = mpv.MPV()
|
||||||
self.player = self.instance.media_player_new()
|
|
||||||
log.debug("Media player instantiated.")
|
# Fires at the end of every file and attempts to play the next one.
|
||||||
self.event_manager = self.player.event_manager()
|
@self.player.event_callback('end-file')
|
||||||
self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback)
|
def handle_end_idle(event):
|
||||||
self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
|
if event.as_dict()["reason"] == b"aborted" or event.as_dict()["reason"] == b"stop":
|
||||||
log.debug("Bound media playback events.")
|
return
|
||||||
|
log.debug("Reached end of file stream.")
|
||||||
|
if len(self.queue) > 1:
|
||||||
|
log.debug("Requesting next item...")
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
def get_output_devices(self):
|
||||||
|
""" Retrieve enabled output devices so we can switch or use those later. """
|
||||||
|
return None
|
||||||
|
|
||||||
|
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 play(self, item):
|
def play(self, item):
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
@ -41,16 +53,8 @@ class audioPlayer(object):
|
|||||||
if item.download_url == "":
|
if item.download_url == "":
|
||||||
item.get_download_url()
|
item.get_download_url()
|
||||||
log.debug("playing {0}...".format(item.download_url,))
|
log.debug("playing {0}...".format(item.download_url,))
|
||||||
self.stream_new = self.instance.media_new(item.download_url)
|
self.player.play(item.download_url)
|
||||||
self.player.set_media(self.stream_new)
|
self.player.volume = self.vol
|
||||||
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))
|
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
self.is_working = False
|
self.is_working = False
|
||||||
@ -82,7 +86,7 @@ class audioPlayer(object):
|
|||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
self.player.pause()
|
self.player.pause = True
|
||||||
if self.stopped == True:
|
if self.stopped == True:
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
else:
|
else:
|
||||||
@ -97,7 +101,8 @@ class audioPlayer(object):
|
|||||||
if vol <= 100 and vol >= 0:
|
if vol <= 100 and vol >= 0:
|
||||||
config.app["main"]["volume"] = vol
|
config.app["main"]["volume"] = vol
|
||||||
self.vol = vol
|
self.vol = vol
|
||||||
self.player.audio_set_volume(self.vol)
|
if self.player != None:
|
||||||
|
self.player.volume = self.vol
|
||||||
|
|
||||||
def play_all(self, list_of_items, playing=0, shuffle=False):
|
def play_all(self, list_of_items, playing=0, shuffle=False):
|
||||||
if list_of_items != self.queue:
|
if list_of_items != self.queue:
|
||||||
@ -105,41 +110,3 @@ class audioPlayer(object):
|
|||||||
self.shuffle = shuffle
|
self.shuffle = shuffle
|
||||||
self.queue_pos = playing
|
self.queue_pos = playing
|
||||||
self.play(self.queue[self.queue_pos])
|
self.play(self.queue[self.queue_pos])
|
||||||
|
|
||||||
def end_callback(self, event, *args, **kwargs):
|
|
||||||
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4
|
|
||||||
call_threaded(self.next)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
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)
|
|
@ -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)
|
|
@ -1,11 +1,12 @@
|
|||||||
!include "MUI2.nsh"
|
!include "MUI2.nsh"
|
||||||
!include "LogicLib.nsh"
|
!include "LogicLib.nsh"
|
||||||
|
!include "x64.nsh"
|
||||||
Unicode true
|
Unicode true
|
||||||
CRCCheck on
|
CRCCheck on
|
||||||
ManifestSupportedOS all
|
ManifestSupportedOS all
|
||||||
XPStyle on
|
XPStyle on
|
||||||
Name "MusicDL"
|
Name "MusicDL"
|
||||||
OutFile "music_dl_0.5_setup.exe"
|
OutFile "music_dl_setup.exe"
|
||||||
InstallDir "$PROGRAMFILES\musicDL"
|
InstallDir "$PROGRAMFILES\musicDL"
|
||||||
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "InstallLocation"
|
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "InstallLocation"
|
||||||
RequestExecutionLevel admin
|
RequestExecutionLevel admin
|
||||||
@ -13,18 +14,18 @@ SetCompress auto
|
|||||||
SetCompressor /solid lzma
|
SetCompressor /solid lzma
|
||||||
SetDatablockOptimize on
|
SetDatablockOptimize on
|
||||||
VIAddVersionKey ProductName "MusicDL"
|
VIAddVersionKey ProductName "MusicDL"
|
||||||
VIAddVersionKey LegalCopyright "Copyright 2019 Manuel Cortez."
|
VIAddVersionKey LegalCopyright "Copyright 2019 - 2022 MCV Software."
|
||||||
VIAddVersionKey ProductVersion "0.5"
|
VIAddVersionKey ProductVersion "0.7"
|
||||||
VIAddVersionKey FileVersion "0.5"
|
VIAddVersionKey FileVersion "0.7"
|
||||||
VIProductVersion "0.5.0.0"
|
VIProductVersion "0.7.0.0"
|
||||||
VIFileVersion "0.5.0.0"
|
VIFileVersion "0.7.0.0"
|
||||||
!insertmacro MUI_PAGE_WELCOME
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
!insertmacro MUI_PAGE_DIRECTORY
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
var StartMenuFolder
|
var StartMenuFolder
|
||||||
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
|
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
|
||||||
!insertmacro MUI_PAGE_INSTFILES
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
!define MUI_FINISHPAGE_LINK "Visit MusicDL website"
|
!define MUI_FINISHPAGE_LINK "Visit MusicDL website"
|
||||||
!define MUI_FINISHPAGE_LINK_LOCATION "https://manuelcortez.net/music_dl"
|
!define MUI_FINISHPAGE_LINK_LOCATION "https://mcvsoftware.com/music_dl"
|
||||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\musicDL.exe"
|
!define MUI_FINISHPAGE_RUN "$INSTDIR\musicDL.exe"
|
||||||
!insertmacro MUI_PAGE_FINISH
|
!insertmacro MUI_PAGE_FINISH
|
||||||
!insertmacro MUI_UNPAGE_CONFIRM
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
@ -36,7 +37,11 @@ var StartMenuFolder
|
|||||||
Section
|
Section
|
||||||
SetShellVarContext All
|
SetShellVarContext All
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
File /r dist\main\*
|
${If} ${RunningX64}
|
||||||
|
File /r program64\*
|
||||||
|
${Else}
|
||||||
|
File /r program32\*
|
||||||
|
${EndIf}
|
||||||
CreateShortCut "$DESKTOP\musicDL.lnk" "$INSTDIR\musicDL.exe"
|
CreateShortCut "$DESKTOP\musicDL.lnk" "$INSTDIR\musicDL.exe"
|
||||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
|
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
|
||||||
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
|
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
|
||||||
@ -49,7 +54,7 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "
|
|||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "DisplayVersion" "0.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"
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "URLInfoAbout" "https://manuelcortez.net/music_dl"
|
||||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMajor" 0
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMajor" 0
|
||||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMinor" 1
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMinor" 1
|
||||||
@ -65,5 +70,8 @@ Delete "$DESKTOP\MusicDL.lnk"
|
|||||||
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
|
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
|
||||||
SectionEnd
|
SectionEnd
|
||||||
Function .onInit
|
Function .onInit
|
||||||
|
${If} ${RunningX64}
|
||||||
|
StrCpy $instdir "$programfiles64\musicDL"
|
||||||
|
${EndIf}
|
||||||
!insertmacro MUI_LANGDLL_DISPLAY
|
!insertmacro MUI_LANGDLL_DISPLAY
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
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.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
# Copyright (C) 2019 ORGANIZATION
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: 2018-02-28 15:02-0600\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"PO-Revision-Date: 2018-03-17 16:25-0600\n"
|
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||||
"Last-Translator: \n"
|
"PO-Revision-Date: 2020-10-02 15:10+0000\n"
|
||||||
"Language-Team: \n"
|
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
|
"Language-Team: Spanish "
|
||||||
|
"<http://translations.manuelcortez.net/projects/musicdl/interface/es/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: Babel 2.9.0\n"
|
||||||
"X-Generator: Poedit 2.0.2\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\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.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
# Copyright (C) 2018 ORGANIZATION
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: 2018-03-03 09:38+Hora estándar central (México)\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"PO-Revision-Date: 2018-03-12 01:35+0400\n"
|
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"PO-Revision-Date: 2020-07-30 05:13-0500\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"Language-Team: ru <LL@li.org>\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||||
|
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: Babel 2.9.0\n"
|
||||||
"X-Generator: Poedit 1.5.7\n"
|
|
||||||
|
|
||||||
#: ../src\application.py:7
|
|
||||||
msgid ""
|
|
||||||
" Is an application that will allow you to download music from popular sites "
|
|
||||||
"such as youtube, zaycev.net."
|
|
||||||
msgstr ""
|
|
||||||
" Это приложение, позволяющее скачивать музыку с таких популярных сайтов как "
|
|
||||||
"Youtube, zaycev.net."
|
|
||||||
|
|
||||||
#: ../src\application.py:12
|
|
||||||
msgid "Manuel Cortez (Spanish)"
|
|
||||||
msgstr "Manuel Cortez (Испанский)"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:27
|
|
||||||
msgid "Ready"
|
|
||||||
msgstr "Готово"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:42
|
|
||||||
msgid "Showing {0} results."
|
|
||||||
msgstr "Показано {0} результатов."
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:46
|
|
||||||
msgid "Shuffle on"
|
|
||||||
msgstr "Случайный порядок включён"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:118
|
|
||||||
#: ../src\controller\mainController.py:138 ../src\wxUI\mainWindow.py:13
|
|
||||||
#: ../src\wxUI\mainWindow.py:62
|
|
||||||
msgid "Play"
|
|
||||||
msgstr "Воспроизвести"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:121
|
|
||||||
#: ../src\controller\mainController.py:133
|
|
||||||
msgid "Pause"
|
|
||||||
msgstr "Приостановить"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:213
|
|
||||||
msgid "File downloaded: {0}"
|
|
||||||
msgstr "Файл загружен: {0}"
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:236
|
|
||||||
msgid "Searching {0}... "
|
|
||||||
msgstr "Поиск {0}... "
|
|
||||||
|
|
||||||
#: ../src\controller\mainController.py:242
|
|
||||||
msgid "No results found. "
|
|
||||||
msgstr "Нет результатов."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:43
|
|
||||||
msgid "Error playing {0}. {1}."
|
|
||||||
msgstr "Ошибка воспроизведения {0}. {1}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:49
|
|
||||||
msgid "Playing {0}."
|
|
||||||
msgstr "Сейчас играет {0}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:117 ../src\utils.py:53
|
|
||||||
msgid "Downloading {0}."
|
|
||||||
msgstr "Сейчас загружается {0}."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:122 ../src\utils.py:63
|
|
||||||
msgid "Downloading {0} ({1}%)."
|
|
||||||
msgstr "Загрузка {0} ({1}%)."
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:133
|
|
||||||
msgid "Error"
|
|
||||||
msgstr "Ошибка"
|
|
||||||
|
|
||||||
#: ../src\controller\player.py:133
|
|
||||||
msgid "There was an error while trying to access the file you have requested."
|
|
||||||
msgstr "Произошла ошибка при попытке открыть запрашиваемый файл."
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:27
|
|
||||||
msgid "%d day, "
|
|
||||||
msgstr "%d день, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:29
|
|
||||||
msgid "%d days, "
|
|
||||||
msgstr "%d дней, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:31
|
|
||||||
msgid "%d hour, "
|
|
||||||
msgstr "%d час, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:33
|
|
||||||
msgid "%d hours, "
|
|
||||||
msgstr "%d часов, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:35
|
|
||||||
msgid "%d minute, "
|
|
||||||
msgstr "%d минута, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:37
|
|
||||||
msgid "%d minutes, "
|
|
||||||
msgstr "%d минут, "
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:39
|
|
||||||
msgid "%s second"
|
|
||||||
msgstr "%s секунда"
|
|
||||||
|
|
||||||
#: ../src\update\utils.py:41
|
|
||||||
msgid "%s seconds"
|
|
||||||
msgstr "%s секунд"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:9
|
|
||||||
msgid "New version for %s"
|
|
||||||
msgstr "Новая версия %s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:9
|
|
||||||
msgid ""
|
|
||||||
"There's a new %s version available. Would you like to download it now?\n"
|
|
||||||
"\n"
|
|
||||||
" %s version: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Changes:\n"
|
|
||||||
"%s"
|
|
||||||
msgstr ""
|
|
||||||
"Доступна новая версия %s. Желаете скачать её?\n"
|
|
||||||
"\n"
|
|
||||||
" %s версия: %s\n"
|
|
||||||
"\n"
|
|
||||||
"Изменения:\n"
|
|
||||||
"%s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:16
|
|
||||||
msgid "Download in Progress"
|
|
||||||
msgstr "Процесс скачивания"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:16
|
|
||||||
msgid "Downloading the new version..."
|
|
||||||
msgstr "Скачивание новой версии..."
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:26
|
|
||||||
msgid "Updating... %s of %s"
|
|
||||||
msgstr "Обновление... %s из %s"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:29
|
|
||||||
msgid "Done!"
|
|
||||||
msgstr "Готово!"
|
|
||||||
|
|
||||||
#: ../src\update\wxUpdater.py:29
|
|
||||||
msgid ""
|
|
||||||
"The update has been downloaded and installed successfully. Press OK to "
|
|
||||||
"continue."
|
|
||||||
msgstr ""
|
|
||||||
"Обновление было успешно загружено и установлено. Нажмите ОК для продолжения."
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:14 ../src\wxUI\mainWindow.py:63
|
|
||||||
msgid "Stop"
|
|
||||||
msgstr "Остановить"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:15 ../src\wxUI\mainWindow.py:61
|
|
||||||
msgid "Previous"
|
|
||||||
msgstr "Предыдущая композиция"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:16 ../src\wxUI\mainWindow.py:64
|
|
||||||
msgid "Next"
|
|
||||||
msgstr "Следующая композиция"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:17
|
|
||||||
msgid "Shuffle"
|
|
||||||
msgstr "Перемешать"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:18
|
|
||||||
msgid "Volume down"
|
|
||||||
msgstr "Уменьшить громкость"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:19
|
|
||||||
msgid "Volume up"
|
|
||||||
msgstr "Увеличить громкость"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:20
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr "Выключить звук"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:22
|
|
||||||
msgid "About {0}"
|
|
||||||
msgstr "О {0}"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:23
|
|
||||||
msgid "Check for updates"
|
|
||||||
msgstr "Проверить на наличие обновлений"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:24
|
|
||||||
msgid "What's new in this version?"
|
|
||||||
msgstr "Что нового в этой версии?"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:25
|
|
||||||
msgid "Visit website"
|
|
||||||
msgstr "Посетить вебсайт"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:26
|
|
||||||
msgid "Player"
|
|
||||||
msgstr "Плеер"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:27
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "Помощь"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:37
|
|
||||||
msgid "search"
|
|
||||||
msgstr "Поиск"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:42
|
|
||||||
msgid "Search in"
|
|
||||||
msgstr "Искать с помощью"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:45
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Поиск"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:49
|
|
||||||
msgid "Results"
|
|
||||||
msgstr "Результаты"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:55
|
|
||||||
msgid "Position"
|
|
||||||
msgstr "Позиция"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:58
|
|
||||||
msgid "Volume"
|
|
||||||
msgstr "Громкость"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:100
|
|
||||||
msgid "Audio Files(*.mp3)|*.mp3"
|
|
||||||
msgstr "Аудио Файлы(*.mp3)|*.mp3"
|
|
||||||
|
|
||||||
#: ../src\wxUI\mainWindow.py:100
|
|
||||||
msgid "Save this file"
|
|
||||||
msgstr "Сохранить этот файл"
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:7
|
|
||||||
msgid "Play/Pause"
|
|
||||||
msgstr "Воспроизвести/Приостановить"
|
|
||||||
|
|
||||||
#: ../src\wxUI\menus.py:9
|
|
||||||
msgid "Download"
|
|
||||||
msgstr "Скачать"
|
|
||||||
|
10
src/main.py
10
src/main.py
@ -1,9 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals # at top of module
|
|
||||||
# this is the first fix we have to import just before the paths module would.
|
|
||||||
# it changes a call from wintypes to ctypes.
|
|
||||||
from fixes import fix_winpaths
|
|
||||||
fix_winpaths.fix()
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import storage
|
import storage
|
||||||
@ -12,10 +7,7 @@ import sys
|
|||||||
storage.setup()
|
storage.setup()
|
||||||
# Let's import config module here as it is dependent on storage being setup.
|
# Let's import config module here as it is dependent on storage being setup.
|
||||||
import config
|
import config
|
||||||
logging.basicConfig(filename=os.path.join(storage.data_directory, "info.log"), level=logging.DEBUG, filemode="w")
|
logging.basicConfig(handlers=[logging.FileHandler(os.path.join(storage.data_directory, "info.log"), "w", "utf-8")], level=logging.DEBUG)
|
||||||
# Let's mute the google discovery_cache logger as we won't use it and we'll avoid some tracebacks.
|
|
||||||
glog = logging.getLogger("googleapiclient.discovery_cache")
|
|
||||||
glog.setLevel(logging.CRITICAL)
|
|
||||||
# Let's capture all exceptions raised in our log file (especially useful for pyinstaller builds).
|
# Let's capture all exceptions raised in our log file (especially useful for pyinstaller builds).
|
||||||
sys.excepthook = lambda x, y, z: logging.critical(''.join(traceback.format_exception(x, y, z)))
|
sys.excepthook = lambda x, y, z: logging.critical(''.join(traceback.format_exception(x, y, z)))
|
||||||
log = logging.getLogger("main")
|
log = logging.getLogger("main")
|
||||||
|
@ -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')
|
|
160
src/paths.py
160
src/paths.py
@ -1,116 +1,70 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
import inspect
|
import sys
|
||||||
import platform
|
import platform
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import glob
|
||||||
import sys
|
from platform_utils import paths as paths_
|
||||||
import string
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
def app_data_path(app_name=None):
|
mode = "portable"
|
||||||
"""Cross-platform method for determining where to put application data."""
|
directory = None
|
||||||
"""Requires the name of the application"""
|
fsencoding = sys.getfilesystemencoding()
|
||||||
plat = platform.system()
|
|
||||||
if plat == 'Windows':
|
|
||||||
import winpaths
|
|
||||||
path = winpaths.get_appdata()
|
|
||||||
elif plat == 'Darwin':
|
|
||||||
path = os.path.join(os.path.expanduser('~'), 'Library', 'Application Support')
|
|
||||||
elif plat == 'Linux':
|
|
||||||
path = os.path.expanduser('~')
|
|
||||||
app_name = '.%s' % app_name.replace(' ', '_')
|
|
||||||
return os.path.join(path, app_name)
|
|
||||||
|
|
||||||
def prepare_app_data_path(app_name):
|
if len(glob.glob("Uninstall.exe")) > 0: # installed copy
|
||||||
"""Creates the application's data directory, given its name."""
|
mode= "installed"
|
||||||
dir = app_data_path(app_name)
|
|
||||||
return ensure_path(dir)
|
|
||||||
|
|
||||||
def embedded_data_path():
|
|
||||||
if platform.system() == 'Darwin' and is_frozen():
|
|
||||||
return os.path.abspath(os.path.join(executable_directory(), '..', 'Resources'))
|
|
||||||
return app_path()
|
|
||||||
|
|
||||||
def is_frozen():
|
|
||||||
"""Return a bool indicating if application is compressed"""
|
|
||||||
import imp
|
|
||||||
return hasattr(sys, 'frozen') or imp.is_frozen("__main__")
|
|
||||||
|
|
||||||
def get_executable():
|
|
||||||
"""Returns the full executable path/name if frozen, or the full path/name of the main module if not."""
|
|
||||||
if is_frozen():
|
|
||||||
if platform.system() != 'Darwin':
|
|
||||||
return sys.executable
|
|
||||||
#On darwin, sys.executable points to python. We want the full path to the exe we ran.
|
|
||||||
exedir = os.path.abspath(os.path.dirname(sys.executable))
|
|
||||||
items = os.listdir(exedir)
|
|
||||||
items.remove('python')
|
|
||||||
return os.path.join(exedir, items[0])
|
|
||||||
#Not frozen
|
|
||||||
try:
|
|
||||||
import __main__
|
|
||||||
return os.path.abspath(__main__.__file__)
|
|
||||||
except AttributeError:
|
|
||||||
return sys.argv[0]
|
|
||||||
|
|
||||||
def get_module(level=2):
|
|
||||||
"""Hacky method for deriving the caller of this function's module."""
|
|
||||||
return inspect.getmodule(inspect.stack()[level][0]).__file__
|
|
||||||
|
|
||||||
def executable_directory():
|
|
||||||
"""Always determine the directory of the executable, even when run with py2exe or otherwise frozen"""
|
|
||||||
executable = get_executable()
|
|
||||||
path = os.path.abspath(os.path.dirname(executable))
|
|
||||||
return path
|
|
||||||
|
|
||||||
def app_path():
|
def app_path():
|
||||||
"""Return the root of the application's directory"""
|
return paths_.app_path()
|
||||||
path = executable_directory()
|
|
||||||
if is_frozen() and platform.system() == 'Darwin':
|
|
||||||
path = os.path.abspath(os.path.join(path, '..', '..'))
|
|
||||||
return path
|
|
||||||
|
|
||||||
def module_path(level=2):
|
def config_path():
|
||||||
return os.path.abspath(os.path.dirname(get_module(level)))
|
global mode, directory
|
||||||
|
if mode == "portable":
|
||||||
def documents_path():
|
if directory != None: path = os.path.join(directory, "config")
|
||||||
"""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."""
|
elif directory == None: path = os.path.join(app_path(), "config")
|
||||||
plat = platform.system()
|
elif mode == "installed":
|
||||||
if plat == 'Windows':
|
path = os.path.join(data_path(), "config")
|
||||||
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 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 ensure_path(path):
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
# log.debug("%s path does not exist, creating..." % (path,))
|
||||||
|
os.mkdir(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def start_file(path):
|
def logs_path():
|
||||||
if platform.system() == 'Windows':
|
global mode, directory
|
||||||
os.startfile(path)
|
if mode == "portable":
|
||||||
else:
|
if directory != None: path = os.path.join(directory, "logs")
|
||||||
subprocess.Popen(['open', path])
|
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 get_applications_path():
|
def data_path(app_name='socializer'):
|
||||||
"""Return the directory where applications are commonly installed on the system."""
|
if platform.system() == "Windows":
|
||||||
plat = platform.system()
|
data_path = os.path.join(os.getenv("AppData"), app_name)
|
||||||
if plat == 'Windows':
|
else:
|
||||||
import winpaths
|
data_path = os.path.join(os.environ['HOME'], ".%s" % app_name)
|
||||||
return winpaths.get_program_files()
|
if not os.path.exists(data_path):
|
||||||
elif plat == 'Darwin':
|
os.mkdir(data_path)
|
||||||
return '/Applications'
|
return data_path
|
||||||
|
|
||||||
|
def locale_path():
|
||||||
|
return os.path.join(app_path(), "locales")
|
||||||
|
|
||||||
|
def sound_path():
|
||||||
|
return os.path.join(app_path(), "sounds")
|
||||||
|
|
||||||
|
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.
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