Compare commits

...

245 Commits
v0.6 ... master

Author SHA1 Message Date
7d340bb7eb
Readded cx_freeze as nuitka was unable to build yt-dlp 2022-10-21 12:55:13 -05:00
fb2395ce67
disabled VK downloader temporarily [Skip CI] 2022-10-21 09:39:57 -05:00
2386401633
Added mpv-2.dll for both supported platforms 2022-10-20 17:29:45 -05:00
aab1230c07
Replaced searches and downloads for yt-dlp implementation [Skip CI] 2022-10-20 17:26:18 -05:00
516e20c3b8
Changes on GUI for new player module 2022-10-20 17:25:29 -05:00
062bbcc842
Start rewrite of player control module with mpv [Skip CI] 2022-10-20 17:25:05 -05:00
f5ecaefe8c
Removed sound_lib on favour of mpv; replaced youtube_dl for yt-dlp 2022-10-20 17:23:59 -05:00
3b17f4b7b0
Updated move command 2022-10-19 17:32:44 -05:00
8aa55b0aa4
Updated python version and wx version for building the app 2022-10-19 17:17:35 -05:00
2613803031
Ask Youtube to retrieve json results 2022-03-25 13:52:57 -06:00
b286e526cc
Generate version at build time 2022-03-25 13:26:44 -06:00
a1b29005bb
Fixed version generate when checking updates 2022-03-25 13:09:26 -06:00
6eaa0f50a1
Generate update files with right version 2022-03-25 12:58:13 -06:00
0ea41cea89
Fixed links for the updates file 2021-12-30 19:41:40 -06:00
5c9169fadd
Fixed install dir for 64 bits executable 2021-12-30 19:39:21 -06:00
817df4219b
Move installer file to artifacts on first build fase for the 64 bits app 2021-12-30 19:22:29 -06:00
70273b2023
Call installer.nsi by using the right path 2021-12-30 19:08:53 -06:00
9bd3b7f448
Fix artifacts creation; try to delete no longer files on last step before upload 2021-12-30 19:01:18 -06:00
0fef6427ff
Try to generate an installer and a 64 bits version 2021-12-30 18:50:42 -06:00
32fb52f941
Updated copyright sections 2021-12-30 18:49:01 -06:00
ef36948ab8
Updated URL to retrieve updates to our new gitlab repository 2021-12-30 18:48:47 -06:00
236539f337
Moved project to our own runner 2021-12-29 10:51:40 -06:00
af16329c72 Added windows dependencies for 32 and 64 bits 2021-09-24 16:13:38 -05:00
ebb400672f Fixed player function on end playback 2021-09-24 13:19:30 -05:00
9bb85688bb Modified setup to remove LibVLc inclussion 2021-09-24 10:18:17 -05:00
da4433894f Removed libVLc dependencies 2021-09-23 16:57:50 -05:00
b60e62a56d Fixed function to get the next song automatically 2021-09-23 16:57:10 -05:00
74d9b5b1a4 Fixed playback status representation in slider controls 2021-09-23 16:55:48 -05:00
14d311f29d Fixed tidal authentication. Now it requires to authorize accounts via a browser 2021-09-23 16:12:03 -05:00
441277a472
fixed a couple types in str definition fo config files 2021-09-22 23:39:26 -05:00
b0f241b2b1 Implemented most of the code for Tidal updates. Only authorization is pending for now 2021-09-22 17:51:43 -05:00
7d64515bff Get and set default devices from sound_lib 2021-09-22 17:33:59 -05:00
cc1216f7fe Updated config spec 2021-09-22 17:32:27 -05:00
226a17b4fe Changed VLC again for sound_lib 2021-09-22 17:32:10 -05:00
1cd7d2ad39 Removed some old code no longer needed 2021-09-22 17:25:22 -05:00
c5d103dc8d disabled automatic generation of new versions on every commit to master for a while 2021-09-22 17:24:31 -05:00
1a5fe83eee Install tidalapi from pip; added sound_lib as a dependency; remove libVLC module from requirements file 2021-09-22 17:21:03 -05:00
ab264d4100 changed indent style 2021-09-22 15:32:52 -05:00
de28e80327 Updated dependencies to match changes in git repositories 2021-09-22 15:30:14 -05:00
ea01e13930 Remove tests for a while 2021-01-19 16:19:43 -06:00
2ef50b73c4 Call to update generator before uploading artifacts 2021-01-19 16:19:14 -06:00
18fd1610bf Changed more files to adapt to the new hosting provider 2021-01-19 15:57:27 -06:00
b498dfb91e Fixed requirements 2021-01-19 15:37:43 -06:00
5f20f2ea91 Updated CI configuration to build with Gitlab runners 2021-01-19 15:32:40 -06:00
0e63c3e148 Modified script to upload to an insecure ftp folder 2021-01-19 15:32:23 -06:00
ee1e13deac Updated locales [skip ci] 2020-12-29 05:08:21 -08:00
1fd75dfa93 Attempt to build MusicDL against Python 3.9.1 2020-12-29 06:57:27 -06:00
52dd577b6c Updated changelog 2020-12-29 06:57:08 -06:00
7cb6a10db4 Updated locales [skip ci] 2020-12-28 21:22:19 -08:00
a09079ba43 Merge branch 'master' of code.manuelcortez.net:manuelcortez/music-dl 2020-12-28 23:09:11 -06:00
c4694ac4b1 Updated VLC components to Version 3.0.11. 2020-12-28 23:08:41 -06:00
a068ea2342 Disabled zaicev.net by default 2020-12-26 22:30:15 -06:00
ba39db0e46 Updated locales [skip ci] 2020-12-26 20:20:56 -08:00
e321cdf259 Generates the po files after everything else 2020-12-26 22:09:37 -06:00
b9707aaf18 Updated locales [skip ci] 2020-12-26 19:56:46 -08:00
4f7b9c5b60 Merge branch 'master' of code.manuelcortez.net:manuelcortez/music-dl 2020-12-26 21:54:19 -06:00
162ce4491b Updated gitlab CI file to avoid duplicating a CI job 2020-12-26 21:54:00 -06:00
b36504a493 Updated locales [skip ci] 2020-12-26 16:25:43 -08:00
fe878b227d Updated locales [skip ci] 2020-12-25 16:25:26 -08:00
3fe5d98f66 Updated locales [skip ci] 2020-12-24 16:25:28 -08:00
eb61d55d20 Updated locales [skip ci] 2020-12-23 16:27:20 -08:00
346f6e15fe Updated locales [skip ci] 2020-12-22 16:26:12 -08:00
52be306691 Updated locales [skip ci] 2020-12-21 16:27:33 -08:00
282b840c02 Updated locales [skip ci] 2020-12-20 16:26:53 -08:00
d300713f94 Updated locales [skip ci] 2020-12-19 16:27:07 -08:00
b8c55af897 Updated locales [skip ci] 2020-12-18 16:26:29 -08:00
643f9c5613 Updated locales [skip ci] 2020-12-17 16:26:36 -08:00
475e9fd573 Updated locales [skip ci] 2020-12-16 16:26:52 -08:00
f6b08cb046 Updated locales [skip ci] 2020-12-15 16:25:44 -08:00
c1931f22bc Updated locales [skip ci] 2020-12-14 16:25:53 -08:00
12c3da2886 Updated locales [skip ci] 2020-12-13 16:26:00 -08:00
79a7de3940 Updated locales [skip ci] 2020-12-12 16:25:23 -08:00
91db712833 Updated locales [skip ci] 2020-12-11 16:25:53 -08:00
de86808eb4 Updated locales [skip ci] 2020-12-10 16:25:59 -08:00
d7f7e508a3 Updated locales [skip ci] 2020-12-09 16:26:07 -08:00
55eb7244a3 Updated locales [skip ci] 2020-12-08 16:25:37 -08:00
2aa39da0d8 Updated locales [skip ci] 2020-12-07 16:25:47 -08:00
e4356d58e0 Updated locales [skip ci] 2020-12-06 16:25:44 -08:00
cc8ea272b9 Updated locales [skip ci] 2020-12-05 16:24:54 -08:00
360f4a7ce4 Updated locales [skip ci] 2020-12-04 16:24:26 -08:00
2d329ac8e9 Updated locales [skip ci] 2020-12-03 16:24:43 -08:00
0d1172dd9e Updated locales [skip ci] 2020-12-02 16:24:45 -08:00
ff17f36781 Updated locales [skip ci] 2020-12-01 16:24:34 -08:00
7087edc1a5 Updated locales [skip ci] 2020-11-30 16:24:29 -08:00
76d62eaca2 Updated locales [skip ci] 2020-11-29 16:24:18 -08:00
1d236d6e0f Updated locales [skip ci] 2020-11-28 16:25:00 -08:00
90b155c04a Updated locales [skip ci] 2020-11-27 16:23:49 -08:00
fb4b0b835e Updated locales [skip ci] 2020-11-26 16:24:29 -08:00
dfdd3785ef Updated locales [skip ci] 2020-11-25 16:24:22 -08:00
8d92b01fa4 Updated locales [skip ci] 2020-11-24 16:24:16 -08:00
e9626b977e Updated locales [skip ci] 2020-11-23 16:24:36 -08:00
68798328d2 Updated locales [skip ci] 2020-11-22 16:24:13 -08:00
908eb64481 Updated locales [skip ci] 2020-11-21 16:24:09 -08:00
cac07c0864 Updated locales [skip ci] 2020-11-20 16:23:49 -08:00
27d593b512 Updated locales [skip ci] 2020-11-19 16:23:57 -08:00
0ead659156 Updated locales [skip ci] 2020-11-18 16:26:36 -08:00
c66c924321 Updated locales [skip ci] 2020-11-17 16:27:42 -08:00
e985f97811 Updated locales [skip ci] 2020-11-16 16:27:55 -08:00
34bee5ad50 Updated locales [skip ci] 2020-11-15 16:28:03 -08:00
d62c4e226d Updated locales [skip ci] 2020-11-14 16:25:52 -08:00
36eb2b2537 Updated locales [skip ci] 2020-11-13 16:27:26 -08:00
98184a9abc Updated locales [skip ci] 2020-11-12 16:26:47 -08:00
4a625b7a29 Updated locales [skip ci] 2020-11-11 16:28:43 -08:00
241a553fce Updated locales [skip ci] 2020-11-10 16:24:22 -08:00
da0bb3a6aa Updated locales [skip ci] 2020-11-09 16:23:45 -08:00
afffd9ba15 Updated locales [skip ci] 2020-11-08 16:24:37 -08:00
4d4e0bf283 Updated locales [skip ci] 2020-11-07 16:24:46 -08:00
6429e80964 Updated locales [skip ci] 2020-11-06 16:24:26 -08:00
2579224d80 Updated locales [skip ci] 2020-11-05 16:25:18 -08:00
0e73d55c06 Updated locales [skip ci] 2020-11-04 16:25:09 -08:00
28a1eb7ee9 Updated locales [skip ci] 2020-11-03 16:25:17 -08:00
5ccee02391 Updated locales [skip ci] 2020-11-02 16:25:06 -08:00
cf14992283 Updated locales [skip ci] 2020-11-01 16:24:52 -08:00
8e29595b08 Updated locales [skip ci] 2020-10-31 17:23:24 -07:00
b3158c8813 Updated locales [skip ci] 2020-10-30 17:23:19 -07:00
84698c29bd Updated locales [skip ci] 2020-10-29 17:24:35 -07:00
153b001930 Updated locales [skip ci] 2020-10-28 17:23:20 -07:00
6e1b6232a4 Updated locales [skip ci] 2020-10-27 17:23:08 -07:00
41f18eb8dd Updated locales [skip ci] 2020-10-26 17:24:08 -07:00
6f820779f5 Updated locales [skip ci] 2020-10-25 17:24:07 -07:00
68c1cd7718 Updated locales [skip ci] 2020-10-24 16:24:17 -07:00
39059725f6 Updated locales [skip ci] 2020-10-23 16:23:04 -07:00
4c10890554 Updated locales [skip ci] 2020-10-22 16:24:29 -07:00
877d588f8f Updated locales [skip ci] 2020-10-21 16:23:38 -07:00
1698cc59c3 Updated locales [skip ci] 2020-10-20 16:24:37 -07:00
351783aa53 Updated locales [skip ci] 2020-10-19 16:25:11 -07:00
932f945b1f Updated locales [skip ci] 2020-10-18 16:24:28 -07:00
033b7f2c6d Updated locales [skip ci] 2020-10-17 16:24:43 -07:00
01d85cdbf6 Updated locales [skip ci] 2020-10-16 16:24:24 -07:00
87065716b0 Updated locales [skip ci] 2020-10-15 16:24:24 -07:00
c1f5244b7d Updated locales [skip ci] 2020-10-14 16:24:44 -07:00
e89d02eeeb Updated locales [skip ci] 2020-10-13 16:24:45 -07:00
66c6e62293 Updated locales [skip ci] 2020-10-12 16:25:27 -07:00
92239613b2 Updated locales [skip ci] 2020-10-11 16:24:32 -07:00
2adbd2f2d7 Updated locales [skip ci] 2020-10-10 16:24:44 -07:00
07158f5ffd Updated locales [skip ci] 2020-10-09 16:24:47 -07:00
158391330c Updated locales [skip ci] 2020-10-08 16:24:40 -07:00
3874b34a77 Updated locales [skip ci] 2020-10-07 16:24:45 -07:00
83abe3376b Updated locales [skip ci] 2020-10-06 16:24:49 -07:00
e85acd6c20 Updated locales [skip ci] 2020-10-05 16:25:08 -07:00
2410af484c Updated locales [skip ci] 2020-10-04 16:24:45 -07:00
5b9a28344e Updated locales [skip ci] 2020-10-03 16:25:03 -07:00
0c0c89debe Updated locales [skip ci] 2020-10-02 16:25:04 -07:00
5f8d3e51de Updated locales [skip ci] 2020-10-02 08:16:02 -07:00
87e3169a19 Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: MusicDL/interface
Translate-URL: http://translations.manuelcortez.net/projects/musicdl/interface/es/
2020-10-02 10:10:30 -05:00
3d7432f7b7 Updated locales [skip ci] 2020-10-01 16:24:26 -07:00
b0c0cdbdba Update translation catalogs [skip CI] 2020-10-01 10:03:41 -05:00
4cb07bd2f0 Changed frequency of building stuff [Skip CI] 2020-10-01 09:21:11 -05:00
ce2435dfb4 Updated locales [skip ci] 2020-10-01 07:14:58 -07:00
5c0d98fc9f fixed a path when generating translation files 2020-10-01 09:09:14 -05:00
893b075f06 Fix some gitlab changes 2020-09-30 17:56:16 -05:00
d7759c75a0 Removed coverage from hard requirements list 2020-09-30 17:48:53 -05:00
18f1a15a81 Started to work in automatic translations workflow 2020-09-30 17:48:24 -05:00
ad2189039e fixed python checking code no longer valid 2020-07-31 10:52:53 -05:00
57a23868e9 Updated russian translation 2020-07-30 05:18:21 -05:00
2a834559ec Fixed a small typo 2020-07-27 11:45:29 -05:00
9481d2868f Updated scripts to be executed on CI environment 2020-07-27 09:44:04 -05:00
fd791fe181 Modified update paths and files in order to deploy the next date based release 2020-07-27 09:39:54 -05:00
1477522681 removed i18n from the test suite [Skip CI] 2020-07-23 06:09:28 -05:00
81d0c48a8b Updated spanish translation [Skip CI] 2020-07-23 06:08:27 -05:00
8fe6302942 Added instructions to deal with translation catalogs 2020-07-23 06:07:54 -05:00
0f062a2fde Added some accelt controls 2020-07-20 03:38:58 -05:00
ca6e6b23bf Updated Readme [Skip CI] 2020-07-19 11:41:14 -05:00
ff8a7492e8 Reverted back to 32 bits support due to missing VLC stuff 2020-07-19 11:38:07 -05:00
aa2c46c618 Attempt to build a 64 bits version 2020-07-19 11:31:10 -05:00
d6e75f8604 Removed app description as it is displayed in windows notifications 2020-07-19 11:29:01 -05:00
abd83553b7 Safe filename function has been added to controller 2020-07-19 10:45:59 -05:00
4c2d696ee6 Always use safe filenames for downloads 2020-07-19 10:41:23 -05:00
884bcfadca Reverted back docker experiments 2020-07-19 10:31:34 -05:00
e342003de5 Fixed second typo 2020-07-19 08:53:29 -05:00
b9c763c4f4 New attempt 2020-07-19 08:36:30 -05:00
9ef726e762 Tune git install 2020-07-19 08:11:46 -05:00
d1ec62e41d Added CMd files 2020-07-19 08:05:00 -05:00
fa3ac19651 Added minGW32 path too 2020-07-19 07:59:20 -05:00
49d6d4093c Fix typos on path 2020-07-19 07:50:42 -05:00
a15f35822e silence progress in request url 2020-07-19 07:44:42 -05:00
3190e90974 Fixed a few tipos 2020-07-19 07:27:15 -05:00
7e2c6d6430 Added git path location correctly 2020-07-19 07:21:50 -05:00
c9b85f31f7 Docker experiments back 2020-07-19 07:10:55 -05:00
841df99d61 Write new alpha version info on every build 2020-07-18 01:52:41 -05:00
fdea954083 Added bootstrap updater into executable 2020-07-18 01:37:13 -05:00
444c132843 Revert back to gitlab CI file prior to the docker experiment 2020-07-18 01:23:44 -05:00
6e86359698 Set path 2020-07-18 01:02:03 -05:00
3f2fc9c9de Added poshgit 2020-07-18 00:52:46 -05:00
0d5cf787fc Refresh powershell env 2020-07-18 00:30:54 -05:00
f4872a1ecd Fixed syntax error 2020-07-18 00:18:45 -05:00
2d8b7b2ed6 Test 2020-07-18 00:17:00 -05:00
503dead2aa Added 32 bits only variant 2020-07-17 23:39:52 -05:00
19b7248074 Autoaccept choco install 2020-07-17 23:35:06 -05:00
b4c8a41757 Removed extra l 2020-07-17 23:32:41 -05:00
91acce2df9 Try to another docker image 2020-07-17 23:22:44 -05:00
3b7df1230c Making test 2020-07-17 23:04:32 -05:00
5e6749cf84 Change a syntax param 2020-07-17 22:52:33 -05:00
a94aaf4ce3 Fixed a command 2020-07-17 22:48:43 -05:00
c7ba0a86c1 Tried to install git again 2020-07-17 22:43:51 -05:00
7abdf00529 Invoque command used instead 2020-07-17 22:38:23 -05:00
b42c7d402f Remove the -command argument from invoque-expression 2020-07-17 22:32:20 -05:00
bff6b3d6c3 A few typos 2020-07-17 22:28:14 -05:00
3e284d5a8b use basic parsing 2020-07-17 22:21:39 -05:00
a690f116d2 Attempt to install git in windows docker 2020-07-17 22:18:40 -05:00
693a9f474e Merge branch 'master' of code.manuelcortez.net:manuelcortez/music-dl 2020-07-17 21:56:06 -05:00
5512b605e1 Started implementation of a failed docker based image 2020-07-17 21:55:44 -05:00
1051b81983 Added alpha version generation for Music DL and autoupdates 2020-07-17 17:51:02 -05:00
f0f23c9b05 Imported updater module directly from socializer 2020-07-17 17:50:18 -05:00
12a4ee65c3 Tidal: Added top:// command to retrieve the top rated tracks for a provided artist. [Skip CI] 2020-07-17 17:36:48 -05:00
96af01279a Updated changes 2020-07-17 16:37:33 -05:00
0cc6f97f01 Added metadata to m4a files downloaded from tidal 2020-07-13 16:04:20 -05:00
fef5c218b0 Fixed a couple of issues 2020-07-12 23:11:41 -05:00
c518d0ea08 Attempt to upload music DL to ftp 2020-07-12 21:57:53 -05:00
a2caaa6449 Attempt to use a virtual environment every time the CI is building 2020-07-12 20:52:01 -05:00
0a102fecc2 More requirements 2020-07-12 20:41:50 -05:00
f9f1a9c864 Added babel 2020-07-12 20:38:58 -05:00
10570c23b6 Added platform_utils as a dependency 2020-07-12 20:35:28 -05:00
42446107f7 Updated to python 3.8 2020-07-12 20:32:13 -05:00
7d9067a611 Fixed some issues in cx_freeze 2020-07-12 20:31:15 -05:00
b9302000f6 Updated changelog 2020-07-12 20:19:36 -05:00
cfd6a92c35 Added a few comments 2020-07-12 20:19:20 -05:00
f1f460bf0a small fix for VK2 2020-07-12 18:21:46 -05:00
561660b700 Added experimental support to VK 2020-07-10 17:38:30 -05:00
bcd3b7b36e Removed extra call to get_download_url 2020-07-08 17:16:35 -05:00
332365d53b Remove references to google discovery services 2020-07-08 16:39:44 -05:00
0768ecc24f Player receives metadata now 2020-07-08 16:39:17 -05:00
6aeffd57bb Added get_metadata into base songs 2020-07-08 13:14:50 -05:00
dae1df79ad Pass metadata to downloaders 2020-07-08 13:14:08 -05:00
c06df2092e Added tidal Settings and apply metadata functions 2020-07-08 13:12:33 -05:00
bb32d79dd7 Updated version numbers 2020-07-08 08:27:08 -05:00
27e422888b Removed pyinstaller spec file 2020-07-07 21:15:59 -05:00
b1f239fe82 Fixed tests 2020-07-07 21:14:22 -05:00
ff8c8ac22b Fixed zipfile generator 2020-07-07 17:56:44 -05:00
ea04cf2eb1 Fixed installer script 2020-07-07 17:52:15 -05:00
861fdb56b5 Started to adopt wxpython 4.1.0 2020-07-07 17:47:49 -05:00
e2d73ec446 Rewrote the CI config file to take advantage of the new runner 2020-07-07 17:33:09 -05:00
b4a0060756 Modified setup file 2020-07-07 17:21:31 -05:00
66f6f32916 Updated some tests. 2020-07-07 17:03:25 -05:00
01f639b809 fixed Youtube searches 2020-07-07 17:03:04 -05:00
10d60e3aa2 tidal: Add track number to results when tracks are part of an album in artist search 2019-10-06 11:26:09 -05:00
16d99aaaac Install tidalapi from their github repo 2019-10-06 11:05:11 -05:00
625931f40b Reverted commits to the last working state 2019-08-16 03:05:37 -05:00
6a188b7ec7 Revert "Added more settings in Tidal"
This reverts commit 207a7110ef111633ce9aae7829c19ad7556f62e3.

