rcdl 2.2.2__py3-none-any.whl → 3.0.0b13__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rcdl
3
- Version: 2.2.2
3
+ Version: 3.0.0b13
4
4
  Summary: Coomer/Kemono CLI Downloader
5
5
  Keywords: downloader,video,media
6
6
  Author: Anonymous
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Dist: click>=8.2
13
13
  Requires-Dist: requests>=2.32
14
14
  Requires-Dist: pathvalidate==3.3.1
15
- Requires-Dist: moviepy==2.2.1
16
15
  Requires-Dist: rich==14.2.0
16
+ Requires-Dist: streamlit==1.52.2
17
17
 
18
18
  # RCDL
19
19
 
@@ -26,13 +26,15 @@ Riton Coomer Download Manager
26
26
  - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
27
27
  - [aria2](https://github.com/aria2/aria2)
28
28
  - [ffmpeg](https://www.ffmpeg.org/download.html) (Only for `fuse` command)
29
+ - [HandBrakeCLI](https://handbrake.fr/docs/en/latest/cli/cli-options.html) (Only for `opti` command)
29
30
  Recommended install:
30
31
  ```bash
31
- pipx install yt-dlp
32
- sudo apt install aria2 ffmpeg
32
+ pipx install yt-dlp streamlit
33
+ sudo apt update
34
+ sudo apt install aria2 ffmpeg handbrake-cli python3-tk
33
35
  ```
34
36
  ### Install RCDL
35
- It is recommended to use pipx
37
+ It is recommended to use `pipx` to install `rcdl`
36
38
  ```bash
37
39
  pipx install rcdl
38
40
  ```
@@ -51,18 +53,55 @@ rcdl --help
51
53
 
52
54
  By default all files will live in `~/Videos/rcdl/`. Cache, configuration and log file will be in a hidden `rcdl/.cache/` folder.
53
55
 
56
+ Main function:
54
57
  ```bash
55
58
  rcdl refresh # look creators.json and find all possible videos
56
59
  rcdl dlsf # download all found videos
57
- rcdl discover # WIP
58
- rcdl fuse # WIP
59
- rcdl log # debug only; show the log file
60
+ rcdl discover # Discover new creator (WIP)
61
+ rcdl opti # Optimized video to reduce disk storage usage
62
+ rcdl fuse # Fuse all videos within a same post if they are fully downloaded
60
63
  ```
61
64
 
62
- Add, rm, list a creator:
65
+ Manage creators:
63
66
  ```bash
67
+ rcdl list # list all current creators
64
68
  rcdl add [URL]
65
69
  rcdl add [service]/[creator_id]
70
+ rcdl remove [creator_id]
71
+ ```
72
+
73
+ Helper function:
74
+ ```bash
75
+ rcdl status # give number of entry in the database per tables and status
76
+ rcdl clean --all # remove all partially downloaded file, external dependencies cache, etc...
77
+ rcdl show-config # print all config var and theirs value (paths, etc...)
78
+ ```
79
+
80
+ ### Settings
81
+ Default settings file:
82
+ ```toml
83
+ [app]
84
+ default_max_page = 10
85
+ max_fail_count = 7
86
+ timeout = 10
87
+
88
+ [fuse]
89
+ max_width = 1920
90
+ max_height = 1080
91
+ fps = 30
92
+ preset = "veryfast"
93
+ threads = 0
94
+
95
+ [paths]
96
+ base_dir = "~/Videos/rcdl"
97
+ handbrake_run_cmd = "HandBrakeCLI"
98
+ ```
99
+
100
+ In `rcdl/.cache/config.toml`:
101
+ ```toml
102
+ [paths]
103
+ handbrake_run_cmd = "HandBrakeCLI" # if installed via apt
104
+ handbrake_run_cmd = "flatpak run --command=HandBrakeCLI fr.handbrake.ghb" # if installed via flatpak
66
105
  ```
67
106
 
68
107
  ## Dev
@@ -79,11 +118,5 @@ pip install -e .
79
118
  ```bash
80
119
  python3 -m pip install --upgrade build
81
120
  python3 -m pip install --upgrade twine
82
- pip install flit packaging requests # necessary to run auto release scripts
83
-
84
- # Use convenience scripts in rcdl/scripts
85
- # Create api_key.txt with the pypi api key in the root folder
86
- python3 rcdl/scripts/upload_pypi.py
87
- python3 rcdl/scripts/migrate_old_format_to_db.py
88
121
  ```
89
122
 
@@ -0,0 +1,28 @@
1
+ rcdl/__init__.py,sha256=MIsNipkMNzSOT2WkNbSmVLvo3sdPBPN0xCOMP2r5z50,175
2
+ rcdl/__main__.py,sha256=ge7lh3rCaWuUgVpwU88zyQGPtZgG7GLW4rslT9wo9j8,714
3
+ rcdl/utils.py,sha256=hcaQExh8CuLgpYjDx4qBzLFjV9flnZIQsEpE1Z8Apb8,5016
4
+ rcdl/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ rcdl/core/adapters.py,sha256=I__3dvSwI2W8ZZfao9RNzdyLivGBOVDk_zfx39KIuHc,7258
6
+ rcdl/core/api.py,sha256=2OAUQV89mq1ELo9Ydj9zCde5qGHgI7bom8XLSeq7Ok8,2342
7
+ rcdl/core/config.py,sha256=9b5HTKLpBWShXqpTbgQUq3xdCqDQUs466nI16fRiWEk,6315
8
+ rcdl/core/db.py,sha256=Ab6SuC093fQvJybbzYHvhYEkCdGW-BheUrpZhM-6j1A,9302
9
+ rcdl/core/db_queries.py,sha256=KZW3SotPZamDxPY1KcCxo9-GvgnW6vjxDFu4h3HI1m8,2225
10
+ rcdl/core/downloader.py,sha256=UyMz_c-kHmmASIhd8Co3khHuaTYOUTRpizp1zr-BHjY,10329
11
+ rcdl/core/downloader_subprocess.py,sha256=jva51d_gONsSoQFzJeGO9kEc3aOWJFEH7C7vCDrnxYc,10279
12
+ rcdl/core/file_io.py,sha256=C-W9GLQReGiuWKTLzVTsisI_ps5j0SeqNQM9VjJot14,1040
13
+ rcdl/core/fuse.py,sha256=OKeWHh-WXWicLeCq_jgqKQovdDPfKAk2fi8lgUV-FKw,4188
14
+ rcdl/core/models.py,sha256=1aMwEupTk68PfHrbY1v6il9p-cmMIMcdFk6Aqp9yU8M,1910
15
+ rcdl/core/opti.py,sha256=jZfYZwHjFbDPp9rjCLJgTsPPMm972LSAxVRrf2r2h8M,2930
16
+ rcdl/core/parser.py,sha256=N9uCg4yDJo8iRvYzzUsE3T6uC4MS3nt9JzZ0zhxeqUg,7774
17
+ rcdl/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ rcdl/gui/__main__.py,sha256=Ti5dQqYxcyHuAOT8FGjYMhV0Y-j9Y-6utRkV7eRmyWA,68
19
+ rcdl/gui/db_viewer.py,sha256=0fM673d2JnWknFqVUDtDhzN6ojP1SGG06pO7T46IOHo,1060
20
+ rcdl/gui/gui.py,sha256=38mn30ZQPNN0lTyqh5AwIuszkNQ_SdE_FdRzkZXSEVE,1054
21
+ rcdl/gui/video_manager.py,sha256=ShOKmlw44ybZcO4E8UamaWAKHY5uQgj3LUHKy_7tKI8,5656
22
+ rcdl/interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ rcdl/interface/cli.py,sha256=mAKvphCoEXWtcnJfD5lsBGU5ooOKJM-G_Y4SeV9aUos,6094
24
+ rcdl/interface/ui.py,sha256=XXip0RiAjGC7yGbU_fctxGOp_TiFKSvHzA2zpUMwdsc,5735
25
+ rcdl-3.0.0b13.dist-info/entry_points.txt,sha256=TSFT1avCCO1d8pJnvuQLzX_1bA96uIDV_alKuruDN4E,42
26
+ rcdl-3.0.0b13.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
27
+ rcdl-3.0.0b13.dist-info/METADATA,sha256=NCpTE5iScpVYyf4Zl6e0EQDCKvn0kMAvZ08KajYC-E8,2945
28
+ rcdl-3.0.0b13.dist-info/RECORD,,
@@ -1,37 +0,0 @@
1
- # scripts/migrage_creators_json_txt.py
2
-
3
- import os
4
- import json
5
-
6
- from rcdl.core.models import Creator
7
- from rcdl.core.config import Config
8
- from rcdl.core.parser import get_domain, append_creator
9
-
10
- JSON_PATH = Config.CACHE_DIR / "creators.json"
11
-
12
- # check file exist
13
- if not os.path.exists(JSON_PATH):
14
- print("creators.json deoes not exist. Check")
15
-
16
- # load file
17
- with open(JSON_PATH, "r") as f:
18
- json_creators = json.load(f)
19
-
20
- # convert to Creator
21
- creators = []
22
- for json_creator in json_creators:
23
- creators.append(
24
- Creator(
25
- creator_id=json_creator["creator_id"],
26
- service=json_creator["service"],
27
- domain=get_domain(json_creator["service"]),
28
- status=None,
29
- )
30
- )
31
-
32
- # save creator
33
- for c in creators:
34
- append_creator(c)
35
- print(f"Saved new creator: {c.service}/{c.creator_id}")
36
-
37
- print(f"You can now delete {JSON_PATH}")
@@ -1,188 +0,0 @@
1
- # scripts/migrate_old_format_to_db.py
2
-
3
- """
4
- Use this script to migrate from pre-2.0 to 2+.
5
- Script migrate_old_format_to_db version: v1.0
6
-
7
- old_format:
8
- - no db
9
- cdl/
10
- creator1/
11
- date_title1_p0.mp4
12
- date_title2_p0.mp4
13
- date_title2_p1.mp4
14
- ...
15
- ...
16
-
17
- for each creator:
18
- - [x] get all posts
19
- - [x] match each videos to its posts
20
- - [x] update db
21
- - [ ] check db with local videos
22
- """
23
-
24
- import logging
25
- import os
26
- from pathlib import Path
27
- import shutil
28
-
29
- from rcdl.core.config import Config
30
- from rcdl.core.models import Creator, Video, VideoStatus
31
- from rcdl.core.file_io import load_json
32
- from rcdl.core.api import URL
33
- from rcdl.core.db import DB
34
- import rcdl.core.downloader as dl
35
- import rcdl.core.parser as parser
36
-
37
- Config.ensure_dirs()
38
- Config.ensure_files()
39
-
40
- with DB() as db:
41
- db.init_table()
42
-
43
- CDL_PATH = Path.home() / "Videos" / "cdl"
44
- CREATORS_JSON = CDL_PATH / ".cache" / "creators.json"
45
- TEMP_JSON = str(CDL_PATH / "temp.json")
46
- LOG_PATH = CDL_PATH / "migrate_log.log"
47
- MV_TXT = CDL_PATH / "mv.txt"
48
- DEST_BASE = Path("/home/elitedesk/Videos/rcdl")
49
-
50
- logging.basicConfig(
51
- filename=LOG_PATH,
52
- filemode="a",
53
- encoding="utf-8",
54
- level=logging.INFO,
55
- format="{asctime} - {levelname} - {message}",
56
- style="{",
57
- datefmt="%Y-%m-%d %H:%M:%S",
58
- )
59
- console = logging.StreamHandler()
60
- console.setFormatter(logging.Formatter("{levelname}: {message}", style="{"))
61
- logging.getLogger().addHandler(console)
62
-
63
- open(MV_TXT, "w").close()
64
-
65
-
66
- def add_to_mvtxt(filepath: str):
67
- with open(MV_TXT, "a") as f:
68
- f.write(filepath + "\n")
69
-
70
-
71
- def update_db_info(video: Video):
72
- with DB() as db:
73
- db._upsert_video(video)
74
-
75
-
76
- def move_files(mv_txt: Path, dest_base: Path):
77
- # Read all file paths
78
- i = 0
79
- with mv_txt.open("r", encoding="utf-8") as f:
80
- paths = [line.strip() for line in f if line.strip()]
81
-
82
- for src_path_str in paths:
83
- src = Path(src_path_str)
84
- if not src.exists():
85
- print(f"Skipping missing file: {src}")
86
- i += 1
87
- continue
88
-
89
- # Compute destination folder
90
- relative_parts = src.parts[len(Path("/home/elitedesk/Videos/cdl").parts) :]
91
- dest_dir = dest_base.joinpath(*relative_parts[:-1])
92
- dest_dir.mkdir(parents=True, exist_ok=True)
93
-
94
- # Destination path
95
- dest = dest_dir / src.name
96
-
97
- # Move the file
98
- shutil.move(str(src), str(dest))
99
- i += 1
100
- print(f"Moved ({i}/{len(paths)}): {src} -> {dest}")
101
-
102
-
103
- if __name__ == "__main__":
104
- logging.info("--- MIGRATE pre-v2 to +2.0 Script START ---")
105
-
106
- # get all local creators
107
- creators_json = load_json(CREATORS_JSON)
108
- creators: list[Creator] = []
109
- for creator in creators_json:
110
- creators.append(
111
- Creator(
112
- creator_id=creator["creator_id"],
113
- service=creator["service"],
114
- domain=creator["domain"],
115
- status=None,
116
- )
117
- )
118
-
119
- for creator in creators:
120
- # get posts
121
- url = URL.get_creator_post_wo_param(creator)
122
- print(f"Request {url} up to {15} max page")
123
- pf = dl.PostsFetcher(url, TEMP_JSON, max_page=15)
124
- pf.request()
125
-
126
- posts = parser.filter_posts_with_videos_from_json(TEMP_JSON)
127
- print(f"Found {len(posts)} posts with videos for creator {creator.creator_id}")
128
-
129
- posts_videos = parser.convert_posts_to_videos(posts)
130
- print(f"Converted {len(posts)} to Video")
131
- for video in posts_videos:
132
- video.status = VideoStatus.DOWNLOADED
133
-
134
- # get local videos
135
- creator_path = os.path.join(CDL_PATH, creator.creator_id)
136
- print(f"Looking in {creator_path}")
137
- files = os.listdir(creator_path)
138
-
139
- local_videos: list[dict] = []
140
- for file in files:
141
- # ignore partial file
142
- if file.endswith(".part") or file.endswith(".aria2"):
143
- continue
144
-
145
- # remove ext
146
- name = file[:-4] if file.endswith(".mp4") else file
147
-
148
- # part number
149
- if "_p" not in name:
150
- print(f"Skipped file due to missing part number: {file}")
151
-
152
- try:
153
- base, part_str = name.rsplit("_p", 1)
154
-
155
- # extract date and title
156
- date, *title_parts = base.split("_")
157
- title = "_".join(title_parts) # keeps underscores in title
158
-
159
- local_videos.append(
160
- {
161
- "date": date, # e.g. "2025-12-25"
162
- "title": title,
163
- "part": part_str, # e.g. "0"
164
- }
165
- )
166
- except: # noqa E277
167
- pass
168
-
169
- print(f"Found {len(local_videos)} local videos")
170
-
171
- # match local vid to videos list
172
- for lv in local_videos:
173
- for pv in posts_videos:
174
- if (
175
- lv["date"] == pv.published
176
- and lv["title"] == pv.title
177
- and lv["part"] == str(pv.part)
178
- ):
179
- print(f"Found a match for {pv.relative_path}")
180
- update_db_info(pv)
181
- add_to_mvtxt(os.path.join(creator_path, pv.relative_path))
182
- break
183
- else:
184
- print(f"No match found {lv['date']}_{lv['title']}_{lv['part']}")
185
-
186
- move_files(MV_TXT, DEST_BASE)
187
- shutil.move(str(MV_TXT), str(MV_TXT) + f".{creator.creator_id}.txt")
188
- open(MV_TXT, "w").close()
@@ -1,98 +0,0 @@
1
- # scripts/upload_pypi.py
2
-
3
- import subprocess
4
- import sys
5
- import os
6
- import requests
7
- from packaging.version import parse as parse_version
8
- import tomllib
9
-
10
- # config
11
- PYPROJECT_FILE = "pyproject.toml"
12
- PYPI_PACKAGE_NAME = "rcdl" # PyPI package name
13
-
14
- # read local version
15
- with open(PYPROJECT_FILE, "rb") as f:
16
- data = tomllib.load(f)
17
-
18
- local_version = data["project"]["version"]
19
- print(f"Local version: {local_version}")
20
-
21
- # check remote latest version
22
- response = requests.get(f"https://pypi.org/pypi/{PYPI_PACKAGE_NAME}/json")
23
- if response.status_code == 200:
24
- latest_version = response.json()["info"]["version"]
25
- print(f"Latest PyPI version: {latest_version}")
26
- else:
27
- latest_version = None
28
- print("ERROR: Package not found on PyPI")
29
-
30
- # chec version number
31
- if latest_version and parse_version(local_version) <= parse_version(latest_version):
32
- print("Error: Local version is not higher than PyPI version.")
33
- sys.exit(1)
34
-
35
- # build with flit
36
- print("Building package...")
37
- subprocess.run([sys.executable, "-m", "flit", "build"], check=True)
38
-
39
- # upload to pypi
40
- print("Uploading to PyPI...")
41
- if not os.path.exists("api_key.txt"):
42
- print(
43
- "ERROR - you have to create an api_key.txt file in your root directory containing only your pypi api key"
44
- )
45
- quit()
46
- with open("api_key.txt", "r") as f:
47
- api_key = f.read().strip()
48
- if api_key == "":
49
- print("ERROR - api_key.txt is empty")
50
-
51
- subprocess.run(
52
- [
53
- sys.executable,
54
- "-m",
55
- "twine",
56
- "upload",
57
- "-u",
58
- "__token__",
59
- "-p",
60
- api_key,
61
- "dist/*",
62
- ],
63
- check=True,
64
- )
65
-
66
- print("PYPI Upload complete!")
67
-
68
- # find wheel file in dist/
69
- dist_files = [f for f in os.listdir("dist") if f.endswith(".whl")]
70
- if not dist_files:
71
- raise FileNotFoundError("No .whl file found in dist/")
72
-
73
- whl_file_path = os.path.join("dist", dist_files[0])
74
- if local_version not in whl_file_path:
75
- raise FileNotFoundError("Version of .whl does not match upload.")
76
-
77
- # create github release
78
- tag = f"v{local_version}"
79
- print(f"Creating github release {tag}")
80
-
81
-
82
- subprocess.run(
83
- [
84
- "gh",
85
- "release",
86
- "create",
87
- tag,
88
- "--title",
89
- f"Release {tag}",
90
- "--notes",
91
- "",
92
- whl_file_path,
93
- ],
94
- check=True,
95
- )
96
-
97
- print("GitHub release created!")
98
- print("--END--")
@@ -1,22 +0,0 @@
1
- rcdl/__init__.py,sha256=6TJotAX_12BuAA2z78HW-KH0MudTmZSSeZXy9G_PeW4,85
2
- rcdl/__main__.py,sha256=sq5lbBnJf7_jh9HD1WAC5z1JDu0HDLWE3c9D-fiL6Jg,479
3
- rcdl/utils.py,sha256=MMeV3UjDEC1r5nxkuFW3GxGIsqCmYtEUTP2G2YWNnHM,149
4
- rcdl/core/api.py,sha256=OajUITAG2vfXEeoifL73wb96o_dsAypqQQkx2lK5LNo,1752
5
- rcdl/core/config.py,sha256=NclmKraunK0parCdm_LFbJ4w4mq11SAnzeLo30I05-8,2487
6
- rcdl/core/db.py,sha256=5Ajppng4gJ-dvTqUBLYs3SFZzBw7t6GUiZDOBy8_CEM,7607
7
- rcdl/core/db_queries.py,sha256=xFS66bn3LSUOzgMIS1JvKOOknPfGI1NJleRSupHvtaM,1695
8
- rcdl/core/downloader.py,sha256=L4huh-JI8zwjT6ChvyoYl-c3rmItYAOlEZNAUwLflXo,9019
9
- rcdl/core/downloader_subprocess.py,sha256=xHyOX8K8gLFq6fA63TliRPAe10IV8H3NWeUbNbow5M8,5245
10
- rcdl/core/file_io.py,sha256=NGkB9Mv7CRVe2qejyY_R9zmLmzLHbtwwxWIqwWVAHfo,731
11
- rcdl/core/fuse.py,sha256=lBDI0EC-jyWnNRNnqBGucVVOdzkgnf1yqb8438swZ7Y,3879
12
- rcdl/core/models.py,sha256=1_VZ68Gu7LH_o_NG7QJwQAzRCyOxIps--UCWwJGK9YI,1171
13
- rcdl/core/parser.py,sha256=8e1vMWnMiVxkzwXtsJG_rxejvJUVLE35rHE7eVQsi2g,7685
14
- rcdl/interface/cli.py,sha256=SiJmtmGvwOyF9b5ePTjgyNgB051cib7c8qGG4VTFeI8,3660
15
- rcdl/interface/ui.py,sha256=rrY5Tq4nxDHpDJW6YdoPEc0WqOO8unDDbu0Vl3hlm_I,6180
16
- rcdl/scripts/migrate_creators_json_txt.py,sha256=5JoySkRmfoQ2ghZCMgnPsVwa8IiOthl-wq2QGN-dqdQ,883
17
- rcdl/scripts/migrate_old_format_to_db.py,sha256=zhn_L7LlYmWRAMbMBvQxxkrVbXEhMn4msE7ml5kwNYU,5496
18
- rcdl/scripts/upload_pypi.py,sha256=dzXasvbcx4AjwTZeyamDtQIqE1dQIViLt5RfMpC198A,2325
19
- rcdl-2.2.2.dist-info/entry_points.txt,sha256=TSFT1avCCO1d8pJnvuQLzX_1bA96uIDV_alKuruDN4E,42
20
- rcdl-2.2.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
21
- rcdl-2.2.2.dist-info/METADATA,sha256=BkOnWpy-ji1sC5IBh26mabyIrcMl7mP6-xiAaEeuW5g,2097
22
- rcdl-2.2.2.dist-info/RECORD,,
File without changes