AkshitShubham commited on
Commit
942dcac
·
verified ·
1 Parent(s): 63364aa

Upload folder using huggingface_hub

Browse files
.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()