Revert to the last commit so the integration of music player features needs to be done in another way.
2019-08-16 02:59:04 -05:00
adc1317401 Added experimental changes for musicDL 2019-07-12 17:54:29 -05:00
207a7110ef Added more settings in Tidal 2019-07-08 16:43:31 -05:00
69452b69bd Updated changelog 2019-07-08 16:06:20 -05:00
084d3b8b10 Renamed extractors module to services 2019-07-08 13:04:00 -05:00
eedf897de0 Fixed a typo in player module 2019-07-08 12:34:42 -05:00
9ac26bc818 Imported paths module from Socializer. That means unicode paths should be fully supported 2019-07-08 12:29:41 -05:00
218 changed files with 2480 additions and 3108 deletions

View File

@ -1,106 +1,121 @@
# This CI configuration file is used to build all available versions of MusicDL. It's intended to launch a new version when a tag is pushed to master.
# In order to work, A Gitlab Runner with Windows Server 2016 Standard with the following packages is used:
# * Latest python for both 2.7 and 3.x branches.
# * Py2exe for Python 2.7
# * Microsoft Visual C++ 2015 redistributable files
# * Nsis
# Declare some variables dependent on the operating system where the runner is installed.
# This CI file assumes we install everything in C:\ (Python 2.7, 3.7 and Nsis).
variables:
PYTHON3: "C:\\python37\\python.exe"
PYINSTALLER: "C:\\python37\\scripts\\pyinstaller.exe"
NSIS: "C:\\nsis\\makensis.exe"
### Stage list
# Build: This will be the main stage generating stuff in the dist folder. Jobs present in this stage will run py2exe or pyinstaller files accordingly.
# pack: Jobs in this stage will take the dist folder and zip it or generate an exe file.
stages:
- test
- build
- pack
- test
- generate_docs
- build
- versions
- upload
# Python 3 tests
test_py3:
stage: test
program64:
interruptible: true
tags:
- windows
- windows10
before_script:
- '%PYTHON3% -v'
- '%PYTHON3% -m pip install --upgrade pip'
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
script:
- cd src
- '%PYTHON3% -m coverage run run_tests.py'
- '%PYTHON3% -m coverage report --omit="test*"'
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
only:
- master
- tags
- schedule_pipelines
# Python 3 version. During this job, the dist folder, containing all files to distribute, will be generated
# and passed to build_zip and build_setup jobs.
build_py3:
stage: build
tags:
- windows10
# Update stuff before building versions
variables:
PYTHON: "C:\\python310\\python.exe"
before_script:
- '%PYTHON3% -v'
- '%PYTHON3% -m pip install --upgrade pip'
- '%PYTHON3% -m pip install --upgrade -r requirements.txt'
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m venv env'
- 'env\Scripts\Activate.ps1'
- 'python -m pip install --upgrade -r requirements.txt'
script:
- cd src
- '%PYINSTALLER% main.spec'
# Build this automatically only when tags are pushed to master or when a pipeline has been scheduled by Gitlab.
only:
- tags
- schedule_pipelines
# Make the dist folder available to other jobs.
# It will expire in 30 mins as we won't need the dist folder after the pipeline is completed.
artifacts:
paths:
- src\\dist
expire_in: 30 mins
# This job takes the src\\dist folder generated in build_py3 and creates a zip file, which will be uploaded to the repository's artifacts.
zip_py3:
stage: pack
tags:
- windows10
only:
- tags
- schedule_pipelines
dependencies:
- build_py3
script:
- 'python write_version_data.py'
- 'python setup.py build'
- cd ..
- 'mkdir build'
- cd scripts
- '%PYTHON3% prepare_zipversion.py'
- 'python prepare_zipversion.py'
- cd ..
- move src\music_dl.zip music_dl.zip
# No expiry date as there will be only releases in the artifacts.
- move src\music_dl.zip build\music_dl_x64.zip
- 'move src/dist build/program64'
- 'move src/installer.nsi build'
only:
- schedules
- master
artifacts:
paths:
- music_dl.zip
- build
expire_in: 1 day
# This job takes the src\\dist generated in build_py3 and creates a setup installer file.
build_setup:
stage: pack
program32:
interruptible: true
tags:
- windows
- windows10
only:
- tags
- schedule_pipelines
dependencies:
- build_py3
stage: build
variables:
PYTHON: "C:\\python310-32\\python.exe"
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m venv env'
- 'env\Scripts\Activate.ps1'
- 'python -m pip install https://github.com/josephsl/wxpy32whl/raw/main/wxPython-4.2.0-cp310-cp310-win32.whl'
- 'python -m pip install --upgrade -r requirements.txt'
script:
- cd src
- '%NSIS% installer.nsi'
- 'python write_version_data.py'
- 'python setup.py build'
- cd ..
- move src\music_dl* .
- 'mkdir build'
- cd scripts
- 'python prepare_zipversion.py'
- cd ..
- move src\music_dl.zip build\music_dl_x86.zip
- 'move src/dist build/program32'
only:
- schedules
- master
artifacts:
paths:
- "music_dl_*"
name: music_dl
- build
expire_in: 1 day
generate_versions:
stage: versions
tags:
- windows
- windows10
variables:
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
script:
- mkdir artifacts
- 'cd build'
- '&$env:NSIS installer.nsi'
- move *.exe ../artifacts
- move *.zip ../artifacts
only:
- tags
- master
- schedules
artifacts:
paths:
- artifacts
expire_in: 1 day
upload:
image:
name: amazon/aws-cli
entrypoint: [""]
tags:
- docker
only:
- tags
- master
interruptible: true
stage: upload
script:
- aws --version
- aws --endpoint-url https://s3.us-west-001.backblazeb2.com s3 cp artifacts s3://$S3_BUCKET/music_dl/ --recursive

View File

@ -12,26 +12,32 @@ MusicDL is an app for downloading music directly from services like Youtube, zay
See the requirements.txt, located in the root of this repository. Additionally, take into account the following.
* In case you want to create your own distributable version with Python 2, you'll need py2exe.
## running
Run the file main.py, located in the src directory.
## Building
### Python 3
I have provided a setup.py file for cx_freeze, so you should be able to do something like:
I have provided a main.spec file for pyinstaller, so you should be able to do something like:
> C:\python3\scripts\pyinstaller.exe main.spec
> python setup.py build
And start building. Check the dist folder for results.
### Python 2
## Updating translation catalog
If you are using Python 2.x and want to build MusicDL, there is a setup.py file made for pyinstaller aswell. Just run it the usual way:
Every time there are new strings in the application a translations catalog update must be performed with the following commands in the src directory:
> C:\python2\python.exe setup.py py2exe
> python setup.py extract_messages -o musicdl.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "Manuel Cortez" --input-dirs .
> python setup.py update_catalog --input-file musicdl.pot --domain musicdl --output-dir locales --ignore-obsolete true
And You will get a distributable version of MusicDL.
And after updating translations they should be compiled with:
> python setup.py compile_catalog --statistics -d locales --domain musicdl
## Adding new translations
The procedure for adding new translations is also easy, thanks to the following command. Just replace xx for the new locale name to add:
> python setup.py extract_messages -o musicdl.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "Copyright (C) 2019, 2020 Manuel Cortez" --input-dirs .
> python setup.py init_catalog --domain musicdl --input-file musicdl.pot -d locales --locale xx

View File

@ -1,5 +1,29 @@
## 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.

View File

@ -1,14 +1,13 @@
coverage
wxpython==4.0.3
wxpython
requests
bs4
pypubsub
python-vlc
google-api-python-client
youtube-dl
pyinstaller
isodate
yt-dlp
python-mpv
cx_freeze
configobj
winpaths
mutagen
babel
tidalapi
mutagen
git+https://github.com/accessibleapps/platform_utils

View 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()

View File

