rcdl 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rcdl-2.0.0/.gitignore ADDED
@@ -0,0 +1,228 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ #.idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+
204
+ # Ruff stuff:
205
+ .ruff_cache/
206
+
207
+ # PyPI configuration file
208
+ .pypirc
209
+
210
+ # Marimo
211
+ marimo/_static/
212
+ marimo/_lsp/
213
+ __marimo__/
214
+
215
+ # Streamlit
216
+ .streamlit/secrets.toml
217
+
218
+ # editor
219
+ .vscode/
220
+
221
+ # dev files
222
+ output/
223
+ dev/
224
+
225
+ # output file
226
+ *.mp4
227
+ *.mp3
228
+ *.txt
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ ---
4
+ ## [Unreleased]
5
+ ---
6
+
7
+ ## [2.0.0] - 2025-12-25
8
+ ## Added
9
+ - Renamed the project from `cdl` to `rcdl` !
10
+ - new command `refresh` to update video to be downloaded without starting the download
11
+ ## Changed
12
+ - default file creation changed (see config.py)
13
+ - progress ETA improved
14
+ - replaced .csv cache with `sqlite3` DB
15
+ - `refresh` functionnality removed from `dlsf`
16
+ ## Fixed
17
+ - Fix multiple bugs
18
+
19
+ ## [1.6.2] - 2025-12-21
20
+ ### Fixed
21
+ - fix empty '' ss duration
22
+
23
+ ## [1.6.0] - 2025-11-01
24
+ ### Added
25
+ - Discovery command: search tags for posts with tag
26
+ - Tag and Max_page aoption arg
27
+
28
+ ## [1.5.1] - 2025-10-21
29
+ ### Added
30
+ - Fuse part videos with cdl fuse command. Warning: delete part videos that are succesfully fused.
31
+ - preset selection in settings.json
32
+ - data gatehring of fuse performance in .cache/fuse.csv
33
+ - progress info for fuse command
34
+ ### Changed
35
+ - Instead of deleting fused file, rename to .mv_oldfilename.mp4
36
+
37
+ ## [1.4.1] - 2025-10-20
38
+ ### Added
39
+ - Updated parser to extract .m4v video too
40
+ ### Fixed
41
+ - Fixed crash when looking for already downloaded posts and error index in .cache/ is missing
42
+ - Fixed issue when video is not written in cache, still check if video not in paths
43
+
44
+ ## [1.4.0] - 2025-10-17
45
+ ### Added
46
+ - custom yt-dlp flags in "yt_dlp_args" in settings.json. aria2 call live here
47
+ ### Fixed
48
+ - fix bug when initialisation wrote default settings despite file already existing
49
+
50
+ ## [1.3.1] - 2025-10-04
51
+ ### Added
52
+ - use aria2 as external downloader in yt-dlp
53
+ - add --version flag
54
+ - show log file in terminal with command cdl log
55
+ - added DEBUG flag --debug, no functionnality yet
56
+ - add support for kemono
57
+ ### Changed
58
+ - Python version required now is >= 3.12 instead of >= 3.13
59
+ - Progress class to track progress, now also give an estimated time remaining
60
+ - Progress eta is now in HH:MM:SS format
61
+ - change log file location
62
+ ### Fixed
63
+ - fix empty title bug
64
+ - fix an issue when multiples URLS where in a post, it only checked if the first post had been downloaded and not all urls before skipping download
65
+
66
+ ## [1.0.0] - 2025-10-03
67
+ ### Added
68
+ - initial release of the project
rcdl-2.0.0/PKG-INFO ADDED
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: rcdl
3
+ Version: 2.0.0
4
+ Summary: Coomer/Kemono CLI Downloader
5
+ Keywords: downloader,video,media
6
+ Author: Anonymous
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Dist: click>=8.2
13
+ Requires-Dist: requests>=2.32
14
+ Requires-Dist: pathvalidate==3.3.1
15
+ Requires-Dist: moviepy==2.2.1
16
+
17
+ # RCDL
18
+
19
+ Riton Coomer Download Manager
20
+ `rcdl` is a tool to automatically download the videos of your favorites creators from [coomer.st](https://coomer.st) and [kemono.cr](https://kemono.cr)
21
+
22
+
23
+ ## Install
24
+ ### Dependencies
25
+ - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
26
+ - [aria2](https://github.com/aria2/aria2)
27
+ - [ffmpeg](https://www.ffmpeg.org/download.html) (Only for `fuse` command)
28
+ Recommended install:
29
+ ```bash
30
+ pipx install yt-dlp
31
+ sudo apt install aria2 ffmpeg
32
+ ```
33
+ ### Install RCDL
34
+ It is recommended to use pipx
35
+ ```bash
36
+ pipx install rcdl
37
+ ```
38
+ or else:
39
+ ```bash
40
+ pip install rcdl
41
+ ```
42
+
43
+ ## How to use
44
+
45
+ Run the CLI with:
46
+
47
+ ```bash
48
+ rcdl --help
49
+ ```
50
+
51
+ By default all files will live in `~/Videos/rcdl/`. Cache, configuration and log file will be in a hidden `rcdl/.cache/` folder.
52
+
53
+ ```bash
54
+ rcdl refresh # look creators.json and find all possible videos
55
+ rcdl dlsf # download all found videos
56
+ rcdl discover # WIP
57
+ rcdl fuse # WIP
58
+ rcdl log # debug only; show the log file
59
+ ```
60
+
61
+ ## Dev
62
+ ### Install
63
+ ```bash
64
+ git clone https://github.com/ritonun/cdl.git rcdl
65
+ cd rcdl
66
+ python3 -m venv .venv
67
+ source .venv/bin/activate
68
+ pip install -e .
69
+ ```
70
+
71
+ ## Deploy
72
+ ```bash
73
+ python3 -m pip install --upgrade build
74
+ python3 -m pip install --upgrade twine
75
+ pip install flit packaging requests # necessary to run auto release scripts
76
+
77
+ # Use convenience scripts in rcdl/scripts
78
+ # Create api_key.txt with the pypi api key in the root folder
79
+ python3 rcdl/scripts/upload_pypi.py
80
+ ```
81
+
rcdl-2.0.0/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # RCDL
2
+
3
+ Riton Coomer Download Manager
4
+ `rcdl` is a tool to automatically download the videos of your favorites creators from [coomer.st](https://coomer.st) and [kemono.cr](https://kemono.cr)
5
+
6
+
7
+ ## Install
8
+ ### Dependencies
9
+ - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
10
+ - [aria2](https://github.com/aria2/aria2)
11
+ - [ffmpeg](https://www.ffmpeg.org/download.html) (Only for `fuse` command)
12
+ Recommended install:
13
+ ```bash
14
+ pipx install yt-dlp
15
+ sudo apt install aria2 ffmpeg
16
+ ```
17
+ ### Install RCDL
18
+ It is recommended to use pipx
19
+ ```bash
20
+ pipx install rcdl
21
+ ```
22
+ or else:
23
+ ```bash
24
+ pip install rcdl
25
+ ```
26
+
27
+ ## How to use
28
+
29
+ Run the CLI with:
30
+
31
+ ```bash
32
+ rcdl --help
33
+ ```
34
+
35
+ By default all files will live in `~/Videos/rcdl/`. Cache, configuration and log file will be in a hidden `rcdl/.cache/` folder.
36
+
37
+ ```bash
38
+ rcdl refresh # look creators.json and find all possible videos
39
+ rcdl dlsf # download all found videos
40
+ rcdl discover # WIP
41
+ rcdl fuse # WIP
42
+ rcdl log # debug only; show the log file
43
+ ```
44
+
45
+ ## Dev
46
+ ### Install
47
+ ```bash
48
+ git clone https://github.com/ritonun/cdl.git rcdl
49
+ cd rcdl
50
+ python3 -m venv .venv
51
+ source .venv/bin/activate
52
+ pip install -e .
53
+ ```
54
+
55
+ ## Deploy
56
+ ```bash
57
+ python3 -m pip install --upgrade build
58
+ python3 -m pip install --upgrade twine
59
+ pip install flit packaging requests # necessary to run auto release scripts
60
+
61
+ # Use convenience scripts in rcdl/scripts
62
+ # Create api_key.txt with the pypi api key in the root folder
63
+ python3 rcdl/scripts/upload_pypi.py
64
+ ```
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "rcdl"
3
+ version = "2.0.0"
4
+ description = "Coomer/Kemono CLI Downloader"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "click>=8.2",
8
+ "requests>=2.32",
9
+ "pathvalidate==3.3.1",
10
+ "moviepy==2.2.1",
11
+ ]
12
+ authors = [
13
+ {name = "Anonymous"},
14
+ ]
15
+ readme = "README.md"
16
+ keywords = ["downloader", "video", "media"]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+
23
+ [project.scripts]
24
+ rcdl = "rcdl.__main__:cli"
25
+
26
+ [build-system]
27
+ requires = ["flit_core<4"]
28
+ build-backend = "flit_core.buildapi"
@@ -0,0 +1,5 @@
1
+ # __init__.py
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("rcdl")
@@ -0,0 +1,23 @@
1
+ # __main__.py
2
+
3
+ import logging
4
+
5
+ from rcdl.core.config import Config, setup_logging
6
+
7
+ # setup file structure
8
+ Config.ensure_dirs()
9
+ Config.ensure_files()
10
+
11
+ # setup logging
12
+ setup_logging(Config.LOG_FILE, level=0)
13
+
14
+ logging.info("--- INIT ---")
15
+ logging.info("Logger initialized")
16
+
17
+ # init database
18
+ from rcdl.core.db import DB # noqa: E402
19
+
20
+ d = DB()
21
+ d.close()
22
+
23
+ from rcdl.interface.cli import cli # noqa: E402, F401
@@ -0,0 +1,54 @@
1
+ # core/api.py
2
+
3
+ from .models import Creator
4
+
5
+
6
+ class URL:
7
+ DOMAINS_BASE_URL = {
8
+ "coomer": "https://coomer.st/api/v1/",
9
+ "kemono": "https://kemono.cr/api/v1/",
10
+ }
11
+
12
+ @staticmethod
13
+ def get_base_url(domain: str) -> str:
14
+ if domain not in URL.DOMAINS_BASE_URL:
15
+ raise KeyError(f"{domain} not in known domains urls")
16
+ return URL.DOMAINS_BASE_URL[domain]
17
+
18
+ @staticmethod
19
+ def get_post_revision(creator: Creator, post_id) -> str:
20
+ return f"{URL.get_base_url(creator.domain)}{creator.service}/user/{creator.creator_id}/post/{post_id}/revisions"
21
+
22
+ @staticmethod
23
+ def get_headers() -> dict:
24
+ return {
25
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0 Safari/537.36",
26
+ "Accept": "text/css",
27
+ }
28
+
29
+ @staticmethod
30
+ def get_url_from_file(domain: str, path_url: str):
31
+ if domain == "coomer":
32
+ return f"https://coomer.st{path_url}"
33
+ elif domain == "kemono":
34
+ return f"https://kemono.cr{path_url}"
35
+ else:
36
+ raise ValueError(
37
+ f"Domain {domain} is not an accepted value/does not exist. Please check your creators.json file"
38
+ )
39
+
40
+ @staticmethod
41
+ def add_params(url: str, params: dict):
42
+ url += "?"
43
+ for key in params:
44
+ url += f"{key}={params[key]}&"
45
+ return url[:-1]
46
+
47
+ @staticmethod
48
+ def get_creator_post_wo_param(creator: Creator) -> str:
49
+ return f"{URL.get_base_url(creator.domain)}{creator.service}/user/{creator.creator_id}/posts"
50
+
51
+ @staticmethod
52
+ def get_posts_page_url_wo_param():
53
+ domain = URL.DOMAINS_BASE_URL["coomer"]
54
+ return f"{domain}posts"
@@ -0,0 +1,115 @@
1
+ # core/config.py
2
+
3
+ from pathlib import Path
4
+ import json
5
+ import logging
6
+ import os
7
+
8
+ from .file_io import write_json
9
+
10
+
11
+ class Config:
12
+ # paths
13
+ APP_NAME = "cdl"
14
+ BASE_DIR = Path.home() / "Videos/rcdl"
15
+
16
+ BASE_DIR = Path(os.environ.get("RCDL_BASE_DIR", Path.home() / "Videos/rcdl"))
17
+
18
+ CACHE_DIR = BASE_DIR / ".cache"
19
+ DB_PATH = CACHE_DIR / "cdl.db"
20
+ LOG_FILE = CACHE_DIR / "cdl.log"
21
+ FUSE_CSV_FILE = CACHE_DIR / "cdl_fuse.csv"
22
+ SETTINGS_FILE = CACHE_DIR / "settings.json"
23
+ CREATORS_FILE = CACHE_DIR / "creators.json"
24
+ DISCOVER_DIR = CACHE_DIR / "discover"
25
+
26
+ # defaults
27
+ DEFAULT_SETTINGS = {
28
+ "work_folder": str(BASE_DIR),
29
+ "yt_dlp_args": "--external-downloader aria2c",
30
+ "preset": "veryfast",
31
+ }
32
+
33
+ # default creators
34
+ DEFAULT_CREATORS = [
35
+ {"creator_id": "boixd", "service": "onlyfans", "domain": "coomer"}
36
+ ]
37
+
38
+ DEBUG = False
39
+ DRY_RUN = False
40
+
41
+ # api settings
42
+ POST_PER_PAGE = 50
43
+ DEFAULT_MAX_PAGE = 10
44
+ MAX_FAIL_COUNT = 7
45
+
46
+ @classmethod
47
+ def ensure_dirs(cls):
48
+ cls.CACHE_DIR.mkdir(parents=True, exist_ok=True)
49
+ cls.DISCOVER_DIR.mkdir(exist_ok=True)
50
+
51
+ @classmethod
52
+ def ensure_files(cls):
53
+ files = [
54
+ cls.DB_PATH,
55
+ cls.FUSE_CSV_FILE,
56
+ cls.CREATORS_FILE,
57
+ ]
58
+ for file in files:
59
+ if not file.exists():
60
+ file.touch()
61
+ logging.info("Created file %s", file)
62
+ if file == cls.CREATORS_FILE:
63
+ write_json(file, cls.DEFAULT_CREATORS)
64
+
65
+ @classmethod
66
+ def load_settings(cls):
67
+ if not cls.SETTINGS_FILE.exists():
68
+ with open(cls.SETTINGS_FILE, "w") as f:
69
+ json.dump(cls.DEFAULT_SETTINGS, f, indent=4)
70
+ return cls.DEFAULT_SETTINGS
71
+
72
+ with open(cls.SETTINGS_FILE, "r") as f:
73
+ return json.load(f)
74
+
75
+ @classmethod
76
+ def creator_folder(cls, creator_id: str) -> Path:
77
+ folder = cls.BASE_DIR / creator_id
78
+ folder.mkdir(exist_ok=True)
79
+ return folder
80
+
81
+ @classmethod
82
+ def cache_file(cls, filename: str, ext: str = ".json") -> Path:
83
+ file_name = filename + ext
84
+ file = cls.CACHE_DIR / file_name
85
+ return file
86
+
87
+ @classmethod
88
+ def set_debug(cls, debug: bool):
89
+ cls.DEBUG = debug
90
+
91
+ @classmethod
92
+ def set_dry_run(cls, dry_run: bool):
93
+ cls.DRY_RUN = dry_run
94
+
95
+
96
+ def setup_logging(log_file: Path, level: int = 0):
97
+ logger = logging.getLogger()
98
+ logger.setLevel(level)
99
+ logger.handlers.clear() # avoid double handlers if called multiple times
100
+
101
+ # file handler
102
+ file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
103
+ file_handler.setFormatter(
104
+ logging.Formatter(
105
+ "{asctime} - {levelname} - {message}",
106
+ style="{",
107
+ datefmt="%Y-%m-%d %H:%M:%S",
108
+ )
109
+ )
110
+ logger.addHandler(file_handler)
111
+
112
+ # console handler (stdout)
113
+ console_handler = logging.StreamHandler()
114
+ console_handler.setFormatter(logging.Formatter("{levelname}: {message}", style="{"))
115
+ logger.addHandler(console_handler)