mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 12:05:35 +00:00
Make readme better
This commit is contained in:
parent
0996f8d415
commit
0faa63b3e2
237
README.md
237
README.md
@ -2,10 +2,10 @@
|
|||||||
<img src="./Src/Assets/min_logo.png">
|
<img src="./Src/Assets/min_logo.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
This repository provide a simple script designed to downloading films and series from a variety of supported streaming platforms. [SITE](#website-status-)
|
||||||
This repository provide a simple script designed to facilitate the downloading of films and series from a popular streaming community platform. The script allows users to download individual films, entire series, or specific episodes, providing a seamless experience for content consumers.
|
|
||||||
|
|
||||||
## Join us 🌟
|
## Join us 🌟
|
||||||
|
|
||||||
You can chat, help improve this repo, or just hang around for some fun in the **Git_StreamingCommunity** Discord [Server](https://discord.com/invite/8vV68UGRc7)
|
You can chat, help improve this repo, or just hang around for some fun in the **Git_StreamingCommunity** Discord [Server](https://discord.com/invite/8vV68UGRc7)
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
@ -28,14 +28,17 @@ You can chat, help improve this repo, or just hang around for some fun in the **
|
|||||||
# INSTALLATION
|
# INSTALLATION
|
||||||
|
|
||||||
## Automatic Installation
|
## Automatic Installation
|
||||||
<a id="automatic-installation-os"></a>
|
|
||||||
|
`<a id="automatic-installation-os"></a>`
|
||||||
|
|
||||||
### Supported OSs for Automatic Installation 💿
|
### Supported OSs for Automatic Installation 💿
|
||||||
|
|
||||||
- Supported ✔️
|
- Supported ✔️
|
||||||
- Not tested ⏳
|
- Not tested ⏳
|
||||||
- Not supported ❌
|
- Not supported ❌
|
||||||
|
|
||||||
| OS | Automatic Installation Support |
|
| OS | Automatic Installation Support |
|
||||||
|:--------------------|:--------------------:|
|
| :-------------- | :----------------------------: |
|
||||||
| Windows 10/11 | ✔️ |
|
| Windows 10/11 | ✔️ |
|
||||||
| Windows 7 | ❌ |
|
| Windows 7 | ❌ |
|
||||||
| Debian Linux | ✔️ |
|
| Debian Linux | ✔️ |
|
||||||
@ -46,37 +49,49 @@ You can chat, help improve this repo, or just hang around for some fun in the **
|
|||||||
| Termux | ❌ |
|
| Termux | ❌ |
|
||||||
|
|
||||||
### Installation ⚙️
|
### Installation ⚙️
|
||||||
|
|
||||||
Run the following command inside the main directory:
|
Run the following command inside the main directory:
|
||||||
|
|
||||||
#### On Windows:
|
#### On Windows:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\win_install.bat
|
.\win_install.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
#### On Linux/MacOS/BSD:
|
#### On Linux/MacOS/BSD:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chmod +x unix_install.sh && ./unix_install.sh
|
sudo chmod +x unix_install.sh && ./unix_install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="usage-automatic"></a>
|
`<a id="usage-automatic"></a>`
|
||||||
|
|
||||||
### Usage 📚
|
### Usage 📚
|
||||||
|
|
||||||
Run the script with the following command:
|
Run the script with the following command:
|
||||||
|
|
||||||
#### On Windows:
|
#### On Windows:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python .\run.py
|
python .\run.py
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
source .venv/bin/activate && python run.py && deactivate
|
source .venv/bin/activate && python run.py && deactivate
|
||||||
```
|
```
|
||||||
|
|
||||||
#### On Linux/MacOS/BSD:
|
#### On Linux/MacOS/BSD:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./run.py
|
./run.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manual Installation
|
## Manual Installation
|
||||||
<a id="requirement"></a>
|
|
||||||
|
`<a id="requirement"></a>`
|
||||||
|
|
||||||
### Requirement 📋
|
### Requirement 📋
|
||||||
|
|
||||||
Make sure you have the following prerequisites installed on your system:
|
Make sure you have the following prerequisites installed on your system:
|
||||||
@ -93,7 +108,6 @@ Install the required Python libraries using the following command:
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="usage-manual"></a>
|
|
||||||
### Usage 📚
|
### Usage 📚
|
||||||
|
|
||||||
Run the script with the following command:
|
Run the script with the following command:
|
||||||
@ -110,8 +124,7 @@ python run.py
|
|||||||
python3 run.py
|
python3 run.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Update
|
||||||
## Update
|
|
||||||
|
|
||||||
Keep your script up to date with the latest features by running:
|
Keep your script up to date with the latest features by running:
|
||||||
|
|
||||||
@ -127,91 +140,150 @@ python update_version.py
|
|||||||
python3 update_version.py
|
python3 update_version.py
|
||||||
```
|
```
|
||||||
|
|
||||||
<a id="configuration"></a>
|
|
||||||
## Configuration ⚙️
|
## Configuration ⚙️
|
||||||
|
|
||||||
You can change some behaviors by tweaking the configuration file.
|
You can change some behaviors by tweaking the configuration file.
|
||||||
|
|
||||||
<details>
|
The configuration file is divided into several main sections:
|
||||||
<summary><strong>DEFAULT</strong></summary>
|
|
||||||
|
|
||||||
* **debug**: Enables or disables debug mode.
|
### DEFAULT Settings
|
||||||
- **Default Value**: `false`
|
|
||||||
|
|
||||||
* **root_path**: Path where the script will add movies and TV series folders (see [Path Examples](#Path-examples)).
|
```json
|
||||||
- **Default Value**: `Video`
|
{
|
||||||
|
"root_path": "Video",
|
||||||
|
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
|
||||||
|
"special_chars_to_remove": "!@#$%^&*()[]{}<>|`~'\";:,?=+\u00e2\u20ac\u00a6",
|
||||||
|
"not_close": false,
|
||||||
|
"show_trending": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* **map_episode_name**: Mapping to choose the name of all episodes of TV Shows (see [Episode Name Usage](#Episode-name-usage)).
|
- `root_path`: Directory where all videos will be saved
|
||||||
- **Default Value**: `%(tv_name)_S%(season)E%(episode)_%(episode_name)`
|
|
||||||
|
|
||||||
* **not_close**: When activated, prevents the script from closing after its initial execution, allowing it to restart automatically after completing the first run.
|
|
||||||
- **Default Value**: `false`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><strong>REQUESTS</strong></summary>
|
|
||||||
|
|
||||||
* **timeout**: The timeout value for requests.
|
|
||||||
- **Default Value**: `15`
|
|
||||||
|
|
||||||
* **verify_ssl**: Whether to verify SSL certificates.
|
|
||||||
- **Default Value**: `false`
|
|
||||||
|
|
||||||
* **proxy**: To use proxy create a file with name list_proxy.txt and copy ip and port like "122.114.232.137:8080". They need to be http
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><strong>M3U8_DOWNLOAD</strong></summary>
|
|
||||||
|
|
||||||
* **tqdm_use_large_bar**: Whether to use large progress bars during downloads (Downloading %desc: %percentage:.2f %bar %elapsed < %remaining %postfix
|
|
||||||
- **Default Value**: `true`
|
|
||||||
- **Example Value**: `false` with Proc: %percentage:.2f %remaining %postfix
|
|
||||||
|
|
||||||
* **specific_list_audio**: A list of specific audio languages to download.
|
|
||||||
- **Example Value**: `['ita']`
|
|
||||||
|
|
||||||
* **specific_list_subtitles**: A list of specific subtitle languages to download.
|
|
||||||
- **Example Value**: `['ara', 'baq', 'cat', 'chi', 'cze', 'dan', 'dut', 'eng', 'fil', 'fin', 'forced-ita', 'fre', 'ger', 'glg', 'gre', 'heb', 'hin', 'hun', 'ind', 'ita', 'jpn', 'kan', 'kor', 'mal', 'may', 'nob', 'nor', 'pol', 'por', 'rum', 'rus', 'spa', 'swe', 'tam', 'tel', 'tha', 'tur', 'ukr', 'vie']`
|
|
||||||
|
|
||||||
* **cleanup_tmp_folder**: Upon final conversion, ensures the removal of all unformatted audio, video tracks, and subtitles from the temporary folder, thereby maintaining cleanliness and efficiency.
|
|
||||||
- **Default Value**: `false`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><strong>M3U8_PARSER</strong></summary>
|
|
||||||
|
|
||||||
* **force_resolution**: Forces the use of a specific resolution. `-1` means no forced resolution.
|
|
||||||
- **Default Value**: `-1`
|
|
||||||
- **Example Value**: `1080`
|
|
||||||
|
|
||||||
* **get_only_link**: Print hls m3u8 link and path file.
|
|
||||||
- **Default Value**: `false`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> If you're on **Windows** you'll need to use double back slash. On Linux/MacOS, one slash is fine.
|
|
||||||
|
|
||||||
#### Path examples:
|
#### Path examples:
|
||||||
|
|
||||||
* Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder).
|
|
||||||
|
* Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder)
|
||||||
* Linux/MacOS: `Desktop/MyLibrary/Folder`
|
* Linux/MacOS: `Desktop/MyLibrary/Folder`
|
||||||
|
`<br/><br/>`
|
||||||
|
- `map_episode_name`: Template for TV series episode filenames
|
||||||
|
|
||||||
#### Episode name usage:
|
#### Episode name usage:
|
||||||
|
|
||||||
You can choose different vars:
|
You can choose different vars:
|
||||||
|
|
||||||
|
|
||||||
* `%(tv_name)` : Is the name of TV Show
|
* `%(tv_name)` : Is the name of TV Show
|
||||||
* `%(season)` : Is the number of the season
|
* `%(season)` : Is the number of the season
|
||||||
* `%(episode)` : Is the number of the episode
|
* `%(episode)` : Is the number of the episode
|
||||||
* `%(episode_name)` : Is the name of the episode
|
* `%(episode_name)` : Is the name of the episode
|
||||||
|
`<br/><br/>`
|
||||||
|
- `special_chars_to_remove`: Special characters to be removed from filenames
|
||||||
|
- `not_close`: If true, continues running after downloading
|
||||||
|
- `show_trending`: Display trending content on startup
|
||||||
|
|
||||||
> NOTE: You don't need to add .mp4 at the end
|
#### qBittorrent Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"config_qbit_tor": {
|
||||||
|
"host": "192.168.1.59",
|
||||||
|
"port": "8080",
|
||||||
|
"user": "admin",
|
||||||
|
"pass": "adminadmin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable qBittorrent integration, follow the setup guide [here](https://github.com/lgallard/qBittorrent-Controller/wiki/How-to-enable-the-qBittorrent-Web-UI).
|
||||||
|
|
||||||
|
### REQUESTS Settings
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timeout": 20,
|
||||||
|
"max_retry": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `timeout`: Maximum timeout (in seconds) for each request
|
||||||
|
- `max_retry`: Number of retry attempts per segment during M3U8 index download
|
||||||
|
|
||||||
|
### BROWSER Settings
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"headless": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `headless`: Controls whether to run browser in headless mode
|
||||||
|
|
||||||
|
### M3U8_DOWNLOAD Settings
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tqdm_delay": 0.01,
|
||||||
|
"tqdm_use_large_bar": true,
|
||||||
|
"default_video_workser": 12,
|
||||||
|
"default_audio_workser": 12,
|
||||||
|
"cleanup_tmp_folder": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `tqdm_delay`: Delay between progress bar updates
|
||||||
|
- `tqdm_use_large_bar`: Use detailed progress bar (recommended for desktop) set to false for mobile
|
||||||
|
- `default_video_workser`: Number of threads for video download
|
||||||
|
- `default_audio_workser`: Number of threads for audio download
|
||||||
|
- `cleanup_tmp_folder`: Remove temporary .ts files after download
|
||||||
|
|
||||||
|
#### Language Settings
|
||||||
|
|
||||||
|
The following codes can be used for `specific_list_audio` and `specific_list_subtitles`:
|
||||||
|
|
||||||
|
```
|
||||||
|
ara - Arabic eng - English ita - Italian por - Portuguese
|
||||||
|
baq - Basque fil - Filipino jpn - Japanese rum - Romanian
|
||||||
|
cat - Catalan fin - Finnish kan - Kannada rus - Russian
|
||||||
|
chi - Chinese fre - French kor - Korean spa - Spanish
|
||||||
|
cze - Czech ger - German mal - Malayalam swe - Swedish
|
||||||
|
dan - Danish glg - Galician may - Malay tam - Tamil
|
||||||
|
dut - Dutch gre - Greek nob - Norw. Bokm tel - Telugu
|
||||||
|
heb - Hebrew nor - Norwegian tha - Thai
|
||||||
|
forced-ita hin - Hindi pol - Polish tur - Turkish
|
||||||
|
hun - Hungarian ukr - Ukrainian
|
||||||
|
ind - Indonesian vie - Vietnamese
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Language code availability may vary by site. Some platforms might:
|
||||||
|
>
|
||||||
|
> - Use different language codes
|
||||||
|
> - Support only a subset of these languages
|
||||||
|
> - Offer additional languages not listed here
|
||||||
|
>
|
||||||
|
> Check the specific site's available options if downloads fail.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> You can configure multiple languages by adding them to the lists:
|
||||||
|
>
|
||||||
|
> ```json
|
||||||
|
> "specific_list_audio": ["ita", "eng", "spa"],
|
||||||
|
> "specific_list_subtitles": ["ita", "eng", "spa"]
|
||||||
|
> ```
|
||||||
|
|
||||||
|
### M3U8_PARSER Settings
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"force_resolution": -1,
|
||||||
|
"get_only_link": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `force_resolution`: Force specific resolution (-1 for best available, or specify 1080, 720, 360)
|
||||||
|
- `get_only_link`: Return M3U8 playlist/index URL instead of downloading
|
||||||
|
|
||||||
<a id="docker"></a>
|
|
||||||
## Docker 🐳
|
## Docker 🐳
|
||||||
|
|
||||||
You can run the script in a docker container, to build the image just run
|
You can run the script in a docker container, to build the image just run
|
||||||
@ -245,14 +317,31 @@ make LOCAL_DIR=/path/to/download run-container
|
|||||||
|
|
||||||
The `run-container` command mounts also the `config.json` file, so any change to the configuration file is reflected immediately without having to rebuild the image.
|
The `run-container` command mounts also the `config.json` file, so any change to the configuration file is reflected immediately without having to rebuild the image.
|
||||||
|
|
||||||
<a id="tutorial"></a>
|
### Website Status 🌐
|
||||||
|
|
||||||
|
- Working ✅
|
||||||
|
- Not Working ❌
|
||||||
|
|
||||||
|
| Website | Status |
|
||||||
|
| :----------------- | :----: |
|
||||||
|
| 1337xx | ✅ |
|
||||||
|
| Altadefinizione | ❌ |
|
||||||
|
| AnimeUnity | ✅ |
|
||||||
|
| BitSearch | ✅ |
|
||||||
|
| CB01New | ✅ |
|
||||||
|
| DDLStreamItaly | ✅ |
|
||||||
|
| GuardaSerie | ✅ |
|
||||||
|
| MostraGuarda | ✅ |
|
||||||
|
| PirateBays | ✅ |
|
||||||
|
| StreamingCommunity | ✅ |
|
||||||
|
|
||||||
## Tutorial 📖
|
## Tutorial 📖
|
||||||
|
|
||||||
[win](https://www.youtube.com/watch?v=mZGqK4wdN-k)
|
[win](https://www.youtube.com/watch?v=mZGqK4wdN-k)
|
||||||
[linux](https://www.youtube.com/watch?v=0qUNXPE_mTg)
|
[linux](https://www.youtube.com/watch?v=0qUNXPE_mTg)
|
||||||
|
|
||||||
<a id="to-do"></a>
|
|
||||||
## To do 📝
|
## To do 📝
|
||||||
|
|
||||||
- GUI
|
- GUI
|
||||||
- Website api
|
- Website api
|
||||||
- Add other site
|
- Add other site
|
||||||
|
@ -28,7 +28,12 @@ class VideoSource:
|
|||||||
- url (str): The URL of the video.
|
- url (str): The URL of the video.
|
||||||
"""
|
"""
|
||||||
self.url = url
|
self.url = url
|
||||||
self.headers = {'user-agent': get_headers()}
|
self.headers = {
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||||
|
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||||
|
'User-Agent': get_headers()
|
||||||
|
}
|
||||||
|
self.client = httpx.Client()
|
||||||
|
|
||||||
def setup(self, url: str) -> None:
|
def setup(self, url: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -51,7 +56,7 @@ class VideoSource:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(url, headers=self.headers, follow_redirects=True, timeout=max_timeout)
|
response = self.client.get(url, headers=self.headers, follow_redirects=True, timeout=max_timeout)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
return response.text
|
return response.text
|
||||||
|
@ -16,7 +16,7 @@ from ..Template.Class.SearchType import MediaManager
|
|||||||
|
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
from .costant import SITE_NAME
|
from .costant import SITE_NAME, DOMAIN_NOW
|
||||||
media_search_manager = MediaManager()
|
media_search_manager = MediaManager()
|
||||||
table_show_manager = TVShowManager()
|
table_show_manager = TVShowManager()
|
||||||
|
|
||||||
@ -31,16 +31,21 @@ def title_search(title_search: str) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
int: The number of titles found.
|
int: The number of titles found.
|
||||||
"""
|
"""
|
||||||
|
client = httpx.Client()
|
||||||
|
|
||||||
# Find new domain if prev dont work
|
# Find new domain if prev dont work
|
||||||
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||||
domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
|
#domain_to_use, _ = search_domain(SITE_NAME, f"https://{SITE_NAME}")
|
||||||
|
|
||||||
# Send request to search for title
|
# Send request to search for title
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = client.get(
|
||||||
url=f"https://{SITE_NAME}.{domain_to_use}/?story={unidecode(title_search.replace(' ', '+'))}&do=search&subaction=search&titleonly=3",
|
url=f"https://{SITE_NAME}.{DOMAIN_NOW}/?story={unidecode(title_search.replace(' ', '+'))}&do=search&subaction=search&titleonly=3",
|
||||||
headers={'user-agent': get_headers()},
|
headers={
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||||
|
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||||
|
'User-Agent': get_headers()
|
||||||
|
},
|
||||||
timeout=max_timeout
|
timeout=max_timeout
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
@ -5,7 +5,7 @@ from Src.Util.console import console, msg
|
|||||||
|
|
||||||
|
|
||||||
# Logic class
|
# Logic class
|
||||||
from .site import title_search, run_get_select_title
|
from .site import title_search, run_get_select_title, media_search_manager
|
||||||
from .film import download_film
|
from .film import download_film
|
||||||
|
|
||||||
|
|
||||||
@ -17,15 +17,21 @@ _priority = 2
|
|||||||
_engineDownload = "mp4"
|
_engineDownload = "mp4"
|
||||||
|
|
||||||
|
|
||||||
def search():
|
def search(string_to_search: str = None, get_onylDatabase:bool = False):
|
||||||
"""
|
"""
|
||||||
Main function of the application for film and series.
|
Main function of the application for film and series.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Make request to site to get content that corrsisponde to that string
|
if string_to_search is None:
|
||||||
string_to_search = msg.ask("\n[purple]Insert word to search in all site").strip()
|
string_to_search = msg.ask("\n[purple]Insert word to search in all site").strip()
|
||||||
|
|
||||||
|
# Search on database
|
||||||
len_database = title_search(string_to_search)
|
len_database = title_search(string_to_search)
|
||||||
|
|
||||||
|
# Return list of elements
|
||||||
|
if get_onylDatabase:
|
||||||
|
return media_search_manager
|
||||||
|
|
||||||
if len_database > 0:
|
if len_database > 0:
|
||||||
|
|
||||||
# Select title from list
|
# Select title from list
|
||||||
|
@ -74,6 +74,7 @@ class PathManager:
|
|||||||
|
|
||||||
# Create the base path by removing the '.mp4' extension from the output filename
|
# Create the base path by removing the '.mp4' extension from the output filename
|
||||||
self.base_path = str(output_filename).replace(".mp4", "")
|
self.base_path = str(output_filename).replace(".mp4", "")
|
||||||
|
logging.info(f"class 'PathManager'; set base path: {self.base_path}")
|
||||||
|
|
||||||
# Define the path for a temporary directory where segments will be stored
|
# Define the path for a temporary directory where segments will be stored
|
||||||
self.base_temp = os.path.join(self.base_path, "tmp")
|
self.base_temp = os.path.join(self.base_path, "tmp")
|
||||||
@ -106,6 +107,7 @@ class HttpClient:
|
|||||||
Returns:
|
Returns:
|
||||||
str: The response body as text if the request is successful, None otherwise.
|
str: The response body as text if the request is successful, None otherwise.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'HttpClient'; make request: {url}")
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=url,
|
url=url,
|
||||||
@ -127,6 +129,7 @@ class HttpClient:
|
|||||||
Returns:
|
Returns:
|
||||||
bytes: The response content as bytes if the request is successful, None otherwise.
|
bytes: The response content as bytes if the request is successful, None otherwise.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'HttpClient'; make request: {url}")
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=url,
|
url=url,
|
||||||
@ -168,6 +171,7 @@ class ContentExtractor:
|
|||||||
"""
|
"""
|
||||||
It checks for available audio languages and the specific audio tracks to download.
|
It checks for available audio languages and the specific audio tracks to download.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'ContentExtractor'; call _collect_audio()")
|
||||||
|
|
||||||
# Collect available audio tracks and their corresponding URIs and names
|
# Collect available audio tracks and their corresponding URIs and names
|
||||||
self.list_available_audio = self.obj_parse._audio.get_all_uris_and_names()
|
self.list_available_audio = self.obj_parse._audio.get_all_uris_and_names()
|
||||||
@ -197,6 +201,7 @@ class ContentExtractor:
|
|||||||
"""
|
"""
|
||||||
It checks for available subtitle languages and the specific subtitles to download.
|
It checks for available subtitle languages and the specific subtitles to download.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'ContentExtractor'; call _collect_subtitle()")
|
||||||
|
|
||||||
# Collect available subtitles and their corresponding URIs and names
|
# Collect available subtitles and their corresponding URIs and names
|
||||||
self.list_available_subtitles = self.obj_parse._subtitle.get_all_uris_and_names()
|
self.list_available_subtitles = self.obj_parse._subtitle.get_all_uris_and_names()
|
||||||
@ -226,6 +231,7 @@ class ContentExtractor:
|
|||||||
"""
|
"""
|
||||||
It identifies the best video quality and displays relevant information to the user.
|
It identifies the best video quality and displays relevant information to the user.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'ContentExtractor'; call _collect_video()")
|
||||||
|
|
||||||
# Collect custom quality video if a specific resolution is set
|
# Collect custom quality video if a specific resolution is set
|
||||||
if FILTER_CUSTOM_REOLUTION != -1:
|
if FILTER_CUSTOM_REOLUTION != -1:
|
||||||
@ -297,6 +303,8 @@ class DownloadTracker:
|
|||||||
Args:
|
Args:
|
||||||
available_video (str): The URL of the video to be downloaded.
|
available_video (str): The URL of the video to be downloaded.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'DownloadTracker'; call add_video() with parameter: {available_video}")
|
||||||
|
|
||||||
self.downloaded_video.append({
|
self.downloaded_video.append({
|
||||||
'type': 'video',
|
'type': 'video',
|
||||||
'url': available_video,
|
'url': available_video,
|
||||||
@ -310,6 +318,8 @@ class DownloadTracker:
|
|||||||
Args:
|
Args:
|
||||||
list_available_audio (list): A list of available audio track objects.
|
list_available_audio (list): A list of available audio track objects.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'DownloadTracker'; call add_audio() with parameter: {list_available_audio}")
|
||||||
|
|
||||||
for obj_audio in list_available_audio:
|
for obj_audio in list_available_audio:
|
||||||
|
|
||||||
# Check if specific audio languages are set for download
|
# Check if specific audio languages are set for download
|
||||||
@ -337,6 +347,7 @@ class DownloadTracker:
|
|||||||
Args:
|
Args:
|
||||||
list_available_subtitles (list): A list of available subtitle objects.
|
list_available_subtitles (list): A list of available subtitle objects.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'DownloadTracker'; call add_subtitle() with parameter: {list_available_subtitles}")
|
||||||
|
|
||||||
for obj_subtitle in list_available_subtitles:
|
for obj_subtitle in list_available_subtitles:
|
||||||
|
|
||||||
@ -377,6 +388,7 @@ class ContentDownloader:
|
|||||||
Args:
|
Args:
|
||||||
downloaded_video (list): A list containing information about the video to download.
|
downloaded_video (list): A list containing information about the video to download.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'ContentDownloader'; call download_video() with parameter: {downloaded_video}")
|
||||||
|
|
||||||
# Check if the video file already exists
|
# Check if the video file already exists
|
||||||
if not os.path.exists(downloaded_video[0].get('path')):
|
if not os.path.exists(downloaded_video[0].get('path')):
|
||||||
@ -407,6 +419,8 @@ class ContentDownloader:
|
|||||||
Args:
|
Args:
|
||||||
downloaded_audio (list): A list containing information about audio tracks to download.
|
downloaded_audio (list): A list containing information about audio tracks to download.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'ContentDownloader'; call download_audio() with parameter: {downloaded_audio}")
|
||||||
|
|
||||||
for obj_audio in downloaded_audio:
|
for obj_audio in downloaded_audio:
|
||||||
folder_name = os.path.dirname(obj_audio.get('path'))
|
folder_name = os.path.dirname(obj_audio.get('path'))
|
||||||
|
|
||||||
@ -435,6 +449,8 @@ class ContentDownloader:
|
|||||||
Args:
|
Args:
|
||||||
downloaded_subtitle (list): A list containing information about subtitles to download.
|
downloaded_subtitle (list): A list containing information about subtitles to download.
|
||||||
"""
|
"""
|
||||||
|
logging.info(f"class 'ContentDownloader'; call download_subtitle() with parameter: {downloaded_subtitle}")
|
||||||
|
|
||||||
for obj_subtitle in downloaded_subtitle:
|
for obj_subtitle in downloaded_subtitle:
|
||||||
sub_language = obj_subtitle.get('language')
|
sub_language = obj_subtitle.get('language')
|
||||||
|
|
||||||
@ -670,6 +686,10 @@ class HLS_Downloader:
|
|||||||
is_playlist_url (bool): Flag indicating if the m3u8_playlist is a URL.
|
is_playlist_url (bool): Flag indicating if the m3u8_playlist is a URL.
|
||||||
is_index_url (bool): Flag indicating if the m3u8_index is a URL.
|
is_index_url (bool): Flag indicating if the m3u8_index is a URL.
|
||||||
"""
|
"""
|
||||||
|
if ((m3u8_playlist == None or m3u8_playlist == "") and output_filename is None) or ((m3u8_index == None or m3u8_index == "") and output_filename is None):
|
||||||
|
logging.info(f"class 'HLS_Downloader'; call __init__(); no parameter")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
self.output_filename = self._generate_output_filename(output_filename, m3u8_playlist, m3u8_index)
|
self.output_filename = self._generate_output_filename(output_filename, m3u8_playlist, m3u8_index)
|
||||||
self.path_manager = PathManager(self.output_filename)
|
self.path_manager = PathManager(self.output_filename)
|
||||||
self.download_tracker = DownloadTracker(self.path_manager)
|
self.download_tracker = DownloadTracker(self.path_manager)
|
||||||
@ -684,6 +704,13 @@ class HLS_Downloader:
|
|||||||
self.expected_real_time = None
|
self.expected_real_time = None
|
||||||
self.instace_parserClass = M3U8_Parser()
|
self.instace_parserClass = M3U8_Parser()
|
||||||
|
|
||||||
|
self.request_m3u8_playlist = None
|
||||||
|
self.request_m3u8_index = None
|
||||||
|
if (m3u8_playlist == None or m3u8_playlist == ""):
|
||||||
|
self.request_m3u8_index = HttpClient().get(self.m3u8_index)
|
||||||
|
if (m3u8_index == None or m3u8_index == ""):
|
||||||
|
self.request_m3u8_playlist = HttpClient().get(self.m3u8_playlist)
|
||||||
|
|
||||||
def _generate_output_filename(self, output_filename, m3u8_playlist, m3u8_index):
|
def _generate_output_filename(self, output_filename, m3u8_playlist, m3u8_index):
|
||||||
"""
|
"""
|
||||||
Generates a valid output filename based on provided parameters.
|
Generates a valid output filename based on provided parameters.
|
||||||
@ -699,6 +726,7 @@ class HLS_Downloader:
|
|||||||
root_path = config_manager.get('DEFAULT', 'root_path')
|
root_path = config_manager.get('DEFAULT', 'root_path')
|
||||||
new_filename = None
|
new_filename = None
|
||||||
new_folder = os.path.join(root_path, "undefined")
|
new_folder = os.path.join(root_path, "undefined")
|
||||||
|
logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); destination folder: {new_folder}")
|
||||||
|
|
||||||
# Auto-generate output file name if not present
|
# Auto-generate output file name if not present
|
||||||
if (output_filename is None) or ("mp4" not in output_filename):
|
if (output_filename is None) or ("mp4" not in output_filename):
|
||||||
@ -728,6 +756,7 @@ class HLS_Downloader:
|
|||||||
new_filename = os.path.join(folder, base_name)
|
new_filename = os.path.join(folder, base_name)
|
||||||
new_filename = unidecode(new_filename)
|
new_filename = unidecode(new_filename)
|
||||||
|
|
||||||
|
logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); return path: {new_filename}")
|
||||||
return new_filename
|
return new_filename
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@ -736,18 +765,17 @@ class HLS_Downloader:
|
|||||||
"""
|
"""
|
||||||
if os.path.exists(self.output_filename):
|
if os.path.exists(self.output_filename):
|
||||||
console.log("[red]Output file already exists.")
|
console.log("[red]Output file already exists.")
|
||||||
return
|
return 400
|
||||||
|
|
||||||
self.path_manager.create_directories()
|
self.path_manager.create_directories()
|
||||||
|
|
||||||
# Determine whether to process a playlist or index
|
# Determine whether to process a playlist or index
|
||||||
if self.m3u8_playlist:
|
if self.m3u8_playlist:
|
||||||
if self.m3u8_playlist is not None:
|
if self.m3u8_playlist is not None:
|
||||||
|
if self.request_m3u8_playlist != 404:
|
||||||
|
logging.info(f"class 'HLS_Downloader'; call start(); parse m3u8 data")
|
||||||
|
|
||||||
# Parse data from url and check if is a master playlist
|
self.instace_parserClass.parse_data(uri=self.m3u8_playlist, raw_content=self.request_m3u8_playlist)
|
||||||
test_raw_content = HttpClient().get(self.m3u8_playlist)
|
|
||||||
if test_raw_content != 404:
|
|
||||||
self.instace_parserClass.parse_data(uri=self.m3u8_playlist, raw_content=test_raw_content)
|
|
||||||
is_masterPlaylist = self.instace_parserClass.is_master_playlist
|
is_masterPlaylist = self.instace_parserClass.is_master_playlist
|
||||||
|
|
||||||
# Check if it's a real master playlist
|
# Check if it's a real master playlist
|
||||||
@ -768,16 +796,17 @@ class HLS_Downloader:
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
console.log("[red]Error: URL passed to M3U8_Parser is an index playlist; expected a master playlist. Crucimorfo strikes again!")
|
console.log("[red]Error: URL passed to M3U8_Parser is an index playlist; expected a master playlist. Crucimorfo strikes again!")
|
||||||
|
else:
|
||||||
|
console.log("[red]Error: m3u8_playlist failed request")
|
||||||
else:
|
else:
|
||||||
console.log("[red]Error: m3u8_playlist is None")
|
console.log("[red]Error: m3u8_playlist is None")
|
||||||
|
|
||||||
elif self.m3u8_index:
|
elif self.m3u8_index:
|
||||||
if self.m3u8_index is not None:
|
if self.m3u8_index is not None:
|
||||||
|
if self.request_m3u8_index != 404:
|
||||||
|
logging.info(f"class 'HLS_Downloader'; call start(); parse m3u8 data")
|
||||||
|
|
||||||
# Parse data from url and check if is a master playlist
|
self.instace_parserClass.parse_data(uri=self.m3u8_index, raw_content=self.request_m3u8_index)
|
||||||
test_raw_content = HttpClient().get(self.m3u8_index)
|
|
||||||
if test_raw_content != 404:
|
|
||||||
self.instace_parserClass.parse_data(uri=self.m3u8_index, raw_content=test_raw_content)
|
|
||||||
is_masterPlaylist = self.instace_parserClass.is_master_playlist
|
is_masterPlaylist = self.instace_parserClass.is_master_playlist
|
||||||
|
|
||||||
# Check if it's a real index playlist
|
# Check if it's a real index playlist
|
||||||
@ -794,10 +823,11 @@ class HLS_Downloader:
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
console.log("[red]Error: URL passed to M3U8_Parser is an master playlist; expected a index playlist. Crucimorfo strikes again!")
|
console.log("[red]Error: URL passed to M3U8_Parser is an master playlist; expected a index playlist. Crucimorfo strikes again!")
|
||||||
|
else:
|
||||||
|
console.log("[red]Error: m3u8_index failed request")
|
||||||
else:
|
else:
|
||||||
console.log("[red]Error: m3u8_index is None")
|
console.log("[red]Error: m3u8_index is None")
|
||||||
|
|
||||||
|
|
||||||
def _clean(self, out_path: str) -> None:
|
def _clean(self, out_path: str) -> None:
|
||||||
"""
|
"""
|
||||||
Cleans up temporary files and folders after downloading and processing.
|
Cleans up temporary files and folders after downloading and processing.
|
||||||
@ -874,20 +904,20 @@ class HLS_Downloader:
|
|||||||
else:
|
else:
|
||||||
logging.info("Video file converted already exists.")
|
logging.info("Video file converted already exists.")
|
||||||
|
|
||||||
def _process_playlist(self):
|
def _valida_playlist(self):
|
||||||
"""
|
"""
|
||||||
Processes the m3u8 playlist to download video, audio, and subtitles.
|
Validates the m3u8 playlist content, saves it to a temporary file, and collects playlist information.
|
||||||
"""
|
"""
|
||||||
|
logging.info("class 'HLS_Downloader'; call _valida_playlist()")
|
||||||
|
|
||||||
# Retrieve the m3u8 playlist content
|
# Retrieve the m3u8 playlist content
|
||||||
if self.is_playlist_url:
|
if self.is_playlist_url:
|
||||||
response_text = HttpClient(headers=headers_index).get(self.m3u8_playlist)
|
if self.request_m3u8_playlist != 404:
|
||||||
|
m3u8_playlist_text = self.request_m3u8_playlist
|
||||||
if response_text != 404:
|
|
||||||
m3u8_playlist_text = response_text
|
|
||||||
m3u8_url_fixer.set_playlist(self.m3u8_playlist)
|
m3u8_url_fixer.set_playlist(self.m3u8_playlist)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
logging.info(f"class 'HLS_Downloader'; call _process_playlist(); return 404")
|
||||||
return 404
|
return 404
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -907,6 +937,12 @@ class HLS_Downloader:
|
|||||||
else:
|
else:
|
||||||
self.content_extractor.start("https://fake.com", m3u8_playlist_text)
|
self.content_extractor.start("https://fake.com", m3u8_playlist_text)
|
||||||
|
|
||||||
|
def _process_playlist(self):
|
||||||
|
"""
|
||||||
|
Processes the m3u8 playlist to download video, audio, and subtitles.
|
||||||
|
"""
|
||||||
|
self._valida_playlist()
|
||||||
|
|
||||||
# Add downloaded elements to the tracker
|
# Add downloaded elements to the tracker
|
||||||
self.download_tracker.add_video(self.content_extractor.m3u8_index)
|
self.download_tracker.add_video(self.content_extractor.m3u8_index)
|
||||||
self.download_tracker.add_audio(self.content_extractor.list_available_audio)
|
self.download_tracker.add_audio(self.content_extractor.list_available_audio)
|
||||||
@ -941,4 +977,3 @@ class HLS_Downloader:
|
|||||||
|
|
||||||
# Clean up temporary files and directories
|
# Clean up temporary files and directories
|
||||||
self._clean(self.content_joiner.converted_out_path)
|
self._clean(self.content_joiner.converted_out_path)
|
||||||
|
|
@ -609,7 +609,6 @@ class M3U8_Parser:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
for segment in m3u8_obj.segments:
|
for segment in m3u8_obj.segments:
|
||||||
|
|
||||||
# Parse key
|
# Parse key
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
__title__ = 'StreamingCommunity'
|
__title__ = 'StreamingCommunity'
|
||||||
__version__ = 'v1.6.0'
|
__version__ = 'v1.7.0'
|
||||||
__author__ = 'Lovi-0'
|
__author__ = 'Lovi-0'
|
||||||
__description__ = 'A command-line program to download film'
|
__description__ = 'A command-line program to download film'
|
||||||
__copyright__ = 'Copyright 2024'
|
__copyright__ = 'Copyright 2024'
|
||||||
|
@ -9,11 +9,15 @@ sys.path.append(src_path)
|
|||||||
|
|
||||||
|
|
||||||
# Import
|
# Import
|
||||||
|
from Src.Util.message import start_message
|
||||||
|
from Src.Util.logger import Logger
|
||||||
from Src.Lib.Downloader import HLS_Downloader
|
from Src.Lib.Downloader import HLS_Downloader
|
||||||
|
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
HLS_Downloader(
|
start_message()
|
||||||
|
logger = Logger()
|
||||||
|
print("Return: ", HLS_Downloader(
|
||||||
output_filename="",
|
output_filename="",
|
||||||
m3u8_playlist=""
|
m3u8_index=""
|
||||||
).start()
|
).start())
|
@ -9,11 +9,15 @@ sys.path.append(src_path)
|
|||||||
|
|
||||||
|
|
||||||
# Import
|
# Import
|
||||||
|
from Src.Util.message import start_message
|
||||||
|
from Src.Util.logger import Logger
|
||||||
from Src.Lib.Downloader import MP4_downloader
|
from Src.Lib.Downloader import MP4_downloader
|
||||||
|
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
MP4_downloader(
|
start_message()
|
||||||
|
logger = Logger()
|
||||||
|
print("Return: ", MP4_downloader(
|
||||||
"",
|
"",
|
||||||
".\Video\undefined.mp4"
|
".\Video\undefined.mp4"
|
||||||
)
|
))
|
||||||
|
@ -9,10 +9,14 @@ sys.path.append(src_path)
|
|||||||
|
|
||||||
|
|
||||||
# Import
|
# Import
|
||||||
|
from Src.Util.message import start_message
|
||||||
|
from Src.Util.logger import Logger
|
||||||
from Src.Lib.Downloader import TOR_downloader
|
from Src.Lib.Downloader import TOR_downloader
|
||||||
|
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
|
start_message()
|
||||||
|
logger = Logger()
|
||||||
manager = TOR_downloader()
|
manager = TOR_downloader()
|
||||||
|
|
||||||
magnet_link = "magnet:?x"
|
magnet_link = "magnet:?x"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user