@ -1,407 +1,19 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Translations template for musicDL.
# Copyright (C) 2020 Manuel Cortez
# This file is distributed under the same license as the musicDL project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-06-24 13:04+Hora de verano central (Mexico)\n"
"Project-Id-Version: musicDL 2020.7.23\n"
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: ../src\application.py:9
msgid " Is an application that will allow you to download music from popular sites such as youtube, zaycev.net."
msgstr ""
#: ../src\application.py:14
msgid "Manuel Cortez (Spanish)"
msgstr ""
#: ../src\application.py:14
msgid "Valeria K (Russian)"
msgstr ""
#: ../src\controller\configuration.py:10 ../src\wxUI\mainWindow.py:15
msgid "Settings"
msgstr ""
#: ../src\controller\mainController.py:31
msgid "Ready"
msgstr ""
#: ../src\controller\mainController.py:47
msgid "Showing {0} results."
msgstr ""
#: ../src\controller\mainController.py:51
msgid "Shuffle on"
msgstr ""
#: ../src\controller\mainController.py:107
msgid "Searching {0}... "
msgstr ""
#: ../src\controller\mainController.py:141
#: ../src\controller\mainController.py:161 ../src\wxUI\mainWindow.py:18
#: ../src\wxUI\mainWindow.py:68
msgid "Play"
msgstr ""
#: ../src\controller\mainController.py:144
#: ../src\controller\mainController.py:156
msgid "Pause"
msgstr ""
#: ../src\controller\mainController.py:243
msgid "File downloaded: {0}"
msgstr ""
#: ../src\controller\mainController.py:263
msgid "No results found. "
msgstr ""
#: ../src\controller\player.py:70
msgid "Error playing {0}. {1}."
msgstr ""
#: ../src\controller\player.py:76
msgid "Playing {0}."
msgstr ""
#: ../src\controller\player.py:146 ../src\utils.py:58
msgid "Downloading {0}."
msgstr ""
#: ../src\controller\player.py:151 ../src\utils.py:69
msgid "Downloading {0} ({1}%)."
msgstr ""
#: ../src\controller\player.py:164
msgid "There was an error while trying to access the file you have requested."
msgstr ""
#: ../src\controller\player.py:164 ../src\issueReporter\wx_ui.py:94
#: ../src\issueReporter\wx_ui.py:97
msgid "Error"
msgstr ""
#: ../src\extractors\tidal.py:102
msgid "Tidal"
msgstr ""
#: ../src\extractors\tidal.py:106
msgid "Lossless"
msgstr ""
#: ../src\extractors\tidal.py:106
msgid "Low"
msgstr ""
#: ../src\extractors\tidal.py:106 ../src\extractors\tidal.py:141
msgid "High"
msgstr ""
#: ../src\extractors\tidal.py:118 ../src\extractors\youtube.py:112
msgid "Enable this service"
msgstr ""
#: ../src\extractors\tidal.py:122
msgid "Tidal username or email address"
msgstr ""
#: ../src\extractors\tidal.py:130
msgid "Password"
msgstr ""
#: ../src\extractors\tidal.py:137
msgid "You can subscribe for a tidal account here"
msgstr ""
#: ../src\extractors\tidal.py:140
msgid "Audio quality"
msgstr ""
#: ../src\extractors\youtube.py:106
msgid "Youtube Settings"
msgstr ""
#: ../src\extractors\youtube.py:116
msgid "Max results per page"
msgstr ""
#: ../src\extractors\youtube.py:123
msgid "Enable transcode when downloading"
msgstr ""
#: ../src\extractors\zaycev.py:52
msgid "zaycev.net"
msgstr ""
#: ../src\extractors\zaycev.py:58
msgid "Enable this service (works only in the Russian Federation)"
msgstr ""
#: ../src\issueReporter\wx_ui.py:26 ../src\wxUI\mainWindow.py:31
msgid "Report an error"
msgstr ""
#: ../src\issueReporter\wx_ui.py:30
msgid "Briefly describe what happened. You will be able to thoroughly explain it later"
msgstr ""
#: ../src\issueReporter\wx_ui.py:40
msgid "First Name"
msgstr ""
#: ../src\issueReporter\wx_ui.py:50
msgid "Last Name"
msgstr ""
#: ../src\issueReporter\wx_ui.py:60
msgid "Email address (Will not be public)"
msgstr ""
#: ../src\issueReporter\wx_ui.py:70
msgid "Here, you can describe the bug in detail"
msgstr ""
#: ../src\issueReporter\wx_ui.py:80
msgid "I know that the {0} bug system will get my email address to contact me and fix the bug quickly"
msgstr ""
#: ../src\issueReporter\wx_ui.py:83
msgid "Send report"
msgstr ""
#: ../src\issueReporter\wx_ui.py:85
msgid "Cancel"
msgstr ""
#: ../src\issueReporter\wx_ui.py:94
msgid "You must fill out the following fields: first name, last name, email address and issue information."
msgstr ""
#: ../src\issueReporter\wx_ui.py:97
msgid "You need to mark the checkbox to provide us your email address to contact you if it is necessary."
msgstr ""
#: ../src\issueReporter\wx_ui.py:100
msgid "Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i"
msgstr ""
#: ../src\issueReporter\wx_ui.py:100
msgid "reported"
msgstr ""
#: ../src\issueReporter\wx_ui.py:104
msgid "Error while reporting"
msgstr ""
#: ../src\issueReporter\wx_ui.py:104
msgid "Something unexpected occurred while trying to report the bug. Please, try again later"
msgstr ""
#: ../src\issueReporter\wx_ui.py:108
msgid "Please wait while your report is being send."
msgstr ""
#: ../src\issueReporter\wx_ui.py:108
msgid "Sending report..."
msgstr ""
#: ../src\test\test_i18n.py:21
msgid "This is a string with no special characters."
msgstr ""
#: ../src\test\test_i18n.py:24
msgid "\320\237\321\200\320\270\320\262\320\265\321\202 \320\262\321\201\320\265\320\274"
msgstr ""
#: ../src\update\utils.py:27
msgid "%d day, "
msgstr ""
#: ../src\update\utils.py:29
msgid "%d days, "
msgstr ""
#: ../src\update\utils.py:31
msgid "%d hour, "
msgstr ""
#: ../src\update\utils.py:33
msgid "%d hours, "
msgstr ""
#: ../src\update\utils.py:35
msgid "%d minute, "
msgstr ""
#: ../src\update\utils.py:37
msgid "%d minutes, "
msgstr ""
#: ../src\update\utils.py:39
msgid "%s second"
msgstr ""
#: ../src\update\utils.py:41
msgid "%s seconds"
msgstr ""
#: ../src\update\wxUpdater.py:10
msgid "New version for %s"
msgstr ""
#: ../src\update\wxUpdater.py:10
msgid ""
"There's a new %s version available. Would you like to download it now?\n"
"\n"
" %s version: %s\n"
"\n"
"Changes:\n"
"%s"
msgstr ""
#: ../src\update\wxUpdater.py:17
msgid "Download in Progress"
msgstr ""
#: ../src\update\wxUpdater.py:17
msgid "Downloading the new version..."
msgstr ""
#: ../src\update\wxUpdater.py:27
msgid "Updating... %s of %s"
msgstr ""
#: ../src\update\wxUpdater.py:30
msgid "Done!"
msgstr ""
#: ../src\update\wxUpdater.py:30
msgid "The update has been downloaded and installed successfully. Press OK to continue."
msgstr ""
#: ../src\wxUI\configuration.py:9
msgid "Output device"
msgstr ""
#: ../src\wxUI\configuration.py:27
msgid "General"
msgstr ""
#: ../src\wxUI\configuration.py:31
msgid "Services"
msgstr ""
#: ../src\wxUI\configuration.py:34
msgid "Save"
msgstr ""
#: ../src\wxUI\configuration.py:36
msgid "Close"
msgstr ""
#: ../src\wxUI\mainWindow.py:16
msgid "Application"
msgstr ""
#: ../src\wxUI\mainWindow.py:19 ../src\wxUI\mainWindow.py:69
msgid "Stop"
msgstr ""
#: ../src\wxUI\mainWindow.py:20 ../src\wxUI\mainWindow.py:67
msgid "Previous"
msgstr ""
#: ../src\wxUI\mainWindow.py:21 ../src\wxUI\mainWindow.py:70
msgid "Next"
msgstr ""
#: ../src\wxUI\mainWindow.py:22
msgid "Shuffle"
msgstr ""
#: ../src\wxUI\mainWindow.py:23
msgid "Volume down"
msgstr ""
#: ../src\wxUI\mainWindow.py:24
msgid "Volume up"
msgstr ""
#: ../src\wxUI\mainWindow.py:25
msgid "Mute"
msgstr ""
#: ../src\wxUI\mainWindow.py:27
msgid "About {0}"
msgstr ""
#: ../src\wxUI\mainWindow.py:28
msgid "Check for updates"
msgstr ""
#: ../src\wxUI\mainWindow.py:29
msgid "What's new in this version?"
msgstr ""
#: ../src\wxUI\mainWindow.py:30
msgid "Visit website"
msgstr ""
#: ../src\wxUI\mainWindow.py:32
msgid "Player"
msgstr ""
#: ../src\wxUI\mainWindow.py:33
msgid "Help"
msgstr ""
#: ../src\wxUI\mainWindow.py:43
msgid "search"
msgstr ""
#: ../src\wxUI\mainWindow.py:48
msgid "Search in"
msgstr ""
#: ../src\wxUI\mainWindow.py:51
msgid "Search"
msgstr ""
#: ../src\wxUI\mainWindow.py:55
msgid "Results"
msgstr ""
#: ../src\wxUI\mainWindow.py:61
msgid "Position"
msgstr ""
#: ../src\wxUI\mainWindow.py:64
msgid "Volume"
msgstr ""
#: ../src\wxUI\mainWindow.py:114
msgid "Audio Files(*.mp3)|*.mp3"
msgstr ""
#: ../src\wxUI\mainWindow.py:114
msgid "Save this file"
msgstr ""
#: ../src\wxUI\menus.py:8
msgid "Play/Pause"
msgstr ""
#: ../src\wxUI\menus.py:9
msgid "Download"
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.0\n"

View File

@ -6,10 +6,7 @@ import sys
def create_archive():
os.chdir("..\\src")
print("Creating zip archive...")
if sys.version[0] == "3":
folder = "dist/main"
else:
folder = "dist"
folder = "dist"
shutil.make_archive("music_dl", "zip", folder)
# if os.path.exists("dist"):
# shutil.rmtree("dist")

80
scripts/upload.py Normal file
View 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()

View File

@ -1,14 +1,24 @@
[main]
volume = integer(default=50)
language = string(default="system")
output_device = string(default="")
output_device = string(default="Default")
[services]
[[tidal]]
enabled = boolean(default=True)
username = string(default="")
password = string(default="")
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)
@ -16,4 +26,4 @@ max_results = integer(default=20)
transcode = boolean(default=True)
[[zaycev]]
enabled = boolean(default=True)
enabled = boolean(default=False)

View File

@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
import sys
python_version = int(sys.version[0])
name = "MusicDL"
version = "0.6"
author = "Manuel Cortéz"
authorEmail = "manuel@manuelcortez.net"
copyright = "Copyright (C) 2019, Manuel Cortez"
author = "MCV Software"
authorEmail = "info@mcvsoftware.com"
copyright = "Copyright (C) 2019-2022, MCV Software"
description = name+_(u" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net.")
url = "https://manuelcortez.net/music_dl"
update_url = "https://manuelcortez.net/music_dl/update"
url = "https://mcvsoftware.com/music_dl"
# The short name will be used for detecting translation files. See languageHandler for more details.
short_name = "musicdl"
translators = [_(u"Manuel Cortez (Spanish)"), _("Valeria K (Russian)"), ]
translators = [_(u"Manuel Cortez (Spanish)")]
bts_name = "music_dl"
bts_access_token = "fe3j2ijirvevv9"
bts_url = "https://issues.manuelcortez.net"
bts_url = "https://issues.manuelcortez.net"
update_url = "https://files.mcvsoftware.com/music_dl/update/latest.json"
version = "2020.07.23"

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import sys
if sys.version[0] == "3":
raise ImportError()
raise ImportError()
import os
import paths
def get():
return os.path.join(paths.app_path(), "cacerts.txt")
return os.path.join(paths.app_path(), "cacerts.txt")

View File

@ -12,7 +12,6 @@ MAINSPEC = "app-configuration.defaults"
app = None
def setup ():
global app
log.debug("Loading global app settings...")
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))
global app
log.debug("Loading global app settings...")
app = config_utils.load_config(os.path.join(storage.data_directory, MAINFILE), os.path.join(paths.app_path(), MAINSPEC))

View File

@ -8,66 +8,66 @@ class ConfigLoadError(Exception): pass
def load_config(config_path, configspec_path=None, *args, **kwargs):
# if os.path.exists(config_path):
# clean_config(config_path)
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
try:
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
except ParseError:
raise ConfigLoadError("Unable to load %r" % config_path)
validator = Validator()
validated = config.validate(validator, copy=True)
if validated == True:
config.write()
return config
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
try:
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
except ParseError:
raise ConfigLoadError("Unable to load %r" % config_path)
validator = Validator()
validated = config.validate(validator, copy=True)
if validated == True:
config.write()
return config
def is_blank(arg):
"Check if a line is blank."
for c in arg:
if c not in string.whitespace:
return False
return True
"Check if a line is blank."
for c in arg:
if c not in string.whitespace:
return False
return True
def get_keys(path):
"Gets the keys of a configobj config file."
res=[]
fin=open(path)
for line in fin:
if not is_blank(line):
res.append(line[0:line.find('=')].strip())
fin.close()
return res
"Gets the keys of a configobj config file."
res=[]
fin=open(path)
for line in fin:
if not is_blank(line):
res.append(line[0:line.find('=')].strip())
fin.close()
return res
def hist(keys):
"Generates a histogram of an iterable."
res={}
for k in keys:
res[k]=res.setdefault(k,0)+1
return res
"Generates a histogram of an iterable."
res={}
for k in keys:
res[k]=res.setdefault(k,0)+1
return res
def find_problems(hist):
"Takes a histogram and returns a list of items occurring more than once."
res=[]
for k,v in hist.items():
if v>1:
res.append(k)
return res
"Takes a histogram and returns a list of items occurring more than once."
res=[]
for k,v in hist.items():
if v>1:
res.append(k)
return res
def clean_config(path):
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
orig=[]
cleaned=[]
fin=open(path)
for line in fin:
orig.append(line)
fin.close()
for p in find_problems(hist(get_keys(path))):
for o in orig:
o.strip()
if p not in o:
cleaned.append(o)
if len(cleaned) != 0:
cam=open(path,'w')
for c in cleaned:
cam.write(c)
cam.close()
return True
else:
return False
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
orig=[]
cleaned=[]
fin=open(path)
for line in fin:
orig.append(line)
fin.close()
for p in find_problems(hist(get_keys(path))):
for o in orig:
o.strip()
if p not in o:
cleaned.append(o)
if len(cleaned) != 0:
cam=open(path,'w')
for c in cleaned:
cam.write(c)
cam.close()
return True
else:
return False

View File

