Spaces:
Runtime error
Runtime error
AkshitShubham
commited on
Upload folder using huggingface_hub
Browse files- .gitattributes +2 -2
- .gitignore +190 -190
- Dockerfile +34 -34
- README.md +101 -101
- beta/api/api.py +68 -68
- beta/api/task_manager.py +42 -42
- beta/shellLogic/HandleBasicCMDUtils.py +41 -41
- beta/shellLogic/HandleKeyAndAvailiblity.py +34 -34
- beta/shellLogic/HandleShellDL.py +62 -62
- beta/shellLogic/logic.py +23 -23
- beta/shellLogic/logicError.py +5 -5
- beta/shellLogic/shell.py +53 -53
- beta/shellLogic/simpleParser.py +5 -5
- beta/util.py +46 -46
- defaults.json +10 -10
- defaults.linux.json +10 -10
- mainLogic/big4/cleanup.py +37 -37
- mainLogic/big4/decrypt/decrypt.py +53 -53
- mainLogic/big4/decrypt/key.old.py +85 -85
- mainLogic/big4/decrypt/key.py +166 -166
- mainLogic/big4/dl.py +175 -175
- mainLogic/big4/merge.py +11 -11
- mainLogic/error.py +78 -78
- mainLogic/main.py +119 -119
- mainLogic/startup/checkup.py +165 -165
- mainLogic/startup/flareCheck.py +17 -17
- mainLogic/startup/userPrefs.py +54 -54
- mainLogic/utils/basicUtils.py +7 -7
- mainLogic/utils/glv.py +69 -69
- mainLogic/utils/keyUtils.py +25 -25
- mainLogic/utils/os2.py +52 -52
- mainLogic/utils/process.py +42 -42
- pwdl.bat +12 -12
- pwdl.py +147 -147
.gitattributes
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
-
# Auto detect text files and perform LF normalization
|
2 |
-
* text=auto
|
3 |
bin/mp4decrypt filter=lfs diff=lfs merge=lfs -text
|
4 |
bin/nm3 filter=lfs diff=lfs merge=lfs -text
|
5 |
bin/nm3.exe filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
# Auto detect text files and perform LF normalization
|
2 |
+
* text=auto
|
3 |
bin/mp4decrypt filter=lfs diff=lfs merge=lfs -text
|
4 |
bin/nm3 filter=lfs diff=lfs merge=lfs -text
|
5 |
bin/nm3.exe filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
@@ -1,190 +1,190 @@
|
|
1 |
-
# Created by https://www.toptal.com/developers/gitignore/api/python
|
2 |
-
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
3 |
-
|
4 |
-
### Python ###
|
5 |
-
# Byte-compiled / optimized / DLL files
|
6 |
-
__pycache__/
|
7 |
-
*.py[cod]
|
8 |
-
*$py.class
|
9 |
-
|
10 |
-
# IDE
|
11 |
-
.idea/
|
12 |
-
|
13 |
-
# Download Files
|
14 |
-
webdl/
|
15 |
-
|
16 |
-
# CSV Files
|
17 |
-
*.csv
|
18 |
-
|
19 |
-
# C extensions
|
20 |
-
*.so
|
21 |
-
|
22 |
-
# Distribution / packaging
|
23 |
-
.Python
|
24 |
-
build/
|
25 |
-
develop-eggs/
|
26 |
-
dist/
|
27 |
-
downloads/
|
28 |
-
eggs/
|
29 |
-
.eggs/
|
30 |
-
lib/
|
31 |
-
lib64/
|
32 |
-
parts/
|
33 |
-
sdist/
|
34 |
-
var/
|
35 |
-
wheels/
|
36 |
-
share/python-wheels/
|
37 |
-
*.egg-info/
|
38 |
-
.installed.cfg
|
39 |
-
*.egg
|
40 |
-
MANIFEST
|
41 |
-
|
42 |
-
# PyInstaller
|
43 |
-
# Usually these files are written by a python script from a template
|
44 |
-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
45 |
-
*.manifest
|
46 |
-
*.spec
|
47 |
-
|
48 |
-
# Installer logs
|
49 |
-
pip-log.txt
|
50 |
-
pip-delete-this-directory.txt
|
51 |
-
|
52 |
-
# Unit test / coverage reports
|
53 |
-
htmlcov/
|
54 |
-
.tox/
|
55 |
-
.nox/
|
56 |
-
.coverage
|
57 |
-
.coverage.*
|
58 |
-
.cache
|
59 |
-
nosetests.xml
|
60 |
-
coverage.xml
|
61 |
-
*.cover
|
62 |
-
*.py,cover
|
63 |
-
.hypothesis/
|
64 |
-
.pytest_cache/
|
65 |
-
cover/
|
66 |
-
|
67 |
-
# Translations
|
68 |
-
*.mo
|
69 |
-
*.pot
|
70 |
-
|
71 |
-
# Django stuff:
|
72 |
-
*.log
|
73 |
-
local_settings.py
|
74 |
-
db.sqlite3
|
75 |
-
db.sqlite3-journal
|
76 |
-
|
77 |
-
# Flask stuff:
|
78 |
-
instance/
|
79 |
-
.webassets-cache
|
80 |
-
|
81 |
-
# Scrapy stuff:
|
82 |
-
.scrapy
|
83 |
-
|
84 |
-
# Sphinx documentation
|
85 |
-
docs/_build/
|
86 |
-
|
87 |
-
# PyBuilder
|
88 |
-
.pybuilder/
|
89 |
-
target/
|
90 |
-
|
91 |
-
# Jupyter Notebook
|
92 |
-
.ipynb_checkpoints
|
93 |
-
|
94 |
-
# IPython
|
95 |
-
profile_default/
|
96 |
-
ipython_config.py
|
97 |
-
|
98 |
-
# pyenv
|
99 |
-
# For a library or package, you might want to ignore these files since the code is
|
100 |
-
# intended to run in multiple environments; otherwise, check them in:
|
101 |
-
# .python-version
|
102 |
-
|
103 |
-
# pipenv
|
104 |
-
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
105 |
-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
106 |
-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
107 |
-
# install all needed dependencies.
|
108 |
-
#Pipfile.lock
|
109 |
-
|
110 |
-
# poetry
|
111 |
-
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
112 |
-
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
113 |
-
# commonly ignored for libraries.
|
114 |
-
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
115 |
-
#poetry.lock
|
116 |
-
|
117 |
-
# pdm
|
118 |
-
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
119 |
-
#pdm.lock
|
120 |
-
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
121 |
-
# in version control.
|
122 |
-
# https://pdm.fming.dev/#use-with-ide
|
123 |
-
.pdm.toml
|
124 |
-
|
125 |
-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
126 |
-
__pypackages__/
|
127 |
-
|
128 |
-
# Celery stuff
|
129 |
-
celerybeat-schedule
|
130 |
-
celerybeat.pid
|
131 |
-
|
132 |
-
# SageMath parsed files
|
133 |
-
*.sage.py
|
134 |
-
|
135 |
-
# Environments
|
136 |
-
.env
|
137 |
-
.venv
|
138 |
-
env/
|
139 |
-
venv/
|
140 |
-
ENV/
|
141 |
-
env.bak/
|
142 |
-
venv.bak/
|
143 |
-
|
144 |
-
# Spyder project settings
|
145 |
-
.spyderproject
|
146 |
-
.spyproject
|
147 |
-
|
148 |
-
# Rope project settings
|
149 |
-
.ropeproject
|
150 |
-
|
151 |
-
# mkdocs documentation
|
152 |
-
/site
|
153 |
-
|
154 |
-
# mypy
|
155 |
-
.mypy_cache/
|
156 |
-
.dmypy.json
|
157 |
-
dmypy.json
|
158 |
-
|
159 |
-
# Pyre type checker
|
160 |
-
.pyre/
|
161 |
-
|
162 |
-
# pytype static type analyzer
|
163 |
-
.pytype/
|
164 |
-
|
165 |
-
# Cython debug symbols
|
166 |
-
cython_debug/
|
167 |
-
|
168 |
-
# PyCharm
|
169 |
-
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
170 |
-
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
171 |
-
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
172 |
-
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
173 |
-
#.idea/
|
174 |
-
|
175 |
-
### Python Patch ###
|
176 |
-
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
177 |
-
poetry.toml
|
178 |
-
|
179 |
-
# ruff
|
180 |
-
.ruff_cache/
|
181 |
-
|
182 |
-
# LSP config files
|
183 |
-
pyrightconfig.json
|
184 |
-
|
185 |
-
# End of https://www.toptal.com/developers/gitignore/api/python
|
186 |
-
pwdlv3.lnk
|
187 |
-
*.mp4
|
188 |
-
/bin/Logs/
|
189 |
-
*.m4s
|
190 |
-
/tmp
|
|
|
1 |
+
# Created by https://www.toptal.com/developers/gitignore/api/python
|
2 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
3 |
+
|
4 |
+
### Python ###
|
5 |
+
# Byte-compiled / optimized / DLL files
|
6 |
+
__pycache__/
|
7 |
+
*.py[cod]
|
8 |
+
*$py.class
|
9 |
+
|
10 |
+
# IDE
|
11 |
+
.idea/
|
12 |
+
|
13 |
+
# Download Files
|
14 |
+
webdl/
|
15 |
+
|
16 |
+
# CSV Files
|
17 |
+
*.csv
|
18 |
+
|
19 |
+
# C extensions
|
20 |
+
*.so
|
21 |
+
|
22 |
+
# Distribution / packaging
|
23 |
+
.Python
|
24 |
+
build/
|
25 |
+
develop-eggs/
|
26 |
+
dist/
|
27 |
+
downloads/
|
28 |
+
eggs/
|
29 |
+
.eggs/
|
30 |
+
lib/
|
31 |
+
lib64/
|
32 |
+
parts/
|
33 |
+
sdist/
|
34 |
+
var/
|
35 |
+
wheels/
|
36 |
+
share/python-wheels/
|
37 |
+
*.egg-info/
|
38 |
+
.installed.cfg
|
39 |
+
*.egg
|
40 |
+
MANIFEST
|
41 |
+
|
42 |
+
# PyInstaller
|
43 |
+
# Usually these files are written by a python script from a template
|
44 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
45 |
+
*.manifest
|
46 |
+
*.spec
|
47 |
+
|
48 |
+
# Installer logs
|
49 |
+
pip-log.txt
|
50 |
+
pip-delete-this-directory.txt
|
51 |
+
|
52 |
+
# Unit test / coverage reports
|
53 |
+
htmlcov/
|
54 |
+
.tox/
|
55 |
+
.nox/
|
56 |
+
.coverage
|
57 |
+
.coverage.*
|
58 |
+
.cache
|
59 |
+
nosetests.xml
|
60 |
+
coverage.xml
|
61 |
+
*.cover
|
62 |
+
*.py,cover
|
63 |
+
.hypothesis/
|
64 |
+
.pytest_cache/
|
65 |
+
cover/
|
66 |
+
|
67 |
+
# Translations
|
68 |
+
*.mo
|
69 |
+
*.pot
|
70 |
+
|
71 |
+
# Django stuff:
|
72 |
+
*.log
|
73 |
+
local_settings.py
|
74 |
+
db.sqlite3
|
75 |
+
db.sqlite3-journal
|
76 |
+
|
77 |
+
# Flask stuff:
|
78 |
+
instance/
|
79 |
+
.webassets-cache
|
80 |
+
|
81 |
+
# Scrapy stuff:
|
82 |
+
.scrapy
|
83 |
+
|
84 |
+
# Sphinx documentation
|
85 |
+
docs/_build/
|
86 |
+
|
87 |
+
# PyBuilder
|
88 |
+
.pybuilder/
|
89 |
+
target/
|
90 |
+
|
91 |
+
# Jupyter Notebook
|
92 |
+
.ipynb_checkpoints
|
93 |
+
|
94 |
+
# IPython
|
95 |
+
profile_default/
|
96 |
+
ipython_config.py
|
97 |
+
|
98 |
+
# pyenv
|
99 |
+
# For a library or package, you might want to ignore these files since the code is
|
100 |
+
# intended to run in multiple environments; otherwise, check them in:
|
101 |
+
# .python-version
|
102 |
+
|
103 |
+
# pipenv
|
104 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
105 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
106 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
107 |
+
# install all needed dependencies.
|
108 |
+
#Pipfile.lock
|
109 |
+
|
110 |
+
# poetry
|
111 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
112 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
113 |
+
# commonly ignored for libraries.
|
114 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
115 |
+
#poetry.lock
|
116 |
+
|
117 |
+
# pdm
|
118 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
119 |
+
#pdm.lock
|
120 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
121 |
+
# in version control.
|
122 |
+
# https://pdm.fming.dev/#use-with-ide
|
123 |
+
.pdm.toml
|
124 |
+
|
125 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
126 |
+
__pypackages__/
|
127 |
+
|
128 |
+
# Celery stuff
|
129 |
+
celerybeat-schedule
|
130 |
+
celerybeat.pid
|
131 |
+
|
132 |
+
# SageMath parsed files
|
133 |
+
*.sage.py
|
134 |
+
|
135 |
+
# Environments
|
136 |
+
.env
|
137 |
+
.venv
|
138 |
+
env/
|
139 |
+
venv/
|
140 |
+
ENV/
|
141 |
+
env.bak/
|
142 |
+
venv.bak/
|
143 |
+
|
144 |
+
# Spyder project settings
|
145 |
+
.spyderproject
|
146 |
+
.spyproject
|
147 |
+
|
148 |
+
# Rope project settings
|
149 |
+
.ropeproject
|
150 |
+
|
151 |
+
# mkdocs documentation
|
152 |
+
/site
|
153 |
+
|
154 |
+
# mypy
|
155 |
+
.mypy_cache/
|
156 |
+
.dmypy.json
|
157 |
+
dmypy.json
|
158 |
+
|
159 |
+
# Pyre type checker
|
160 |
+
.pyre/
|
161 |
+
|
162 |
+
# pytype static type analyzer
|
163 |
+
.pytype/
|
164 |
+
|
165 |
+
# Cython debug symbols
|
166 |
+
cython_debug/
|
167 |
+
|
168 |
+
# PyCharm
|
169 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
170 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
171 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
172 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
173 |
+
#.idea/
|
174 |
+
|
175 |
+
### Python Patch ###
|
176 |
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
177 |
+
poetry.toml
|
178 |
+
|
179 |
+
# ruff
|
180 |
+
.ruff_cache/
|
181 |
+
|
182 |
+
# LSP config files
|
183 |
+
pyrightconfig.json
|
184 |
+
|
185 |
+
# End of https://www.toptal.com/developers/gitignore/api/python
|
186 |
+
pwdlv3.lnk
|
187 |
+
*.mp4
|
188 |
+
/bin/Logs/
|
189 |
+
*.m4s
|
190 |
+
/tmp
|
Dockerfile
CHANGED
@@ -1,34 +1,34 @@
|
|
1 |
-
# Use an official Python runtime as a parent image
|
2 |
-
FROM python:3.12-slim
|
3 |
-
|
4 |
-
# Set the working directory in the container to /app
|
5 |
-
WORKDIR /app
|
6 |
-
|
7 |
-
# Add the current directory contents into the container at /app
|
8 |
-
ADD . /app
|
9 |
-
|
10 |
-
# Install ffmpeg
|
11 |
-
RUN apt-get update && apt-get install -y ffmpeg
|
12 |
-
|
13 |
-
# Create a virtual environment and activate it
|
14 |
-
RUN python -m venv /opt/venv
|
15 |
-
|
16 |
-
# Ensure the virtual environment is used
|
17 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
18 |
-
|
19 |
-
# Install any needed packages specified in requirements.txt
|
20 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
21 |
-
|
22 |
-
# Make port 7680 available to the world outside this container
|
23 |
-
EXPOSE 7680
|
24 |
-
|
25 |
-
COPY ./defaults.linux.json ./defaults.json
|
26 |
-
RUN chmod +x ./bin/*
|
27 |
-
|
28 |
-
RUN chmod +x ./setup.sh
|
29 |
-
RUN ./setup.sh
|
30 |
-
|
31 |
-
RUN mkdir webdl
|
32 |
-
|
33 |
-
# Run gunicorn when the container launches
|
34 |
-
CMD ["gunicorn", "-b", "0.0.0.0:7680", "beta.api.api:app"]
|
|
|
1 |
+
# Use an official Python runtime as a parent image
|
2 |
+
FROM python:3.12-slim
|
3 |
+
|
4 |
+
# Set the working directory in the container to /app
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Add the current directory contents into the container at /app
|
8 |
+
ADD . /app
|
9 |
+
|
10 |
+
# Install ffmpeg
|
11 |
+
RUN apt-get update && apt-get install -y ffmpeg
|
12 |
+
|
13 |
+
# Create a virtual environment and activate it
|
14 |
+
RUN python -m venv /opt/venv
|
15 |
+
|
16 |
+
# Ensure the virtual environment is used
|
17 |
+
ENV PATH="/opt/venv/bin:$PATH"
|
18 |
+
|
19 |
+
# Install any needed packages specified in requirements.txt
|
20 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
21 |
+
|
22 |
+
# Make port 7680 available to the world outside this container
|
23 |
+
EXPOSE 7680
|
24 |
+
|
25 |
+
COPY ./defaults.linux.json ./defaults.json
|
26 |
+
RUN chmod +x ./bin/*
|
27 |
+
|
28 |
+
RUN chmod +x ./setup.sh
|
29 |
+
RUN ./setup.sh
|
30 |
+
|
31 |
+
RUN mkdir webdl
|
32 |
+
|
33 |
+
# Run gunicorn when the container launches
|
34 |
+
CMD ["nohup","gunicorn", "-b", "0.0.0.0:7680", "beta.api.api:app", "&"]
|
README.md
CHANGED
@@ -1,102 +1,102 @@
|
|
1 |
-
---
|
2 |
-
title: pwdlAPIv1
|
3 |
-
emoji: 💻🐳
|
4 |
-
colorFrom: gray
|
5 |
-
colorTo: green
|
6 |
-
sdk: docker
|
7 |
-
pinned: false
|
8 |
-
suggested_storage: small
|
9 |
-
license: mit
|
10 |
-
---
|
11 |
-
# PhysicsWallah M3u8 Parser
|
12 |
-
|
13 |
-
This is a Python script that parses M3u8 files. It uses the argparse library to handle command-line arguments.
|
14 |
-
|
15 |
-
## Dependencies
|
16 |
-
|
17 |
-
The script requires the following executables to be available in the PATH or the user should provide the path to the executables:
|
18 |
-
|
19 |
-
- [ffmpeg](https://ffmpeg.org/download.html)
|
20 |
-
- [mp4decrypt](https://www.bento4.com/downloads/)
|
21 |
-
- [nm3](https://github.com/nilaoda/N_m3u8DL-RE) (renamed to nm3 in the script)
|
22 |
-
|
23 |
-
The script also requires the following Python libraries (which are listed in the `requirements.txt` file
|
24 |
-
|
25 |
-
|
26 |
-
- `requests`: A library for making HTTP requests. It abstracts the complexities of making requests behind a simple API, allowing you to send HTTP/1.1 requests.
|
27 |
-
|
28 |
-
- `colorama`: Makes ANSI escape character sequences work on Windows and Unix systems, allowing colored terminal text and cursor positioning.
|
29 |
-
|
30 |
-
- `argparse`: Provides a way to specify command line arguments and options the program is supposed to accept.
|
31 |
-
|
32 |
-
- `bs4` (BeautifulSoup4): A library for pulling data out of HTML and XML files. It provides Pythonic idioms for iterating, searching, and modifying the parse tree.
|
33 |
-
|
34 |
-
- `flask`: A micro web framework written in Python. It does not require particular tools or libraries, it has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions.
|
35 |
-
|
36 |
-
- `flask_socketio`: Gives Flask applications access to low latency bi-directional communications between the clients and the server. The client-side application can use any of the Socket.IO official clients libraries in Javascript, C++, Java and Swift, or any compatible client to establish a permanent connection to the server.
|
37 |
-
|
38 |
-
To install these dependencies, you would typically run `pip install -r requirements.txt` in your command line.
|
39 |
-
|
40 |
-
or if you want to install them individually, you can run the following commands:
|
41 |
-
|
42 |
-
`pip install requests colorama argparse bs4 flask flask_socketio`
|
43 |
-
|
44 |
-
## Usage
|
45 |
-
|
46 |
-
You can use the script with the following command-line arguments:
|
47 |
-
|
48 |
-
- `--csv-file`: Input csv file. Legacy Support too.
|
49 |
-
- `--id`: PhysicsWallh Video Id for single usage. Incompatible with --csv-file. Must be used with --name.
|
50 |
-
- `--name`: Name for the output file. Incompatible with --csv-file. Must be used with --url.
|
51 |
-
- `--dir`: Output Directory.
|
52 |
-
- `--verbose`: Verbose Output.
|
53 |
-
- `--version`: Shows the version of the program.
|
54 |
-
- `--simulate`: Simulate the download process. No files will be downloaded.
|
55 |
-
|
56 |
-
## Example
|
57 |
-
|
58 |
-
```bash
|
59 |
-
python pwdl.py --csv-file input.csv --dir ./output --verbose
|
60 |
-
```
|
61 |
-
|
62 |
-
This will parse the M3u8 files listed in `input.csv` and save the output in the `./output` directory. The `--verbose` flag is used to enable verbose output.
|
63 |
-
|
64 |
-
## Error Handling
|
65 |
-
|
66 |
-
The script has built-in error handling. If an error occurs during the parsing of a file, the script will print an error message and continue with the next file. If both csv file and id (or name) is provided, the script will exit with error code 3.
|
67 |
-
|
68 |
-
## User Preferences
|
69 |
-
|
70 |
-
User preferences can be loaded from a `defaults.json` file. These preferences include the temporary directory (`tmpDir`), verbosity of output (`verbose`), and whether to display a horizontal rule (`hr`). If these preferences are not set in the `defaults.json` file, the script will use default values.
|
71 |
-
|
72 |
-
## Simulation Mode
|
73 |
-
|
74 |
-
The script includes a simulation mode, which can be enabled with the `--simulate` flag. In this mode, the script will print the files that would be processed, but no files will be downloaded.
|
75 |
-
|
76 |
-
## Shell Mode
|
77 |
-
|
78 |
-
Still in beta
|
79 |
-
|
80 |
-
## Error Codes
|
81 |
-
|
82 |
-
|
83 |
-
| Error Name | Error Code | Error Message |
|
84 |
-
|----------------------------------|------------|-------------------------------------------------------|
|
85 |
-
| noError | 0 | None |
|
86 |
-
| defaultsNotFound | 1 | defaults.json not found. Exiting... |
|
87 |
-
| dependencyNotFound | 2 | Dependency not found. Exiting... |
|
88 |
-
| dependencyNotFoundInPrefs | 3 | Dependency not found in default settings. Exiting... |
|
89 |
-
| csvFileNotFound | 4 | CSV file {fileName} not found. Exiting... |
|
90 |
-
| downloadFailed | 5 | Download failed for {name} with id {id}. Exiting... |
|
91 |
-
| cantLoadFile | 22 | Can't load file {fileName} |
|
92 |
-
| requestFailedDueToUnknownReason | 24 | Request failed due to unknown reason. Status Code: {status_code} |
|
93 |
-
| keyExtractionFailed | 25 | Key extraction failed for id -> {id}. Exiting... |
|
94 |
-
| keyNotProvided | 26 | Key not provided. Exiting... |
|
95 |
-
| couldNotDownloadAudio | 27 | Could not download audio for id -> {id} Exiting... |
|
96 |
-
| couldNotDownloadVideo | 28 | Could not download video for {id} Exiting... |
|
97 |
-
| couldNotDecryptAudio | 29 | Could not decrypt audio. Exiting... |
|
98 |
-
| couldNotDecryptVideo | 30 | Could not decrypt video. Exiting... |
|
99 |
-
| methodPatched | 31 | Method is patched. Exiting... |
|
100 |
-
| couldNotExtractKey | 32 | Could not extract key. Exiting... |
|
101 |
-
|
102 |
Please note that the `{fileName}`, `{name}`, `{id}`, and `{status_code}` in the Error Message column are placeholders and will be replaced with actual values when the error occurs.
|
|
|
1 |
+
---
|
2 |
+
title: pwdlAPIv1
|
3 |
+
emoji: 💻🐳
|
4 |
+
colorFrom: gray
|
5 |
+
colorTo: green
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
suggested_storage: small
|
9 |
+
license: mit
|
10 |
+
---
|
11 |
+
# PhysicsWallah M3u8 Parser
|
12 |
+
|
13 |
+
This is a Python script that parses M3u8 files. It uses the argparse library to handle command-line arguments.
|
14 |
+
|
15 |
+
## Dependencies
|
16 |
+
|
17 |
+
The script requires the following executables to be available in the PATH or the user should provide the path to the executables:
|
18 |
+
|
19 |
+
- [ffmpeg](https://ffmpeg.org/download.html)
|
20 |
+
- [mp4decrypt](https://www.bento4.com/downloads/)
|
21 |
+
- [nm3](https://github.com/nilaoda/N_m3u8DL-RE) (renamed to nm3 in the script)
|
22 |
+
|
23 |
+
The script also requires the following Python libraries (which are listed in the `requirements.txt` file
|
24 |
+
|
25 |
+
|
26 |
+
- `requests`: A library for making HTTP requests. It abstracts the complexities of making requests behind a simple API, allowing you to send HTTP/1.1 requests.
|
27 |
+
|
28 |
+
- `colorama`: Makes ANSI escape character sequences work on Windows and Unix systems, allowing colored terminal text and cursor positioning.
|
29 |
+
|
30 |
+
- `argparse`: Provides a way to specify command line arguments and options the program is supposed to accept.
|
31 |
+
|
32 |
+
- `bs4` (BeautifulSoup4): A library for pulling data out of HTML and XML files. It provides Pythonic idioms for iterating, searching, and modifying the parse tree.
|
33 |
+
|
34 |
+
- `flask`: A micro web framework written in Python. It does not require particular tools or libraries, it has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions.
|
35 |
+
|
36 |
+
- `flask_socketio`: Gives Flask applications access to low latency bi-directional communications between the clients and the server. The client-side application can use any of the Socket.IO official clients libraries in Javascript, C++, Java and Swift, or any compatible client to establish a permanent connection to the server.
|
37 |
+
|
38 |
+
To install these dependencies, you would typically run `pip install -r requirements.txt` in your command line.
|
39 |
+
|
40 |
+
or if you want to install them individually, you can run the following commands:
|
41 |
+
|
42 |
+
`pip install requests colorama argparse bs4 flask flask_socketio`
|
43 |
+
|
44 |
+
## Usage
|
45 |
+
|
46 |
+
You can use the script with the following command-line arguments:
|
47 |
+
|
48 |
+
- `--csv-file`: Input csv file. Legacy Support too.
|
49 |
+
- `--id`: PhysicsWallh Video Id for single usage. Incompatible with --csv-file. Must be used with --name.
|
50 |
+
- `--name`: Name for the output file. Incompatible with --csv-file. Must be used with --url.
|
51 |
+
- `--dir`: Output Directory.
|
52 |
+
- `--verbose`: Verbose Output.
|
53 |
+
- `--version`: Shows the version of the program.
|
54 |
+
- `--simulate`: Simulate the download process. No files will be downloaded.
|
55 |
+
|
56 |
+
## Example
|
57 |
+
|
58 |
+
```bash
|
59 |
+
python pwdl.py --csv-file input.csv --dir ./output --verbose
|
60 |
+
```
|
61 |
+
|
62 |
+
This will parse the M3u8 files listed in `input.csv` and save the output in the `./output` directory. The `--verbose` flag is used to enable verbose output.
|
63 |
+
|
64 |
+
## Error Handling
|
65 |
+
|
66 |
+
The script has built-in error handling. If an error occurs during the parsing of a file, the script will print an error message and continue with the next file. If both csv file and id (or name) is provided, the script will exit with error code 3.
|
67 |
+
|
68 |
+
## User Preferences
|
69 |
+
|
70 |
+
User preferences can be loaded from a `defaults.json` file. These preferences include the temporary directory (`tmpDir`), verbosity of output (`verbose`), and whether to display a horizontal rule (`hr`). If these preferences are not set in the `defaults.json` file, the script will use default values.
|
71 |
+
|
72 |
+
## Simulation Mode
|
73 |
+
|
74 |
+
The script includes a simulation mode, which can be enabled with the `--simulate` flag. In this mode, the script will print the files that would be processed, but no files will be downloaded.
|
75 |
+
|
76 |
+
## Shell Mode
|
77 |
+
|
78 |
+
Still in beta
|
79 |
+
|
80 |
+
## Error Codes
|
81 |
+
|
82 |
+
|
83 |
+
| Error Name | Error Code | Error Message |
|
84 |
+
|----------------------------------|------------|-------------------------------------------------------|
|
85 |
+
| noError | 0 | None |
|
86 |
+
| defaultsNotFound | 1 | defaults.json not found. Exiting... |
|
87 |
+
| dependencyNotFound | 2 | Dependency not found. Exiting... |
|
88 |
+
| dependencyNotFoundInPrefs | 3 | Dependency not found in default settings. Exiting... |
|
89 |
+
| csvFileNotFound | 4 | CSV file {fileName} not found. Exiting... |
|
90 |
+
| downloadFailed | 5 | Download failed for {name} with id {id}. Exiting... |
|
91 |
+
| cantLoadFile | 22 | Can't load file {fileName} |
|
92 |
+
| requestFailedDueToUnknownReason | 24 | Request failed due to unknown reason. Status Code: {status_code} |
|
93 |
+
| keyExtractionFailed | 25 | Key extraction failed for id -> {id}. Exiting... |
|
94 |
+
| keyNotProvided | 26 | Key not provided. Exiting... |
|
95 |
+
| couldNotDownloadAudio | 27 | Could not download audio for id -> {id} Exiting... |
|
96 |
+
| couldNotDownloadVideo | 28 | Could not download video for {id} Exiting... |
|
97 |
+
| couldNotDecryptAudio | 29 | Could not decrypt audio. Exiting... |
|
98 |
+
| couldNotDecryptVideo | 30 | Could not decrypt video. Exiting... |
|
99 |
+
| methodPatched | 31 | Method is patched. Exiting... |
|
100 |
+
| couldNotExtractKey | 32 | Could not extract key. Exiting... |
|
101 |
+
|
102 |
Please note that the `{fileName}`, `{name}`, `{id}`, and `{status_code}` in the Error Message column are placeholders and will be replaced with actual values when the error occurs.
|
beta/api/api.py
CHANGED
@@ -1,68 +1,68 @@
|
|
1 |
-
import os
|
2 |
-
import time
|
3 |
-
from flask import Flask, jsonify, request, send_from_directory, send_file
|
4 |
-
from beta.api.task_manager import TaskManager # Assuming the TaskManager class is in task_manager.py
|
5 |
-
from mainLogic.main import Main
|
6 |
-
from mainLogic.startup.checkup import CheckState
|
7 |
-
from mainLogic.utils.glv import Global
|
8 |
-
from mainLogic.utils.basicUtils import BasicUtils
|
9 |
-
|
10 |
-
app = Flask(__name__)
|
11 |
-
task_manager = TaskManager()
|
12 |
-
|
13 |
-
OUT_DIR = Global.api_webdl_directory
|
14 |
-
|
15 |
-
try:
|
16 |
-
if not os.path.exists(OUT_DIR): os.makedirs(OUT_DIR)
|
17 |
-
except Exception as e:
|
18 |
-
Global.errprint(f"Could not create output directory {OUT_DIR}")
|
19 |
-
Global.sprint(f"Defaulting to './' ")
|
20 |
-
Global.errprint(f"Error: {e}")
|
21 |
-
OUT_DIR = './'
|
22 |
-
|
23 |
-
def setup_directory():
|
24 |
-
pass
|
25 |
-
def download_pw_video(task_id, id, name, out_dir, progress_callback):
|
26 |
-
|
27 |
-
print(f"Downloading {name} with id {id} to {out_dir}")
|
28 |
-
|
29 |
-
ch = CheckState()
|
30 |
-
prefs = ch.checkup(Global.EXECUTABLES, directory="./", verbose=True)
|
31 |
-
nm3 = prefs['nm3']
|
32 |
-
ffmpeg = prefs['ffmpeg']
|
33 |
-
mp4d = prefs['mp4decrypt']
|
34 |
-
verbose = True
|
35 |
-
Main(id=id, name=f"{name}-{task_id}", directory=out_dir, tmpDir="/*auto*/", nm3Path=nm3, ffmpeg=ffmpeg, mp4d=mp4d, verbose=verbose, progress_callback=progress_callback).process()
|
36 |
-
|
37 |
-
|
38 |
-
@app.route('/create_task', methods=['POST'])
|
39 |
-
def create_task():
|
40 |
-
data = request.json
|
41 |
-
id = data.get('id')
|
42 |
-
name = data.get('name')
|
43 |
-
|
44 |
-
if not id or not name:
|
45 |
-
return jsonify({'error': 'id and name are required'}), 400
|
46 |
-
|
47 |
-
task_id = task_manager.create_task(download_pw_video, id, name, OUT_DIR)
|
48 |
-
return jsonify({'task_id': task_id}), 202
|
49 |
-
|
50 |
-
@app.route('/progress/<task_id>', methods=['GET'])
|
51 |
-
def get_progress(task_id):
|
52 |
-
progress = task_manager.get_progress(task_id)
|
53 |
-
return jsonify(progress), 200
|
54 |
-
|
55 |
-
@app.route('/get-file/<task_id>/<name>', methods=['GET'])
|
56 |
-
def get_file(task_id,name):
|
57 |
-
file = BasicUtils.abspath(f"{OUT_DIR}/{name}-{task_id}.mp4")
|
58 |
-
if not os.path.exists(file):
|
59 |
-
return jsonify({'error': 'file not found'}), 404
|
60 |
-
return send_file( f"{file}", as_attachment=True), 200
|
61 |
-
|
62 |
-
|
63 |
-
@app.route('/', methods=['GET'])
|
64 |
-
def index():
|
65 |
-
return jsonify({'message': 'Hello, World!'}), 200
|
66 |
-
|
67 |
-
# if __name__ == '__main__':
|
68 |
-
# app.run(debug=True)
|
|
|
1 |
+
import os
|
2 |
+
import time
|
3 |
+
from flask import Flask, jsonify, request, send_from_directory, send_file
|
4 |
+
from beta.api.task_manager import TaskManager # Assuming the TaskManager class is in task_manager.py
|
5 |
+
from mainLogic.main import Main
|
6 |
+
from mainLogic.startup.checkup import CheckState
|
7 |
+
from mainLogic.utils.glv import Global
|
8 |
+
from mainLogic.utils.basicUtils import BasicUtils
|
9 |
+
|
10 |
+
app = Flask(__name__)
|
11 |
+
task_manager = TaskManager()
|
12 |
+
|
13 |
+
OUT_DIR = Global.api_webdl_directory
|
14 |
+
|
15 |
+
try:
|
16 |
+
if not os.path.exists(OUT_DIR): os.makedirs(OUT_DIR)
|
17 |
+
except Exception as e:
|
18 |
+
Global.errprint(f"Could not create output directory {OUT_DIR}")
|
19 |
+
Global.sprint(f"Defaulting to './' ")
|
20 |
+
Global.errprint(f"Error: {e}")
|
21 |
+
OUT_DIR = './'
|
22 |
+
|
23 |
+
def setup_directory():
|
24 |
+
pass
|
25 |
+
def download_pw_video(task_id, id, name, out_dir, progress_callback):
|
26 |
+
|
27 |
+
print(f"Downloading {name} with id {id} to {out_dir}")
|
28 |
+
|
29 |
+
ch = CheckState()
|
30 |
+
prefs = ch.checkup(Global.EXECUTABLES, directory="./", verbose=True)
|
31 |
+
nm3 = prefs['nm3']
|
32 |
+
ffmpeg = prefs['ffmpeg']
|
33 |
+
mp4d = prefs['mp4decrypt']
|
34 |
+
verbose = True
|
35 |
+
Main(id=id, name=f"{name}-{task_id}", directory=out_dir, tmpDir="/*auto*/", nm3Path=nm3, ffmpeg=ffmpeg, mp4d=mp4d, verbose=verbose, progress_callback=progress_callback).process()
|
36 |
+
|
37 |
+
|
38 |
+
@app.route('/create_task', methods=['POST'])
|
39 |
+
def create_task():
|
40 |
+
data = request.json
|
41 |
+
id = data.get('id')
|
42 |
+
name = data.get('name')
|
43 |
+
|
44 |
+
if not id or not name:
|
45 |
+
return jsonify({'error': 'id and name are required'}), 400
|
46 |
+
|
47 |
+
task_id = task_manager.create_task(download_pw_video, id, name, OUT_DIR)
|
48 |
+
return jsonify({'task_id': task_id}), 202
|
49 |
+
|
50 |
+
@app.route('/progress/<task_id>', methods=['GET'])
|
51 |
+
def get_progress(task_id):
|
52 |
+
progress = task_manager.get_progress(task_id)
|
53 |
+
return jsonify(progress), 200
|
54 |
+
|
55 |
+
@app.route('/get-file/<task_id>/<name>', methods=['GET'])
|
56 |
+
def get_file(task_id,name):
|
57 |
+
file = BasicUtils.abspath(f"{OUT_DIR}/{name}-{task_id}.mp4")
|
58 |
+
if not os.path.exists(file):
|
59 |
+
return jsonify({'error': 'file not found'}), 404
|
60 |
+
return send_file( f"{file}", as_attachment=True), 200
|
61 |
+
|
62 |
+
|
63 |
+
@app.route('/', methods=['GET'])
|
64 |
+
def index():
|
65 |
+
return jsonify({'message': 'Hello, World!'}), 200
|
66 |
+
|
67 |
+
# if __name__ == '__main__':
|
68 |
+
# app.run(debug=True)
|
beta/api/task_manager.py
CHANGED
@@ -1,42 +1,42 @@
|
|
1 |
-
import threading
|
2 |
-
import time
|
3 |
-
import uuid
|
4 |
-
|
5 |
-
class TaskManager:
|
6 |
-
|
7 |
-
def handle_completion (self, task_id):
|
8 |
-
print(f"Task {task_id} completed")
|
9 |
-
self.tasks[task_id]['status'] = 'completed'
|
10 |
-
|
11 |
-
on_task_complete = handle_completion
|
12 |
-
|
13 |
-
def __init__(self):
|
14 |
-
self.tasks = {}
|
15 |
-
self.lock = threading.Lock()
|
16 |
-
|
17 |
-
def create_task(self, target, *args):
|
18 |
-
task_id = str(uuid.uuid4())
|
19 |
-
thread = threading.Thread(target=self._run_task, args=(task_id, target, *args))
|
20 |
-
with self.lock:
|
21 |
-
self.tasks[task_id] = {'progress': "0", 'status': 'running'}
|
22 |
-
thread.start()
|
23 |
-
return task_id
|
24 |
-
|
25 |
-
def _run_task(self, task_id, target, *args):
|
26 |
-
try:
|
27 |
-
target(task_id,*args, progress_callback=lambda progress: self._update_progress(task_id, progress))
|
28 |
-
with self.lock:
|
29 |
-
self.tasks[task_id]['status'] = 'completed'
|
30 |
-
except Exception as e:
|
31 |
-
with self.lock:
|
32 |
-
self.tasks[task_id]['status'] = 'failed'
|
33 |
-
self.tasks[task_id]['error'] = str(e)
|
34 |
-
|
35 |
-
def _update_progress(self, task_id, progress):
|
36 |
-
with self.lock:
|
37 |
-
if task_id in self.tasks:
|
38 |
-
self.tasks[task_id]['progress'] = progress
|
39 |
-
|
40 |
-
def get_progress(self, task_id):
|
41 |
-
with self.lock:
|
42 |
-
return self.tasks.get(task_id, {'status': 'not found'})
|
|
|
1 |
+
import threading
|
2 |
+
import time
|
3 |
+
import uuid
|
4 |
+
|
5 |
+
class TaskManager:
|
6 |
+
|
7 |
+
def handle_completion (self, task_id):
|
8 |
+
print(f"Task {task_id} completed")
|
9 |
+
self.tasks[task_id]['status'] = 'completed'
|
10 |
+
|
11 |
+
on_task_complete = handle_completion
|
12 |
+
|
13 |
+
def __init__(self):
|
14 |
+
self.tasks = {}
|
15 |
+
self.lock = threading.Lock()
|
16 |
+
|
17 |
+
def create_task(self, target, *args):
|
18 |
+
task_id = str(uuid.uuid4())
|
19 |
+
thread = threading.Thread(target=self._run_task, args=(task_id, target, *args))
|
20 |
+
with self.lock:
|
21 |
+
self.tasks[task_id] = {'progress': "0", 'status': 'running'}
|
22 |
+
thread.start()
|
23 |
+
return task_id
|
24 |
+
|
25 |
+
def _run_task(self, task_id, target, *args):
|
26 |
+
try:
|
27 |
+
target(task_id,*args, progress_callback=lambda progress: self._update_progress(task_id, progress))
|
28 |
+
with self.lock:
|
29 |
+
self.tasks[task_id]['status'] = 'completed'
|
30 |
+
except Exception as e:
|
31 |
+
with self.lock:
|
32 |
+
self.tasks[task_id]['status'] = 'failed'
|
33 |
+
self.tasks[task_id]['error'] = str(e)
|
34 |
+
|
35 |
+
def _update_progress(self, task_id, progress):
|
36 |
+
with self.lock:
|
37 |
+
if task_id in self.tasks:
|
38 |
+
self.tasks[task_id]['progress'] = progress
|
39 |
+
|
40 |
+
def get_progress(self, task_id):
|
41 |
+
with self.lock:
|
42 |
+
return self.tasks.get(task_id, {'status': 'not found'})
|
beta/shellLogic/HandleBasicCMDUtils.py
CHANGED
@@ -1,41 +1,41 @@
|
|
1 |
-
import sys
|
2 |
-
from beta.shellLogic import simpleParser
|
3 |
-
from mainLogic.utils.os2 import SysFunc
|
4 |
-
|
5 |
-
os2 = SysFunc()
|
6 |
-
|
7 |
-
|
8 |
-
class HandleBasicCMDUtils:
|
9 |
-
# basic class for handling basic commands
|
10 |
-
# every such class must have a method to parse command (regex based) a help for each command handled by the class
|
11 |
-
|
12 |
-
def __init__(self):
|
13 |
-
self.commandList = {
|
14 |
-
"cls":
|
15 |
-
{
|
16 |
-
"desc": "Clear the screen",
|
17 |
-
"regex": r"cls",
|
18 |
-
"func": self.cls
|
19 |
-
},
|
20 |
-
"exit":
|
21 |
-
{
|
22 |
-
"desc": "Exit the shell",
|
23 |
-
"regex": r"exit",
|
24 |
-
"func": self.exit_shell
|
25 |
-
},
|
26 |
-
}
|
27 |
-
|
28 |
-
def cls(self,args=[]):
|
29 |
-
os2.clear()
|
30 |
-
if args: print(args)
|
31 |
-
|
32 |
-
def exit_shell(self,args=[]):
|
33 |
-
sys.exit(10)
|
34 |
-
|
35 |
-
def parseAndRun(self, command,args=[]):
|
36 |
-
# for key in self.commandList:
|
37 |
-
# if re.match(self.commandList[key]["regex"], command):
|
38 |
-
# self.commandList[key]["func"]()
|
39 |
-
# return
|
40 |
-
# raise logicError.commandNotFound(command)
|
41 |
-
simpleParser.parseAndRun(self.commandList, command, args)
|
|
|
1 |
+
import sys
|
2 |
+
from beta.shellLogic import simpleParser
|
3 |
+
from mainLogic.utils.os2 import SysFunc
|
4 |
+
|
5 |
+
os2 = SysFunc()
|
6 |
+
|
7 |
+
|
8 |
+
class HandleBasicCMDUtils:
|
9 |
+
# basic class for handling basic commands
|
10 |
+
# every such class must have a method to parse command (regex based) a help for each command handled by the class
|
11 |
+
|
12 |
+
def __init__(self):
|
13 |
+
self.commandList = {
|
14 |
+
"cls":
|
15 |
+
{
|
16 |
+
"desc": "Clear the screen",
|
17 |
+
"regex": r"cls",
|
18 |
+
"func": self.cls
|
19 |
+
},
|
20 |
+
"exit":
|
21 |
+
{
|
22 |
+
"desc": "Exit the shell",
|
23 |
+
"regex": r"exit",
|
24 |
+
"func": self.exit_shell
|
25 |
+
},
|
26 |
+
}
|
27 |
+
|
28 |
+
def cls(self,args=[]):
|
29 |
+
os2.clear()
|
30 |
+
if args: print(args)
|
31 |
+
|
32 |
+
def exit_shell(self,args=[]):
|
33 |
+
sys.exit(10)
|
34 |
+
|
35 |
+
def parseAndRun(self, command,args=[]):
|
36 |
+
# for key in self.commandList:
|
37 |
+
# if re.match(self.commandList[key]["regex"], command):
|
38 |
+
# self.commandList[key]["func"]()
|
39 |
+
# return
|
40 |
+
# raise logicError.commandNotFound(command)
|
41 |
+
simpleParser.parseAndRun(self.commandList, command, args)
|
beta/shellLogic/HandleKeyAndAvailiblity.py
CHANGED
@@ -1,34 +1,34 @@
|
|
1 |
-
from mainLogic.big4.decrypt.key import getKey
|
2 |
-
from beta.shellLogic import simpleParser
|
3 |
-
|
4 |
-
|
5 |
-
class HandleKeyAndAvailiblity:
|
6 |
-
|
7 |
-
def __init__(self):
|
8 |
-
self.commandList = {
|
9 |
-
"get_key":{
|
10 |
-
"regex": r"(get_key|key)",
|
11 |
-
"func": self.get_key,
|
12 |
-
},
|
13 |
-
"check":{
|
14 |
-
"func": self.check
|
15 |
-
}
|
16 |
-
|
17 |
-
}
|
18 |
-
|
19 |
-
def get_key(self,args=[]):
|
20 |
-
if args:
|
21 |
-
getKey(args[0])
|
22 |
-
|
23 |
-
def check(self,args=[]):
|
24 |
-
print("Checking the availiblity of the key...")
|
25 |
-
if args:
|
26 |
-
if getKey(args[0],verbose=False):
|
27 |
-
print("Key is available")
|
28 |
-
else:
|
29 |
-
print("Key is not available")
|
30 |
-
else:
|
31 |
-
print("Please provide a key to check")
|
32 |
-
|
33 |
-
def parseAndRun(self,command,args=[]):
|
34 |
-
simpleParser.parseAndRun(self.commandList, command, args)
|
|
|
1 |
+
from mainLogic.big4.decrypt.key import getKey
|
2 |
+
from beta.shellLogic import simpleParser
|
3 |
+
|
4 |
+
|
5 |
+
class HandleKeyAndAvailiblity:
|
6 |
+
|
7 |
+
def __init__(self):
|
8 |
+
self.commandList = {
|
9 |
+
"get_key":{
|
10 |
+
"regex": r"(get_key|key)",
|
11 |
+
"func": self.get_key,
|
12 |
+
},
|
13 |
+
"check":{
|
14 |
+
"func": self.check
|
15 |
+
}
|
16 |
+
|
17 |
+
}
|
18 |
+
|
19 |
+
def get_key(self,args=[]):
|
20 |
+
if args:
|
21 |
+
getKey(args[0])
|
22 |
+
|
23 |
+
def check(self,args=[]):
|
24 |
+
print("Checking the availiblity of the key...")
|
25 |
+
if args:
|
26 |
+
if getKey(args[0],verbose=False):
|
27 |
+
print("Key is available")
|
28 |
+
else:
|
29 |
+
print("Key is not available")
|
30 |
+
else:
|
31 |
+
print("Please provide a key to check")
|
32 |
+
|
33 |
+
def parseAndRun(self,command,args=[]):
|
34 |
+
simpleParser.parseAndRun(self.commandList, command, args)
|
beta/shellLogic/HandleShellDL.py
CHANGED
@@ -1,62 +1,62 @@
|
|
1 |
-
from mainLogic.big4.dl import DL
|
2 |
-
from mainLogic.startup.checkup import CheckState
|
3 |
-
from mainLogic.utils.glv import Global
|
4 |
-
from mainLogic.main import Main
|
5 |
-
from beta.shellLogic import simpleParser
|
6 |
-
|
7 |
-
|
8 |
-
class HandleShellDL:
|
9 |
-
|
10 |
-
def __init__(self):
|
11 |
-
self.commandList = {
|
12 |
-
"edl":{
|
13 |
-
"func": self.edownload
|
14 |
-
},
|
15 |
-
"dl":{
|
16 |
-
"func": self.download
|
17 |
-
}
|
18 |
-
}
|
19 |
-
|
20 |
-
def edownload(self,args=[]):
|
21 |
-
# print(args)
|
22 |
-
if not args or len(args) < 2:
|
23 |
-
print("Please provide a name and id")
|
24 |
-
return
|
25 |
-
|
26 |
-
name = args[0]
|
27 |
-
id = args[1]
|
28 |
-
|
29 |
-
dl = DL()
|
30 |
-
ch =CheckState()
|
31 |
-
prefs = ch.checkup(Global.EXECUTABLES,verbose=False)
|
32 |
-
dl.downloadAudioAndVideo(name=name,
|
33 |
-
id=id,
|
34 |
-
directory='./',
|
35 |
-
nm3Path=prefs['nm3'],
|
36 |
-
verbose=False if not 'verbose' in prefs else prefs['verbose'],
|
37 |
-
)
|
38 |
-
|
39 |
-
def download(self,args=[]):
|
40 |
-
if not args or len(args) < 2:
|
41 |
-
print("Please provide a name and id")
|
42 |
-
return
|
43 |
-
|
44 |
-
name = args[0]
|
45 |
-
id = args[1]
|
46 |
-
|
47 |
-
ch = CheckState()
|
48 |
-
prefs = ch.checkup(Global.EXECUTABLES,verbose=False)
|
49 |
-
|
50 |
-
Main(id=id,
|
51 |
-
name=name,
|
52 |
-
directory='./',
|
53 |
-
nm3Path=prefs['nm3'],
|
54 |
-
mp4d=prefs['mp4decrypt'],
|
55 |
-
ffmpeg=prefs['ffmpeg']
|
56 |
-
).process()
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
def parseAndRun(self,command,args=[]):
|
61 |
-
simpleParser.parseAndRun(self.commandList, command, args)
|
62 |
-
|
|
|
1 |
+
from mainLogic.big4.dl import DL
|
2 |
+
from mainLogic.startup.checkup import CheckState
|
3 |
+
from mainLogic.utils.glv import Global
|
4 |
+
from mainLogic.main import Main
|
5 |
+
from beta.shellLogic import simpleParser
|
6 |
+
|
7 |
+
|
8 |
+
class HandleShellDL:
|
9 |
+
|
10 |
+
def __init__(self):
|
11 |
+
self.commandList = {
|
12 |
+
"edl":{
|
13 |
+
"func": self.edownload
|
14 |
+
},
|
15 |
+
"dl":{
|
16 |
+
"func": self.download
|
17 |
+
}
|
18 |
+
}
|
19 |
+
|
20 |
+
def edownload(self,args=[]):
|
21 |
+
# print(args)
|
22 |
+
if not args or len(args) < 2:
|
23 |
+
print("Please provide a name and id")
|
24 |
+
return
|
25 |
+
|
26 |
+
name = args[0]
|
27 |
+
id = args[1]
|
28 |
+
|
29 |
+
dl = DL()
|
30 |
+
ch =CheckState()
|
31 |
+
prefs = ch.checkup(Global.EXECUTABLES,verbose=False)
|
32 |
+
dl.downloadAudioAndVideo(name=name,
|
33 |
+
id=id,
|
34 |
+
directory='./',
|
35 |
+
nm3Path=prefs['nm3'],
|
36 |
+
verbose=False if not 'verbose' in prefs else prefs['verbose'],
|
37 |
+
)
|
38 |
+
|
39 |
+
def download(self,args=[]):
|
40 |
+
if not args or len(args) < 2:
|
41 |
+
print("Please provide a name and id")
|
42 |
+
return
|
43 |
+
|
44 |
+
name = args[0]
|
45 |
+
id = args[1]
|
46 |
+
|
47 |
+
ch = CheckState()
|
48 |
+
prefs = ch.checkup(Global.EXECUTABLES,verbose=False)
|
49 |
+
|
50 |
+
Main(id=id,
|
51 |
+
name=name,
|
52 |
+
directory='./',
|
53 |
+
nm3Path=prefs['nm3'],
|
54 |
+
mp4d=prefs['mp4decrypt'],
|
55 |
+
ffmpeg=prefs['ffmpeg']
|
56 |
+
).process()
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
def parseAndRun(self,command,args=[]):
|
61 |
+
simpleParser.parseAndRun(self.commandList, command, args)
|
62 |
+
|
beta/shellLogic/logic.py
CHANGED
@@ -1,24 +1,24 @@
|
|
1 |
-
from mainLogic.utils.os2 import SysFunc
|
2 |
-
from beta.shellLogic.HandleBasicCMDUtils import HandleBasicCMDUtils
|
3 |
-
from beta.shellLogic.HandleKeyAndAvailiblity import HandleKeyAndAvailiblity
|
4 |
-
from beta.shellLogic.HandleShellDL import HandleShellDL
|
5 |
-
|
6 |
-
os2 = SysFunc()
|
7 |
-
f1 = HandleBasicCMDUtils()
|
8 |
-
key_utils = HandleKeyAndAvailiblity()
|
9 |
-
dl_utils = HandleShellDL()
|
10 |
-
|
11 |
-
commands_available={
|
12 |
-
# command: [location_of_function,help_class]
|
13 |
-
"exit": [f1.parseAndRun,""],
|
14 |
-
"cls" : [f1.parseAndRun,""],
|
15 |
-
"get_key":[key_utils.parseAndRun,""],
|
16 |
-
"check": [key_utils.parseAndRun,""],
|
17 |
-
"edl": [dl_utils.parseAndRun,""],
|
18 |
-
"dl":[dl_utils.parseAndRun,""]
|
19 |
-
|
20 |
-
}
|
21 |
-
|
22 |
-
def execute(command,args=[]):
|
23 |
-
if command in commands_available:
|
24 |
commands_available[command][0](command,args)
|
|
|
1 |
+
from mainLogic.utils.os2 import SysFunc
|
2 |
+
from beta.shellLogic.HandleBasicCMDUtils import HandleBasicCMDUtils
|
3 |
+
from beta.shellLogic.HandleKeyAndAvailiblity import HandleKeyAndAvailiblity
|
4 |
+
from beta.shellLogic.HandleShellDL import HandleShellDL
|
5 |
+
|
6 |
+
os2 = SysFunc()
|
7 |
+
f1 = HandleBasicCMDUtils()
|
8 |
+
key_utils = HandleKeyAndAvailiblity()
|
9 |
+
dl_utils = HandleShellDL()
|
10 |
+
|
11 |
+
commands_available={
|
12 |
+
# command: [location_of_function,help_class]
|
13 |
+
"exit": [f1.parseAndRun,""],
|
14 |
+
"cls" : [f1.parseAndRun,""],
|
15 |
+
"get_key":[key_utils.parseAndRun,""],
|
16 |
+
"check": [key_utils.parseAndRun,""],
|
17 |
+
"edl": [dl_utils.parseAndRun,""],
|
18 |
+
"dl":[dl_utils.parseAndRun,""]
|
19 |
+
|
20 |
+
}
|
21 |
+
|
22 |
+
def execute(command,args=[]):
|
23 |
+
if command in commands_available:
|
24 |
commands_available[command][0](command,args)
|
beta/shellLogic/logicError.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
class commandNotFound(Exception):
|
2 |
-
def __init__(self, command):
|
3 |
-
self.command = command
|
4 |
-
|
5 |
-
def __str__(self):
|
6 |
return f"Command '{self.command}' not found"
|
|
|
1 |
+
class commandNotFound(Exception):
|
2 |
+
def __init__(self, command):
|
3 |
+
self.command = command
|
4 |
+
|
5 |
+
def __str__(self):
|
6 |
return f"Command '{self.command}' not found"
|
beta/shellLogic/shell.py
CHANGED
@@ -1,53 +1,53 @@
|
|
1 |
-
from prompt_toolkit import PromptSession
|
2 |
-
from mainLogic.utils.glv import Global
|
3 |
-
from mainLogic.startup.checkup import CheckState
|
4 |
-
import json
|
5 |
-
from mainLogic.utils.os2 import SysFunc
|
6 |
-
|
7 |
-
glv = Global()
|
8 |
-
EXECUTABLES = glv.EXECUTABLES
|
9 |
-
os2 = SysFunc()
|
10 |
-
|
11 |
-
# Initialize Prompt Toolkit session
|
12 |
-
session = PromptSession()
|
13 |
-
|
14 |
-
def main():
|
15 |
-
# Perform checkup and get preferences
|
16 |
-
# Hardcoded verbose to False
|
17 |
-
state = CheckState().checkup(EXECUTABLES, False)
|
18 |
-
prefs = state['prefs']
|
19 |
-
|
20 |
-
# Convert preferences to JSON string for display
|
21 |
-
prefs_json = json.dumps(prefs, indent=4)
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
# Define available commands for auto-completion
|
26 |
-
#commands = ['show_prefs', 'exit']
|
27 |
-
#completer = WordCompleter(commands, ignore_case=True)
|
28 |
-
|
29 |
-
from beta.shellLogic import logic
|
30 |
-
|
31 |
-
# Command-line interface loop
|
32 |
-
while True:
|
33 |
-
try:
|
34 |
-
user_input = session.prompt('|pwdl> ',)
|
35 |
-
|
36 |
-
# just in case the user hits enter without typing anything
|
37 |
-
if not user_input: continue
|
38 |
-
|
39 |
-
command = user_input.split()[0]
|
40 |
-
args = user_input.split()[1:]
|
41 |
-
if not args: args = []
|
42 |
-
|
43 |
-
logic.execute(command, args)
|
44 |
-
|
45 |
-
except KeyboardInterrupt:
|
46 |
-
continue
|
47 |
-
except EOFError:
|
48 |
-
break
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
if __name__ == "__main__":
|
53 |
-
main()
|
|
|
1 |
+
from prompt_toolkit import PromptSession
|
2 |
+
from mainLogic.utils.glv import Global
|
3 |
+
from mainLogic.startup.checkup import CheckState
|
4 |
+
import json
|
5 |
+
from mainLogic.utils.os2 import SysFunc
|
6 |
+
|
7 |
+
glv = Global()
|
8 |
+
EXECUTABLES = glv.EXECUTABLES
|
9 |
+
os2 = SysFunc()
|
10 |
+
|
11 |
+
# Initialize Prompt Toolkit session
|
12 |
+
session = PromptSession()
|
13 |
+
|
14 |
+
def main():
|
15 |
+
# Perform checkup and get preferences
|
16 |
+
# Hardcoded verbose to False
|
17 |
+
state = CheckState().checkup(EXECUTABLES, False)
|
18 |
+
prefs = state['prefs']
|
19 |
+
|
20 |
+
# Convert preferences to JSON string for display
|
21 |
+
prefs_json = json.dumps(prefs, indent=4)
|
22 |
+
|
23 |
+
|
24 |
+
|
25 |
+
# Define available commands for auto-completion
|
26 |
+
#commands = ['show_prefs', 'exit']
|
27 |
+
#completer = WordCompleter(commands, ignore_case=True)
|
28 |
+
|
29 |
+
from beta.shellLogic import logic
|
30 |
+
|
31 |
+
# Command-line interface loop
|
32 |
+
while True:
|
33 |
+
try:
|
34 |
+
user_input = session.prompt('|pwdl> ',)
|
35 |
+
|
36 |
+
# just in case the user hits enter without typing anything
|
37 |
+
if not user_input: continue
|
38 |
+
|
39 |
+
command = user_input.split()[0]
|
40 |
+
args = user_input.split()[1:]
|
41 |
+
if not args: args = []
|
42 |
+
|
43 |
+
logic.execute(command, args)
|
44 |
+
|
45 |
+
except KeyboardInterrupt:
|
46 |
+
continue
|
47 |
+
except EOFError:
|
48 |
+
break
|
49 |
+
|
50 |
+
|
51 |
+
|
52 |
+
if __name__ == "__main__":
|
53 |
+
main()
|
beta/shellLogic/simpleParser.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
def parseAndRun(commandlist,command,args=[],obj=None):
|
2 |
-
if command in commandlist: func = commandlist[command]["func"]
|
3 |
-
|
4 |
-
if not func: return
|
5 |
-
|
6 |
func(args)
|
|
|
1 |
+
def parseAndRun(commandlist,command,args=[],obj=None):
|
2 |
+
if command in commandlist: func = commandlist[command]["func"]
|
3 |
+
|
4 |
+
if not func: return
|
5 |
+
|
6 |
func(args)
|
beta/util.py
CHANGED
@@ -1,47 +1,47 @@
|
|
1 |
-
import json
|
2 |
-
|
3 |
-
csv_file = input('Enter CSV file:')
|
4 |
-
|
5 |
-
json = ''
|
6 |
-
|
7 |
-
x = json.loads(json)
|
8 |
-
|
9 |
-
import re
|
10 |
-
|
11 |
-
def extract_uuid(text):
|
12 |
-
"""
|
13 |
-
Extracts UUIDs from a string using a regular expression.
|
14 |
-
|
15 |
-
Args:
|
16 |
-
text: The string to search for UUIDs.
|
17 |
-
|
18 |
-
Returns:
|
19 |
-
A list of extracted UUIDs, or an empty list if none are found.
|
20 |
-
"""
|
21 |
-
pattern = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
22 |
-
matches = re.findall(pattern, text)
|
23 |
-
return matches
|
24 |
-
|
25 |
-
def generate_safe_filename(filename):
|
26 |
-
"""
|
27 |
-
Converts a filename to a safe format containing only alphabets, periods (.), and colons (:).
|
28 |
-
|
29 |
-
Args:
|
30 |
-
filename: The original filename to be converted.
|
31 |
-
|
32 |
-
Returns:
|
33 |
-
A safe filename string with only allowed characters.
|
34 |
-
"""
|
35 |
-
# Replace all characters except alphabets, periods, and colons with underscores
|
36 |
-
safe_filename = re.sub(r"[^\w\.\:]", "_", filename)
|
37 |
-
return safe_filename
|
38 |
-
|
39 |
-
lines = []
|
40 |
-
|
41 |
-
for videos in x['data']:
|
42 |
-
|
43 |
-
line = f"{generate_safe_filename(videos['title'])},{extract_uuid(videos['content'][0]['videoUrl'])[0]}"
|
44 |
-
lines.append(line)
|
45 |
-
|
46 |
-
with open(csv_file, 'w') as f:
|
47 |
f.write('\n'.join(lines))
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
csv_file = input('Enter CSV file:')
|
4 |
+
|
5 |
+
json = ''
|
6 |
+
|
7 |
+
x = json.loads(json)
|
8 |
+
|
9 |
+
import re
|
10 |
+
|
11 |
+
def extract_uuid(text):
|
12 |
+
"""
|
13 |
+
Extracts UUIDs from a string using a regular expression.
|
14 |
+
|
15 |
+
Args:
|
16 |
+
text: The string to search for UUIDs.
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
A list of extracted UUIDs, or an empty list if none are found.
|
20 |
+
"""
|
21 |
+
pattern = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
22 |
+
matches = re.findall(pattern, text)
|
23 |
+
return matches
|
24 |
+
|
25 |
+
def generate_safe_filename(filename):
|
26 |
+
"""
|
27 |
+
Converts a filename to a safe format containing only alphabets, periods (.), and colons (:).
|
28 |
+
|
29 |
+
Args:
|
30 |
+
filename: The original filename to be converted.
|
31 |
+
|
32 |
+
Returns:
|
33 |
+
A safe filename string with only allowed characters.
|
34 |
+
"""
|
35 |
+
# Replace all characters except alphabets, periods, and colons with underscores
|
36 |
+
safe_filename = re.sub(r"[^\w\.\:]", "_", filename)
|
37 |
+
return safe_filename
|
38 |
+
|
39 |
+
lines = []
|
40 |
+
|
41 |
+
for videos in x['data']:
|
42 |
+
|
43 |
+
line = f"{generate_safe_filename(videos['title'])},{extract_uuid(videos['content'][0]['videoUrl'])[0]}"
|
44 |
+
lines.append(line)
|
45 |
+
|
46 |
+
with open(csv_file, 'w') as f:
|
47 |
f.write('\n'.join(lines))
|
defaults.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
-
{
|
2 |
-
"cloudfront_id" : "d1d34p8vz63oiq",
|
3 |
-
"patched" : false,
|
4 |
-
"os-info" : "winX64",
|
5 |
-
"tmpDir" : "%temp%" ,
|
6 |
-
"verbose" : true,
|
7 |
-
"nm3" :"$script/bin/nm3.exe",
|
8 |
-
"ffmpeg" :"",
|
9 |
-
"hr" : true,
|
10 |
-
"mp4decrypt" :"$script/bin/mp4decrypt.exe"
|
11 |
}
|
|
|
1 |
+
{
|
2 |
+
"cloudfront_id" : "d1d34p8vz63oiq",
|
3 |
+
"patched" : false,
|
4 |
+
"os-info" : "winX64",
|
5 |
+
"tmpDir" : "%temp%" ,
|
6 |
+
"verbose" : true,
|
7 |
+
"nm3" :"$script/bin/nm3.exe",
|
8 |
+
"ffmpeg" :"",
|
9 |
+
"hr" : true,
|
10 |
+
"mp4decrypt" :"$script/bin/mp4decrypt.exe"
|
11 |
}
|
defaults.linux.json
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
-
{
|
2 |
-
"flare_url" : "http://localhost:8191/v1",
|
3 |
-
"cloudfront_id" : "d1d34p8vz63oiq",
|
4 |
-
"patched" : false,
|
5 |
-
"os-info" : "linux",
|
6 |
-
"tmpDir" : "/tmp" ,
|
7 |
-
"verbose" : false,
|
8 |
-
"nm3" :"$script/bin/nm3",
|
9 |
-
"ffmpeg" :"",
|
10 |
-
"mp4decrypt" :"$script/bin/mp4decrypt"
|
11 |
}
|
|
|
1 |
+
{
|
2 |
+
"flare_url" : "http://localhost:8191/v1",
|
3 |
+
"cloudfront_id" : "d1d34p8vz63oiq",
|
4 |
+
"patched" : false,
|
5 |
+
"os-info" : "linux",
|
6 |
+
"tmpDir" : "/tmp" ,
|
7 |
+
"verbose" : false,
|
8 |
+
"nm3" :"$script/bin/nm3",
|
9 |
+
"ffmpeg" :"",
|
10 |
+
"mp4decrypt" :"$script/bin/mp4decrypt"
|
11 |
}
|
mainLogic/big4/cleanup.py
CHANGED
@@ -1,37 +1,37 @@
|
|
1 |
-
import os
|
2 |
-
from mainLogic.utils.glv import Global
|
3 |
-
class Clean:
|
4 |
-
|
5 |
-
def removeFile(self,file,verbose):
|
6 |
-
try:
|
7 |
-
os.remove(file)
|
8 |
-
if verbose: Global.sprint(f"Removed file: {file}")
|
9 |
-
except:
|
10 |
-
Global.errprint(f"Could not remove file: {file}")
|
11 |
-
|
12 |
-
def remove(self,path,file,verbose=True):
|
13 |
-
|
14 |
-
audio_enc = f"{path}/{file}-enc.m4a" if os.path.exists(f"{path}/{file}-enc.m4a") else f"{path}/{file}-enc.en.m4a"
|
15 |
-
video_enc = f"{path}/{file}-enc.mp4"
|
16 |
-
|
17 |
-
audio = f"{path}/{file}-Audio.mp4"
|
18 |
-
video = f"{path}/{file}-Video.mp4"
|
19 |
-
|
20 |
-
if verbose:
|
21 |
-
Global.hr()
|
22 |
-
Global.dprint("Removing TemporaryDL Files...")
|
23 |
-
Global.hr()
|
24 |
-
|
25 |
-
if verbose: Global.dprint("Removing Audio...")
|
26 |
-
self.removeFile(audio_enc,verbose)
|
27 |
-
|
28 |
-
if verbose: Global.dprint("Removing Video...")
|
29 |
-
self.removeFile(video_enc,verbose)
|
30 |
-
|
31 |
-
if verbose: Global.dprint("Removing Dncrypted Audio...")
|
32 |
-
self.removeFile(audio,verbose)
|
33 |
-
|
34 |
-
if verbose: Global.dprint("Removing Dncrypted Video...")
|
35 |
-
self.removeFile(video,verbose)
|
36 |
-
|
37 |
-
|
|
|
1 |
+
import os
|
2 |
+
from mainLogic.utils.glv import Global
|
3 |
+
class Clean:
|
4 |
+
|
5 |
+
def removeFile(self,file,verbose):
|
6 |
+
try:
|
7 |
+
os.remove(file)
|
8 |
+
if verbose: Global.sprint(f"Removed file: {file}")
|
9 |
+
except:
|
10 |
+
Global.errprint(f"Could not remove file: {file}")
|
11 |
+
|
12 |
+
def remove(self,path,file,verbose=True):
|
13 |
+
|
14 |
+
audio_enc = f"{path}/{file}-enc.m4a" if os.path.exists(f"{path}/{file}-enc.m4a") else f"{path}/{file}-enc.en.m4a"
|
15 |
+
video_enc = f"{path}/{file}-enc.mp4"
|
16 |
+
|
17 |
+
audio = f"{path}/{file}-Audio.mp4"
|
18 |
+
video = f"{path}/{file}-Video.mp4"
|
19 |
+
|
20 |
+
if verbose:
|
21 |
+
Global.hr()
|
22 |
+
Global.dprint("Removing TemporaryDL Files...")
|
23 |
+
Global.hr()
|
24 |
+
|
25 |
+
if verbose: Global.dprint("Removing Audio...")
|
26 |
+
self.removeFile(audio_enc,verbose)
|
27 |
+
|
28 |
+
if verbose: Global.dprint("Removing Video...")
|
29 |
+
self.removeFile(video_enc,verbose)
|
30 |
+
|
31 |
+
if verbose: Global.dprint("Removing Dncrypted Audio...")
|
32 |
+
self.removeFile(audio,verbose)
|
33 |
+
|
34 |
+
if verbose: Global.dprint("Removing Dncrypted Video...")
|
35 |
+
self.removeFile(video,verbose)
|
36 |
+
|
37 |
+
|
mainLogic/big4/decrypt/decrypt.py
CHANGED
@@ -1,54 +1,54 @@
|
|
1 |
-
from mainLogic.utils.glv import Global
|
2 |
-
from mainLogic.utils.process import shell
|
3 |
-
from mainLogic.utils.basicUtils import BasicUtils
|
4 |
-
from mainLogic import error
|
5 |
-
import os
|
6 |
-
|
7 |
-
class Decrypt:
|
8 |
-
|
9 |
-
def decrypt(self,path,name,key,mp4d="mp4decrypt",out="None",outfile="",verbose=True,suppress_exit=False):
|
10 |
-
|
11 |
-
Global.hr()
|
12 |
-
|
13 |
-
# making path absolute if not already absolute
|
14 |
-
path = BasicUtils.abspath(path)
|
15 |
-
Global.dprint(f"Decrypting {out}...")
|
16 |
-
|
17 |
-
# during various tests
|
18 |
-
# it was found that the decrypted audio file is named as <name>.en.m4a
|
19 |
-
# hence a simple logic to work around this issue is to check if the file exists
|
20 |
-
if not os.path.exists(f'{path}/{name}.m4a') and out == "Audio":
|
21 |
-
name = name + ".en"
|
22 |
-
|
23 |
-
# setting extension based on out
|
24 |
-
# i.e if out is Audio then extension is 'm4a' else 'mp4'
|
25 |
-
extension = "m4a" if out == "Audio" else "mp4"
|
26 |
-
|
27 |
-
decrypt_command = f'{mp4d} --key 1:{key} {path}/{name}.{extension} {path}/{"" if not outfile else outfile+"-" }{out}.mp4'
|
28 |
-
|
29 |
-
if verbose: Global.dprint(f"{out} Decryption Started..."); Global.dprint(f'{decrypt_command}')
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
# the main part where the decryption happens
|
34 |
-
code = shell(f'{decrypt_command}',stderr="",stdout="")
|
35 |
-
|
36 |
-
# simple check to see if the decryption was successful or not
|
37 |
-
if code == 0:
|
38 |
-
Global.dprint(f"{out} Decrypted Successfully")
|
39 |
-
else:
|
40 |
-
|
41 |
-
# if decryption failed then print error message and exit
|
42 |
-
error.errorList[f"couldNotDecrypt{out}"]["func"]()
|
43 |
-
if not suppress_exit:
|
44 |
-
exit(error.errorList[f"couldNotDecrypt{out}"]["code"])
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
# decrypts audio
|
49 |
-
def decryptAudio(self,path,name,key,mp4d="mp4decrypt",outfile='None',verbose=True,suppress_exit=False):
|
50 |
-
self.decrypt(path,name,key,mp4d,"Audio",outfile,verbose,suppress_exit=suppress_exit)
|
51 |
-
|
52 |
-
# decrypts video
|
53 |
-
def decryptVideo(self,path,name,key,mp4d="mp4decrypt",outfile='None',verbose=True,suppress_exit=False):
|
54 |
self.decrypt(path,name,key,mp4d,"Video",outfile,verbose,suppress_exit=suppress_exit)
|
|
|
1 |
+
from mainLogic.utils.glv import Global
|
2 |
+
from mainLogic.utils.process import shell
|
3 |
+
from mainLogic.utils.basicUtils import BasicUtils
|
4 |
+
from mainLogic import error
|
5 |
+
import os
|
6 |
+
|
7 |
+
class Decrypt:
|
8 |
+
|
9 |
+
def decrypt(self,path,name,key,mp4d="mp4decrypt",out="None",outfile="",verbose=True,suppress_exit=False):
|
10 |
+
|
11 |
+
Global.hr()
|
12 |
+
|
13 |
+
# making path absolute if not already absolute
|
14 |
+
path = BasicUtils.abspath(path)
|
15 |
+
Global.dprint(f"Decrypting {out}...")
|
16 |
+
|
17 |
+
# during various tests
|
18 |
+
# it was found that the decrypted audio file is named as <name>.en.m4a
|
19 |
+
# hence a simple logic to work around this issue is to check if the file exists
|
20 |
+
if not os.path.exists(f'{path}/{name}.m4a') and out == "Audio":
|
21 |
+
name = name + ".en"
|
22 |
+
|
23 |
+
# setting extension based on out
|
24 |
+
# i.e if out is Audio then extension is 'm4a' else 'mp4'
|
25 |
+
extension = "m4a" if out == "Audio" else "mp4"
|
26 |
+
|
27 |
+
decrypt_command = f'{mp4d} --key 1:{key} {path}/{name}.{extension} {path}/{"" if not outfile else outfile+"-" }{out}.mp4'
|
28 |
+
|
29 |
+
if verbose: Global.dprint(f"{out} Decryption Started..."); Global.dprint(f'{decrypt_command}')
|
30 |
+
|
31 |
+
|
32 |
+
|
33 |
+
# the main part where the decryption happens
|
34 |
+
code = shell(f'{decrypt_command}',stderr="",stdout="")
|
35 |
+
|
36 |
+
# simple check to see if the decryption was successful or not
|
37 |
+
if code == 0:
|
38 |
+
Global.dprint(f"{out} Decrypted Successfully")
|
39 |
+
else:
|
40 |
+
|
41 |
+
# if decryption failed then print error message and exit
|
42 |
+
error.errorList[f"couldNotDecrypt{out}"]["func"]()
|
43 |
+
if not suppress_exit:
|
44 |
+
exit(error.errorList[f"couldNotDecrypt{out}"]["code"])
|
45 |
+
|
46 |
+
|
47 |
+
|
48 |
+
# decrypts audio
|
49 |
+
def decryptAudio(self,path,name,key,mp4d="mp4decrypt",outfile='None',verbose=True,suppress_exit=False):
|
50 |
+
self.decrypt(path,name,key,mp4d,"Audio",outfile,verbose,suppress_exit=suppress_exit)
|
51 |
+
|
52 |
+
# decrypts video
|
53 |
+
def decryptVideo(self,path,name,key,mp4d="mp4decrypt",outfile='None',verbose=True,suppress_exit=False):
|
54 |
self.decrypt(path,name,key,mp4d,"Video",outfile,verbose,suppress_exit=suppress_exit)
|
mainLogic/big4/decrypt/key.old.py
CHANGED
@@ -1,86 +1,86 @@
|
|
1 |
-
# import requests
|
2 |
-
# import json
|
3 |
-
# from bs4 import BeautifulSoup as BS
|
4 |
-
# import re
|
5 |
-
# from utils.keyUtils import base64_to_hex
|
6 |
-
# from utils.glv import Global
|
7 |
-
# import error
|
8 |
-
#
|
9 |
-
#
|
10 |
-
# def log_info(id, verbose, attempt=0):
|
11 |
-
# if verbose:
|
12 |
-
# Global.dprint("Starting the script for key extraction" +( f" Retry: {attempt}" if attempt > 0 else ""))
|
13 |
-
# Global.sprint(f'id -> {id}')
|
14 |
-
# Global.sprint("Sending request to the server")
|
15 |
-
# Global.dprint(f"Hardcoded URL: request.get -> http://studyrays.site/drmplayer.php?v=https://d1d34p8vz63oiq"
|
16 |
-
# f".cloudfront.net/{id}/master.mpd")
|
17 |
-
#
|
18 |
-
#
|
19 |
-
# def send_request(id):
|
20 |
-
# try:
|
21 |
-
#
|
22 |
-
# import http.client
|
23 |
-
#
|
24 |
-
# conn = http.client.HTTPSConnection("api.scrapingant.com")
|
25 |
-
#
|
26 |
-
# conn.request("GET",
|
27 |
-
# f"/v2/general?url=https%3A%2F%2Fstudyrays.site%2Fdrmplayer.php%3Fv%3Dhttps%3A%2F%2Fd1d34p8vz63oiq.cloudfront.net%2F{id}%2Fmaster.mpd&x-api-key=806b77b95dd643caae01d4e240da9159&proxy_type=residential&proxy_country=IN&browser=false")
|
28 |
-
#
|
29 |
-
# res = conn.getresponse()
|
30 |
-
#
|
31 |
-
# return res.read()
|
32 |
-
#
|
33 |
-
# except requests.exceptions.RequestException as e:
|
34 |
-
# error.errorList["flareNotStarted"]["func"]()
|
35 |
-
# exit(error.errorList["flareNotStarted"]["code"])
|
36 |
-
#
|
37 |
-
#
|
38 |
-
# def parse_response(response):
|
39 |
-
# try:
|
40 |
-
#
|
41 |
-
# return response.decode('utf-8')
|
42 |
-
#
|
43 |
-
# except (KeyError, json.JSONDecodeError):
|
44 |
-
# error.errorList["requestFailedDueToUnknownReason"]["func"](response.status_code)
|
45 |
-
# exit(error.errorList["requestFailedDueToUnknownReason"]["code"])
|
46 |
-
#
|
47 |
-
#
|
48 |
-
# def extract_key(html):
|
49 |
-
#
|
50 |
-
# soup = BS(html, 'html.parser')
|
51 |
-
# scripts = soup.find_all('script')
|
52 |
-
#
|
53 |
-
# for script in scripts:
|
54 |
-
# script_content = script.text
|
55 |
-
# if 'const protData' in script_content:
|
56 |
-
# protData_script = script_content
|
57 |
-
# break
|
58 |
-
# else:
|
59 |
-
# return None
|
60 |
-
#
|
61 |
-
# pattern = r'const\s+protData\s*=\s*({.*?});'
|
62 |
-
# match = re.search(pattern, protData_script, re.DOTALL)
|
63 |
-
#
|
64 |
-
# if match:
|
65 |
-
# protData_content = match.group(1)
|
66 |
-
# keylist = json.loads(protData_content)['org.w3.clearkey']['clearkeys']
|
67 |
-
# for kid in keylist:
|
68 |
-
# return base64_to_hex(keylist[kid])
|
69 |
-
# return None
|
70 |
-
#
|
71 |
-
# # main function
|
72 |
-
# def getKey(id, verbose=True,retries=2):
|
73 |
-
#
|
74 |
-
# for attempt in range(retries):
|
75 |
-
# log_info(id, verbose, attempt)
|
76 |
-
#
|
77 |
-
# response = send_request(id)
|
78 |
-
# html = parse_response(response)
|
79 |
-
#
|
80 |
-
# key = extract_key(html)
|
81 |
-
# if key:
|
82 |
-
# return key
|
83 |
-
# else:
|
84 |
-
# if verbose:
|
85 |
-
# Global.sprint("protData variable not found in the script. Retrying!")
|
86 |
# return -1
|
|
|
1 |
+
# import requests
|
2 |
+
# import json
|
3 |
+
# from bs4 import BeautifulSoup as BS
|
4 |
+
# import re
|
5 |
+
# from utils.keyUtils import base64_to_hex
|
6 |
+
# from utils.glv import Global
|
7 |
+
# import error
|
8 |
+
#
|
9 |
+
#
|
10 |
+
# def log_info(id, verbose, attempt=0):
|
11 |
+
# if verbose:
|
12 |
+
# Global.dprint("Starting the script for key extraction" +( f" Retry: {attempt}" if attempt > 0 else ""))
|
13 |
+
# Global.sprint(f'id -> {id}')
|
14 |
+
# Global.sprint("Sending request to the server")
|
15 |
+
# Global.dprint(f"Hardcoded URL: request.get -> http://studyrays.site/drmplayer.php?v=https://d1d34p8vz63oiq"
|
16 |
+
# f".cloudfront.net/{id}/master.mpd")
|
17 |
+
#
|
18 |
+
#
|
19 |
+
# def send_request(id):
|
20 |
+
# try:
|
21 |
+
#
|
22 |
+
# import http.client
|
23 |
+
#
|
24 |
+
# conn = http.client.HTTPSConnection("api.scrapingant.com")
|
25 |
+
#
|
26 |
+
# conn.request("GET",
|
27 |
+
# f"/v2/general?url=https%3A%2F%2Fstudyrays.site%2Fdrmplayer.php%3Fv%3Dhttps%3A%2F%2Fd1d34p8vz63oiq.cloudfront.net%2F{id}%2Fmaster.mpd&x-api-key=806b77b95dd643caae01d4e240da9159&proxy_type=residential&proxy_country=IN&browser=false")
|
28 |
+
#
|
29 |
+
# res = conn.getresponse()
|
30 |
+
#
|
31 |
+
# return res.read()
|
32 |
+
#
|
33 |
+
# except requests.exceptions.RequestException as e:
|
34 |
+
# error.errorList["flareNotStarted"]["func"]()
|
35 |
+
# exit(error.errorList["flareNotStarted"]["code"])
|
36 |
+
#
|
37 |
+
#
|
38 |
+
# def parse_response(response):
|
39 |
+
# try:
|
40 |
+
#
|
41 |
+
# return response.decode('utf-8')
|
42 |
+
#
|
43 |
+
# except (KeyError, json.JSONDecodeError):
|
44 |
+
# error.errorList["requestFailedDueToUnknownReason"]["func"](response.status_code)
|
45 |
+
# exit(error.errorList["requestFailedDueToUnknownReason"]["code"])
|
46 |
+
#
|
47 |
+
#
|
48 |
+
# def extract_key(html):
|
49 |
+
#
|
50 |
+
# soup = BS(html, 'html.parser')
|
51 |
+
# scripts = soup.find_all('script')
|
52 |
+
#
|
53 |
+
# for script in scripts:
|
54 |
+
# script_content = script.text
|
55 |
+
# if 'const protData' in script_content:
|
56 |
+
# protData_script = script_content
|
57 |
+
# break
|
58 |
+
# else:
|
59 |
+
# return None
|
60 |
+
#
|
61 |
+
# pattern = r'const\s+protData\s*=\s*({.*?});'
|
62 |
+
# match = re.search(pattern, protData_script, re.DOTALL)
|
63 |
+
#
|
64 |
+
# if match:
|
65 |
+
# protData_content = match.group(1)
|
66 |
+
# keylist = json.loads(protData_content)['org.w3.clearkey']['clearkeys']
|
67 |
+
# for kid in keylist:
|
68 |
+
# return base64_to_hex(keylist[kid])
|
69 |
+
# return None
|
70 |
+
#
|
71 |
+
# # main function
|
72 |
+
# def getKey(id, verbose=True,retries=2):
|
73 |
+
#
|
74 |
+
# for attempt in range(retries):
|
75 |
+
# log_info(id, verbose, attempt)
|
76 |
+
#
|
77 |
+
# response = send_request(id)
|
78 |
+
# html = parse_response(response)
|
79 |
+
#
|
80 |
+
# key = extract_key(html)
|
81 |
+
# if key:
|
82 |
+
# return key
|
83 |
+
# else:
|
84 |
+
# if verbose:
|
85 |
+
# Global.sprint("protData variable not found in the script. Retrying!")
|
86 |
# return -1
|
mainLogic/big4/decrypt/key.py
CHANGED
@@ -1,166 +1,166 @@
|
|
1 |
-
import requests
|
2 |
-
import re
|
3 |
-
import base64
|
4 |
-
from mainLogic.big4.dl import DL
|
5 |
-
import json
|
6 |
-
from mainLogic.utils.glv import Global
|
7 |
-
|
8 |
-
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTgzMzQ2NjUuMDA3LCJkYXRhIjp7Il9pZCI6IjY0MzE3NTQyNDBlOTc5MDAxODAwMjAyYiIsInVzZXJuYW1lIjoiOTQ3MjUwNzEwMCIsImZpcnN0TmFtZSI6IkFrc2hpdCBTaHViaGFtIiwibGFzdE5hbWUiOiIiLCJvcmdhbml6YXRpb24iOnsiX2lkIjoiNWViMzkzZWU5NWZhYjc0NjhhNzlkMTg5Iiwid2Vic2l0ZSI6InBoeXNpY3N3YWxsYWguY29tIiwibmFtZSI6IlBoeXNpY3N3YWxsYWgifSwiZW1haWwiOiJha3NoaXRzaHViaGFtbWFzQGdtYWlsLmNvbSIsInJvbGVzIjpbIjViMjdiZDk2NTg0MmY5NTBhNzc4YzZlZiJdLCJjb3VudHJ5R3JvdXAiOiJJTiIsInR5cGUiOiJVU0VSIn0sImlhdCI6MTcxNzcyOTg2NX0.1dbIi4bsDfKfyTkBegiS-ComvLXGnI4DmCO3Qc12e8I"
|
9 |
-
|
10 |
-
def buildLicenseUrl(encoded_otp_key):
|
11 |
-
return f"https://api.penpencil.co/v1/videos/get-otp?key={encoded_otp_key}&isEncoded=true"
|
12 |
-
|
13 |
-
def getHeaders():
|
14 |
-
headers = {
|
15 |
-
"accept": "*/*",
|
16 |
-
"accept-language": "en-US,en;q=0.9,la;q=0.8",
|
17 |
-
"authorization": f"Bearer {TOKEN}",
|
18 |
-
"cache-control": "no-cache",
|
19 |
-
"client-id": "5eb393ee95fab7468a79d189",
|
20 |
-
"client-type": "WEB",
|
21 |
-
"client-version": "200",
|
22 |
-
"content-type": "application/json",
|
23 |
-
"dnt": "1",
|
24 |
-
"origin": "https://www.pw.live",
|
25 |
-
"pragma": "no-cache",
|
26 |
-
"priority": "u=1, i",
|
27 |
-
"randomid": "180ff4c6-9ec3-4329-b1b5-1ad2f6746795",
|
28 |
-
"referer": "https://www.pw.live/",
|
29 |
-
"sec-ch-ua": "\"Google Chrome\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"",
|
30 |
-
"sec-ch-ua-mobile": "?0",
|
31 |
-
"sec-ch-ua-platform": "\"Windows\"",
|
32 |
-
"sec-fetch-dest": "empty",
|
33 |
-
"sec-fetch-mode": "cors",
|
34 |
-
"sec-fetch-site": "cross-site",
|
35 |
-
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
36 |
-
}
|
37 |
-
return headers
|
38 |
-
|
39 |
-
# required by xor_encrypt
|
40 |
-
def key_char_at(key, i):
|
41 |
-
return ord(key[i % len(key)])
|
42 |
-
|
43 |
-
# converts the xor_encrypted data to base64 (xor_encrypt was given TOKEN, kid)
|
44 |
-
def b64_encode(data):
|
45 |
-
if not data:
|
46 |
-
return data
|
47 |
-
encoded = base64.b64encode(bytes(data)).decode('utf-8')
|
48 |
-
return encoded
|
49 |
-
|
50 |
-
# converts the base64 encoded data to hex
|
51 |
-
# the received OTP is base64 encoded, so we need to convert it to hex
|
52 |
-
# the otp is nothing but key for decryption
|
53 |
-
# key type: clearkeys
|
54 |
-
# code copied from 'https://github.com/jarvistuts/Penpencil_Keys/blob/main/penpencil.py'
|
55 |
-
# originally by 'https://github.com/ItsVJp'
|
56 |
-
#
|
57 |
-
def get_key_final(otp: str, token: str) -> str:
|
58 |
-
decoded_bytes = base64.b64decode(otp)
|
59 |
-
length = len(decoded_bytes)
|
60 |
-
decoded_ints = [int(byte) for byte in decoded_bytes]
|
61 |
-
|
62 |
-
result = "".join(
|
63 |
-
chr(
|
64 |
-
decoded_ints[i] ^ ord(token[i % len(token)])
|
65 |
-
)
|
66 |
-
for i in range(length)
|
67 |
-
)
|
68 |
-
|
69 |
-
return result
|
70 |
-
|
71 |
-
|
72 |
-
# encrypting algorithm for the kid so that the request to the server remains secure
|
73 |
-
def xor_encrypt(token, data):
|
74 |
-
return [ord(c) ^ key_char_at(token, i) for i, c in enumerate(data)]
|
75 |
-
|
76 |
-
# custom function to insert zeros in the hex string (as the hex string is completely void of padding)
|
77 |
-
# current logic [start_with_ '00' -> add '00' every 2 characters -> end must not contain '00']
|
78 |
-
def insert_zeros(hex_string):
|
79 |
-
# Initialize an empty result string
|
80 |
-
result = "00"
|
81 |
-
# Loop through the input string two characters at a time
|
82 |
-
for i in range(0, len(hex_string), 2):
|
83 |
-
|
84 |
-
# Append the current two characters to the result
|
85 |
-
result += hex_string[i:i+2]
|
86 |
-
# If we're not at the end of the string, append "00"
|
87 |
-
if i + 2 < len(hex_string):
|
88 |
-
result += "00"
|
89 |
-
return result
|
90 |
-
|
91 |
-
def extract_kid_from_mpd(url):
|
92 |
-
# Fetch the MPD file content
|
93 |
-
response = requests.get(url)
|
94 |
-
response.raise_for_status() # Check for request errors
|
95 |
-
|
96 |
-
# Extract the content as a string
|
97 |
-
mpd_content = response.text
|
98 |
-
|
99 |
-
# Define the regex pattern to find default_KID
|
100 |
-
pattern = r'default_KID="([0-9a-fA-F-]+)"'
|
101 |
-
|
102 |
-
# Search for the pattern in the MPD content
|
103 |
-
match = re.search(pattern, mpd_content)
|
104 |
-
|
105 |
-
# Return the KID if found, otherwise return None
|
106 |
-
if match:
|
107 |
-
return match.group(1)
|
108 |
-
else:
|
109 |
-
return None
|
110 |
-
|
111 |
-
def getKey(id,verbose=True):
|
112 |
-
|
113 |
-
Global.hr();
|
114 |
-
|
115 |
-
if verbose: Global.dprint("Beginning to get the key for the video... & Audio :) ")
|
116 |
-
if verbose: Global.dprint(f"ID: {id}"); Global.dprint("Building the URL to get the key...")
|
117 |
-
|
118 |
-
try:
|
119 |
-
url = DL.buildUrl(id)
|
120 |
-
if verbose: Global.sprint(f"URL: {url}")
|
121 |
-
|
122 |
-
if verbose: Global.dprint("Extracting the KID from the MPD file...")
|
123 |
-
kid = extract_kid_from_mpd(url).replace("-", "")
|
124 |
-
if verbose: Global.sprint(f"KID: {kid}")
|
125 |
-
|
126 |
-
if verbose: Global.dprint("Encrypting the KID to get the key...")
|
127 |
-
otp_key = b64_encode(xor_encrypt(TOKEN, kid))
|
128 |
-
if verbose: Global.sprint(f"OTP Key: {otp_key}")
|
129 |
-
|
130 |
-
if verbose: Global.dprint("Encoding the OTP key to hex...")
|
131 |
-
encoded_otp_key_step1 = otp_key.encode('utf-8').hex()
|
132 |
-
encoded_otp_key = insert_zeros(encoded_otp_key_step1)
|
133 |
-
if verbose: Global.sprint(f"Encoded OTP Key: {encoded_otp_key}")
|
134 |
-
|
135 |
-
if verbose: Global.dprint("Building the license URL...")
|
136 |
-
license_url = buildLicenseUrl(encoded_otp_key)
|
137 |
-
if verbose: Global.sprint(f"License URL: {license_url}")
|
138 |
-
|
139 |
-
if verbose: Global.dprint("Getting the headers...")
|
140 |
-
headers = getHeaders()
|
141 |
-
if verbose: Global.sprint(f"Headers: {json.dumps(headers, indent=4)}")
|
142 |
-
|
143 |
-
if verbose: Global.dprint("Making a request to the server to get the license (key)...")
|
144 |
-
# make a request to the server to get the license(key)
|
145 |
-
response = requests.get(license_url, headers=headers)
|
146 |
-
if verbose: Global.sprint(f"Response: {response}")
|
147 |
-
|
148 |
-
# get the key from the response
|
149 |
-
if response.status_code == 200:
|
150 |
-
if 'data' in response.json():
|
151 |
-
if 'otp' in response.json()['data']:
|
152 |
-
if verbose: Global.sprint("Key received successfully!")
|
153 |
-
key = get_key_final(response.json()['data']['otp'], TOKEN)
|
154 |
-
if verbose: Global.sprint(f"Key: {key}")
|
155 |
-
|
156 |
-
Global.hr()
|
157 |
-
return key
|
158 |
-
else:
|
159 |
-
Global.errprint("Could not get the key from the server. Exiting...")
|
160 |
-
return None
|
161 |
-
|
162 |
-
except Exception as e:
|
163 |
-
Global.errprint(f"An error occurred while getting the key: {e}")
|
164 |
-
return None
|
165 |
-
|
166 |
-
|
|
|
1 |
+
import requests
|
2 |
+
import re
|
3 |
+
import base64
|
4 |
+
from mainLogic.big4.dl import DL
|
5 |
+
import json
|
6 |
+
from mainLogic.utils.glv import Global
|
7 |
+
|
8 |
+
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTgzMzQ2NjUuMDA3LCJkYXRhIjp7Il9pZCI6IjY0MzE3NTQyNDBlOTc5MDAxODAwMjAyYiIsInVzZXJuYW1lIjoiOTQ3MjUwNzEwMCIsImZpcnN0TmFtZSI6IkFrc2hpdCBTaHViaGFtIiwibGFzdE5hbWUiOiIiLCJvcmdhbml6YXRpb24iOnsiX2lkIjoiNWViMzkzZWU5NWZhYjc0NjhhNzlkMTg5Iiwid2Vic2l0ZSI6InBoeXNpY3N3YWxsYWguY29tIiwibmFtZSI6IlBoeXNpY3N3YWxsYWgifSwiZW1haWwiOiJha3NoaXRzaHViaGFtbWFzQGdtYWlsLmNvbSIsInJvbGVzIjpbIjViMjdiZDk2NTg0MmY5NTBhNzc4YzZlZiJdLCJjb3VudHJ5R3JvdXAiOiJJTiIsInR5cGUiOiJVU0VSIn0sImlhdCI6MTcxNzcyOTg2NX0.1dbIi4bsDfKfyTkBegiS-ComvLXGnI4DmCO3Qc12e8I"
|
9 |
+
|
10 |
+
def buildLicenseUrl(encoded_otp_key):
|
11 |
+
return f"https://api.penpencil.co/v1/videos/get-otp?key={encoded_otp_key}&isEncoded=true"
|
12 |
+
|
13 |
+
def getHeaders():
|
14 |
+
headers = {
|
15 |
+
"accept": "*/*",
|
16 |
+
"accept-language": "en-US,en;q=0.9,la;q=0.8",
|
17 |
+
"authorization": f"Bearer {TOKEN}",
|
18 |
+
"cache-control": "no-cache",
|
19 |
+
"client-id": "5eb393ee95fab7468a79d189",
|
20 |
+
"client-type": "WEB",
|
21 |
+
"client-version": "200",
|
22 |
+
"content-type": "application/json",
|
23 |
+
"dnt": "1",
|
24 |
+
"origin": "https://www.pw.live",
|
25 |
+
"pragma": "no-cache",
|
26 |
+
"priority": "u=1, i",
|
27 |
+
"randomid": "180ff4c6-9ec3-4329-b1b5-1ad2f6746795",
|
28 |
+
"referer": "https://www.pw.live/",
|
29 |
+
"sec-ch-ua": "\"Google Chrome\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"",
|
30 |
+
"sec-ch-ua-mobile": "?0",
|
31 |
+
"sec-ch-ua-platform": "\"Windows\"",
|
32 |
+
"sec-fetch-dest": "empty",
|
33 |
+
"sec-fetch-mode": "cors",
|
34 |
+
"sec-fetch-site": "cross-site",
|
35 |
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
36 |
+
}
|
37 |
+
return headers
|
38 |
+
|
39 |
+
# required by xor_encrypt
|
40 |
+
def key_char_at(key, i):
|
41 |
+
return ord(key[i % len(key)])
|
42 |
+
|
43 |
+
# converts the xor_encrypted data to base64 (xor_encrypt was given TOKEN, kid)
|
44 |
+
def b64_encode(data):
|
45 |
+
if not data:
|
46 |
+
return data
|
47 |
+
encoded = base64.b64encode(bytes(data)).decode('utf-8')
|
48 |
+
return encoded
|
49 |
+
|
50 |
+
# converts the base64 encoded data to hex
|
51 |
+
# the received OTP is base64 encoded, so we need to convert it to hex
|
52 |
+
# the otp is nothing but key for decryption
|
53 |
+
# key type: clearkeys
|
54 |
+
# code copied from 'https://github.com/jarvistuts/Penpencil_Keys/blob/main/penpencil.py'
|
55 |
+
# originally by 'https://github.com/ItsVJp'
|
56 |
+
#
|
57 |
+
def get_key_final(otp: str, token: str) -> str:
|
58 |
+
decoded_bytes = base64.b64decode(otp)
|
59 |
+
length = len(decoded_bytes)
|
60 |
+
decoded_ints = [int(byte) for byte in decoded_bytes]
|
61 |
+
|
62 |
+
result = "".join(
|
63 |
+
chr(
|
64 |
+
decoded_ints[i] ^ ord(token[i % len(token)])
|
65 |
+
)
|
66 |
+
for i in range(length)
|
67 |
+
)
|
68 |
+
|
69 |
+
return result
|
70 |
+
|
71 |
+
|
72 |
+
# encrypting algorithm for the kid so that the request to the server remains secure
|
73 |
+
def xor_encrypt(token, data):
|
74 |
+
return [ord(c) ^ key_char_at(token, i) for i, c in enumerate(data)]
|
75 |
+
|
76 |
+
# custom function to insert zeros in the hex string (as the hex string is completely void of padding)
|
77 |
+
# current logic [start_with_ '00' -> add '00' every 2 characters -> end must not contain '00']
|
78 |
+
def insert_zeros(hex_string):
|
79 |
+
# Initialize an empty result string
|
80 |
+
result = "00"
|
81 |
+
# Loop through the input string two characters at a time
|
82 |
+
for i in range(0, len(hex_string), 2):
|
83 |
+
|
84 |
+
# Append the current two characters to the result
|
85 |
+
result += hex_string[i:i+2]
|
86 |
+
# If we're not at the end of the string, append "00"
|
87 |
+
if i + 2 < len(hex_string):
|
88 |
+
result += "00"
|
89 |
+
return result
|
90 |
+
|
91 |
+
def extract_kid_from_mpd(url):
|
92 |
+
# Fetch the MPD file content
|
93 |
+
response = requests.get(url)
|
94 |
+
response.raise_for_status() # Check for request errors
|
95 |
+
|
96 |
+
# Extract the content as a string
|
97 |
+
mpd_content = response.text
|
98 |
+
|
99 |
+
# Define the regex pattern to find default_KID
|
100 |
+
pattern = r'default_KID="([0-9a-fA-F-]+)"'
|
101 |
+
|
102 |
+
# Search for the pattern in the MPD content
|
103 |
+
match = re.search(pattern, mpd_content)
|
104 |
+
|
105 |
+
# Return the KID if found, otherwise return None
|
106 |
+
if match:
|
107 |
+
return match.group(1)
|
108 |
+
else:
|
109 |
+
return None
|
110 |
+
|
111 |
+
def getKey(id,verbose=True):
|
112 |
+
|
113 |
+
Global.hr();
|
114 |
+
|
115 |
+
if verbose: Global.dprint("Beginning to get the key for the video... & Audio :) ")
|
116 |
+
if verbose: Global.dprint(f"ID: {id}"); Global.dprint("Building the URL to get the key...")
|
117 |
+
|
118 |
+
try:
|
119 |
+
url = DL.buildUrl(id)
|
120 |
+
if verbose: Global.sprint(f"URL: {url}")
|
121 |
+
|
122 |
+
if verbose: Global.dprint("Extracting the KID from the MPD file...")
|
123 |
+
kid = extract_kid_from_mpd(url).replace("-", "")
|
124 |
+
if verbose: Global.sprint(f"KID: {kid}")
|
125 |
+
|
126 |
+
if verbose: Global.dprint("Encrypting the KID to get the key...")
|
127 |
+
otp_key = b64_encode(xor_encrypt(TOKEN, kid))
|
128 |
+
if verbose: Global.sprint(f"OTP Key: {otp_key}")
|
129 |
+
|
130 |
+
if verbose: Global.dprint("Encoding the OTP key to hex...")
|
131 |
+
encoded_otp_key_step1 = otp_key.encode('utf-8').hex()
|
132 |
+
encoded_otp_key = insert_zeros(encoded_otp_key_step1)
|
133 |
+
if verbose: Global.sprint(f"Encoded OTP Key: {encoded_otp_key}")
|
134 |
+
|
135 |
+
if verbose: Global.dprint("Building the license URL...")
|
136 |
+
license_url = buildLicenseUrl(encoded_otp_key)
|
137 |
+
if verbose: Global.sprint(f"License URL: {license_url}")
|
138 |
+
|
139 |
+
if verbose: Global.dprint("Getting the headers...")
|
140 |
+
headers = getHeaders()
|
141 |
+
if verbose: Global.sprint(f"Headers: {json.dumps(headers, indent=4)}")
|
142 |
+
|
143 |
+
if verbose: Global.dprint("Making a request to the server to get the license (key)...")
|
144 |
+
# make a request to the server to get the license(key)
|
145 |
+
response = requests.get(license_url, headers=headers)
|
146 |
+
if verbose: Global.sprint(f"Response: {response}")
|
147 |
+
|
148 |
+
# get the key from the response
|
149 |
+
if response.status_code == 200:
|
150 |
+
if 'data' in response.json():
|
151 |
+
if 'otp' in response.json()['data']:
|
152 |
+
if verbose: Global.sprint("Key received successfully!")
|
153 |
+
key = get_key_final(response.json()['data']['otp'], TOKEN)
|
154 |
+
if verbose: Global.sprint(f"Key: {key}")
|
155 |
+
|
156 |
+
Global.hr()
|
157 |
+
return key
|
158 |
+
else:
|
159 |
+
Global.errprint("Could not get the key from the server. Exiting...")
|
160 |
+
return None
|
161 |
+
|
162 |
+
except Exception as e:
|
163 |
+
Global.errprint(f"An error occurred while getting the key: {e}")
|
164 |
+
return None
|
165 |
+
|
166 |
+
|
mainLogic/big4/dl.py
CHANGED
@@ -1,175 +1,175 @@
|
|
1 |
-
import re
|
2 |
-
|
3 |
-
from mainLogic import error
|
4 |
-
from mainLogic.utils.process import shell
|
5 |
-
from mainLogic.utils.glv import Global
|
6 |
-
from mainLogic.utils.basicUtils import BasicUtils
|
7 |
-
class DL:
|
8 |
-
|
9 |
-
@staticmethod
|
10 |
-
def buildUrl(id):
|
11 |
-
if id == None:
|
12 |
-
error.errorList["idNotProvided"]["func"]()
|
13 |
-
exit(error.errorList["idNotProvided"]["code"])
|
14 |
-
|
15 |
-
return f"https://d1d34p8vz63oiq.cloudfront.net/{id}/master.mpd"
|
16 |
-
|
17 |
-
def download(self,id,name=None,
|
18 |
-
type="",
|
19 |
-
directory="./",
|
20 |
-
tmpDir="/*auto*/",
|
21 |
-
nm3Path='nm3',
|
22 |
-
ffmpeg='ffmpeg',
|
23 |
-
verbose=True,
|
24 |
-
progress_callback=None):
|
25 |
-
if id == None:
|
26 |
-
error.errorList["idNotProvided"]["func"]()
|
27 |
-
exit(error.errorList["idNotProvided"]["code"])
|
28 |
-
|
29 |
-
if name == None: name = id
|
30 |
-
|
31 |
-
|
32 |
-
url = DL.buildUrl(id)
|
33 |
-
|
34 |
-
# setting identifier and filter based on type
|
35 |
-
|
36 |
-
# identifier is used to identify the type of file
|
37 |
-
identifier = "a" if type == "Audio" else "v" if type == "Video" else "av"
|
38 |
-
|
39 |
-
# filter is used to filter the output of the shell command
|
40 |
-
filter = r"^Aud" if type == "Audio" else r"^Vid"
|
41 |
-
|
42 |
-
# command to download the file
|
43 |
-
command = f'{nm3Path} {url} --save-dir {directory} {"--tmp-dir "+tmpDir if not tmpDir == "/*auto*/" else "" } --save-name {name} -s{identifier} best'
|
44 |
-
if verbose: Global.sprint(f"Command to download: {command}")
|
45 |
-
|
46 |
-
# Download the audio file using the id
|
47 |
-
code = shell(f'{command}',
|
48 |
-
filter=filter,
|
49 |
-
progress_callback=progress_callback,
|
50 |
-
handleProgress=self.handleDownloadProgress,
|
51 |
-
)
|
52 |
-
|
53 |
-
if code == 0:
|
54 |
-
return True
|
55 |
-
else:
|
56 |
-
error.errorList[f"couldNotDownload{type}"]["func"]()
|
57 |
-
exit(error.errorList[f"couldNotDownload{type}"]["code"])
|
58 |
-
|
59 |
-
def downloadAudioAndVideo(self,
|
60 |
-
id,
|
61 |
-
name=None,
|
62 |
-
directory="./",
|
63 |
-
tmpDir="/*auto*/",
|
64 |
-
nm3Path='nm3',
|
65 |
-
ffmpeg='ffmpeg',
|
66 |
-
verbose=True,
|
67 |
-
progress_callback=None):
|
68 |
-
if id == None:
|
69 |
-
error.errorList["idNotProvided"]["func"]()
|
70 |
-
exit(error.errorList["idNotProvided"]["code"])
|
71 |
-
|
72 |
-
if name == None: name = id; Global.dprint(f"Name not provided, using id as name: {name}")
|
73 |
-
|
74 |
-
# removing limitations of relative path
|
75 |
-
if not tmpDir == "/*auto*/": BasicUtils.abspath(tmpDir)
|
76 |
-
directory = BasicUtils.abspath(directory)
|
77 |
-
|
78 |
-
if verbose:
|
79 |
-
Global.hr()
|
80 |
-
Global.dprint(f"ID: {id}")
|
81 |
-
Global.dprint(f"Name: {name}")
|
82 |
-
Global.dprint(f"Directory: {directory}")
|
83 |
-
Global.dprint(f"TmpDir: {tmpDir}")
|
84 |
-
Global.dprint(f"Nm3Path: {nm3Path}")
|
85 |
-
Global.hr()
|
86 |
-
Global.dprint(f"Starting DL...")
|
87 |
-
|
88 |
-
# section to download audio
|
89 |
-
Global.hr(); Global.dprint("Downloading Audio..."); Global.hr()
|
90 |
-
self.dlAudio(id,
|
91 |
-
name,
|
92 |
-
directory,
|
93 |
-
tmpDir,
|
94 |
-
nm3Path,
|
95 |
-
verbose,
|
96 |
-
progress_callback=progress_callback)
|
97 |
-
|
98 |
-
# section to download video
|
99 |
-
Global.hr(); Global.dprint("Downloading Video..."); Global.hr()
|
100 |
-
self.dlVideo(id,
|
101 |
-
name,
|
102 |
-
directory,
|
103 |
-
tmpDir,
|
104 |
-
nm3Path,
|
105 |
-
verbose,
|
106 |
-
progress_callback=progress_callback)
|
107 |
-
|
108 |
-
if progress_callback:
|
109 |
-
progress_callback({
|
110 |
-
"progress": 80,
|
111 |
-
"str": "download-completed",
|
112 |
-
"next": "decryption"
|
113 |
-
})
|
114 |
-
|
115 |
-
# return the paths of the downloaded files
|
116 |
-
return [f"{directory}/{name}.mp4",f"{directory}/{name}.m4a"]
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
def dlAudio(self,id,name=None,directory="./",tmpDir="/*auto*/",nm3Path='nm3',verbose=True,progress_callback=None):
|
122 |
-
self.download(id,
|
123 |
-
name,
|
124 |
-
"Audio",
|
125 |
-
directory,
|
126 |
-
tmpDir,
|
127 |
-
nm3Path,
|
128 |
-
verbose=verbose,
|
129 |
-
progress_callback=progress_callback)
|
130 |
-
|
131 |
-
def dlVideo(self,id,name=None,directory="./",tmpDir="/*auto*/",nm3Path='nm3',verbose=True,progress_callback=None):
|
132 |
-
self.download(id,
|
133 |
-
name,
|
134 |
-
"Video",
|
135 |
-
directory,
|
136 |
-
tmpDir,
|
137 |
-
nm3Path,
|
138 |
-
verbose=verbose,
|
139 |
-
progress_callback=progress_callback)
|
140 |
-
|
141 |
-
|
142 |
-
def handleDownloadProgress(self,output):
|
143 |
-
|
144 |
-
|
145 |
-
progress = {
|
146 |
-
"str": output,
|
147 |
-
"dl-progress": 0,
|
148 |
-
"progress": 0,
|
149 |
-
"next": "Aud"
|
150 |
-
}
|
151 |
-
|
152 |
-
# formats the output to get the progress
|
153 |
-
pattern = re.compile(r"[0-9][0-9][0-9]?%")
|
154 |
-
progress_percent = pattern.findall(output)
|
155 |
-
|
156 |
-
if progress_percent:
|
157 |
-
|
158 |
-
progress["dl-progress"] = int(progress_percent[0].replace("%",""))
|
159 |
-
|
160 |
-
if "Aud" in output:
|
161 |
-
progress["progress"] = progress["dl-progress"] * 0.4
|
162 |
-
progress["next"] = "Vid"
|
163 |
-
|
164 |
-
if "Vid" in output:
|
165 |
-
progress["progress"] = progress["dl-progress"] * 0.4 + 40
|
166 |
-
progress["next"] = "decryption"
|
167 |
-
|
168 |
-
|
169 |
-
return progress
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
from mainLogic import error
|
4 |
+
from mainLogic.utils.process import shell
|
5 |
+
from mainLogic.utils.glv import Global
|
6 |
+
from mainLogic.utils.basicUtils import BasicUtils
|
7 |
+
class DL:
|
8 |
+
|
9 |
+
@staticmethod
|
10 |
+
def buildUrl(id):
|
11 |
+
if id == None:
|
12 |
+
error.errorList["idNotProvided"]["func"]()
|
13 |
+
exit(error.errorList["idNotProvided"]["code"])
|
14 |
+
|
15 |
+
return f"https://d1d34p8vz63oiq.cloudfront.net/{id}/master.mpd"
|
16 |
+
|
17 |
+
def download(self,id,name=None,
|
18 |
+
type="",
|
19 |
+
directory="./",
|
20 |
+
tmpDir="/*auto*/",
|
21 |
+
nm3Path='nm3',
|
22 |
+
ffmpeg='ffmpeg',
|
23 |
+
verbose=True,
|
24 |
+
progress_callback=None):
|
25 |
+
if id == None:
|
26 |
+
error.errorList["idNotProvided"]["func"]()
|
27 |
+
exit(error.errorList["idNotProvided"]["code"])
|
28 |
+
|
29 |
+
if name == None: name = id
|
30 |
+
|
31 |
+
|
32 |
+
url = DL.buildUrl(id)
|
33 |
+
|
34 |
+
# setting identifier and filter based on type
|
35 |
+
|
36 |
+
# identifier is used to identify the type of file
|
37 |
+
identifier = "a" if type == "Audio" else "v" if type == "Video" else "av"
|
38 |
+
|
39 |
+
# filter is used to filter the output of the shell command
|
40 |
+
filter = r"^Aud" if type == "Audio" else r"^Vid"
|
41 |
+
|
42 |
+
# command to download the file
|
43 |
+
command = f'{nm3Path} {url} --save-dir {directory} {"--tmp-dir "+tmpDir if not tmpDir == "/*auto*/" else "" } --save-name {name} -s{identifier} best'
|
44 |
+
if verbose: Global.sprint(f"Command to download: {command}")
|
45 |
+
|
46 |
+
# Download the audio file using the id
|
47 |
+
code = shell(f'{command}',
|
48 |
+
filter=filter,
|
49 |
+
progress_callback=progress_callback,
|
50 |
+
handleProgress=self.handleDownloadProgress,
|
51 |
+
)
|
52 |
+
|
53 |
+
if code == 0:
|
54 |
+
return True
|
55 |
+
else:
|
56 |
+
error.errorList[f"couldNotDownload{type}"]["func"]()
|
57 |
+
exit(error.errorList[f"couldNotDownload{type}"]["code"])
|
58 |
+
|
59 |
+
def downloadAudioAndVideo(self,
|
60 |
+
id,
|
61 |
+
name=None,
|
62 |
+
directory="./",
|
63 |
+
tmpDir="/*auto*/",
|
64 |
+
nm3Path='nm3',
|
65 |
+
ffmpeg='ffmpeg',
|
66 |
+
verbose=True,
|
67 |
+
progress_callback=None):
|
68 |
+
if id == None:
|
69 |
+
error.errorList["idNotProvided"]["func"]()
|
70 |
+
exit(error.errorList["idNotProvided"]["code"])
|
71 |
+
|
72 |
+
if name == None: name = id; Global.dprint(f"Name not provided, using id as name: {name}")
|
73 |
+
|
74 |
+
# removing limitations of relative path
|
75 |
+
if not tmpDir == "/*auto*/": BasicUtils.abspath(tmpDir)
|
76 |
+
directory = BasicUtils.abspath(directory)
|
77 |
+
|
78 |
+
if verbose:
|
79 |
+
Global.hr()
|
80 |
+
Global.dprint(f"ID: {id}")
|
81 |
+
Global.dprint(f"Name: {name}")
|
82 |
+
Global.dprint(f"Directory: {directory}")
|
83 |
+
Global.dprint(f"TmpDir: {tmpDir}")
|
84 |
+
Global.dprint(f"Nm3Path: {nm3Path}")
|
85 |
+
Global.hr()
|
86 |
+
Global.dprint(f"Starting DL...")
|
87 |
+
|
88 |
+
# section to download audio
|
89 |
+
Global.hr(); Global.dprint("Downloading Audio..."); Global.hr()
|
90 |
+
self.dlAudio(id,
|
91 |
+
name,
|
92 |
+
directory,
|
93 |
+
tmpDir,
|
94 |
+
nm3Path,
|
95 |
+
verbose,
|
96 |
+
progress_callback=progress_callback)
|
97 |
+
|
98 |
+
# section to download video
|
99 |
+
Global.hr(); Global.dprint("Downloading Video..."); Global.hr()
|
100 |
+
self.dlVideo(id,
|
101 |
+
name,
|
102 |
+
directory,
|
103 |
+
tmpDir,
|
104 |
+
nm3Path,
|
105 |
+
verbose,
|
106 |
+
progress_callback=progress_callback)
|
107 |
+
|
108 |
+
if progress_callback:
|
109 |
+
progress_callback({
|
110 |
+
"progress": 80,
|
111 |
+
"str": "download-completed",
|
112 |
+
"next": "decryption"
|
113 |
+
})
|
114 |
+
|
115 |
+
# return the paths of the downloaded files
|
116 |
+
return [f"{directory}/{name}.mp4",f"{directory}/{name}.m4a"]
|
117 |
+
|
118 |
+
|
119 |
+
|
120 |
+
|
121 |
+
def dlAudio(self,id,name=None,directory="./",tmpDir="/*auto*/",nm3Path='nm3',verbose=True,progress_callback=None):
|
122 |
+
self.download(id,
|
123 |
+
name,
|
124 |
+
"Audio",
|
125 |
+
directory,
|
126 |
+
tmpDir,
|
127 |
+
nm3Path,
|
128 |
+
verbose=verbose,
|
129 |
+
progress_callback=progress_callback)
|
130 |
+
|
131 |
+
def dlVideo(self,id,name=None,directory="./",tmpDir="/*auto*/",nm3Path='nm3',verbose=True,progress_callback=None):
|
132 |
+
self.download(id,
|
133 |
+
name,
|
134 |
+
"Video",
|
135 |
+
directory,
|
136 |
+
tmpDir,
|
137 |
+
nm3Path,
|
138 |
+
verbose=verbose,
|
139 |
+
progress_callback=progress_callback)
|
140 |
+
|
141 |
+
|
142 |
+
def handleDownloadProgress(self,output):
|
143 |
+
|
144 |
+
|
145 |
+
progress = {
|
146 |
+
"str": output,
|
147 |
+
"dl-progress": 0,
|
148 |
+
"progress": 0,
|
149 |
+
"next": "Aud"
|
150 |
+
}
|
151 |
+
|
152 |
+
# formats the output to get the progress
|
153 |
+
pattern = re.compile(r"[0-9][0-9][0-9]?%")
|
154 |
+
progress_percent = pattern.findall(output)
|
155 |
+
|
156 |
+
if progress_percent:
|
157 |
+
|
158 |
+
progress["dl-progress"] = int(progress_percent[0].replace("%",""))
|
159 |
+
|
160 |
+
if "Aud" in output:
|
161 |
+
progress["progress"] = progress["dl-progress"] * 0.4
|
162 |
+
progress["next"] = "Vid"
|
163 |
+
|
164 |
+
if "Vid" in output:
|
165 |
+
progress["progress"] = progress["dl-progress"] * 0.4 + 40
|
166 |
+
progress["next"] = "decryption"
|
167 |
+
|
168 |
+
|
169 |
+
return progress
|
170 |
+
|
171 |
+
|
172 |
+
|
173 |
+
|
174 |
+
|
175 |
+
|
mainLogic/big4/merge.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
-
from mainLogic.utils.process import shell
|
2 |
-
from mainLogic.utils.glv import Global
|
3 |
-
class Merge:
|
4 |
-
def ffmpegMerge(self,input1,input2,output,ffmpeg_path="ffmpeg",verbose=False):
|
5 |
-
|
6 |
-
|
7 |
-
if verbose: Global.hr();Global.dprint('Attempting ffmpeg merge')
|
8 |
-
if verbose: Global.dprint(f'{ffmpeg_path} -i {input1} -i {input2} -c copy {output}')
|
9 |
-
|
10 |
-
shell(f'{ffmpeg_path} -i {input1} -i {input2} -c copy {output}',r'^\[out#0\/mp4')
|
11 |
-
|
12 |
return output
|
|
|
1 |
+
from mainLogic.utils.process import shell
|
2 |
+
from mainLogic.utils.glv import Global
|
3 |
+
class Merge:
|
4 |
+
def ffmpegMerge(self,input1,input2,output,ffmpeg_path="ffmpeg",verbose=False):
|
5 |
+
|
6 |
+
|
7 |
+
if verbose: Global.hr();Global.dprint('Attempting ffmpeg merge')
|
8 |
+
if verbose: Global.dprint(f'{ffmpeg_path} -i {input1} -i {input2} -c copy {output}')
|
9 |
+
|
10 |
+
shell(f'{ffmpeg_path} -i {input1} -i {input2} -c copy {output}',r'^\[out#0\/mp4')
|
11 |
+
|
12 |
return output
|
mainLogic/error.py
CHANGED
@@ -1,78 +1,78 @@
|
|
1 |
-
from mainLogic.utils.glv import Global
|
2 |
-
|
3 |
-
errorList = {
|
4 |
-
"noError": {
|
5 |
-
"code": 0,
|
6 |
-
"func": lambda: None,
|
7 |
-
},
|
8 |
-
"defaultsNotFound" : {
|
9 |
-
"code": 1,
|
10 |
-
"func": lambda: Global.errprint("defaults.json not found. Exiting..."),
|
11 |
-
},
|
12 |
-
"dependencyNotFound": {
|
13 |
-
"code": 2,
|
14 |
-
"func": lambda x=None: Global.errprint(f"{'Dependency' if x == None else x } not found. Exiting..."),
|
15 |
-
},
|
16 |
-
"dependencyNotFoundInPrefs":
|
17 |
-
{
|
18 |
-
"code": 3,
|
19 |
-
"func": lambda x=None: Global.errprint(f"{'Dependency' if x == None else x } not found in default settings. Exiting..."),
|
20 |
-
},
|
21 |
-
"csvFileNotFound": {
|
22 |
-
"code": 4,
|
23 |
-
"func": lambda fileName: Global.errprint(f"CSV file {fileName} not found. Exiting..."),
|
24 |
-
},
|
25 |
-
"downloadFailed": {
|
26 |
-
"code": 5,
|
27 |
-
"func": lambda name, id: Global.errprint(f"Download failed for {name} with id {id}. Exiting..."),
|
28 |
-
},
|
29 |
-
"couldNotMakeDir":{
|
30 |
-
"code": 6,
|
31 |
-
"func": lambda dirName: Global.errprint(f"Could not make directory {dirName}. Exiting..."),
|
32 |
-
},
|
33 |
-
"cantLoadFile": {
|
34 |
-
"code": 22,
|
35 |
-
"func": lambda fileName: Global.errprint(f"Can't load file {fileName}"),
|
36 |
-
},
|
37 |
-
"flareNotStarted": {
|
38 |
-
"code": 23,
|
39 |
-
"func": lambda: Global.errprint("Flare is not started. Start the flare server first.")
|
40 |
-
},
|
41 |
-
"requestFailedDueToUnknownReason": {
|
42 |
-
"code": 24,
|
43 |
-
"func": lambda status_code: Global.errprint("Request failed due to unknown reason. Status Code: " + str(status_code))
|
44 |
-
},
|
45 |
-
"keyExtractionFailed": {
|
46 |
-
"code": 25,
|
47 |
-
"func": lambda id: Global.errprint(f"Key extraction failed for id -> {id}. Exiting...")
|
48 |
-
},
|
49 |
-
"keyNotProvided": {
|
50 |
-
"code": 26,
|
51 |
-
"func": lambda: Global.errprint("Key not provided. Exiting...")
|
52 |
-
},
|
53 |
-
"couldNotDownloadAudio": {
|
54 |
-
"code": 27,
|
55 |
-
"func": lambda id: Global.errprint(f"Could not download audio for id -> {id} Exiting...")
|
56 |
-
},
|
57 |
-
"couldNotDownloadVideo": {
|
58 |
-
"code": 28,
|
59 |
-
"func": lambda: Global.errprint(f"Could not download video for {id} Exiting...")
|
60 |
-
},
|
61 |
-
"couldNotDecryptAudio": {
|
62 |
-
"code": 29,
|
63 |
-
"func": lambda: Global.errprint("Could not decrypt audio. Exiting...")
|
64 |
-
},
|
65 |
-
"couldNotDecryptVideo": {
|
66 |
-
"code": 30,
|
67 |
-
"func": lambda: Global.errprint("Could not decrypt video. Exiting...")
|
68 |
-
},
|
69 |
-
"methodPatched": {
|
70 |
-
"code": 31,
|
71 |
-
"func": lambda: Global.errprint("Method is patched. Exiting...")
|
72 |
-
},
|
73 |
-
"couldNotExtractKey": {
|
74 |
-
"code": 32,
|
75 |
-
"func": lambda: Global.errprint("Could not extract key. Exiting...")
|
76 |
-
},
|
77 |
-
}
|
78 |
-
|
|
|
1 |
+
from mainLogic.utils.glv import Global
|
2 |
+
|
3 |
+
errorList = {
|
4 |
+
"noError": {
|
5 |
+
"code": 0,
|
6 |
+
"func": lambda: None,
|
7 |
+
},
|
8 |
+
"defaultsNotFound" : {
|
9 |
+
"code": 1,
|
10 |
+
"func": lambda: Global.errprint("defaults.json not found. Exiting..."),
|
11 |
+
},
|
12 |
+
"dependencyNotFound": {
|
13 |
+
"code": 2,
|
14 |
+
"func": lambda x=None: Global.errprint(f"{'Dependency' if x == None else x } not found. Exiting..."),
|
15 |
+
},
|
16 |
+
"dependencyNotFoundInPrefs":
|
17 |
+
{
|
18 |
+
"code": 3,
|
19 |
+
"func": lambda x=None: Global.errprint(f"{'Dependency' if x == None else x } not found in default settings. Exiting..."),
|
20 |
+
},
|
21 |
+
"csvFileNotFound": {
|
22 |
+
"code": 4,
|
23 |
+
"func": lambda fileName: Global.errprint(f"CSV file {fileName} not found. Exiting..."),
|
24 |
+
},
|
25 |
+
"downloadFailed": {
|
26 |
+
"code": 5,
|
27 |
+
"func": lambda name, id: Global.errprint(f"Download failed for {name} with id {id}. Exiting..."),
|
28 |
+
},
|
29 |
+
"couldNotMakeDir":{
|
30 |
+
"code": 6,
|
31 |
+
"func": lambda dirName: Global.errprint(f"Could not make directory {dirName}. Exiting..."),
|
32 |
+
},
|
33 |
+
"cantLoadFile": {
|
34 |
+
"code": 22,
|
35 |
+
"func": lambda fileName: Global.errprint(f"Can't load file {fileName}"),
|
36 |
+
},
|
37 |
+
"flareNotStarted": {
|
38 |
+
"code": 23,
|
39 |
+
"func": lambda: Global.errprint("Flare is not started. Start the flare server first.")
|
40 |
+
},
|
41 |
+
"requestFailedDueToUnknownReason": {
|
42 |
+
"code": 24,
|
43 |
+
"func": lambda status_code: Global.errprint("Request failed due to unknown reason. Status Code: " + str(status_code))
|
44 |
+
},
|
45 |
+
"keyExtractionFailed": {
|
46 |
+
"code": 25,
|
47 |
+
"func": lambda id: Global.errprint(f"Key extraction failed for id -> {id}. Exiting...")
|
48 |
+
},
|
49 |
+
"keyNotProvided": {
|
50 |
+
"code": 26,
|
51 |
+
"func": lambda: Global.errprint("Key not provided. Exiting...")
|
52 |
+
},
|
53 |
+
"couldNotDownloadAudio": {
|
54 |
+
"code": 27,
|
55 |
+
"func": lambda id: Global.errprint(f"Could not download audio for id -> {id} Exiting...")
|
56 |
+
},
|
57 |
+
"couldNotDownloadVideo": {
|
58 |
+
"code": 28,
|
59 |
+
"func": lambda: Global.errprint(f"Could not download video for {id} Exiting...")
|
60 |
+
},
|
61 |
+
"couldNotDecryptAudio": {
|
62 |
+
"code": 29,
|
63 |
+
"func": lambda: Global.errprint("Could not decrypt audio. Exiting...")
|
64 |
+
},
|
65 |
+
"couldNotDecryptVideo": {
|
66 |
+
"code": 30,
|
67 |
+
"func": lambda: Global.errprint("Could not decrypt video. Exiting...")
|
68 |
+
},
|
69 |
+
"methodPatched": {
|
70 |
+
"code": 31,
|
71 |
+
"func": lambda: Global.errprint("Method is patched. Exiting...")
|
72 |
+
},
|
73 |
+
"couldNotExtractKey": {
|
74 |
+
"code": 32,
|
75 |
+
"func": lambda: Global.errprint("Could not extract key. Exiting...")
|
76 |
+
},
|
77 |
+
}
|
78 |
+
|
mainLogic/main.py
CHANGED
@@ -1,119 +1,119 @@
|
|
1 |
-
from mainLogic.utils.basicUtils import BasicUtils
|
2 |
-
from mainLogic.utils.glv import Global
|
3 |
-
from mainLogic.big4.cleanup import Clean
|
4 |
-
import os
|
5 |
-
|
6 |
-
|
7 |
-
class Main:
|
8 |
-
"""
|
9 |
-
Main class to handle the processing of video and audio files including download,
|
10 |
-
decryption, merging, and cleanup.
|
11 |
-
|
12 |
-
Attributes:
|
13 |
-
id (str): Identifier for the process.
|
14 |
-
name (str): Name for the process. Defaults to the value of `id`.
|
15 |
-
directory (str): Directory to store the files. Defaults to "./".
|
16 |
-
tmpDir (str): Temporary directory for intermediate files. Defaults to './tmp/'.
|
17 |
-
nm3Path (str): Path to the NM3 binary. Defaults to 'nm3'.
|
18 |
-
ffmpeg (str): Path to the ffmpeg binary. Defaults to 'ffmpeg'.
|
19 |
-
mp4d (str): Path to the mp4decrypt binary. Defaults to 'mp4decrypt'.
|
20 |
-
flare_url (str): URL for the flare service. Defaults to 'http://localhost:8191/v1'.
|
21 |
-
verbose (bool): Flag for verbose output. Defaults to True.
|
22 |
-
suppress_exit (bool): Flag to suppress exit on error. Defaults to False.
|
23 |
-
"""
|
24 |
-
|
25 |
-
def __init__(self, id, name=None, directory="./", tmpDir="/*auto*/", nm3Path='nm3', ffmpeg="ffmpeg",
|
26 |
-
mp4d="mp4decrypt", verbose=True, suppress_exit=False,progress_callback=None):
|
27 |
-
"""
|
28 |
-
Initialize the Main class with the given parameters.
|
29 |
-
|
30 |
-
Args:
|
31 |
-
id (str): Identifier for the process.
|
32 |
-
name (str, optional): Name for the process. Defaults to None.
|
33 |
-
directory (str, optional): Directory to store the files. Defaults to "./".
|
34 |
-
tmpDir (str, optional): Temporary directory for intermediate files. Defaults to '/*auto*/'.
|
35 |
-
nm3Path (str, optional): Path to the NM3 binary. Defaults to 'nm3'.
|
36 |
-
ffmpeg (str, optional): Path to the ffmpeg binary. Defaults to 'ffmpeg'.
|
37 |
-
mp4d (str, optional): Path to the mp4decrypt binary. Defaults to 'mp4decrypt'.
|
38 |
-
# flare_url (str, optional): URL for the flare service. Defaults to 'http://localhost:8191/v1'.
|
39 |
-
verbose (bool, optional): Flag for verbose output. Defaults to True.
|
40 |
-
suppress_exit (bool, optional): Flag to suppress exit on error. Defaults to False.
|
41 |
-
progress_callback (function, optional): Callback function to report progress. Defaults to None.
|
42 |
-
"""
|
43 |
-
self.id = id
|
44 |
-
self.name = name if name else id
|
45 |
-
self.directory = directory
|
46 |
-
self.tmpDir = BasicUtils.abspath(tmpDir) if tmpDir != '/*auto*/' else BasicUtils.abspath('./tmp/')
|
47 |
-
# Create tmp directory if it does not exist
|
48 |
-
try:
|
49 |
-
if not os.path.exists(self.tmpDir):
|
50 |
-
os.makedirs(self.tmpDir)
|
51 |
-
except:
|
52 |
-
Global.errprint("Could not create tmp directory")
|
53 |
-
exit(-1)
|
54 |
-
self.nm3Path = BasicUtils.abspath(nm3Path) if nm3Path != 'nm3' else 'nm3'
|
55 |
-
self.ffmpeg = BasicUtils.abspath(ffmpeg) if ffmpeg != 'ffmpeg' else 'ffmpeg'
|
56 |
-
self.mp4d = BasicUtils.abspath(mp4d) if mp4d != 'mp4decrypt' else 'mp4decrypt'
|
57 |
-
self.verbose = verbose
|
58 |
-
self.suppress_exit = suppress_exit
|
59 |
-
self.progress_callback = progress_callback
|
60 |
-
|
61 |
-
def process(self):
|
62 |
-
"""
|
63 |
-
Main processing function to handle downloading, decrypting, merging, and cleanup of files.
|
64 |
-
"""
|
65 |
-
|
66 |
-
from mainLogic.big4.dl import DL
|
67 |
-
from mainLogic.big4.decrypt import key
|
68 |
-
from mainLogic.big4.decrypt import decrypt
|
69 |
-
from mainLogic.big4 import merge
|
70 |
-
|
71 |
-
if self.verbose:
|
72 |
-
Global.dprint("Starting Main Process... for ID: " + self.id)
|
73 |
-
|
74 |
-
# 1. Downloading Files
|
75 |
-
|
76 |
-
dl = DL()
|
77 |
-
audio, video = dl.downloadAudioAndVideo(self.id, f'{self.name}-enc', self.directory, self.tmpDir, self.nm3Path,
|
78 |
-
self.ffmpeg, self.verbose, progress_callback=self.progress_callback)
|
79 |
-
|
80 |
-
|
81 |
-
# 2. Decrypting Files
|
82 |
-
|
83 |
-
Global.sprint("Please wait while we decrypt the files...\nFetching key may take some time.")
|
84 |
-
|
85 |
-
key = key.getKey(self.id, self.verbose)
|
86 |
-
|
87 |
-
decrypt = decrypt.Decrypt()
|
88 |
-
|
89 |
-
decrypt.decryptAudio(self.directory, f'{self.name}-enc', key, mp4d=self.mp4d, outfile=self.name,
|
90 |
-
verbose=self.verbose, suppress_exit=self.suppress_exit)
|
91 |
-
decrypt.decryptVideo(self.directory, f'{self.name}-enc', key, mp4d=self.mp4d, outfile=self.name,
|
92 |
-
verbose=self.verbose, suppress_exit=self.suppress_exit)
|
93 |
-
|
94 |
-
# Call the progress callback for decryption completion
|
95 |
-
if self.progress_callback:
|
96 |
-
self.progress_callback({
|
97 |
-
"progress": 90,
|
98 |
-
"str": "decryption-completed",
|
99 |
-
"next": "merging"
|
100 |
-
})
|
101 |
-
|
102 |
-
# 3. Merging Files
|
103 |
-
|
104 |
-
merge = merge.Merge()
|
105 |
-
merge.ffmpegMerge(f"{self.directory}/{self.name}-Video.mp4", f"{self.directory}/{self.name}-Audio.mp4",
|
106 |
-
f"{self.directory}/{self.name}.mp4", ffmpeg_path=self.ffmpeg, verbose=self.verbose)
|
107 |
-
|
108 |
-
# Call the progress callback for merge completion
|
109 |
-
if self.progress_callback:
|
110 |
-
self.progress_callback({
|
111 |
-
"progress": 100,
|
112 |
-
"str": "merge-completed",
|
113 |
-
"next": "cleanup"
|
114 |
-
})
|
115 |
-
|
116 |
-
|
117 |
-
# 4. Cleanup
|
118 |
-
clean = Clean()
|
119 |
-
clean.remove(self.directory, f'{self.name}', self.verbose)
|
|
|
1 |
+
from mainLogic.utils.basicUtils import BasicUtils
|
2 |
+
from mainLogic.utils.glv import Global
|
3 |
+
from mainLogic.big4.cleanup import Clean
|
4 |
+
import os
|
5 |
+
|
6 |
+
|
7 |
+
class Main:
|
8 |
+
"""
|
9 |
+
Main class to handle the processing of video and audio files including download,
|
10 |
+
decryption, merging, and cleanup.
|
11 |
+
|
12 |
+
Attributes:
|
13 |
+
id (str): Identifier for the process.
|
14 |
+
name (str): Name for the process. Defaults to the value of `id`.
|
15 |
+
directory (str): Directory to store the files. Defaults to "./".
|
16 |
+
tmpDir (str): Temporary directory for intermediate files. Defaults to './tmp/'.
|
17 |
+
nm3Path (str): Path to the NM3 binary. Defaults to 'nm3'.
|
18 |
+
ffmpeg (str): Path to the ffmpeg binary. Defaults to 'ffmpeg'.
|
19 |
+
mp4d (str): Path to the mp4decrypt binary. Defaults to 'mp4decrypt'.
|
20 |
+
flare_url (str): URL for the flare service. Defaults to 'http://localhost:8191/v1'.
|
21 |
+
verbose (bool): Flag for verbose output. Defaults to True.
|
22 |
+
suppress_exit (bool): Flag to suppress exit on error. Defaults to False.
|
23 |
+
"""
|
24 |
+
|
25 |
+
def __init__(self, id, name=None, directory="./", tmpDir="/*auto*/", nm3Path='nm3', ffmpeg="ffmpeg",
|
26 |
+
mp4d="mp4decrypt", verbose=True, suppress_exit=False,progress_callback=None):
|
27 |
+
"""
|
28 |
+
Initialize the Main class with the given parameters.
|
29 |
+
|
30 |
+
Args:
|
31 |
+
id (str): Identifier for the process.
|
32 |
+
name (str, optional): Name for the process. Defaults to None.
|
33 |
+
directory (str, optional): Directory to store the files. Defaults to "./".
|
34 |
+
tmpDir (str, optional): Temporary directory for intermediate files. Defaults to '/*auto*/'.
|
35 |
+
nm3Path (str, optional): Path to the NM3 binary. Defaults to 'nm3'.
|
36 |
+
ffmpeg (str, optional): Path to the ffmpeg binary. Defaults to 'ffmpeg'.
|
37 |
+
mp4d (str, optional): Path to the mp4decrypt binary. Defaults to 'mp4decrypt'.
|
38 |
+
# flare_url (str, optional): URL for the flare service. Defaults to 'http://localhost:8191/v1'.
|
39 |
+
verbose (bool, optional): Flag for verbose output. Defaults to True.
|
40 |
+
suppress_exit (bool, optional): Flag to suppress exit on error. Defaults to False.
|
41 |
+
progress_callback (function, optional): Callback function to report progress. Defaults to None.
|
42 |
+
"""
|
43 |
+
self.id = id
|
44 |
+
self.name = name if name else id
|
45 |
+
self.directory = directory
|
46 |
+
self.tmpDir = BasicUtils.abspath(tmpDir) if tmpDir != '/*auto*/' else BasicUtils.abspath('./tmp/')
|
47 |
+
# Create tmp directory if it does not exist
|
48 |
+
try:
|
49 |
+
if not os.path.exists(self.tmpDir):
|
50 |
+
os.makedirs(self.tmpDir)
|
51 |
+
except:
|
52 |
+
Global.errprint("Could not create tmp directory")
|
53 |
+
exit(-1)
|
54 |
+
self.nm3Path = BasicUtils.abspath(nm3Path) if nm3Path != 'nm3' else 'nm3'
|
55 |
+
self.ffmpeg = BasicUtils.abspath(ffmpeg) if ffmpeg != 'ffmpeg' else 'ffmpeg'
|
56 |
+
self.mp4d = BasicUtils.abspath(mp4d) if mp4d != 'mp4decrypt' else 'mp4decrypt'
|
57 |
+
self.verbose = verbose
|
58 |
+
self.suppress_exit = suppress_exit
|
59 |
+
self.progress_callback = progress_callback
|
60 |
+
|
61 |
+
def process(self):
|
62 |
+
"""
|
63 |
+
Main processing function to handle downloading, decrypting, merging, and cleanup of files.
|
64 |
+
"""
|
65 |
+
|
66 |
+
from mainLogic.big4.dl import DL
|
67 |
+
from mainLogic.big4.decrypt import key
|
68 |
+
from mainLogic.big4.decrypt import decrypt
|
69 |
+
from mainLogic.big4 import merge
|
70 |
+
|
71 |
+
if self.verbose:
|
72 |
+
Global.dprint("Starting Main Process... for ID: " + self.id)
|
73 |
+
|
74 |
+
# 1. Downloading Files
|
75 |
+
|
76 |
+
dl = DL()
|
77 |
+
audio, video = dl.downloadAudioAndVideo(self.id, f'{self.name}-enc', self.directory, self.tmpDir, self.nm3Path,
|
78 |
+
self.ffmpeg, self.verbose, progress_callback=self.progress_callback)
|
79 |
+
|
80 |
+
|
81 |
+
# 2. Decrypting Files
|
82 |
+
|
83 |
+
Global.sprint("Please wait while we decrypt the files...\nFetching key may take some time.")
|
84 |
+
|
85 |
+
key = key.getKey(self.id, self.verbose)
|
86 |
+
|
87 |
+
decrypt = decrypt.Decrypt()
|
88 |
+
|
89 |
+
decrypt.decryptAudio(self.directory, f'{self.name}-enc', key, mp4d=self.mp4d, outfile=self.name,
|
90 |
+
verbose=self.verbose, suppress_exit=self.suppress_exit)
|
91 |
+
decrypt.decryptVideo(self.directory, f'{self.name}-enc', key, mp4d=self.mp4d, outfile=self.name,
|
92 |
+
verbose=self.verbose, suppress_exit=self.suppress_exit)
|
93 |
+
|
94 |
+
# Call the progress callback for decryption completion
|
95 |
+
if self.progress_callback:
|
96 |
+
self.progress_callback({
|
97 |
+
"progress": 90,
|
98 |
+
"str": "decryption-completed",
|
99 |
+
"next": "merging"
|
100 |
+
})
|
101 |
+
|
102 |
+
# 3. Merging Files
|
103 |
+
|
104 |
+
merge = merge.Merge()
|
105 |
+
merge.ffmpegMerge(f"{self.directory}/{self.name}-Video.mp4", f"{self.directory}/{self.name}-Audio.mp4",
|
106 |
+
f"{self.directory}/{self.name}.mp4", ffmpeg_path=self.ffmpeg, verbose=self.verbose)
|
107 |
+
|
108 |
+
# Call the progress callback for merge completion
|
109 |
+
if self.progress_callback:
|
110 |
+
self.progress_callback({
|
111 |
+
"progress": 100,
|
112 |
+
"str": "merge-completed",
|
113 |
+
"next": "cleanup"
|
114 |
+
})
|
115 |
+
|
116 |
+
|
117 |
+
# 4. Cleanup
|
118 |
+
clean = Clean()
|
119 |
+
clean.remove(self.directory, f'{self.name}', self.verbose)
|
mainLogic/startup/checkup.py
CHANGED
@@ -1,165 +1,165 @@
|
|
1 |
-
from mainLogic import error
|
2 |
-
import os
|
3 |
-
from mainLogic.utils.os2 import SysFunc
|
4 |
-
from mainLogic.utils.glv import Global
|
5 |
-
|
6 |
-
class CheckState:
|
7 |
-
|
8 |
-
def __init__(self) -> None:
|
9 |
-
pass
|
10 |
-
|
11 |
-
def post_checkup(self,prefs,verbose=True):
|
12 |
-
|
13 |
-
"""
|
14 |
-
Post Checkup Function
|
15 |
-
1. Setting up the tmpDir
|
16 |
-
2. Setting up the output directory
|
17 |
-
3. Setting up the horizontal rule
|
18 |
-
"""
|
19 |
-
|
20 |
-
OUT_DIRECTORY = ""
|
21 |
-
|
22 |
-
# setting up prefs
|
23 |
-
if 'tmpDir' in prefs:
|
24 |
-
tmpDir = SysFunc.modify_path(prefs['tmpDir'])
|
25 |
-
if not os.path.exists(tmpDir):
|
26 |
-
try:
|
27 |
-
os.makedirs(tmpDir)
|
28 |
-
except OSError as exc: # Guard against failure
|
29 |
-
error.errorList["couldNotMakeDir"]['func'](tmpDir)
|
30 |
-
Global.errprint("Failed to create TmpDir")
|
31 |
-
Global.errprint("Falling Back to Default")
|
32 |
-
else:
|
33 |
-
tmpDir = './tmp/'
|
34 |
-
|
35 |
-
# setting up directory for pwdl
|
36 |
-
if "dir" in prefs:
|
37 |
-
try: OUT_DIRECTORY = os.path.abspath(os.path.expandvars(prefs['dir']))
|
38 |
-
|
39 |
-
# if the user provides a non-string value for the directory or dir is not found
|
40 |
-
except TypeError: OUT_DIRECTORY = './'
|
41 |
-
|
42 |
-
# if the directory is not found
|
43 |
-
except Exception as e:
|
44 |
-
Global.errprint(f"Error: {e}")
|
45 |
-
Global.errprint("Falling back to default")
|
46 |
-
OUT_DIRECTORY = './'
|
47 |
-
else:
|
48 |
-
OUT_DIRECTORY = './'
|
49 |
-
|
50 |
-
# setting up hr (horizontal rule)
|
51 |
-
if not 'hr' in prefs:
|
52 |
-
Global.disable_hr = False
|
53 |
-
elif not prefs['hr']:
|
54 |
-
Global.disable_hr = True
|
55 |
-
|
56 |
-
prefs['tmpDir'] = tmpDir
|
57 |
-
prefs['dir'] = OUT_DIRECTORY
|
58 |
-
|
59 |
-
|
60 |
-
def checkup(self,executable,directory="./",verbose=True):
|
61 |
-
|
62 |
-
state = {}
|
63 |
-
|
64 |
-
# set script path to ../startup
|
65 |
-
# this is the path to the folder containing the pwdl.py file
|
66 |
-
# since the checkup.py is in the startup folder, we need to go one level up
|
67 |
-
if verbose: Global.hr();Global.dprint("Setting script path...")
|
68 |
-
if verbose: Global.errprint('Warning! Hard Coded \'$script\' location to checkup.py/../../')
|
69 |
-
|
70 |
-
Global.script_path = os.path.abspath(os.path.join(os.path.dirname(__file__),'../..'))
|
71 |
-
default_json = os.path.join(Global.script_path,'defaults.json')
|
72 |
-
|
73 |
-
# check if defaults.json exists
|
74 |
-
# and if it does, load the preferences
|
75 |
-
if verbose: Global.hr();Global.dprint("Checking for default settings...")
|
76 |
-
|
77 |
-
if verbose: Global.hr();Global.dprint(f"Checking at {default_json}")
|
78 |
-
if verbose: Global.errprint('Warning!\nHard Coded \'defaults.json\' location to $script/default.json ')
|
79 |
-
|
80 |
-
if not os.path.exists(default_json):
|
81 |
-
error.errorList["defaultsNotFound"]["func"]()
|
82 |
-
exit(error.errorList["defaultsNotFound"]["code"])
|
83 |
-
|
84 |
-
if verbose: Global.sprint("Default settings found."); Global.hr()
|
85 |
-
|
86 |
-
# load the preferences
|
87 |
-
from mainLogic.startup.userPrefs import PreferencesLoader
|
88 |
-
prefs = PreferencesLoader(file_name=default_json,verbose=verbose).prefs
|
89 |
-
|
90 |
-
# check if method is patched (currently via userPrefs.py)
|
91 |
-
if 'patched' in prefs:
|
92 |
-
if prefs['patched']:
|
93 |
-
error.errorList["methodPatched"]["func"]()
|
94 |
-
exit(error.errorList["methodPatched"]["code"])
|
95 |
-
|
96 |
-
# FLare no longer required
|
97 |
-
# if verbose: Global.hr(); Global.dprint("Checking for Flare...")
|
98 |
-
# default url is localhost:8191
|
99 |
-
# however user can change it in the preferences file
|
100 |
-
# if verbose: Global.dprint(f"Checking at {prefs['flare_url'] if 'flare_url' in prefs else 'http://localhost:8191/v1'}")
|
101 |
-
# if not checkFlare(prefs['flare_url'] if 'flare_url' in prefs else 'http://localhost:8191/v1'):
|
102 |
-
# error.errorList["flareNotStarted"]["func"]()
|
103 |
-
# exit(error.errorList["flareNotStarted"]["code"])
|
104 |
-
#
|
105 |
-
# if verbose: Global.sprint("Flare is running."); Global.hr()
|
106 |
-
|
107 |
-
os2 = SysFunc()
|
108 |
-
|
109 |
-
found= []
|
110 |
-
notFound = []
|
111 |
-
|
112 |
-
for exe in executable:
|
113 |
-
if verbose: Global.hr(); Global.dprint(f"Checking for {exe}...")
|
114 |
-
|
115 |
-
if os2.which(exe) == 1:
|
116 |
-
if verbose: error.errorList["dependencyNotFound"]["func"](exe)
|
117 |
-
if verbose: print(f"{exe} not found on path! Checking in default settings...")
|
118 |
-
|
119 |
-
# add exe's which are found to the found list
|
120 |
-
found.append(exe)
|
121 |
-
# add exe's which are not found to the notFound list
|
122 |
-
notFound.append(exe)
|
123 |
-
|
124 |
-
else:
|
125 |
-
if verbose: Global.sprint(f"{exe} found.")
|
126 |
-
state[exe] = exe
|
127 |
-
|
128 |
-
if len(notFound) > 0:
|
129 |
-
|
130 |
-
if verbose: Global.hr();Global.dprint("Following dependencies were not found on path. Checking in default settings...")
|
131 |
-
if verbose: Global.dprint(notFound); Global.hr()
|
132 |
-
|
133 |
-
for exe in notFound:
|
134 |
-
|
135 |
-
if verbose: Global.dprint(f"Checking for {exe} in default settings...")
|
136 |
-
|
137 |
-
if exe in prefs:
|
138 |
-
|
139 |
-
if verbose: Global.sprint(f"Key for {exe} found in default settings.")
|
140 |
-
if verbose: Global.sprint(f"Value: {prefs[exe]}")
|
141 |
-
if verbose: Global.dprint(f"Checking for {exe} at '{prefs[exe].strip()}' ...")
|
142 |
-
|
143 |
-
if not os.path.exists(prefs[exe].strip()):
|
144 |
-
Global.errprint(f"{exe} not found at {prefs[exe].strip()}")
|
145 |
-
error.errorList["dependencyNotFoundInPrefs"]["func"](exe)
|
146 |
-
exit(error.errorList["dependencyNotFoundInPrefs"]["code"])
|
147 |
-
|
148 |
-
if verbose: Global.sprint(f"{exe} found at {prefs[exe].strip()}")
|
149 |
-
state[exe] = prefs[exe].strip()
|
150 |
-
|
151 |
-
|
152 |
-
else:
|
153 |
-
error.errorList["dependencyNotFoundInPrefs"]["func"](exe)
|
154 |
-
exit(error.errorList["dependencyNotFoundInPrefs"]["code"])
|
155 |
-
|
156 |
-
if verbose: Global.hr()
|
157 |
-
|
158 |
-
state['prefs'] = prefs
|
159 |
-
prefs['dir'] = directory
|
160 |
-
self.post_checkup(prefs,verbose)
|
161 |
-
|
162 |
-
|
163 |
-
return state
|
164 |
-
|
165 |
-
|
|
|
1 |
+
from mainLogic import error
|
2 |
+
import os
|
3 |
+
from mainLogic.utils.os2 import SysFunc
|
4 |
+
from mainLogic.utils.glv import Global
|
5 |
+
|
6 |
+
class CheckState:
|
7 |
+
|
8 |
+
def __init__(self) -> None:
|
9 |
+
pass
|
10 |
+
|
11 |
+
def post_checkup(self,prefs,verbose=True):
|
12 |
+
|
13 |
+
"""
|
14 |
+
Post Checkup Function
|
15 |
+
1. Setting up the tmpDir
|
16 |
+
2. Setting up the output directory
|
17 |
+
3. Setting up the horizontal rule
|
18 |
+
"""
|
19 |
+
|
20 |
+
OUT_DIRECTORY = ""
|
21 |
+
|
22 |
+
# setting up prefs
|
23 |
+
if 'tmpDir' in prefs:
|
24 |
+
tmpDir = SysFunc.modify_path(prefs['tmpDir'])
|
25 |
+
if not os.path.exists(tmpDir):
|
26 |
+
try:
|
27 |
+
os.makedirs(tmpDir)
|
28 |
+
except OSError as exc: # Guard against failure
|
29 |
+
error.errorList["couldNotMakeDir"]['func'](tmpDir)
|
30 |
+
Global.errprint("Failed to create TmpDir")
|
31 |
+
Global.errprint("Falling Back to Default")
|
32 |
+
else:
|
33 |
+
tmpDir = './tmp/'
|
34 |
+
|
35 |
+
# setting up directory for pwdl
|
36 |
+
if "dir" in prefs:
|
37 |
+
try: OUT_DIRECTORY = os.path.abspath(os.path.expandvars(prefs['dir']))
|
38 |
+
|
39 |
+
# if the user provides a non-string value for the directory or dir is not found
|
40 |
+
except TypeError: OUT_DIRECTORY = './'
|
41 |
+
|
42 |
+
# if the directory is not found
|
43 |
+
except Exception as e:
|
44 |
+
Global.errprint(f"Error: {e}")
|
45 |
+
Global.errprint("Falling back to default")
|
46 |
+
OUT_DIRECTORY = './'
|
47 |
+
else:
|
48 |
+
OUT_DIRECTORY = './'
|
49 |
+
|
50 |
+
# setting up hr (horizontal rule)
|
51 |
+
if not 'hr' in prefs:
|
52 |
+
Global.disable_hr = False
|
53 |
+
elif not prefs['hr']:
|
54 |
+
Global.disable_hr = True
|
55 |
+
|
56 |
+
prefs['tmpDir'] = tmpDir
|
57 |
+
prefs['dir'] = OUT_DIRECTORY
|
58 |
+
|
59 |
+
|
60 |
+
def checkup(self,executable,directory="./",verbose=True):
|
61 |
+
|
62 |
+
state = {}
|
63 |
+
|
64 |
+
# set script path to ../startup
|
65 |
+
# this is the path to the folder containing the pwdl.py file
|
66 |
+
# since the checkup.py is in the startup folder, we need to go one level up
|
67 |
+
if verbose: Global.hr();Global.dprint("Setting script path...")
|
68 |
+
if verbose: Global.errprint('Warning! Hard Coded \'$script\' location to checkup.py/../../')
|
69 |
+
|
70 |
+
Global.script_path = os.path.abspath(os.path.join(os.path.dirname(__file__),'../..'))
|
71 |
+
default_json = os.path.join(Global.script_path,'defaults.json')
|
72 |
+
|
73 |
+
# check if defaults.json exists
|
74 |
+
# and if it does, load the preferences
|
75 |
+
if verbose: Global.hr();Global.dprint("Checking for default settings...")
|
76 |
+
|
77 |
+
if verbose: Global.hr();Global.dprint(f"Checking at {default_json}")
|
78 |
+
if verbose: Global.errprint('Warning!\nHard Coded \'defaults.json\' location to $script/default.json ')
|
79 |
+
|
80 |
+
if not os.path.exists(default_json):
|
81 |
+
error.errorList["defaultsNotFound"]["func"]()
|
82 |
+
exit(error.errorList["defaultsNotFound"]["code"])
|
83 |
+
|
84 |
+
if verbose: Global.sprint("Default settings found."); Global.hr()
|
85 |
+
|
86 |
+
# load the preferences
|
87 |
+
from mainLogic.startup.userPrefs import PreferencesLoader
|
88 |
+
prefs = PreferencesLoader(file_name=default_json,verbose=verbose).prefs
|
89 |
+
|
90 |
+
# check if method is patched (currently via userPrefs.py)
|
91 |
+
if 'patched' in prefs:
|
92 |
+
if prefs['patched']:
|
93 |
+
error.errorList["methodPatched"]["func"]()
|
94 |
+
exit(error.errorList["methodPatched"]["code"])
|
95 |
+
|
96 |
+
# FLare no longer required
|
97 |
+
# if verbose: Global.hr(); Global.dprint("Checking for Flare...")
|
98 |
+
# default url is localhost:8191
|
99 |
+
# however user can change it in the preferences file
|
100 |
+
# if verbose: Global.dprint(f"Checking at {prefs['flare_url'] if 'flare_url' in prefs else 'http://localhost:8191/v1'}")
|
101 |
+
# if not checkFlare(prefs['flare_url'] if 'flare_url' in prefs else 'http://localhost:8191/v1'):
|
102 |
+
# error.errorList["flareNotStarted"]["func"]()
|
103 |
+
# exit(error.errorList["flareNotStarted"]["code"])
|
104 |
+
#
|
105 |
+
# if verbose: Global.sprint("Flare is running."); Global.hr()
|
106 |
+
|
107 |
+
os2 = SysFunc()
|
108 |
+
|
109 |
+
found= []
|
110 |
+
notFound = []
|
111 |
+
|
112 |
+
for exe in executable:
|
113 |
+
if verbose: Global.hr(); Global.dprint(f"Checking for {exe}...")
|
114 |
+
|
115 |
+
if os2.which(exe) == 1:
|
116 |
+
if verbose: error.errorList["dependencyNotFound"]["func"](exe)
|
117 |
+
if verbose: print(f"{exe} not found on path! Checking in default settings...")
|
118 |
+
|
119 |
+
# add exe's which are found to the found list
|
120 |
+
found.append(exe)
|
121 |
+
# add exe's which are not found to the notFound list
|
122 |
+
notFound.append(exe)
|
123 |
+
|
124 |
+
else:
|
125 |
+
if verbose: Global.sprint(f"{exe} found.")
|
126 |
+
state[exe] = exe
|
127 |
+
|
128 |
+
if len(notFound) > 0:
|
129 |
+
|
130 |
+
if verbose: Global.hr();Global.dprint("Following dependencies were not found on path. Checking in default settings...")
|
131 |
+
if verbose: Global.dprint(notFound); Global.hr()
|
132 |
+
|
133 |
+
for exe in notFound:
|
134 |
+
|
135 |
+
if verbose: Global.dprint(f"Checking for {exe} in default settings...")
|
136 |
+
|
137 |
+
if exe in prefs:
|
138 |
+
|
139 |
+
if verbose: Global.sprint(f"Key for {exe} found in default settings.")
|
140 |
+
if verbose: Global.sprint(f"Value: {prefs[exe]}")
|
141 |
+
if verbose: Global.dprint(f"Checking for {exe} at '{prefs[exe].strip()}' ...")
|
142 |
+
|
143 |
+
if not os.path.exists(prefs[exe].strip()):
|
144 |
+
Global.errprint(f"{exe} not found at {prefs[exe].strip()}")
|
145 |
+
error.errorList["dependencyNotFoundInPrefs"]["func"](exe)
|
146 |
+
exit(error.errorList["dependencyNotFoundInPrefs"]["code"])
|
147 |
+
|
148 |
+
if verbose: Global.sprint(f"{exe} found at {prefs[exe].strip()}")
|
149 |
+
state[exe] = prefs[exe].strip()
|
150 |
+
|
151 |
+
|
152 |
+
else:
|
153 |
+
error.errorList["dependencyNotFoundInPrefs"]["func"](exe)
|
154 |
+
exit(error.errorList["dependencyNotFoundInPrefs"]["code"])
|
155 |
+
|
156 |
+
if verbose: Global.hr()
|
157 |
+
|
158 |
+
state['prefs'] = prefs
|
159 |
+
prefs['dir'] = directory
|
160 |
+
self.post_checkup(prefs,verbose)
|
161 |
+
|
162 |
+
|
163 |
+
return state
|
164 |
+
|
165 |
+
|
mainLogic/startup/flareCheck.py
CHANGED
@@ -1,17 +1,17 @@
|
|
1 |
-
import requests
|
2 |
-
|
3 |
-
def checkFlare(flareUrl="http://localhost:8191/v1"):
|
4 |
-
|
5 |
-
url = f"{flareUrl}"
|
6 |
-
headers = {"Content-Type": "application/json"}
|
7 |
-
data = {
|
8 |
-
"cmd": "request.get",
|
9 |
-
"url": "http://www.google.com/",
|
10 |
-
"maxTimeout": 60000
|
11 |
-
}
|
12 |
-
try:
|
13 |
-
response = requests.post(url, headers=headers, json=data)
|
14 |
-
return response.ok
|
15 |
-
except Exception as e:
|
16 |
-
return False
|
17 |
-
|
|
|
1 |
+
import requests
|
2 |
+
|
3 |
+
def checkFlare(flareUrl="http://localhost:8191/v1"):
|
4 |
+
|
5 |
+
url = f"{flareUrl}"
|
6 |
+
headers = {"Content-Type": "application/json"}
|
7 |
+
data = {
|
8 |
+
"cmd": "request.get",
|
9 |
+
"url": "http://www.google.com/",
|
10 |
+
"maxTimeout": 60000
|
11 |
+
}
|
12 |
+
try:
|
13 |
+
response = requests.post(url, headers=headers, json=data)
|
14 |
+
return response.ok
|
15 |
+
except Exception as e:
|
16 |
+
return False
|
17 |
+
|
mainLogic/startup/userPrefs.py
CHANGED
@@ -1,55 +1,55 @@
|
|
1 |
-
import json
|
2 |
-
from mainLogic import error
|
3 |
-
import os
|
4 |
-
from mainLogic.utils.basicUtils import BasicUtils
|
5 |
-
|
6 |
-
class PreferencesLoader:
|
7 |
-
def __init__(self, file_name='defaults.json', verbose=True):
|
8 |
-
self.file_name = file_name
|
9 |
-
self.prefs = {}
|
10 |
-
|
11 |
-
# defining some variables that can be used in the preferences file
|
12 |
-
self.vars = {
|
13 |
-
|
14 |
-
# $script is the path to the folder containing the pwdl.py file
|
15 |
-
# Since the userPrefs.py is in the startup folder,
|
16 |
-
# we need to go one level up however we make the exception that if the pwdl.py is in the same folder as
|
17 |
-
# the startup folder, we don't need to go one level up
|
18 |
-
"$script" : BasicUtils.abspath(os.path.dirname(__file__)+ ('/../..' if not os.path.exists(os.path.dirname(__file__) + '../pwdl.py') else '')),
|
19 |
-
"$home" : os.path.expanduser("~"),
|
20 |
-
}
|
21 |
-
|
22 |
-
self.load_preferences()
|
23 |
-
|
24 |
-
# if verbose is true, print the preferences
|
25 |
-
if verbose:
|
26 |
-
self.print_preferences()
|
27 |
-
|
28 |
-
|
29 |
-
def load_preferences(self):
|
30 |
-
try:
|
31 |
-
|
32 |
-
with open(self.file_name, 'r') as json_file:
|
33 |
-
|
34 |
-
# read the contents of the file (so that we can replace the variables with their values)
|
35 |
-
contents = json_file.read()
|
36 |
-
|
37 |
-
# replace the variables with their values
|
38 |
-
for var in self.vars:
|
39 |
-
contents = contents.replace(var,self.vars[var])
|
40 |
-
|
41 |
-
# replace the backslashes with forward slashes
|
42 |
-
contents.replace('\\','/')
|
43 |
-
|
44 |
-
self.prefs = json.loads(contents)
|
45 |
-
|
46 |
-
# if the file is not found, print an error message and exit
|
47 |
-
except FileNotFoundError:
|
48 |
-
error.errorList["cantLoadFile"]["func"](self.file_name)
|
49 |
-
exit(error.errorList["cantLoadFile"]["code"])
|
50 |
-
|
51 |
-
|
52 |
-
# print the preferences (internal function)
|
53 |
-
def print_preferences(self):
|
54 |
-
for key in self.prefs:
|
55 |
print(f'{key} : {self.prefs[key]}')
|
|
|
1 |
+
import json
|
2 |
+
from mainLogic import error
|
3 |
+
import os
|
4 |
+
from mainLogic.utils.basicUtils import BasicUtils
|
5 |
+
|
6 |
+
class PreferencesLoader:
|
7 |
+
def __init__(self, file_name='defaults.json', verbose=True):
|
8 |
+
self.file_name = file_name
|
9 |
+
self.prefs = {}
|
10 |
+
|
11 |
+
# defining some variables that can be used in the preferences file
|
12 |
+
self.vars = {
|
13 |
+
|
14 |
+
# $script is the path to the folder containing the pwdl.py file
|
15 |
+
# Since the userPrefs.py is in the startup folder,
|
16 |
+
# we need to go one level up however we make the exception that if the pwdl.py is in the same folder as
|
17 |
+
# the startup folder, we don't need to go one level up
|
18 |
+
"$script" : BasicUtils.abspath(os.path.dirname(__file__)+ ('/../..' if not os.path.exists(os.path.dirname(__file__) + '../pwdl.py') else '')),
|
19 |
+
"$home" : os.path.expanduser("~"),
|
20 |
+
}
|
21 |
+
|
22 |
+
self.load_preferences()
|
23 |
+
|
24 |
+
# if verbose is true, print the preferences
|
25 |
+
if verbose:
|
26 |
+
self.print_preferences()
|
27 |
+
|
28 |
+
|
29 |
+
def load_preferences(self):
|
30 |
+
try:
|
31 |
+
|
32 |
+
with open(self.file_name, 'r') as json_file:
|
33 |
+
|
34 |
+
# read the contents of the file (so that we can replace the variables with their values)
|
35 |
+
contents = json_file.read()
|
36 |
+
|
37 |
+
# replace the variables with their values
|
38 |
+
for var in self.vars:
|
39 |
+
contents = contents.replace(var,self.vars[var])
|
40 |
+
|
41 |
+
# replace the backslashes with forward slashes
|
42 |
+
contents.replace('\\','/')
|
43 |
+
|
44 |
+
self.prefs = json.loads(contents)
|
45 |
+
|
46 |
+
# if the file is not found, print an error message and exit
|
47 |
+
except FileNotFoundError:
|
48 |
+
error.errorList["cantLoadFile"]["func"](self.file_name)
|
49 |
+
exit(error.errorList["cantLoadFile"]["code"])
|
50 |
+
|
51 |
+
|
52 |
+
# print the preferences (internal function)
|
53 |
+
def print_preferences(self):
|
54 |
+
for key in self.prefs:
|
55 |
print(f'{key} : {self.prefs[key]}')
|
mainLogic/utils/basicUtils.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
-
import os
|
2 |
-
|
3 |
-
class BasicUtils:
|
4 |
-
|
5 |
-
@staticmethod
|
6 |
-
def abspath(path):
|
7 |
-
return str(os.path.abspath(os.path.expandvars(path))).replace("\\", "/")
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
class BasicUtils:
|
4 |
+
|
5 |
+
@staticmethod
|
6 |
+
def abspath(path):
|
7 |
+
return str(os.path.abspath(os.path.expandvars(path))).replace("\\", "/")
|
mainLogic/utils/glv.py
CHANGED
@@ -1,70 +1,70 @@
|
|
1 |
-
from colorama import Fore, Style, init
|
2 |
-
import shutil
|
3 |
-
|
4 |
-
# Initialize colorama
|
5 |
-
init()
|
6 |
-
|
7 |
-
class Global:
|
8 |
-
|
9 |
-
disable_hr = False
|
10 |
-
EXECUTABLES = ['ffmpeg', 'mp4decrypt', 'nm3']
|
11 |
-
api_webdl_directory = "webdl"
|
12 |
-
|
13 |
-
def __init__(self, vout=True, outDir="./"):
|
14 |
-
self.outDir = outDir
|
15 |
-
self.vout = vout
|
16 |
-
|
17 |
-
@staticmethod
|
18 |
-
def set_color(color, style=None):
|
19 |
-
"""Prints text in the specified color and style."""
|
20 |
-
print(getattr(Fore, color), end="")
|
21 |
-
if style:
|
22 |
-
print(getattr(Style, style), end="")
|
23 |
-
|
24 |
-
@staticmethod
|
25 |
-
def reset():
|
26 |
-
"""Resets text color and style to defaults."""
|
27 |
-
print(Style.RESET_ALL, end="")
|
28 |
-
|
29 |
-
@staticmethod
|
30 |
-
def print_colored(text, color, style=None):
|
31 |
-
"""Prints text in the specified color and style, resetting afterward."""
|
32 |
-
Global.set_color(color, style)
|
33 |
-
print(text)
|
34 |
-
Global.reset()
|
35 |
-
|
36 |
-
@staticmethod
|
37 |
-
def dprint(text):
|
38 |
-
"""Prints debug text in yellow."""
|
39 |
-
Global.print_colored(text, "YELLOW")
|
40 |
-
|
41 |
-
@staticmethod
|
42 |
-
def errprint(text):
|
43 |
-
"""Prints error text in red."""
|
44 |
-
Global.print_colored(text, "RED")
|
45 |
-
|
46 |
-
@staticmethod
|
47 |
-
def setDebug():
|
48 |
-
"""Sets the text color to yellow (for debugging)."""
|
49 |
-
Global.set_color("YELLOW")
|
50 |
-
|
51 |
-
@staticmethod
|
52 |
-
def setSuccess():
|
53 |
-
"""Sets the text color to green (for success messages)."""
|
54 |
-
Global.set_color("GREEN")
|
55 |
-
|
56 |
-
@staticmethod
|
57 |
-
def sprint(text):
|
58 |
-
"""Prints success text in green."""
|
59 |
-
Global.print_colored(text, "GREEN")
|
60 |
-
|
61 |
-
@staticmethod
|
62 |
-
def hr():
|
63 |
-
|
64 |
-
# Disable horizontal rule if set
|
65 |
-
if Global.disable_hr:
|
66 |
-
return
|
67 |
-
|
68 |
-
"""Fills the entire terminal with = (one row only)."""
|
69 |
-
columns, _ = shutil.get_terminal_size()
|
70 |
print("-" * columns)
|
|
|
1 |
+
from colorama import Fore, Style, init
|
2 |
+
import shutil
|
3 |
+
|
4 |
+
# Initialize colorama
|
5 |
+
init()
|
6 |
+
|
7 |
+
class Global:
|
8 |
+
|
9 |
+
disable_hr = False
|
10 |
+
EXECUTABLES = ['ffmpeg', 'mp4decrypt', 'nm3']
|
11 |
+
api_webdl_directory = "webdl"
|
12 |
+
|
13 |
+
def __init__(self, vout=True, outDir="./"):
|
14 |
+
self.outDir = outDir
|
15 |
+
self.vout = vout
|
16 |
+
|
17 |
+
@staticmethod
|
18 |
+
def set_color(color, style=None):
|
19 |
+
"""Prints text in the specified color and style."""
|
20 |
+
print(getattr(Fore, color), end="")
|
21 |
+
if style:
|
22 |
+
print(getattr(Style, style), end="")
|
23 |
+
|
24 |
+
@staticmethod
|
25 |
+
def reset():
|
26 |
+
"""Resets text color and style to defaults."""
|
27 |
+
print(Style.RESET_ALL, end="")
|
28 |
+
|
29 |
+
@staticmethod
|
30 |
+
def print_colored(text, color, style=None):
|
31 |
+
"""Prints text in the specified color and style, resetting afterward."""
|
32 |
+
Global.set_color(color, style)
|
33 |
+
print(text)
|
34 |
+
Global.reset()
|
35 |
+
|
36 |
+
@staticmethod
|
37 |
+
def dprint(text):
|
38 |
+
"""Prints debug text in yellow."""
|
39 |
+
Global.print_colored(text, "YELLOW")
|
40 |
+
|
41 |
+
@staticmethod
|
42 |
+
def errprint(text):
|
43 |
+
"""Prints error text in red."""
|
44 |
+
Global.print_colored(text, "RED")
|
45 |
+
|
46 |
+
@staticmethod
|
47 |
+
def setDebug():
|
48 |
+
"""Sets the text color to yellow (for debugging)."""
|
49 |
+
Global.set_color("YELLOW")
|
50 |
+
|
51 |
+
@staticmethod
|
52 |
+
def setSuccess():
|
53 |
+
"""Sets the text color to green (for success messages)."""
|
54 |
+
Global.set_color("GREEN")
|
55 |
+
|
56 |
+
@staticmethod
|
57 |
+
def sprint(text):
|
58 |
+
"""Prints success text in green."""
|
59 |
+
Global.print_colored(text, "GREEN")
|
60 |
+
|
61 |
+
@staticmethod
|
62 |
+
def hr():
|
63 |
+
|
64 |
+
# Disable horizontal rule if set
|
65 |
+
if Global.disable_hr:
|
66 |
+
return
|
67 |
+
|
68 |
+
"""Fills the entire terminal with = (one row only)."""
|
69 |
+
columns, _ = shutil.get_terminal_size()
|
70 |
print("-" * columns)
|
mainLogic/utils/keyUtils.py
CHANGED
@@ -1,25 +1,25 @@
|
|
1 |
-
"""
|
2 |
-
Obsolete module:
|
3 |
-
no longer used in the project
|
4 |
-
used when key.old.py was being used
|
5 |
-
"""
|
6 |
-
|
7 |
-
import base64
|
8 |
-
|
9 |
-
def base64_to_hex(base64_str):
|
10 |
-
# Replace special characters not in base64 list with '/'
|
11 |
-
base64_str = base64_str.replace('-', '+').replace('_', '/')
|
12 |
-
|
13 |
-
# Add padding if necessary
|
14 |
-
padding = len(base64_str) % 4
|
15 |
-
if padding:
|
16 |
-
base64_str += '=' * (4 - padding)
|
17 |
-
|
18 |
-
# Convert base64 to bytes
|
19 |
-
base64_bytes = base64_str.encode('utf-8')
|
20 |
-
|
21 |
-
# Decode base64 bytes to hex bytes
|
22 |
-
hex_bytes = base64.b64decode(base64_bytes).hex()
|
23 |
-
|
24 |
-
return hex_bytes
|
25 |
-
|
|
|
1 |
+
"""
|
2 |
+
Obsolete module:
|
3 |
+
no longer used in the project
|
4 |
+
used when key.old.py was being used
|
5 |
+
"""
|
6 |
+
|
7 |
+
import base64
|
8 |
+
|
9 |
+
def base64_to_hex(base64_str):
|
10 |
+
# Replace special characters not in base64 list with '/'
|
11 |
+
base64_str = base64_str.replace('-', '+').replace('_', '/')
|
12 |
+
|
13 |
+
# Add padding if necessary
|
14 |
+
padding = len(base64_str) % 4
|
15 |
+
if padding:
|
16 |
+
base64_str += '=' * (4 - padding)
|
17 |
+
|
18 |
+
# Convert base64 to bytes
|
19 |
+
base64_bytes = base64_str.encode('utf-8')
|
20 |
+
|
21 |
+
# Decode base64 bytes to hex bytes
|
22 |
+
hex_bytes = base64.b64decode(base64_bytes).hex()
|
23 |
+
|
24 |
+
return hex_bytes
|
25 |
+
|
mainLogic/utils/os2.py
CHANGED
@@ -1,53 +1,53 @@
|
|
1 |
-
import platform
|
2 |
-
import os
|
3 |
-
from mainLogic import error
|
4 |
-
from mainLogic.utils.process import shell
|
5 |
-
# 0 - linux
|
6 |
-
# 1 - windows
|
7 |
-
# 2 - mac (currently not supported)
|
8 |
-
|
9 |
-
class SysFunc:
|
10 |
-
def __init__(self,os=1 if "Windows" in platform.system() else 0 if "Linux" in platform.system() else -1):
|
11 |
-
if os == -1:
|
12 |
-
raise Exception("UnsupportedOS")
|
13 |
-
self.os = os
|
14 |
-
|
15 |
-
|
16 |
-
def clear(self):
|
17 |
-
if self.os == 0:
|
18 |
-
os.system("clear")
|
19 |
-
elif self.os == 1:
|
20 |
-
os.system("cls")
|
21 |
-
else:
|
22 |
-
raise Exception("UnsupportedOS")
|
23 |
-
|
24 |
-
|
25 |
-
def which(self,program):
|
26 |
-
|
27 |
-
if self.os == 0:
|
28 |
-
if shell('which',stderr="",stdout="") != 1:
|
29 |
-
error.errorList["dependencyNotFound"]["func"]('which')
|
30 |
-
exit(error.errorList["dependencyNotFound"]["code"])
|
31 |
-
else:
|
32 |
-
self.whichPresent = True
|
33 |
-
|
34 |
-
return shell(f"which {program}",stderr="",stdout="")
|
35 |
-
|
36 |
-
elif self.os == 1:
|
37 |
-
|
38 |
-
if shell('where',stderr="",stdout="") != 2:
|
39 |
-
error.errorList["dependencyNotFound"]["func"]('where')
|
40 |
-
exit(error.errorList["dependencyNotFound"]["code"])
|
41 |
-
else:
|
42 |
-
self.whichPresent = True
|
43 |
-
return shell(f"where {program}" , stderr="",stdout="")
|
44 |
-
else:
|
45 |
-
raise Exception("UnsupportedOS")
|
46 |
-
|
47 |
-
@staticmethod
|
48 |
-
def modify_path(path):
|
49 |
-
expanded_path = os.path.expandvars(path)
|
50 |
-
absolute_path = os.path.abspath(expanded_path)
|
51 |
-
modified_path = absolute_path.replace(os.sep, '/')
|
52 |
-
return modified_path
|
53 |
|
|
|
1 |
+
import platform
|
2 |
+
import os
|
3 |
+
from mainLogic import error
|
4 |
+
from mainLogic.utils.process import shell
|
5 |
+
# 0 - linux
|
6 |
+
# 1 - windows
|
7 |
+
# 2 - mac (currently not supported)
|
8 |
+
|
9 |
+
class SysFunc:
|
10 |
+
def __init__(self,os=1 if "Windows" in platform.system() else 0 if "Linux" in platform.system() else -1):
|
11 |
+
if os == -1:
|
12 |
+
raise Exception("UnsupportedOS")
|
13 |
+
self.os = os
|
14 |
+
|
15 |
+
|
16 |
+
def clear(self):
|
17 |
+
if self.os == 0:
|
18 |
+
os.system("clear")
|
19 |
+
elif self.os == 1:
|
20 |
+
os.system("cls")
|
21 |
+
else:
|
22 |
+
raise Exception("UnsupportedOS")
|
23 |
+
|
24 |
+
|
25 |
+
def which(self,program):
|
26 |
+
|
27 |
+
if self.os == 0:
|
28 |
+
if shell('which',stderr="",stdout="") != 1:
|
29 |
+
error.errorList["dependencyNotFound"]["func"]('which')
|
30 |
+
exit(error.errorList["dependencyNotFound"]["code"])
|
31 |
+
else:
|
32 |
+
self.whichPresent = True
|
33 |
+
|
34 |
+
return shell(f"which {program}",stderr="",stdout="")
|
35 |
+
|
36 |
+
elif self.os == 1:
|
37 |
+
|
38 |
+
if shell('where',stderr="",stdout="") != 2:
|
39 |
+
error.errorList["dependencyNotFound"]["func"]('where')
|
40 |
+
exit(error.errorList["dependencyNotFound"]["code"])
|
41 |
+
else:
|
42 |
+
self.whichPresent = True
|
43 |
+
return shell(f"where {program}" , stderr="",stdout="")
|
44 |
+
else:
|
45 |
+
raise Exception("UnsupportedOS")
|
46 |
+
|
47 |
+
@staticmethod
|
48 |
+
def modify_path(path):
|
49 |
+
expanded_path = os.path.expandvars(path)
|
50 |
+
absolute_path = os.path.abspath(expanded_path)
|
51 |
+
modified_path = absolute_path.replace(os.sep, '/')
|
52 |
+
return modified_path
|
53 |
|
mainLogic/utils/process.py
CHANGED
@@ -1,42 +1,42 @@
|
|
1 |
-
import subprocess
|
2 |
-
import re
|
3 |
-
import sys
|
4 |
-
|
5 |
-
|
6 |
-
def shell(command, filter=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, progress_callback=None, handleProgress=None):
|
7 |
-
import os
|
8 |
-
|
9 |
-
# Set PYTHONUNBUFFERED environment variable
|
10 |
-
os.environ['PYTHONUNBUFFERED'] = '1'
|
11 |
-
|
12 |
-
command = to_list(command)
|
13 |
-
|
14 |
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
|
15 |
-
|
16 |
-
# Read and print the output in real-time
|
17 |
-
while True:
|
18 |
-
output = process.stdout.readline()
|
19 |
-
if output == '' and process.poll() is not None:
|
20 |
-
break
|
21 |
-
if output and filter is not None and re.search(filter, output):
|
22 |
-
|
23 |
-
# call the progress callback with the filtered output
|
24 |
-
if progress_callback:
|
25 |
-
if handleProgress: progress_callback(handleProgress(output))
|
26 |
-
else: progress_callback(output)
|
27 |
-
print(output.strip())
|
28 |
-
|
29 |
-
# Wait for the process to complete and get the return code
|
30 |
-
return_code = process.poll()
|
31 |
-
|
32 |
-
return return_code
|
33 |
-
|
34 |
-
|
35 |
-
def to_list(variable):
|
36 |
-
if isinstance(variable, list):
|
37 |
-
return variable
|
38 |
-
elif variable is None:
|
39 |
-
return []
|
40 |
-
else:
|
41 |
-
# Convert to string and then to list by splitting at whitespaces
|
42 |
-
return variable.split()
|
|
|
1 |
+
import subprocess
|
2 |
+
import re
|
3 |
+
import sys
|
4 |
+
|
5 |
+
|
6 |
+
def shell(command, filter=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, progress_callback=None, handleProgress=None):
|
7 |
+
import os
|
8 |
+
|
9 |
+
# Set PYTHONUNBUFFERED environment variable
|
10 |
+
os.environ['PYTHONUNBUFFERED'] = '1'
|
11 |
+
|
12 |
+
command = to_list(command)
|
13 |
+
|
14 |
+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
|
15 |
+
|
16 |
+
# Read and print the output in real-time
|
17 |
+
while True:
|
18 |
+
output = process.stdout.readline()
|
19 |
+
if output == '' and process.poll() is not None:
|
20 |
+
break
|
21 |
+
if output and filter is not None and re.search(filter, output):
|
22 |
+
|
23 |
+
# call the progress callback with the filtered output
|
24 |
+
if progress_callback:
|
25 |
+
if handleProgress: progress_callback(handleProgress(output))
|
26 |
+
else: progress_callback(output)
|
27 |
+
print(output.strip())
|
28 |
+
|
29 |
+
# Wait for the process to complete and get the return code
|
30 |
+
return_code = process.poll()
|
31 |
+
|
32 |
+
return return_code
|
33 |
+
|
34 |
+
|
35 |
+
def to_list(variable):
|
36 |
+
if isinstance(variable, list):
|
37 |
+
return variable
|
38 |
+
elif variable is None:
|
39 |
+
return []
|
40 |
+
else:
|
41 |
+
# Convert to string and then to list by splitting at whitespaces
|
42 |
+
return variable.split()
|
pwdl.bat
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
-
@echo off
|
2 |
-
REM Batch script for running the Python script pwdl.py
|
3 |
-
|
4 |
-
REM Check if Python is installed
|
5 |
-
py --version >nul 2>&1
|
6 |
-
if errorlevel 1 (
|
7 |
-
echo Python is not installed or not found in the PATH.
|
8 |
-
exit /b 1
|
9 |
-
)
|
10 |
-
|
11 |
-
REM Run the Python script with the provided arguments
|
12 |
-
py "%~dp0pwdl.py" %*
|
|
|
1 |
+
@echo off
|
2 |
+
REM Batch script for running the Python script pwdl.py
|
3 |
+
|
4 |
+
REM Check if Python is installed
|
5 |
+
py --version >nul 2>&1
|
6 |
+
if errorlevel 1 (
|
7 |
+
echo Python is not installed or not found in the PATH.
|
8 |
+
exit /b 1
|
9 |
+
)
|
10 |
+
|
11 |
+
REM Run the Python script with the provided arguments
|
12 |
+
py "%~dp0pwdl.py" %*
|
pwdl.py
CHANGED
@@ -1,148 +1,148 @@
|
|
1 |
-
import argparse
|
2 |
-
from mainLogic.error import errorList
|
3 |
-
from mainLogic.utils.glv import Global
|
4 |
-
import sys
|
5 |
-
from mainLogic.utils.os2 import SysFunc
|
6 |
-
import os
|
7 |
-
from mainLogic.main import Main
|
8 |
-
from beta.shellLogic import shell
|
9 |
-
from mainLogic.startup.checkup import CheckState
|
10 |
-
|
11 |
-
# global variables
|
12 |
-
prefs = {}
|
13 |
-
glv = Global()
|
14 |
-
os2 = SysFunc()
|
15 |
-
|
16 |
-
# hardcoding the list of executables required for the script to run
|
17 |
-
# should be available in the PATH or the user should provide the path to the executables
|
18 |
-
EXECUTABLES = glv.EXECUTABLES
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
def main():
|
24 |
-
|
25 |
-
# parsing the arguments
|
26 |
-
parser = argparse.ArgumentParser(description='PhysicsWallah M3u8 parser.')
|
27 |
-
|
28 |
-
parser.add_argument('--csv-file', type=str, help='Input csv file. Legacy Support too.')
|
29 |
-
parser.add_argument('--id', type=str,
|
30 |
-
help='PhysicsWallh Video Id for single usage. Incompatible with --csv-file. Must be used '
|
31 |
-
'with --name')
|
32 |
-
parser.add_argument('--name', type=str,
|
33 |
-
help='Name for the output file. Incompatible with --csv-file. Must be used with --id')
|
34 |
-
parser.add_argument('--dir', type=str, help='Output Directory')
|
35 |
-
parser.add_argument('--verbose', action='store_true', help='Verbose Output')
|
36 |
-
parser.add_argument('--shell',action='store_true',help='Start the shell')
|
37 |
-
parser.add_argument('--version', action='version', version='%(prog)s 1.0')
|
38 |
-
parser.add_argument('--simulate', action='store_true',
|
39 |
-
help='Simulate the download process. No files will be downloaded.)')
|
40 |
-
|
41 |
-
args = parser.parse_args()
|
42 |
-
|
43 |
-
if args.shell:
|
44 |
-
shell.main()
|
45 |
-
|
46 |
-
|
47 |
-
# user_input is given preference i.e if --verbose is true it will override
|
48 |
-
# however if --verbose is false but prefs['verbose'] is true
|
49 |
-
glv.vout = args.verbose
|
50 |
-
|
51 |
-
global prefs
|
52 |
-
|
53 |
-
# check if all dependencies are installed
|
54 |
-
state = CheckState().checkup(EXECUTABLES, directory=args.dir,verbose=glv.vout)
|
55 |
-
prefs = state['prefs']
|
56 |
-
|
57 |
-
|
58 |
-
# --------------------------------------------------------------------------------------------------------------------------------------
|
59 |
-
# setting verbose output
|
60 |
-
# gives preference to user input
|
61 |
-
if not glv.vout and prefs['verbose']: glv.vout = prefs['verbose']
|
62 |
-
|
63 |
-
verbose = glv.vout
|
64 |
-
|
65 |
-
OUT_DIRECTORY = prefs['dir']
|
66 |
-
if verbose: Global.hr(); glv.dprint(f"Tmp Dir is: {SysFunc.modify_path(prefs['tmpDir'])}")
|
67 |
-
if verbose: Global.hr(); glv.dprint(f'Output Directory: {OUT_DIRECTORY}')
|
68 |
-
if verbose: Global.hr(); glv.dprint(f"Horizontal Rule: {not Global.disable_hr}")
|
69 |
-
|
70 |
-
# --------------------------------------------------------------------------------------------------------------------------------------
|
71 |
-
# end of loading user preferences
|
72 |
-
|
73 |
-
|
74 |
-
# starting the main process
|
75 |
-
|
76 |
-
#if both csv file and (id or name) is provided then -> exit with error code 3
|
77 |
-
if args.csv_file and (args.id or args.name):
|
78 |
-
print("Both csv file and id (or name) is provided. Unable to decide. Aborting! ...")
|
79 |
-
sys.exit(3)
|
80 |
-
|
81 |
-
# handle in case --csv-file is provided
|
82 |
-
if args.csv_file:
|
83 |
-
|
84 |
-
# simulation mode
|
85 |
-
if args.simulate:
|
86 |
-
print("Simulating the download csv process. No files will be downloaded.")
|
87 |
-
print("File to be processed: ", args.csv_file)
|
88 |
-
exit(0)
|
89 |
-
|
90 |
-
# exiting in case the CSV File is not found
|
91 |
-
if not os.path.exists(args.csv_file):
|
92 |
-
errorList['csvFileNotFound']['func'](args.csv_file)
|
93 |
-
sys.exit(errorList['csvFileNotFound']['code'])
|
94 |
-
|
95 |
-
with open(args.csv_file, 'r') as f:
|
96 |
-
for line in f:
|
97 |
-
name, id = line.strip().split(',')
|
98 |
-
|
99 |
-
# adding support for csv file with partial errors
|
100 |
-
try:
|
101 |
-
Main(id=id,
|
102 |
-
name=name,
|
103 |
-
directory=OUT_DIRECTORY,
|
104 |
-
ffmpeg=state['ffmpeg'],
|
105 |
-
nm3Path=state['nm3'],
|
106 |
-
mp4d=state['mp4decrypt'],
|
107 |
-
tmpDir=prefs['tmpDir'],
|
108 |
-
verbose=verbose,
|
109 |
-
suppress_exit=True # suppress exit in case of error (as multiple files are being processed)
|
110 |
-
).process()
|
111 |
-
|
112 |
-
except Exception as e:
|
113 |
-
errorList['downloadFailed']['func'](name, id)
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
# handle in case key and name is given
|
118 |
-
elif args.id and args.name:
|
119 |
-
|
120 |
-
# simulation mode
|
121 |
-
if args.simulate:
|
122 |
-
print("Simulating the download process. No files will be downloaded.")
|
123 |
-
print("Id to be processed: ", args.id)
|
124 |
-
print("Name to be processed: ", args.name)
|
125 |
-
exit(0)
|
126 |
-
|
127 |
-
try:
|
128 |
-
|
129 |
-
Main(id=args.id,
|
130 |
-
name=args.name,
|
131 |
-
directory=OUT_DIRECTORY,
|
132 |
-
ffmpeg=state['ffmpeg'],
|
133 |
-
nm3Path=state['nm3'],
|
134 |
-
mp4d=state['mp4decrypt'],
|
135 |
-
tmpDir=prefs['tmpDir'],
|
136 |
-
verbose=verbose).process()
|
137 |
-
|
138 |
-
except Exception as e:
|
139 |
-
errorList['downloadFailed']['func'](args.name, args.id)
|
140 |
-
sys.exit(errorList['downloadFailed']['code'])
|
141 |
-
|
142 |
-
# in case neither is used
|
143 |
-
else:
|
144 |
-
exit(1)
|
145 |
-
|
146 |
-
|
147 |
-
if __name__ == "__main__":
|
148 |
main()
|
|
|
1 |
+
import argparse
|
2 |
+
from mainLogic.error import errorList
|
3 |
+
from mainLogic.utils.glv import Global
|
4 |
+
import sys
|
5 |
+
from mainLogic.utils.os2 import SysFunc
|
6 |
+
import os
|
7 |
+
from mainLogic.main import Main
|
8 |
+
from beta.shellLogic import shell
|
9 |
+
from mainLogic.startup.checkup import CheckState
|
10 |
+
|
11 |
+
# global variables
|
12 |
+
prefs = {}
|
13 |
+
glv = Global()
|
14 |
+
os2 = SysFunc()
|
15 |
+
|
16 |
+
# hardcoding the list of executables required for the script to run
|
17 |
+
# should be available in the PATH or the user should provide the path to the executables
|
18 |
+
EXECUTABLES = glv.EXECUTABLES
|
19 |
+
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
def main():
|
24 |
+
|
25 |
+
# parsing the arguments
|
26 |
+
parser = argparse.ArgumentParser(description='PhysicsWallah M3u8 parser.')
|
27 |
+
|
28 |
+
parser.add_argument('--csv-file', type=str, help='Input csv file. Legacy Support too.')
|
29 |
+
parser.add_argument('--id', type=str,
|
30 |
+
help='PhysicsWallh Video Id for single usage. Incompatible with --csv-file. Must be used '
|
31 |
+
'with --name')
|
32 |
+
parser.add_argument('--name', type=str,
|
33 |
+
help='Name for the output file. Incompatible with --csv-file. Must be used with --id')
|
34 |
+
parser.add_argument('--dir', type=str, help='Output Directory')
|
35 |
+
parser.add_argument('--verbose', action='store_true', help='Verbose Output')
|
36 |
+
parser.add_argument('--shell',action='store_true',help='Start the shell')
|
37 |
+
parser.add_argument('--version', action='version', version='%(prog)s 1.0')
|
38 |
+
parser.add_argument('--simulate', action='store_true',
|
39 |
+
help='Simulate the download process. No files will be downloaded.)')
|
40 |
+
|
41 |
+
args = parser.parse_args()
|
42 |
+
|
43 |
+
if args.shell:
|
44 |
+
shell.main()
|
45 |
+
|
46 |
+
|
47 |
+
# user_input is given preference i.e if --verbose is true it will override
|
48 |
+
# however if --verbose is false but prefs['verbose'] is true
|
49 |
+
glv.vout = args.verbose
|
50 |
+
|
51 |
+
global prefs
|
52 |
+
|
53 |
+
# check if all dependencies are installed
|
54 |
+
state = CheckState().checkup(EXECUTABLES, directory=args.dir,verbose=glv.vout)
|
55 |
+
prefs = state['prefs']
|
56 |
+
|
57 |
+
|
58 |
+
# --------------------------------------------------------------------------------------------------------------------------------------
|
59 |
+
# setting verbose output
|
60 |
+
# gives preference to user input
|
61 |
+
if not glv.vout and prefs['verbose']: glv.vout = prefs['verbose']
|
62 |
+
|
63 |
+
verbose = glv.vout
|
64 |
+
|
65 |
+
OUT_DIRECTORY = prefs['dir']
|
66 |
+
if verbose: Global.hr(); glv.dprint(f"Tmp Dir is: {SysFunc.modify_path(prefs['tmpDir'])}")
|
67 |
+
if verbose: Global.hr(); glv.dprint(f'Output Directory: {OUT_DIRECTORY}')
|
68 |
+
if verbose: Global.hr(); glv.dprint(f"Horizontal Rule: {not Global.disable_hr}")
|
69 |
+
|
70 |
+
# --------------------------------------------------------------------------------------------------------------------------------------
|
71 |
+
# end of loading user preferences
|
72 |
+
|
73 |
+
|
74 |
+
# starting the main process
|
75 |
+
|
76 |
+
#if both csv file and (id or name) is provided then -> exit with error code 3
|
77 |
+
if args.csv_file and (args.id or args.name):
|
78 |
+
print("Both csv file and id (or name) is provided. Unable to decide. Aborting! ...")
|
79 |
+
sys.exit(3)
|
80 |
+
|
81 |
+
# handle in case --csv-file is provided
|
82 |
+
if args.csv_file:
|
83 |
+
|
84 |
+
# simulation mode
|
85 |
+
if args.simulate:
|
86 |
+
print("Simulating the download csv process. No files will be downloaded.")
|
87 |
+
print("File to be processed: ", args.csv_file)
|
88 |
+
exit(0)
|
89 |
+
|
90 |
+
# exiting in case the CSV File is not found
|
91 |
+
if not os.path.exists(args.csv_file):
|
92 |
+
errorList['csvFileNotFound']['func'](args.csv_file)
|
93 |
+
sys.exit(errorList['csvFileNotFound']['code'])
|
94 |
+
|
95 |
+
with open(args.csv_file, 'r') as f:
|
96 |
+
for line in f:
|
97 |
+
name, id = line.strip().split(',')
|
98 |
+
|
99 |
+
# adding support for csv file with partial errors
|
100 |
+
try:
|
101 |
+
Main(id=id,
|
102 |
+
name=name,
|
103 |
+
directory=OUT_DIRECTORY,
|
104 |
+
ffmpeg=state['ffmpeg'],
|
105 |
+
nm3Path=state['nm3'],
|
106 |
+
mp4d=state['mp4decrypt'],
|
107 |
+
tmpDir=prefs['tmpDir'],
|
108 |
+
verbose=verbose,
|
109 |
+
suppress_exit=True # suppress exit in case of error (as multiple files are being processed)
|
110 |
+
).process()
|
111 |
+
|
112 |
+
except Exception as e:
|
113 |
+
errorList['downloadFailed']['func'](name, id)
|
114 |
+
|
115 |
+
|
116 |
+
|
117 |
+
# handle in case key and name is given
|
118 |
+
elif args.id and args.name:
|
119 |
+
|
120 |
+
# simulation mode
|
121 |
+
if args.simulate:
|
122 |
+
print("Simulating the download process. No files will be downloaded.")
|
123 |
+
print("Id to be processed: ", args.id)
|
124 |
+
print("Name to be processed: ", args.name)
|
125 |
+
exit(0)
|
126 |
+
|
127 |
+
try:
|
128 |
+
|
129 |
+
Main(id=args.id,
|
130 |
+
name=args.name,
|
131 |
+
directory=OUT_DIRECTORY,
|
132 |
+
ffmpeg=state['ffmpeg'],
|
133 |
+
nm3Path=state['nm3'],
|
134 |
+
mp4d=state['mp4decrypt'],
|
135 |
+
tmpDir=prefs['tmpDir'],
|
136 |
+
verbose=verbose).process()
|
137 |
+
|
138 |
+
except Exception as e:
|
139 |
+
errorList['downloadFailed']['func'](args.name, args.id)
|
140 |
+
sys.exit(errorList['downloadFailed']['code'])
|
141 |
+
|
142 |
+
# in case neither is used
|
143 |
+
else:
|
144 |
+
exit(1)
|
145 |
+
|
146 |
+
|
147 |
+
if __name__ == "__main__":
|
148 |
main()
|