@ -1,52 +1,44 @@
# -*- coding: utf-8 -*-
import config
from utils import get_extractors
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 __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["name"] 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 str(i["id"]) == current_output_device:
self.view.set_value("general", "output_device", i["name"])
break
self.view.realize()
extractors = get_extractors(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 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")
selected_device_id = None
for i in self.output_devices:
# Vlc returns everything as bytes object whereas WX works with string objects, so I need to convert the wx returned string to bytes before
# Otherwise the comparison will be false.
# toDo: Check if utf-8 would be enough or we'd have to use the fylesystem encode for handling this.
if i["name"] == bytes(selected_output_device, "utf-8"):
selected_device_id = i["id"]
break
if config.app["main"]["output_device"] != selected_device_id:
config.app["main"]["output_device"] = selected_device_id
player.player.set_output_device(config.app["main"]["output_device"])
for i in range(0, self.view.notebook.GetPageCount()):
page = self.view.notebook.GetPage(i)
if hasattr(page, "save"):
page.save()
config.app.write()
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()

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
""" main controller for MusicDL"""
from __future__ import unicode_literals # at top of module
import types
import webbrowser
import wx
@ -13,259 +12,262 @@ from pubsub import pub
from issueReporter import issueReporter
from wxUI import mainWindow, menus
from update import updater
from utils import get_extractors
from utils import get_services
from . import player, configuration
log = logging.getLogger("controller.main")
class Controller(object):
def __init__(self):
super(Controller, self).__init__()
log.debug("Starting main controller...")
# Setting up the player object
player.setup()
# Get main window
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_extractors()])
log.debug("Main window created")
self.window.change_status(_(u"Ready"))
# Here we will save results for searches as song objects.
self.results = []
self.connect_events()
self.timer = wx.Timer(self.window)
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
self.timer.Start(75)
self.window.vol_slider.SetValue(player.player.volume)
# Shows window.
utils.call_threaded(updater.do_update)
log.debug("Music DL is ready")
self.window.Show()
def __init__(self):
super(Controller, self).__init__()
log.debug("Starting main controller...")
# Setting up the player object
player.setup()
# Get main window
self.window = mainWindow.mainWindow(extractors=[i.interface.name for i in get_services()])
log.debug("Main window created")
self.window.change_status(_(u"Ready"))
# Here we will save results for searches as song objects.
self.results = []
self.connect_events()
self.timer = wx.Timer(self.window)
self.window.Bind(wx.EVT_TIMER, self.on_timer, self.timer)
self.timer.Start(75)
self.window.vol_slider.SetValue(player.player.volume)
# Shows window.
utils.call_threaded(updater.do_update)
log.debug("Music DL is ready")
self.window.Show()
def get_status_info(self):
""" Formatting string for status bar messages """
if len(self.results) > 0:
results = _(u"Showing {0} results.").format(len(self.results))
else:
results = u""
if player.player.shuffle:
shuffle = _(u"Shuffle on")
else:
shuffle = u""
final = u"{0} {1}".format(results, shuffle)
return final
def get_status_info(self):
""" Formatting string for status bar messages """
if len(self.results) > 0:
results = _(u"Showing {0} results.").format(len(self.results))
else:
results = u""
if player.player.shuffle:
shuffle = _(u"Shuffle on")
else:
shuffle = u""
final = u"{0} {1}".format(results, shuffle)
return final
def connect_events(self):
""" connects all widgets to their corresponding events."""
log.debug("Binding events...")
widgetUtils.connect_event(self.window.search, widgetUtils.BUTTON_PRESSED, self.on_search)
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_down, menuitem=self.window.player_volume_down)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_up, menuitem=self.window.player_volume_up)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_mute, menuitem=self.window.player_mute)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_shuffle, menuitem=self.window.player_shuffle)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_check_for_updates, menuitem=self.window.check_for_updates)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_changelog, menuitem=self.window.changelog)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_website, menuitem=self.window.website)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report)
widgetUtils.connect_event(self.window.previous, widgetUtils.BUTTON_PRESSED, self.on_previous)
widgetUtils.connect_event(self.window.play, widgetUtils.BUTTON_PRESSED, self.on_play_pause)
widgetUtils.connect_event(self.window.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
widgetUtils.connect_event(self.window.next, widgetUtils.BUTTON_PRESSED, self.on_next)
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_set_volume, self.window.vol_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_set_volume, self.window.vol_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_time_change, self.window.time_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_time_change, self.window.time_slider)
self.window.list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_play)
self.window.list.Bind(wx.EVT_CONTEXT_MENU, self.on_context)
self.window.Bind(wx.EVT_CLOSE, self.on_close)
pub.subscribe(self.change_status, "change_status")
pub.subscribe(self.on_download_finished, "download_finished")
pub.subscribe(self.on_notify, "notify")
pub.subscribe(self.on_update_progress, "update-progress")
def connect_events(self):
""" connects all widgets to their corresponding events."""
log.debug("Binding events...")
widgetUtils.connect_event(self.window.search, widgetUtils.BUTTON_PRESSED, self.on_search)
widgetUtils.connect_event(self.window.list, widgetUtils.LISTBOX_ITEM_ACTIVATED, self.on_activated)
widgetUtils.connect_event(self.window.list, widgetUtils.KEYPRESS, self.on_keypress)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_play, menuitem=self.window.player_play)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_settings, menuitem=self.window.settings)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_next, menuitem=self.window.player_next)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_previous, menuitem=self.window.player_previous)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_stop, menuitem=self.window.player_stop)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_down, menuitem=self.window.player_volume_down)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_volume_up, menuitem=self.window.player_volume_up)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_mute, menuitem=self.window.player_mute)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_shuffle, menuitem=self.window.player_shuffle)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_check_for_updates, menuitem=self.window.check_for_updates)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_changelog, menuitem=self.window.changelog)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_visit_website, menuitem=self.window.website)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report)
widgetUtils.connect_event(self.window.previous, widgetUtils.BUTTON_PRESSED, self.on_previous)
widgetUtils.connect_event(self.window.play, widgetUtils.BUTTON_PRESSED, self.on_play_pause)
widgetUtils.connect_event(self.window.stop, widgetUtils.BUTTON_PRESSED, self.on_stop)
widgetUtils.connect_event(self.window.next, widgetUtils.BUTTON_PRESSED, self.on_next)
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_set_volume, self.window.vol_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_set_volume, self.window.vol_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_time_change, self.window.time_slider)
self.window.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_time_change, self.window.time_slider)
self.window.list.Bind(wx.EVT_LISTBOX_DCLICK, self.on_play)
self.window.list.Bind(wx.EVT_CONTEXT_MENU, self.on_context)
self.window.Bind(wx.EVT_CLOSE, self.on_close)
pub.subscribe(self.change_status, "change_status")
pub.subscribe(self.on_download_finished, "download_finished")
pub.subscribe(self.on_notify, "notify")
pub.subscribe(self.on_update_progress, "update-progress")
# Event functions. These functions will call other functions in a thread and are bound to widget events.
# 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_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_settings(self, *args, **kwargs):
settings = configuration.configuration()
self.reload_extractors()
def on_search(self, *args, **kwargs):
text = self.window.get_text()
if text == "":
return
extractor = self.window.extractor.GetValue()
self.change_status(_(u"Searching {0}... ").format(text))
utils.call_threaded(self.search, text=text, extractor=extractor)
def on_search(self, *args, **kwargs):
text = self.window.get_text()
if text == "":
return
extractor = self.window.extractor.GetValue()
self.change_status(_(u"Searching {0}... ").format(text))
utils.call_threaded(self.search, text=text, extractor=extractor)
def on_activated(self, *args, **kwargs):
self.on_play()
def on_activated(self, *args, **kwargs):
self.on_play()
def on_keypress(self, ev):
if ev.GetKeyCode() == wx.WXK_RETURN:
return self.on_play()
elif ev.GetKeyCode() == wx.WXK_SPACE:
return self.on_play_pause()
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
position = player.player.player.get_time()
if position > 5000:
player.player.player.set_time(position-5000)
else:
player.player.player.set_time(0)
return
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
position = player.player.player.get_time()
player.player.player.set_time(position+5000)
return
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
return self.on_volume_up()
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
return self.on_volume_down()
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
return self.on_previous()
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
return self.on_next()
ev.Skip()
def on_keypress(self, ev):
if ev.GetKeyCode() == wx.WXK_RETURN:
return self.on_play()
elif ev.GetKeyCode() == wx.WXK_SPACE:
return self.on_play_pause()
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.ShiftDown():
position = player.player.player.get_position()
if position > 5000:
player.player.player.set_position(position-5000)
else:
player.player.player.set_position(0)
return
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.ShiftDown():
position = player.player.player.get_position()
player.player.player.set_position(position+5000)
return
elif ev.GetKeyCode() == wx.WXK_UP and ev.ControlDown():
return self.on_volume_up()
elif ev.GetKeyCode() == wx.WXK_DOWN and ev.ControlDown():
return self.on_volume_down()
elif ev.GetKeyCode() == wx.WXK_LEFT and ev.AltDown():
return self.on_previous()
elif ev.GetKeyCode() == wx.WXK_RIGHT and ev.AltDown():
return self.on_next()
ev.Skip()
def on_play_pause(self, *args, **kwargs):
if player.player.player.is_playing() == 1:
self.window.play.SetLabel(_(u"Play"))
return player.player.pause()
else:
self.window.play.SetLabel(_(u"Pause"))
return player.player.player.play()
def on_play_pause(self, *args, **kwargs):
if player.player.player.playback_time != None and player.player.player.pause == False:
self.window.play.SetLabel(_("Play"))
return player.player.pause()
else:
self.window.play.SetLabel(_("Pause"))
player.player.player.pause = False
def on_next(self, *args, **kwargs):
return utils.call_threaded(player.player.next)
def on_next(self, *args, **kwargs):
return utils.call_threaded(player.player.next)
def on_previous(self, *args, **kwargs):
return utils.call_threaded(player.player.previous)
def on_previous(self, *args, **kwargs):
return utils.call_threaded(player.player.previous)
def on_play(self, *args, **kwargs):
items = self.results[::]
playing_item = self.window.get_item()
self.window.play.SetLabel(_(u"Pause"))
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
def on_play(self, *args, **kwargs):
items = self.results[::]
playing_item = self.window.get_item()
self.window.play.SetLabel(_(u"Pause"))
return utils.call_threaded(player.player.play_all, items, playing=playing_item, shuffle=self.window.player_shuffle.IsChecked())
def on_stop(self, *args, **kwargs):
player.player.stop()
self.window.play.SetLabel(_(u"Play"))
def on_stop(self, *args, **kwargs):
player.player.stop()
self.window.play.SetLabel(_(u"Play"))
def on_volume_down(self, *args, **kwargs):
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
self.on_set_volume()
def on_volume_down(self, *args, **kwargs):
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()-5)
self.on_set_volume()
def on_volume_up(self, *args, **kwargs):
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
self.on_set_volume()
def on_volume_up(self, *args, **kwargs):
self.window.vol_slider.SetValue(self.window.vol_slider.GetValue()+5)
self.on_set_volume()
def on_mute(self, *args, **kwargs):
self.window.vol_slider.SetValue(0)
self.on_set_volume()
def on_mute(self, *args, **kwargs):
self.window.vol_slider.SetValue(0)
self.on_set_volume()
def on_shuffle(self, *args, **kwargs):
player.player.shuffle = self.window.player_shuffle.IsChecked()
def on_shuffle(self, *args, **kwargs):
player.player.shuffle = self.window.player_shuffle.IsChecked()
def on_context(self, *args, **kwargs):
item = self.window.get_item()
if item == -1:
return wx.Bell()
menu = menus.contextMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
self.window.PopupMenu(menu, wx.GetMousePosition())
menu.Destroy()
def on_context(self, *args, **kwargs):
item = self.window.get_item()
if item == -1:
return wx.Bell()
menu = menus.contextMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_play, menuitem=menu.play)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.on_download, menuitem=menu.download)
self.window.PopupMenu(menu, wx.GetMousePosition())
menu.Destroy()
def on_download(self, *args, **kwargs):
item = self.results[self.window.get_item()]
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format())
if item.download_url == "":
item.get_download_url()
path = self.window.get_destination_path(f)
if path != None:
log.debug("User has requested the following path: {0}".format(path,))
if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder
utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format())
else:
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
utils.call_threaded(utils.download_file, item.download_url, path)
def on_download(self, *args, **kwargs):
item = self.results[self.window.get_item()]
if item.download_url == "":
item.get_download_url()
log.debug("Starting requested download: {0} (using extractor: {1})".format(item.title, self.extractor.name))
f = "{item_name}.{item_extension}".format(item_name=item.format_track(), item_extension=item.extractor.get_file_format())
path = self.window.get_destination_path(utils.safe_filename(f))
if path != None:
log.debug("User has requested the following path: {0}".format(path,))
if self.extractor.transcoder_enabled() == True: # Send download to vlc based transcoder
utils.call_threaded(player.player.transcode_audio, item, path, _format=item.extractor.get_file_format(), metadata=item.get_metadata())
else:
log.debug("downloading %s URL to %s filename" % (item.download_url, path,))
utils.call_threaded(utils.download_file, item.download_url, path, metadata=item.get_metadata())
def on_set_volume(self, *args, **kwargs):
volume = self.window.vol_slider.GetValue()
player.player.volume = volume
def on_set_volume(self, *args, **kwargs):
volume = self.window.vol_slider.GetValue()
player.player.volume = volume
def on_time_change(self, event, *args, **kwargs):
p = event.GetPosition()
player.player.player.set_position(p/100.0)
event.Skip()
def on_time_change(self, event, *args, **kwargs):
p = event.GetPosition()
if player.player.player != None:
progress = int((player.player.player.get_length()/100)*p)
player.player.player.set_position(progress)
event.Skip()
def on_timer(self, *args, **kwargs):
if not self.window.time_slider.HasFocus():
progress = player.player.player.get_position()*100
self.window.time_slider.SetValue(progress)
def on_timer(self, *args, **kwargs):
if not self.window.time_slider.HasFocus():
if player.player.player.playback_time != None:
progress = (player.player.player.playback_time/(player.player.player.playback_time+player.player.player.playtime_remaining))*100
self.window.time_slider.SetValue(int(progress))
def on_close(self, event):
log.debug("Exiting...")
self.timer.Stop()
pub.unsubscribe(self.on_download_finished, "download_finished")
config.app.write()
event.Skip()
widgetUtils.exit_application()
def on_close(self, event):
log.debug("Exiting...")
self.timer.Stop()
pub.unsubscribe(self.on_download_finished, "download_finished")
config.app.write()
event.Skip()
widgetUtils.exit_application()
def change_status(self, status):
""" Function used for changing the status bar from outside the main controller module."""
self.window.change_status(u"{0} {1}".format(status, self.get_status_info()))
def change_status(self, status):
""" Function used for changing the status bar from outside the main controller module."""
self.window.change_status(u"{0} {1}".format(status, self.get_status_info()))
def on_visit_website(self, *args, **kwargs):
webbrowser.open_new_tab(application.url)
def on_visit_website(self, *args, **kwargs):
webbrowser.open_new_tab(application.url)
def on_report_error(self, *args, **kwargs):
r = issueReporter.reportBug()
def on_report_error(self, *args, **kwargs):
r = issueReporter.reportBug()
def on_visit_changelog(self, *args, **kwargs):
webbrowser.open_new_tab(application.url+"/news")
def on_visit_changelog(self, *args, **kwargs):
webbrowser.open_new_tab(application.url+"/news")
def on_check_for_updates(self, *args, **kwargs):
utils.call_threaded(updater.do_update)
def on_check_for_updates(self, *args, **kwargs):
utils.call_threaded(updater.do_update)
def on_download_finished(self, file):
title = "MusicDL"
msg = _(u"File downloaded: {0}").format(file,)
self.window.notify(title, msg)
def on_download_finished(self, file):
title = "MusicDL"
msg = _(u"File downloaded: {0}").format(file,)
self.window.notify(title, msg)
def on_notify(self, title, message):
self.window.notify(title, message)
def on_notify(self, title, message):
self.window.notify(title, message)
# real functions. These functions really are doing the work.
def search(self, text, extractor, *args, **kwargs):
extractors = get_extractors()
for i in extractors:
if extractor == i.interface.name:
self.extractor = i.interface()
break
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
wx.CallAfter(self.window.list.Clear)
self.extractor.search(text)
self.results = self.extractor.results
for i in self.results:
wx.CallAfter(self.window.list.Append, i.format_track())
if len(self.results) == 0:
wx.CallAfter(self.change_status, _(u"No results found. "))
else:
wx.CallAfter(self.change_status, u"")
wx.CallAfter(self.window.list.SetFocus)
# real functions. These functions really are doing the work.
def search(self, text, extractor, *args, **kwargs):
extractors = get_services()
for i in extractors:
if extractor == i.interface.name:
self.extractor = i.interface()
break
log.debug("Started search for {0} (selected extractor: {1})".format(text, self.extractor.name))
wx.CallAfter(self.window.list.Clear)
self.extractor.search(text)
self.results = self.extractor.results
for i in self.results:
wx.CallAfter(self.window.list.Append, i.format_track())
if len(self.results) == 0:
wx.CallAfter(self.change_status, _(u"No results found. "))
else:
wx.CallAfter(self.change_status, u"")
wx.CallAfter(self.window.list.SetFocus)
def reload_extractors(self):
extractors = [i.interface.name for i in get_extractors()]
self.window.extractor.SetItems(extractors)
self.window.extractor.SetValue(extractors[0])
def reload_extractors(self):
extractors = [i.interface.name for i in get_services()]
self.window.extractor.SetItems(extractors)
self.window.extractor.SetValue(extractors[0])

View File

@ -1,169 +1,112 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals # at top of module
import os
import random
import vlc
import logging
import config
import time
import mpv
from pubsub import pub
from utils import call_threaded
player = None
log = logging.getLogger("controller.player")
def setup():
global player
if player == None:
player = audioPlayer()
global player
if player == None:
player = audioPlayer()
class audioPlayer(object):
def __init__(self):
self.is_playing = False
self.vol = config.app["main"]["volume"]
self.is_working = False
self.queue = []
self.stopped = True
self.queue_pos = 0
self.shuffle = False
self.instance = vlc.Instance()
self.player = self.instance.media_player_new()
log.debug("Media player instantiated.")
self.event_manager = self.player.event_manager()
self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback)
self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
log.debug("Bound media playback events.")
# configure output device
self.set_output_device(config.app["main"]["output_device"])
def __init__(self):
self.is_playing = False
self.vol = config.app["main"]["volume"]
self.is_working = False
self.queue = []
self.stopped = True
self.queue_pos = 0
self.shuffle = False
self.player = mpv.MPV()
def get_output_devices(self):
""" Retrieve enabled output devices so we can switch or use those later. """
log.debug("Retrieving output devices...")
devices = []
mods = self.player.audio_output_device_enum()
if mods:
mod = mods
while mod:
mod = mod.contents
devices.append(dict(id=mod.device, name=mod.description))
mod = mod.next
vlc.libvlc_audio_output_device_list_release(mods)
return devices
# Fires at the end of every file and attempts to play the next one.
@self.player.event_callback('end-file')
def handle_end_idle(event):
if event.as_dict()["reason"] == b"aborted" or event.as_dict()["reason"] == b"stop":
return
log.debug("Reached end of file stream.")
if len(self.queue) > 1:
log.debug("Requesting next item...")
self.next()
def set_output_device(self, device_id):
""" Set Output device to be ued in LibVLC"""
log.debug("Setting output audio device to {device}...".format(device=device_id,))
self.player.audio_output_device_set(None, device_id)
def get_output_devices(self):
""" Retrieve enabled output devices so we can switch or use those later. """
return None
def play(self, item):
self.stopped = True
if self.is_working == False:
self.is_working = True
if item.download_url == "":
item.get_download_url()
log.debug("playing {0}...".format(item.download_url,))
self.stream_new = self.instance.media_new(item.download_url)
self.player.set_media(self.stream_new)
if self.player.play() == -1:
log.debug("Error when playing the file {0}".format(item.title,))
pub.sendMessage("change_status", status=_("Error playing {0}. {1}.").format(item.title, e.description))
self.stopped = True
self.is_working = False
self.next()
return
self.player.audio_set_volume(self.vol)
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
self.stopped = False
self.is_working = False
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 next(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos < len(self.queue)-1:
self.queue_pos += 1
else:
self.queue_pos = 0
self.play(self.queue[self.queue_pos])
def play(self, item):
self.stopped = True
if self.is_working == False:
self.is_working = True
if item.download_url == "":
item.get_download_url()
log.debug("playing {0}...".format(item.download_url,))
self.player.play(item.download_url)
self.player.volume = self.vol
pub.sendMessage("change_status", status=_("Playing {0}.").format(item.title))
self.stopped = False
self.is_working = False
def previous(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos > 0:
self.queue_pos -= 1
else:
self.queue_pos = len(self.queue)-1
self.play(self.queue[self.queue_pos])
def next(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos < len(self.queue)-1:
self.queue_pos += 1
else:
self.queue_pos = 0
self.play(self.queue[self.queue_pos])
def stop(self):
self.player.stop()
self.stopped = True
def previous(self):
if len(self.queue) > 0:
if self.shuffle:
self.queue_pos = random.randint(0, len(self.queue)-1)
else:
if self.queue_pos > 0:
self.queue_pos -= 1
else:
self.queue_pos = len(self.queue)-1
self.play(self.queue[self.queue_pos])
def pause(self):
self.player.pause()
if self.stopped == True:
self.stopped = False
else:
self.stopped = True
def stop(self):
self.player.stop()
self.stopped = True
@property
def volume(self):
return self.vol
def pause(self):
self.player.pause = True
if self.stopped == True:
self.stopped = False
else:
self.stopped = True
@volume.setter
def volume(self, vol):
if vol <= 100 and vol >= 0:
config.app["main"]["volume"] = vol
self.vol = vol
self.player.audio_set_volume(self.vol)
@property
def volume(self):
return self.vol
def play_all(self, list_of_items, playing=0, shuffle=False):
if list_of_items != self.queue:
self.queue = list_of_items
self.shuffle = shuffle
self.queue_pos = playing
self.play(self.queue[self.queue_pos])
@volume.setter
def volume(self, vol):
if vol <= 100 and vol >= 0:
config.app["main"]["volume"] = vol
self.vol = vol
if self.player != None:
self.player.volume = self.vol
def end_callback(self, event, *args, **kwargs):
#https://github.com/ZeBobo5/Vlc.DotNet/issues/4
call_threaded(self.next)
def transcode_audio(self, item, path, _format="mp3", bitrate=320):
""" 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=%s,ab=%d}:file{mux=raw,dst=\"%s\"}"% (_format, bitrate, temporary_path,)])
transcoder = transcoding_instance.media_player_new()
transcoder.set_mrl(item.download_url)
pub.sendMessage("change_status", status=_(u"Downloading {0}.").format(item.title,))
media = transcoder.get_media()
transcoder.play()
while True:
state = media.get_state()
pub.sendMessage("change_status", status=_("Downloading {0} ({1}%).").format(item.title, int(transcoder.get_position()*100)))
pub.sendMessage("update-progress", value=int(transcoder.get_position()*100))
if str(state) == 'State.Ended':
break
elif str(state) == 'state.error':
os.remove(temporary_path)
break
transcoder.release()
os.rename(temporary_path, path)
log.debug("Download finished sucsessfully.")
pub.sendMessage("download_finished", file=os.path.basename(path))
def playback_error(self, event):
pub.sendMessage("notify", title=_("Error"), message=_("There was an error while trying to access the file you have requested."))
def __del__(self):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEndReached)
if hasattr(self, "event_manager"):
self.event_manager.event_detach(vlc.EventType.MediaPlayerEncounteredError, self.playback_error)
def play_all(self, list_of_items, playing=0, shuffle=False):
if list_of_items != self.queue:
self.queue = list_of_items
self.shuffle = shuffle
self.queue_pos = playing
self.play(self.queue[self.queue_pos])

View File

@ -1,70 +0,0 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
""" Base components useful for all other extractors. """
import logging
import wx
import config
log = logging.getLogger("extractors.config")
class baseInterface(object):
name = "base"
enabled = False
needs_transcode = False
results = []
def __init__(self):
super(baseInterface, self).__init__()
log.debug("started extraction service for {0}".format(self.name,))
def search(self, text, *args, **kwargs):
raise NotImplementedError()
def get_download_url(self, url):
raise NotImplementedError()
def format_track(self, item):
raise NotImplementedError()
def get_file_format(self):
return "mp3"
def transcoder_enabled(self):
return False
class song(object):
""" Represents a song in all services. Data will be filled by the service itself"""
def __init__(self, extractor):
self.extractor = extractor
self.bitrate = 0
self.title = ""
self.artist = ""
self.duration = ""
self.size = 0
self.url = ""
self.download_url = ""
self.info = None
def format_track(self):
return self.extractor.format_track(self)
def get_download_url(self):
self.download_url = self.extractor.get_download_url(self.url)
class baseSettings(wx.Panel):
config_section = "base"
def __init__(self, *args, **kwargs):
super(baseSettings, self).__init__(*args, **kwargs)
self.map = []
def save(self):
for i in self.map:
config.app["services"][self.config_section][i[0]] = i[1].GetValue()
def load(self):
for i in self.map:
if i[0] in config.app["services"][self.config_section]:
i[1].SetValue(config.app["services"][self.config_section][i[0]])
else:
log.error("No key available: {key} on extractor {extractor}".format(key=i[0], extractor=self.config_section))

View File

@ -1,168 +0,0 @@
# -*- coding: utf-8 -*-
import logging
import webbrowser
import wx
import tidalapi
import config
from update.utils import seconds_to_string
from .import base
log = logging.getLogger("extractors.tidal.com")
class interface(base.baseInterface):
name = "tidal"
enabled = config.app["services"]["tidal"].get("enabled")
# This should not be enabled if credentials are not in config.
if config.app["services"]["tidal"]["username"] == "" or config.app["services"]["tidal"]["password"] == "":
enabled = False
def __init__(self):
super(interface, self).__init__()
self.setup()
def setup(self):
# Assign quality or switch to high if not specified/not found.
if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]):
quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"])
else:
quality = tidalapi.Quality.high
_config = tidalapi.Config(quality=quality)
username = config.app["services"]["tidal"]["username"]
password = config.app["services"]["tidal"]["password"]
log.debug("Using quality: %s" % (quality,))
self.session = tidalapi.Session(config=_config)
self.session.login(username=username, password=password)
def get_file_format(self):
if config.app["services"]["tidal"]["quality"] == "lossless":
self.file_extension = "flac"
else:
self.file_extension = "mp3"
return self.file_extension
def transcoder_enabled(self):
if config.app["services"]["tidal"]["quality"] == "lossless":
return False
else:
return True
def search(self, text, page=1):
if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from Tidal...")
fieldtypes = ["artist", "album", "playlist"]
field = "track"
for i in fieldtypes:
if text.startswith(i+"://"):
field = i
text = text.replace(i+"://", "")
log.debug("Searching for %s..." % (field))
search_response = self.session.search(value=text, field=field)
self.results = []
if field == "track":
data = search_response.tracks
elif field == "artist":
data = []
artist = search_response.artists[0].id
albums = self.session.get_artist_albums(artist)
for album in albums:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
compilations = self.session.get_artist_albums_other(artist)
for album in compilations:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
singles = self.session.get_artist_albums_ep_singles(artist)
for album in singles:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
for search_result in data:
s = base.song(self)
s.title = search_result.name
s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id
s.info = search_result
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def get_download_url(self, url):
url = self.session.get_media_url(url)
if url.startswith("https://") or url.startswith("http://") == False:
url = "rtmp://"+url
return url
def format_track(self, item):
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)
class settings(base.baseSettings):
name = _("Tidal")
config_section = "tidal"
def get_quality_list(self):
results = dict(low=_("Low"), high=_("High"), lossless=_("Lossless"))
return results
def get_quality_value(self, *args, **kwargs):
q = self.get_quality_list()
for i in q.keys():
if q.get(i) == self.quality.GetStringSelection():
return i
def set_quality_value(self, value, *args, **kwargs):
q = self.get_quality_list()
for i in q.keys():
if i == value:
self.quality.SetStringSelection(q.get(i))
break
def __init__(self, parent):
super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5)
username = wx.StaticText(self, wx.NewId(), _("Tidal username or email address"))
self.username = wx.TextCtrl(self, wx.NewId())
usernamebox = wx.BoxSizer(wx.HORIZONTAL)
usernamebox.Add(username, 0, wx.ALL, 5)
usernamebox.Add(self.username, 0, wx.ALL, 5)
sizer.Add(usernamebox, 0, wx.ALL, 5)
self.map.append(("username", self.username))
password = wx.StaticText(self, wx.NewId(), _("Password"))
self.password = wx.TextCtrl(self, wx.NewId(), style=wx.TE_PASSWORD)
passwordbox = wx.BoxSizer(wx.HORIZONTAL)
passwordbox.Add(password, 0, wx.ALL, 5)
passwordbox.Add(self.password, 0, wx.ALL, 5)
sizer.Add(passwordbox, 0, wx.ALL, 5)
self.map.append(("password", self.password))
self.get_account = wx.Button(self, wx.NewId(), _("You can subscribe for a tidal account here"))
self.get_account.Bind(wx.EVT_BUTTON, self.on_get_account)
sizer.Add(self.get_account, 0, wx.ALL, 5)
quality = wx.StaticText(self, wx.NewId(), _("Audio quality"))
self.quality = wx.ComboBox(self, wx.NewId(), choices=[i for i in self.get_quality_list().values()], value=_("High"), style=wx.CB_READONLY)
qualitybox = wx.BoxSizer(wx.HORIZONTAL)
qualitybox.Add(quality, 0, wx.ALL, 5)
qualitybox.Add(self.quality, 0, wx.ALL, 5)
sizer.Add(qualitybox, 0, wx.ALL, 5)
# Monkeypatch for getting the right quality value here.
self.quality.GetValue = self.get_quality_value
self.quality.SetValue = self.set_quality_value
self.map.append(("quality", self.quality))
self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs):
for i in self.map:
if i[1] != self.enabled:
if self.enabled.GetValue() == True:
i[1].Enable(True)
else:
i[1].Enable(False)
def on_get_account(self, *args, **kwargs):
webbrowser.open_new_tab("https://tidal.com")

View File

@ -1,134 +0,0 @@
# -*- coding: utf-8 -*-
import isodate
import youtube_dl
import logging
import wx
import config
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from update.utils import seconds_to_string
from .import base
DEVELOPER_KEY = "AIzaSyCU_hvZJEjLlAGAnlscquKEkE8l0lVOfn0"
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"
log = logging.getLogger("extractors.youtube.com")
class interface(base.baseInterface):
name = "YouTube"
enabled = config.app["services"]["youtube"].get("enabled")
def search(self, text, page=1):
if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.")
if text.startswith("https") or text.startswith("http"):
return self.search_from_url(text)
type = "video"
max_results = config.app["services"]["youtube"]["max_results"]
log.debug("Retrieving data from Youtube...")
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)
search_response = youtube.search().list(q=text, part="id,snippet", maxResults=max_results, type=type).execute()
self.results = []
ids = []
for search_result in search_response.get("items", []):
if search_result["id"]["kind"] == "youtube#video":
s = base.song(self)
s.title = search_result["snippet"]["title"]
ids.append(search_result["id"]["videoId"])
s.url = "https://www.youtube.com/watch?v="+search_result["id"]["videoId"]
self.results.append(s)
ssr = youtube.videos().list(id=",".join(ids), part="contentDetails", maxResults=1).execute()
for i in range(len(self.results)):
self.results[i].duration = seconds_to_string(isodate.parse_duration(ssr["items"][i]["contentDetails"]["duration"]).total_seconds())
log.debug("{0} results found.".format(len(self.results)))
def search_from_url(self, url):
log.debug("Getting download URL for {0}".format(url,))
if "playlist?list=" in url:
return self.search_from_playlist(url)
ydl = youtube_dl.YoutubeDL({'quiet': True, 'no_warnings': True, 'logger': log, 'prefer-free-formats': True, 'format': 'bestaudio', 'outtmpl': u'%(id)s%(ext)s'})
with ydl:
result = ydl.extract_info(url, download=False)
if 'entries' in result:
videos = result['entries']
else:
videos = [result]
for video in videos:
s = baseFile.song(self)
s.title = video["title"]
s.url = video["webpage_url"] # Cannot use direct URL here cause Youtube URLS expire after a minute.
s.duration = seconds_to_string(video["duration"])
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def search_from_playlist(self, url):
id = url.split("=")[1]
max_results = 50
log.debug("Retrieving data from Youtube...")
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)
search_response = youtube.playlistItems().list(playlistId=id, part="id, status, snippet", maxResults=max_results).execute()
self.results = []
ids = []
for search_result in search_response.get("items", []):
if search_result["status"]["privacyStatus"] != "public":
continue
s = baseFile.song(self)
s.title = search_result["snippet"]["title"]
ids.append(search_result["snippet"]["resourceId"]["videoId"])
s.url = "https://www.youtube.com/watch?v="+search_result["snippet"]["resourceId"]["videoId"]
self.results.append(s)
ssr = youtube.videos().list(id=",".join(ids), part="contentDetails", maxResults=50).execute()
for i in range(len(self.results)):
self.results[i].duration = seconds_to_string(isodate.parse_duration(ssr["items"][i]["contentDetails"]["duration"]).total_seconds())
log.debug("{0} results found.".format(len(self.results)))
def get_download_url(self, url):
log.debug("Getting download URL for {0}".format(url,))
ydl = youtube_dl.YoutubeDL({'quiet': True, 'no_warnings': True, 'logger': log, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'})
with ydl:
result = ydl.extract_info(url, download=False)
if 'entries' in result:
video = result['entries'][0]
else:
video = result
# From here we should extract the first format so it will contain audio only.
log.debug("Download URL: {0}".format(video["formats"][0]["url"],))
return video["formats"][0]["url"]
def format_track(self, item):
return "{0} {1}".format(item.title, item.duration)
def transcoder_enabled(self):
return config.app["services"]["youtube"]["transcode"]
class settings(base.baseSettings):
name = _("Youtube Settings")
config_section = "youtube"
def __init__(self, parent):
super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service"))
self.enabled.Bind(wx.EVT_CHECKBOX, self.on_enabled)
self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5)
max_results_label = wx.StaticText(self, wx.NewId(), _("Max results per page"))
self.max_results = wx.SpinCtrl(self, wx.NewId())
self.max_results.SetRange(1, 50)
max_results_sizer = wx.BoxSizer(wx.HORIZONTAL)
max_results_sizer.Add(max_results_label, 0, wx.ALL, 5)
max_results_sizer.Add(self.max_results, 0, wx.ALL, 5)
self.map.append(("max_results", self.max_results))
# self.transcode = wx.CheckBox(self, wx.NewId(), _("Enable transcode when downloading"))
# self.map.append(("transcode", self.transcode))
# sizer.Add(self.transcode, 0, wx.ALL, 5)
self.SetSizer(sizer)
def on_enabled(self, *args, **kwargs):
for i in self.map:
if i[1] != self.enabled:
if self.enabled.GetValue() == True:
i[1].Enable(True)
else:
i[1].Enable(False)

View File

@ -1,61 +0,0 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import re
import json
import requests
import logging
import wx
import config
from bs4 import BeautifulSoup
from . import base
log = logging.getLogger("extractors.zaycev.net")
class interface(base.baseInterface):
name = "zaycev.net"
enabled = config.app["services"]["zaycev"].get("enabled")
def search(self, text, page=1):
if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.")
site = "http://zaycev.net/search.html?query_search=%s" % (text,)
log.debug("Retrieving data from {0}...".format(site,))
r = requests.get(site)
soup = BeautifulSoup(r.text, 'html.parser')
search_results = soup.find_all("div", {"class": "musicset-track__title track-geo__title"})
self.results = []
for i in search_results:
# The easiest method to get artist and song names is to fetch links. There are only two links per result here.
data = i.find_all("a")
# from here, data[0] contains artist info and data[1] contains info of the retrieved song.
s = base.song(self)
s.title = data[1].text
s.artist = data[0].text
s.url = "http://zaycev.net%s" % (data[1].attrs["href"])
# s.duration = self.hd[i]["duration"]
# s.size = self.hd[i]["size"]
# s.bitrate = self.hd[i]["bitrate"]
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def get_download_url(self, url):
log.debug("Getting download URL for {0}".format(url,))
soups = BeautifulSoup(requests.get(url).text, 'html.parser')
data = json.loads(requests.get('http://zaycev.net' + soups.find('div', {'class':"musicset-track"}).get('data-url')).text)
log.debug("Download URL: {0}".format(data["url"]))
return data["url"]
def format_track(self, item):
return "{0}. {1}. {2}".format(item.title, item.duration, item.size)
class settings(base.baseSettings):
name = _("zaycev.net")
config_section = "zaycev"
def __init__(self, parent):
super(settings, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.enabled = wx.CheckBox(self, wx.NewId(), _("Enable this service (works only in the Russian Federation)"))
self.map.append(("enabled", self.enabled))
sizer.Add(self.enabled, 0, wx.ALL, 5)
self.SetSizer(sizer)

View File

@ -4,4 +4,4 @@ from . import fix_requests
from .import fix_winpaths
def setup():
fix_requests.fix()
fix_requests.fix()

View File

@ -7,6 +7,6 @@ import paths
log = logging.getLogger("fixes.fix_requests")
def fix():
log.debug("Applying fix for requests...")
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "cacerts.txt")
log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"],))
log.debug("Applying fix for requests...")
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "cacerts.txt")
log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"],))

View File

@ -4,9 +4,9 @@ import winpaths
from ctypes import wintypes
def _get_path_buf(csidl):
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
return path_buf.value
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
result = winpaths._SHGetFolderPath(0, csidl, 0, 0, path_buf)
return path_buf.value
def fix():
winpaths._get_path_buf = _get_path_buf
winpaths._get_path_buf = _get_path_buf

View File

@ -9,10 +9,10 @@ import paths
log = logging.getLogger("i18n")
def setup():
lang = locale.getdefaultlocale()[0]
os.environ["lang"] = lang
log.debug("System detected language: {0}".format(lang,))
if sys.version[0] == "3":
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
else:
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)
lang = locale.getdefaultlocale()[0]
os.environ["lang"] = lang
log.debug("System detected language: {0}".format(lang,))
if sys.version[0] == "3":
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"))
else:
gettext.install("musicdl", localedir=os.path.join(paths.app_path(), "locales"), unicode=True)

View File

@ -1,11 +1,12 @@
!include "MUI2.nsh"
!include "LogicLib.nsh"
!include "x64.nsh"
Unicode true
CRCCheck on
ManifestSupportedOS all
XPStyle on
Name "MusicDL"
OutFile "music_dl_0.6_setup.exe"
OutFile "music_dl_setup.exe"
InstallDir "$PROGRAMFILES\musicDL"
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "InstallLocation"
RequestExecutionLevel admin
@ -13,18 +14,18 @@ SetCompress auto
SetCompressor /solid lzma
SetDatablockOptimize on
VIAddVersionKey ProductName "MusicDL"
VIAddVersionKey LegalCopyright "Copyright 2019 Manuel Cortez."
VIAddVersionKey ProductVersion "0.6"
VIAddVersionKey FileVersion "0.6"
VIProductVersion "0.6.0.0"
VIFileVersion "0.6.0.0"
VIAddVersionKey LegalCopyright "Copyright 2019 - 2022 MCV Software."
VIAddVersionKey ProductVersion "0.7"
VIAddVersionKey FileVersion "0.7"
VIProductVersion "0.7.0.0"
VIFileVersion "0.7.0.0"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
var StartMenuFolder
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_LINK "Visit MusicDL website"
!define MUI_FINISHPAGE_LINK_LOCATION "https://manuelcortez.net/music_dl"
!define MUI_FINISHPAGE_LINK_LOCATION "https://mcvsoftware.com/music_dl"
!define MUI_FINISHPAGE_RUN "$INSTDIR\musicDL.exe"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
@ -36,7 +37,11 @@ var StartMenuFolder
Section
SetShellVarContext All
SetOutPath "$INSTDIR"
File /r dist\main\*
${If} ${RunningX64}
File /r program64\*
${Else}
File /r program32\*
${EndIf}
CreateShortCut "$DESKTOP\musicDL.lnk" "$INSTDIR\musicDL.exe"
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
@ -49,7 +54,7 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "DisplayVersion" "0.6"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "DisplayVersion" "0.7"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "URLInfoAbout" "https://manuelcortez.net/music_dl"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\musicDL" "VersionMinor" 1
@ -65,5 +70,8 @@ Delete "$DESKTOP\MusicDL.lnk"
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
SectionEnd
Function .onInit
${If} ${RunningX64}
StrCpy $instdir "$programfiles64\musicDL"
${EndIf}
!insertmacro MUI_LANGDLL_DISPLAY
FunctionEnd

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2018 Manuel Cortez <manuel@manuelcortez.net>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
@ -27,37 +27,37 @@ from utils import call_threaded
from . import wx_ui
class reportBug(object):
def __init__(self):
self.dialog = wx_ui.reportBugDialog()
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response()
def __init__(self):
self.dialog = wx_ui.reportBugDialog()
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response()
def do_report(self, *args, **kwargs):
r = requests.post(*args, **kwargs)
if r.status_code > 300:
wx.CallAfter(self.dialog.error)
wx.CallAfter(self.dialog.progress.Destroy)
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
def do_report(self, *args, **kwargs):
r = requests.post(*args, **kwargs)
if r.status_code > 300:
wx.CallAfter(self.dialog.error)
wx.CallAfter(self.dialog.progress.Destroy)
wx.CallAfter(self.dialog.success, r.json()["data"]["issue"]["id"])
def send(self, *args, **kwargs):
if self.dialog.get("summary") == "" or self.dialog.get("description") == "" or self.dialog.get("first_name") == "" or self.dialog.get("last_name") == "":
self.dialog.no_filled()
return
if self.dialog.get("agree") == False:
self.dialog.no_checkbox()
return
title = self.dialog.get("summary")
body = self.dialog.get("description")
issue_type = "issue" # for now just have issue
app_type = storage.app_type
app_version = application.version
reporter_name = "{first_name} {last_name}".format(first_name=self.dialog.get("first_name"), last_name=self.dialog.get("last_name"))
reporter_contact_type = "email" # For now just email is supported in the issue reporter
reporter_contact_handle = self.dialog.get("email")
operating_system = platform.platform()
json = dict(title=title, issue_type=issue_type, body=body, operating_system=operating_system, app_type=app_type, app_version=app_version, reporter_name=reporter_name, reporter_contact_handle=reporter_contact_handle, reporter_contact_type=reporter_contact_type)
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
call_threaded(self.do_report, url, json=json, auth=auth)
self.dialog.show_progress()
self.dialog.EndModal(wx.ID_OK)
def send(self, *args, **kwargs):
if self.dialog.get("summary") == "" or self.dialog.get("description") == "" or self.dialog.get("first_name") == "" or self.dialog.get("last_name") == "":
self.dialog.no_filled()
return
if self.dialog.get("agree") == False:
self.dialog.no_checkbox()
return
title = self.dialog.get("summary")
body = self.dialog.get("description")
issue_type = "issue" # for now just have issue
app_type = storage.app_type
app_version = application.version
reporter_name = "{first_name} {last_name}".format(first_name=self.dialog.get("first_name"), last_name=self.dialog.get("last_name"))
reporter_contact_type = "email" # For now just email is supported in the issue reporter
reporter_contact_handle = self.dialog.get("email")
operating_system = platform.platform()
json = dict(title=title, issue_type=issue_type, body=body, operating_system=operating_system, app_type=app_type, app_version=app_version, reporter_name=reporter_name, reporter_contact_handle=reporter_contact_handle, reporter_contact_type=reporter_contact_type)
auth=HTTPBasicAuth(application.bts_name, application.bts_access_token)
url = "{bts_url}/issue/new".format(bts_url=application.bts_url)
call_threaded(self.do_report, url, json=json, auth=auth)
self.dialog.show_progress()
self.dialog.EndModal(wx.ID_OK)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2018 Manuel cortez <manuel@manuelcortez.net>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
@ -21,89 +21,89 @@ import widgetUtils
import application
class reportBugDialog(widgetUtils.BaseDialog):
def __init__(self):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
def __init__(self):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
self.summary = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 0, wx.ALL, 5)
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
self.summary = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 0, wx.ALL, 5)
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
self.first_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.first_name)
dc.SetFont(self.first_name.GetFont())
self.first_name.SetSize(dc.GetTextExtent("a"*40))
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
sizer.Add(first_nameB, 0, wx.ALL, 5)
first_nameLabel = wx.StaticText(panel, -1, _(u"First Name"), size=wx.DefaultSize)
self.first_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.first_name)
dc.SetFont(self.first_name.GetFont())
self.first_name.SetSize(dc.GetTextExtent("a"*40))
first_nameB = wx.BoxSizer(wx.HORIZONTAL)
first_nameB.Add(first_nameLabel, 0, wx.ALL, 5)
first_nameB.Add(self.first_name, 0, wx.ALL, 5)
sizer.Add(first_nameB, 0, wx.ALL, 5)
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
self.last_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.last_name)
dc.SetFont(self.last_name.GetFont())
self.last_name.SetSize(dc.GetTextExtent("a"*40))
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
last_nameB.Add(self.last_name, 0, wx.ALL, 5)
sizer.Add(last_nameB, 0, wx.ALL, 5)
last_nameLabel = wx.StaticText(panel, -1, _(u"Last Name"), size=wx.DefaultSize)
self.last_name = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.last_name)
dc.SetFont(self.last_name.GetFont())
self.last_name.SetSize(dc.GetTextExtent("a"*40))
last_nameB = wx.BoxSizer(wx.HORIZONTAL)
last_nameB.Add(last_nameLabel, 0, wx.ALL, 5)
last_nameB.Add(self.last_name, 0, wx.ALL, 5)
sizer.Add(last_nameB, 0, wx.ALL, 5)
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
self.email = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.email)
dc.SetFont(self.email.GetFont())
self.email.SetSize(dc.GetTextExtent("a"*30))
emailB = wx.BoxSizer(wx.HORIZONTAL)
emailB.Add(emailLabel, 0, wx.ALL, 5)
emailB.Add(self.email, 0, wx.ALL, 5)
sizer.Add(emailB, 0, wx.ALL, 5)
emailLabel = wx.StaticText(panel, -1, _(u"Email address (Will not be public)"), size=wx.DefaultSize)
self.email = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.email)
dc.SetFont(self.email.GetFont())
self.email.SetSize(dc.GetTextExtent("a"*30))
emailB = wx.BoxSizer(wx.HORIZONTAL)
emailB.Add(emailLabel, 0, wx.ALL, 5)
emailB.Add(self.email, 0, wx.ALL, 5)
sizer.Add(emailB, 0, wx.ALL, 5)
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont())
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 0, wx.ALL, 5)
sizer.Add(descBox, 0, wx.ALL, 5)
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my email address to contact me and fix the bug quickly").format(application.name,))
self.agree.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont())
(x, y) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 0, wx.ALL, 5)
sizer.Add(descBox, 0, wx.ALL, 5)
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my email address to contact me and fix the bug quickly").format(application.name,))
self.agree.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def no_filled(self):
wx.MessageDialog(self, _(u"You must fill out the following fields: first name, last name, email address and issue information."), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
def no_filled(self):
wx.MessageDialog(self, _(u"You must fill out the following fields: first name, last name, email address and issue information."), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
def no_checkbox(self):
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your email address to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
def no_checkbox(self):
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your email address to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
def success(self, id):
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
self.Destroy()
def success(self, id):
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You have received an email with more information regarding your report. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
self.Destroy()
def error(self):
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
self.Destroy()
def error(self):
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
self.Destroy()
def show_progress(self):
self.progress = wx.ProgressDialog(title=_(u"Sending report..."), message=_(u"Please wait while your report is being send."), maximum=100, parent=self)
self.progress.ShowModal()
def show_progress(self):
self.progress = wx.ProgressDialog(title=_(u"Sending report..."), message=_(u"Please wait while your report is being send."), maximum=100, parent=self)
self.progress.ShowModal()

Binary file not shown.

Binary file not shown.

View File

@ -1,452 +1,20 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Copyright (C) 2019 ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2019-06-24 13:05-0500\n"
"PO-Revision-Date: 2019-06-24 13:18-0500\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
"PO-Revision-Date: 2020-10-02 15:10+0000\n"
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
"Language-Team: \n"
"Language: es\n"
"Language-Team: Spanish "
"<http://translations.manuelcortez.net/projects/musicdl/interface/es/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.0.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Generated-By: Babel 2.9.0\n"
#: ../src\application.py:9
msgid ""
" Is an application that will allow you to download music from popular sites "
"such as youtube, zaycev.net."
msgstr ""
" Es una aplicación que te permite descargar música de sitios populares como "
"YouTube y zaycev.net."
#: ../src\application.py:14
msgid "Manuel Cortez (Spanish)"
msgstr "Manuel Cortez (Español)"
#: ../src\application.py:14
msgid "Valeria K (Russian)"
msgstr "Valeria K (Ruso)"
#: ../src\controller\configuration.py:10 ../src\wxUI\mainWindow.py:15
msgid "Settings"
msgstr "Preferencias"
#: ../src\controller\mainController.py:31
msgid "Ready"
msgstr "Listo"
#: ../src\controller\mainController.py:47
msgid "Showing {0} results."
msgstr "Mostrando {0} resultados"
#: ../src\controller\mainController.py:51
msgid "Shuffle on"
msgstr "Modo aleatorio activo"
#: ../src\controller\mainController.py:107
msgid "Searching {0}... "
msgstr "Buscando {0}..."
#: ../src\controller\mainController.py:141
#: ../src\controller\mainController.py:161 ../src\wxUI\mainWindow.py:18
#: ../src\wxUI\mainWindow.py:68
msgid "Play"
msgstr "Reproducir"
#: ../src\controller\mainController.py:144
#: ../src\controller\mainController.py:156
msgid "Pause"
msgstr "pausa"
#: ../src\controller\mainController.py:243
msgid "File downloaded: {0}"
msgstr "Archivo descargado: {0}"
#: ../src\controller\mainController.py:263
msgid "No results found. "
msgstr "No se han encontrado resultados."
#: ../src\controller\player.py:70
msgid "Error playing {0}. {1}."
msgstr "Error reproduciendo {0}. {1}."
#: ../src\controller\player.py:76
msgid "Playing {0}."
msgstr "Reproduciendo {0}."
#: ../src\controller\player.py:146 ../src\utils.py:58
msgid "Downloading {0}."
msgstr "Descargando {0}."
#: ../src\controller\player.py:151 ../src\utils.py:69
msgid "Downloading {0} ({1}%)."
msgstr "Descargando {0} ({1}%)."
#: ../src\controller\player.py:164
msgid "There was an error while trying to access the file you have requested."
msgstr "Ocurrió un error al intentar acceder al fichero solicitado."
#: ../src\controller\player.py:164 ../src\issueReporter\wx_ui.py:94
#: ../src\issueReporter\wx_ui.py:97
msgid "Error"
msgstr "Error"
#: ../src\extractors\tidal.py:102
msgid "Tidal"
msgstr "Tidal"
#: ../src\extractors\tidal.py:106
msgid "Lossless"
msgstr "Calidad más alta (FLAC)"
#: ../src\extractors\tidal.py:106
msgid "Low"
msgstr "Baja"
#: ../src\extractors\tidal.py:106 ../src\extractors\tidal.py:141
msgid "High"
msgstr "Alta"
#: ../src\extractors\tidal.py:118 ../src\extractors\youtube.py:112
msgid "Enable this service"
msgstr "Activar servicio"
#: ../src\extractors\tidal.py:122
msgid "Tidal username or email address"
msgstr "Correo o nombre de usuario de Tidal"
#: ../src\extractors\tidal.py:130
msgid "Password"
msgstr "Contraseña"
#: ../src\extractors\tidal.py:137
msgid "You can subscribe for a tidal account here"
msgstr "Puedes obtener tu cuenta de Tidal aquí"
#: ../src\extractors\tidal.py:140
msgid "Audio quality"
msgstr "Calidad de audio"
#: ../src\extractors\youtube.py:106
msgid "Youtube Settings"
msgstr "Opciones de Youtube"
#: ../src\extractors\youtube.py:116
msgid "Max results per page"
msgstr "Resultados máximos a cargar por cada búsqueda"
#: ../src\extractors\youtube.py:123
msgid "Enable transcode when downloading"
msgstr "Activar transcodificación al descargar"
#: ../src\extractors\zaycev.py:52
msgid "zaycev.net"
msgstr "zaycev.net"
#: ../src\extractors\zaycev.py:58
msgid "Enable this service (works only in the Russian Federation)"
msgstr "Activar servicio (funciona solo en la federación de Rusia)"
#: ../src\issueReporter\wx_ui.py:26 ../src\wxUI\mainWindow.py:31
msgid "Report an error"
msgstr "Reportar un error"
#: ../src\issueReporter\wx_ui.py:30
msgid ""
"Briefly describe what happened. You will be able to thoroughly explain it "
"later"
msgstr ""
"Describe brevemente lo que ha ocurrido. Más adelante podrás añadir más "
"detalles."
#: ../src\issueReporter\wx_ui.py:40
msgid "First Name"
msgstr "Nombre (s)"
#: ../src\issueReporter\wx_ui.py:50
msgid "Last Name"
msgstr "Apellido (s)"
#: ../src\issueReporter\wx_ui.py:60
msgid "Email address (Will not be public)"
msgstr "Dirección de correo electrónico (No será publicada)"
#: ../src\issueReporter\wx_ui.py:70
msgid "Here, you can describe the bug in detail"
msgstr "Aquí puedes describir el problema con más detalles."
#: ../src\issueReporter\wx_ui.py:80
msgid ""
"I know that the {0} bug system will get my email address to contact me and "
"fix the bug quickly"
msgstr ""
"Estoy enterado que el sistema de reporte de errores de {0} podrá utilizar mi "
"correo electrónico para contactarme en caso de necesitar más detalles para "
"arreglar el problema rápidamente."
#: ../src\issueReporter\wx_ui.py:83
msgid "Send report"
msgstr "Enviar reporte"
#: ../src\issueReporter\wx_ui.py:85
msgid "Cancel"
msgstr "Cancelar"
#: ../src\issueReporter\wx_ui.py:94
msgid ""
"You must fill out the following fields: first name, last name, email address "
"and issue information."
msgstr ""
"Debes llenar los siguientes campos: Nombre, apellido, dirección de correo "
"electrónico y la información del problema."
#: ../src\issueReporter\wx_ui.py:97
msgid ""
"You need to mark the checkbox to provide us your email address to contact "
"you if it is necessary."
msgstr ""
"Debes marcar la casilla para proporcionarnos tu correo electrónico y poder "
"contactarte si es necesario."
#: ../src\issueReporter\wx_ui.py:100
msgid ""
"Thanks for reporting this bug! In future versions, you may be able to find "
"it in the changes list. You have received an email with more information "
"regarding your report. You've reported the bug number %i"
msgstr ""
"¡Gracias por reportar este error! Esperamos que puedas encontrar este "
"problema resuelto en la lista de cambios de una versión futura. Has recibido "
"un correo electrónico con más información sobre tu reporte. Has reportado el "
"error número %i"
#: ../src\issueReporter\wx_ui.py:100
msgid "reported"
msgstr "Reportado"
#: ../src\issueReporter\wx_ui.py:104
msgid "Error while reporting"
msgstr "Error al intentar reportar"
#: ../src\issueReporter\wx_ui.py:104
msgid ""
"Something unexpected occurred while trying to report the bug. Please, try "
"again later"
msgstr ""
"Algo inesperado ha ocurrido al intentar realizar el reporte de error. Por "
"favor, inténtalo nuevamente más tarde."
#: ../src\issueReporter\wx_ui.py:108
msgid "Please wait while your report is being send."
msgstr "Por favor, espera mientras tu reporte es enviado."
#: ../src\issueReporter\wx_ui.py:108
msgid "Sending report..."
msgstr "Enviando reporte..."
#: ../src\test\test_i18n.py:21
msgid "This is a string with no special characters."
msgstr ""
#: ../src\test\test_i18n.py:24
msgid ""
"\\320\\237\\321\\200\\320\\270\\320\\262\\320\\265\\321\\202 "
"\\320\\262\\321\\201\\320\\265\\320\\274"
msgstr ""
#: ../src\update\utils.py:27
msgid "%d day, "
msgstr "%d día, "
#: ../src\update\utils.py:29
msgid "%d days, "
msgstr "%d días, "
#: ../src\update\utils.py:31
msgid "%d hour, "
msgstr "%d hora, "
#: ../src\update\utils.py:33
msgid "%d hours, "
msgstr "%d horas, "
#: ../src\update\utils.py:35
msgid "%d minute, "
msgstr "%d minuto, "
#: ../src\update\utils.py:37
msgid "%d minutes, "
msgstr "%d minutos, "
#: ../src\update\utils.py:39
msgid "%s second"
msgstr "%s segundo"
#: ../src\update\utils.py:41
msgid "%s seconds"
msgstr "%s segundos"
#: ../src\update\wxUpdater.py:10
msgid "New version for %s"
msgstr "Nueva versión de %s"
#: ../src\update\wxUpdater.py:10
msgid ""
"There's a new %s version available. Would you like to download it now?\n"
"\n"
" %s version: %s\n"
"\n"
"Changes:\n"
"%s"
msgstr ""
"Hay una nueva versión de %s disponible. ¿Te gustaría descargarla ahora?\n"
"\n"
" %s versión: %s\n"
"\n"
"Novedades:\n"
"%s"
#: ../src\update\wxUpdater.py:17
msgid "Download in Progress"
msgstr "Descarga en progreso"
#: ../src\update\wxUpdater.py:17
msgid "Downloading the new version..."
msgstr "Descargando la nueva versión..."
#: ../src\update\wxUpdater.py:27
msgid "Updating... %s of %s"
msgstr "Actualizando... %s de %s"
#: ../src\update\wxUpdater.py:30
msgid "Done!"
msgstr "¡Hecho!"
#: ../src\update\wxUpdater.py:30
msgid ""
"The update has been downloaded and installed successfully. Press OK to "
"continue."
msgstr ""
"La actualización ha sido descargada e instalada satisfactoriamente. Pulsa "
"aceptar para continuar."
#: ../src\wxUI\configuration.py:9
msgid "Output device"
msgstr "Dispositivo de salida"
#: ../src\wxUI\configuration.py:27
msgid "General"
msgstr "General"
#: ../src\wxUI\configuration.py:31
msgid "Services"
msgstr "Servicios"
#: ../src\wxUI\configuration.py:34
msgid "Save"
msgstr "Guardar"
#: ../src\wxUI\configuration.py:36
msgid "Close"
msgstr "Cerrar"
#: ../src\wxUI\mainWindow.py:16
msgid "Application"
msgstr "Aplicación"
#: ../src\wxUI\mainWindow.py:19 ../src\wxUI\mainWindow.py:69
msgid "Stop"
msgstr "Detener"
#: ../src\wxUI\mainWindow.py:20 ../src\wxUI\mainWindow.py:67
msgid "Previous"
msgstr "Anterior"
#: ../src\wxUI\mainWindow.py:21 ../src\wxUI\mainWindow.py:70
msgid "Next"
msgstr "Siguiente"
#: ../src\wxUI\mainWindow.py:22
msgid "Shuffle"
msgstr "Aleatorio"
#: ../src\wxUI\mainWindow.py:23
msgid "Volume down"
msgstr "Bajar volumen"
#: ../src\wxUI\mainWindow.py:24
msgid "Volume up"
msgstr "Subir volumen"
#: ../src\wxUI\mainWindow.py:25
msgid "Mute"
msgstr "Silenciar"
#: ../src\wxUI\mainWindow.py:27
msgid "About {0}"
msgstr "Sobre {0}"
#: ../src\wxUI\mainWindow.py:28
msgid "Check for updates"
msgstr "Comprobar actualizaciones"
#: ../src\wxUI\mainWindow.py:29
msgid "What's new in this version?"
msgstr "¿qué hay de nuevo?"
#: ../src\wxUI\mainWindow.py:30
msgid "Visit website"
msgstr "Visitar sitio web"
#: ../src\wxUI\mainWindow.py:32
msgid "Player"
msgstr "Reproductor"
#: ../src\wxUI\mainWindow.py:33
msgid "Help"
msgstr "Ayuda"
#: ../src\wxUI\mainWindow.py:43
msgid "search"
msgstr "Buscar"
#: ../src\wxUI\mainWindow.py:48
msgid "Search in"
msgstr "Buscar en"
#: ../src\wxUI\mainWindow.py:51
msgid "Search"
msgstr "Buscar"
#: ../src\wxUI\mainWindow.py:55
msgid "Results"
msgstr "Resultados"
#: ../src\wxUI\mainWindow.py:61
msgid "Position"
msgstr "Posición"
#: ../src\wxUI\mainWindow.py:64
msgid "Volume"
msgstr "Volumen"
#: ../src\wxUI\mainWindow.py:114
msgid "Audio Files(*.mp3)|*.mp3"
msgstr "Archivos de audio (*.mp3)|*.mp3"
#: ../src\wxUI\mainWindow.py:114
msgid "Save this file"
msgstr "Guardar archivo"
#: ../src\wxUI\menus.py:8
msgid "Play/Pause"
msgstr "Reproducir/pausar"
#: ../src\wxUI\menus.py:9
msgid "Download"
msgstr "Descargar"

Binary file not shown.

View File

@ -1,254 +1,20 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Copyright (C) 2018 ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2018-03-03 09:38+Hora estándar central (México)\n"
"PO-Revision-Date: 2018-03-12 01:35+0400\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-29 05:08-0800\n"
"PO-Revision-Date: 2020-07-30 05:13-0500\n"
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
"Language: ru\n"
"Language-Team: ru <LL@li.org>\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.5.7\n"
"Generated-By: Babel 2.9.0\n"
#: ../src\application.py:7
msgid ""
" Is an application that will allow you to download music from popular sites "
"such as youtube, zaycev.net."
msgstr ""
" Это приложение, позволяющее скачивать музыку с таких популярных сайтов как "
"Youtube, zaycev.net."
#: ../src\application.py:12
msgid "Manuel Cortez (Spanish)"
msgstr "Manuel Cortez (Испанский)"
#: ../src\controller\mainController.py:27
msgid "Ready"
msgstr "Готово"
#: ../src\controller\mainController.py:42
msgid "Showing {0} results."
msgstr "Показано {0} результатов."
#: ../src\controller\mainController.py:46
msgid "Shuffle on"
msgstr "Случайный порядок включён"
#: ../src\controller\mainController.py:118
#: ../src\controller\mainController.py:138 ../src\wxUI\mainWindow.py:13
#: ../src\wxUI\mainWindow.py:62
msgid "Play"
msgstr "Воспроизвести"
#: ../src\controller\mainController.py:121
#: ../src\controller\mainController.py:133
msgid "Pause"
msgstr "Приостановить"
#: ../src\controller\mainController.py:213
msgid "File downloaded: {0}"
msgstr "Файл загружен: {0}"
#: ../src\controller\mainController.py:236
msgid "Searching {0}... "
msgstr "Поиск {0}... "
#: ../src\controller\mainController.py:242
msgid "No results found. "
msgstr "Нет результатов."
#: ../src\controller\player.py:43
msgid "Error playing {0}. {1}."
msgstr "Ошибка воспроизведения {0}. {1}."
#: ../src\controller\player.py:49
msgid "Playing {0}."
msgstr "Сейчас играет {0}."
#: ../src\controller\player.py:117 ../src\utils.py:53
msgid "Downloading {0}."
msgstr "Сейчас загружается {0}."
#: ../src\controller\player.py:122 ../src\utils.py:63
msgid "Downloading {0} ({1}%)."
msgstr "Загрузка {0} ({1}%)."
#: ../src\controller\player.py:133
msgid "Error"
msgstr "Ошибка"
#: ../src\controller\player.py:133
msgid "There was an error while trying to access the file you have requested."
msgstr "Произошла ошибка при попытке открыть запрашиваемый файл."
#: ../src\update\utils.py:27
msgid "%d day, "
msgstr "%d день, "
#: ../src\update\utils.py:29
msgid "%d days, "
msgstr "%d дней, "
#: ../src\update\utils.py:31
msgid "%d hour, "
msgstr "%d час, "
#: ../src\update\utils.py:33
msgid "%d hours, "
msgstr "%d часов, "
#: ../src\update\utils.py:35
msgid "%d minute, "
msgstr "%d минута, "
#: ../src\update\utils.py:37
msgid "%d minutes, "
msgstr "%d минут, "
#: ../src\update\utils.py:39
msgid "%s second"
msgstr "%s секунда"
#: ../src\update\utils.py:41
msgid "%s seconds"
msgstr "%s секунд"
#: ../src\update\wxUpdater.py:9
msgid "New version for %s"
msgstr "Новая версия %s"
#: ../src\update\wxUpdater.py:9
msgid ""
"There's a new %s version available. Would you like to download it now?\n"
"\n"
" %s version: %s\n"
"\n"
"Changes:\n"
"%s"
msgstr ""
"Доступна новая версия %s. Желаете скачать её?\n"
"\n"
" %s версия: %s\n"
"\n"
"Изменения:\n"
"%s"
#: ../src\update\wxUpdater.py:16
msgid "Download in Progress"
msgstr "Процесс скачивания"
#: ../src\update\wxUpdater.py:16
msgid "Downloading the new version..."
msgstr "Скачивание новой версии..."
#: ../src\update\wxUpdater.py:26
msgid "Updating... %s of %s"
msgstr "Обновление... %s из %s"
#: ../src\update\wxUpdater.py:29
msgid "Done!"
msgstr "Готово!"
#: ../src\update\wxUpdater.py:29
msgid ""
"The update has been downloaded and installed successfully. Press OK to "
"continue."
msgstr ""
"Обновление было успешно загружено и установлено. Нажмите ОК для продолжения."
#: ../src\wxUI\mainWindow.py:14 ../src\wxUI\mainWindow.py:63
msgid "Stop"
msgstr "Остановить"
#: ../src\wxUI\mainWindow.py:15 ../src\wxUI\mainWindow.py:61
msgid "Previous"
msgstr "Предыдущая композиция"
#: ../src\wxUI\mainWindow.py:16 ../src\wxUI\mainWindow.py:64
msgid "Next"
msgstr "Следующая композиция"
#: ../src\wxUI\mainWindow.py:17
msgid "Shuffle"
msgstr "Перемешать"
#: ../src\wxUI\mainWindow.py:18
msgid "Volume down"
msgstr "Уменьшить громкость"
#: ../src\wxUI\mainWindow.py:19
msgid "Volume up"
msgstr "Увеличить громкость"
#: ../src\wxUI\mainWindow.py:20
msgid "Mute"
msgstr "Выключить звук"
#: ../src\wxUI\mainWindow.py:22
msgid "About {0}"
msgstr "О {0}"
#: ../src\wxUI\mainWindow.py:23
msgid "Check for updates"
msgstr "Проверить на наличие обновлений"
#: ../src\wxUI\mainWindow.py:24
msgid "What's new in this version?"
msgstr "Что нового в этой версии?"
#: ../src\wxUI\mainWindow.py:25
msgid "Visit website"
msgstr "Посетить вебсайт"
#: ../src\wxUI\mainWindow.py:26
msgid "Player"
msgstr "Плеер"
#: ../src\wxUI\mainWindow.py:27
msgid "Help"
msgstr "Помощь"
#: ../src\wxUI\mainWindow.py:37
msgid "search"
msgstr "Поиск"
#: ../src\wxUI\mainWindow.py:42
msgid "Search in"
msgstr "Искать с помощью"
#: ../src\wxUI\mainWindow.py:45
msgid "Search"
msgstr "Поиск"
#: ../src\wxUI\mainWindow.py:49
msgid "Results"
msgstr "Результаты"
#: ../src\wxUI\mainWindow.py:55
msgid "Position"
msgstr "Позиция"
#: ../src\wxUI\mainWindow.py:58
msgid "Volume"
msgstr "Громкость"
#: ../src\wxUI\mainWindow.py:100
msgid "Audio Files(*.mp3)|*.mp3"
msgstr "Аудио Файлы(*.mp3)|*.mp3"
#: ../src\wxUI\mainWindow.py:100
msgid "Save this file"
msgstr "Сохранить этот файл"
#: ../src\wxUI\menus.py:7
msgid "Play/Pause"
msgstr "Воспроизвести/Приостановить"
#: ../src\wxUI\menus.py:9
msgid "Download"
msgstr "Скачать"

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals # at top of module
# this is the first fix we have to import just before the paths module would.
# it changes a call from wintypes to ctypes.
from fixes import fix_winpaths
fix_winpaths.fix()
import os
import logging
import storage
@ -13,19 +8,16 @@ storage.setup()
# Let's import config module here as it is dependent on storage being setup.
import config
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).
sys.excepthook = lambda x, y, z: logging.critical(''.join(traceback.format_exception(x, y, z)))
log = logging.getLogger("main")
log.debug("Logger initialized. Saving debug to {0}".format(storage.data_directory,))
log.debug("Using Python version {0}".format(sys.version,))
if sys.version[0] == "2":
if hasattr(sys, "frozen"):
log.debug("Applying fixes for Python 2 frozen executables.")
import fixes
fixes.setup()
if hasattr(sys, "frozen"):
log.debug("Applying fixes for Python 2 frozen executables.")
import fixes
fixes.setup()
import i18n
i18n.setup()
config.setup()
@ -34,12 +26,12 @@ import widgetUtils
import paths
def setup():
log.debug("Starting music-dl %s" % (application.version,))
log.debug("Application path is %s" % (paths.app_path(),))
from controller import mainController
app = widgetUtils.mainLoopObject()
log.debug("Created Application mainloop object")
r = mainController.Controller()
app.run()
log.debug("Starting music-dl %s" % (application.version,))
log.debug("Application path is %s" % (paths.app_path(),))
from controller import mainController
app = widgetUtils.mainLoopObject()
log.debug("Created Application mainloop object")
r = mainController.Controller()
app.run()
setup()

View File

@ -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')

View File

@ -1,116 +1,70 @@
# -*- coding: utf-8 -*-
import inspect
from __future__ import unicode_literals
import sys
import platform
import os
import subprocess
import sys
import string
import unicodedata
import glob
from platform_utils import paths as paths_
from functools import wraps
def app_data_path(app_name=None):
"""Cross-platform method for determining where to put application data."""
"""Requires the name of the application"""
plat = platform.system()
if plat == 'Windows':
import winpaths
path = winpaths.get_appdata()
elif plat == 'Darwin':
path = os.path.join(os.path.expanduser('~'), 'Library', 'Application Support')
elif plat == 'Linux':
path = os.path.expanduser('~')
app_name = '.%s' % app_name.replace(' ', '_')
return os.path.join(path, app_name)
mode = "portable"
directory = None
fsencoding = sys.getfilesystemencoding()
def prepare_app_data_path(app_name):
"""Creates the application's data directory, given its name."""
dir = app_data_path(app_name)
return ensure_path(dir)
def embedded_data_path():
if platform.system() == 'Darwin' and is_frozen():
return os.path.abspath(os.path.join(executable_directory(), '..', 'Resources'))
return app_path()
def is_frozen():
"""Return a bool indicating if application is compressed"""
import imp
return hasattr(sys, 'frozen') or imp.is_frozen("__main__")
def get_executable():
"""Returns the full executable path/name if frozen, or the full path/name of the main module if not."""
if is_frozen():
if platform.system() != 'Darwin':
return sys.executable
#On darwin, sys.executable points to python. We want the full path to the exe we ran.
exedir = os.path.abspath(os.path.dirname(sys.executable))
items = os.listdir(exedir)
items.remove('python')
return os.path.join(exedir, items[0])
#Not frozen
try:
import __main__
return os.path.abspath(__main__.__file__)
except AttributeError:
return sys.argv[0]
def get_module(level=2):
"""Hacky method for deriving the caller of this function's module."""
return inspect.getmodule(inspect.stack()[level][0]).__file__
def executable_directory():
"""Always determine the directory of the executable, even when run with py2exe or otherwise frozen"""
executable = get_executable()
path = os.path.abspath(os.path.dirname(executable))
return path
if len(glob.glob("Uninstall.exe")) > 0: # installed copy
mode= "installed"
def app_path():
"""Return the root of the application's directory"""
path = executable_directory()
if is_frozen() and platform.system() == 'Darwin':
path = os.path.abspath(os.path.join(path, '..', '..'))
return path
return paths_.app_path()
def module_path(level=2):
return os.path.abspath(os.path.dirname(get_module(level)))
def config_path():
global mode, directory
if mode == "portable":
if directory != None: path = os.path.join(directory, "config")
elif directory == None: path = os.path.join(app_path(), "config")
elif mode == "installed":
path = os.path.join(data_path(), "config")
if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path)
return path
def documents_path():
"""On windows, returns the path to My Documents. On OSX, returns the user's Documents folder. For anything else, returns the user's home directory."""
plat = platform.system()
if plat == 'Windows':
import winpaths
path = winpaths.get_my_documents()
elif plat == 'Darwin':
path = os.path.join(os.path.expanduser('~'), 'Documents')
else:
path = os.path.expanduser('~')
return path
def logs_path():
global mode, directory
if mode == "portable":
if directory != None: path = os.path.join(directory, "logs")
elif directory == None: path = os.path.join(app_path(), "logs")
elif mode == "installed":
path = os.path.join(data_path(), "logs")
if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path)
return path
def safe_filename(filename):
"""Given a filename, returns a safe version with no characters that would not work on different platforms."""
SAFE_FILE_CHARS = "'-_.()[]{}!@#$%^&+=`~ "
filename = unicode(filename)
new_filename = ''.join(c for c in filename if c in SAFE_FILE_CHARS or c.isalnum())
#Windows doesn't like directory names ending in space, macs consider filenames beginning with a dot as hidden, and windows removes dots at the ends of filenames.
return new_filename.strip(' .')
def data_path(app_name='socializer'):
if platform.system() == "Windows":
data_path = os.path.join(os.getenv("AppData"), app_name)
else:
data_path = os.path.join(os.environ['HOME'], ".%s" % app_name)
if not os.path.exists(data_path):
os.mkdir(data_path)
return data_path
def ensure_path(path):
if not os.path.exists(path):
os.makedirs(path)
return path
def locale_path():
return os.path.join(app_path(), "locales")
def start_file(path):
if platform.system() == 'Windows':
os.startfile(path)
else:
subprocess.Popen(['open', path])
def sound_path():
return os.path.join(app_path(), "sounds")
def get_applications_path():
"""Return the directory where applications are commonly installed on the system."""
plat = platform.system()
if plat == 'Windows':
import winpaths
return winpaths.get_program_files()
elif plat == 'Darwin':
return '/Applications'
def com_path():
global mode, directory
if mode == "portable":
if directory != None: path = os.path.join(directory, "com_cache")
elif directory == None: path = os.path.join(app_path(), "com_cache")
elif mode == "installed":
path = os.path.join(data_path(), "com_cache")
if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path)
return path

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More