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.
- rcdl/__init__.py +5 -0
- rcdl/__main__.py +15 -3
- rcdl/core/__init__.py +0 -0
- rcdl/core/adapters.py +241 -0
- rcdl/core/api.py +31 -9
- rcdl/core/config.py +133 -14
- rcdl/core/db.py +239 -191
- rcdl/core/db_queries.py +75 -44
- rcdl/core/downloader.py +184 -142
- rcdl/core/downloader_subprocess.py +257 -85
- rcdl/core/file_io.py +13 -6
- rcdl/core/fuse.py +115 -106
- rcdl/core/models.py +83 -34
- rcdl/core/opti.py +90 -0
- rcdl/core/parser.py +80 -78
- rcdl/gui/__init__.py +0 -0
- rcdl/gui/__main__.py +5 -0
- rcdl/gui/db_viewer.py +41 -0
- rcdl/gui/gui.py +54 -0
- rcdl/gui/video_manager.py +170 -0
- rcdl/interface/__init__.py +0 -0
- rcdl/interface/cli.py +100 -20
- rcdl/interface/ui.py +105 -116
- rcdl/utils.py +163 -5
- {rcdl-2.2.2.dist-info → rcdl-3.0.0b13.dist-info}/METADATA +48 -15
- rcdl-3.0.0b13.dist-info/RECORD +28 -0
- rcdl/scripts/migrate_creators_json_txt.py +0 -37
- rcdl/scripts/migrate_old_format_to_db.py +0 -188
- rcdl/scripts/upload_pypi.py +0 -98
- rcdl-2.2.2.dist-info/RECORD +0 -22
- {rcdl-2.2.2.dist-info → rcdl-3.0.0b13.dist-info}/WHEEL +0 -0
- {rcdl-2.2.2.dist-info → rcdl-3.0.0b13.dist-info}/entry_points.txt +0 -0
rcdl/core/parser.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# core/parser.py
|
|
2
2
|
|
|
3
|
+
"""Handle function to parse post and files"""
|
|
4
|
+
|
|
3
5
|
import logging
|
|
4
6
|
from pathvalidate import sanitize_filename
|
|
5
7
|
|
|
6
|
-
from .models import Video, VideoStatus, Creator
|
|
7
|
-
from .file_io import load_json, load_txt, write_txt
|
|
8
|
-
from .config import Config
|
|
9
8
|
from rcdl.interface.ui import UI
|
|
9
|
+
from rcdl.core.models import Media, Creator, Post, CreatorStatus
|
|
10
|
+
from rcdl.core.file_io import load_json, load_txt, write_txt
|
|
11
|
+
from rcdl.core.config import Config
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
COOMER_PAYSITES = ["onlyfans", "fansly", "candfans"]
|
|
@@ -21,7 +23,7 @@ KEMONO_PAYSITES = [
|
|
|
21
23
|
]
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
def get_domain(arg: str | dict |
|
|
26
|
+
def get_domain(arg: str | dict | Media) -> str:
|
|
25
27
|
"""From a service get the domain (coomer or kemono)
|
|
26
28
|
Input is either: service(str), post(dict), video(models.Video)
|
|
27
29
|
"""
|
|
@@ -31,25 +33,34 @@ def get_domain(arg: str | dict | Video) -> str:
|
|
|
31
33
|
return "coomer"
|
|
32
34
|
if service in KEMONO_PAYSITES:
|
|
33
35
|
return "kemono"
|
|
34
|
-
logging.error(
|
|
36
|
+
logging.error("Service %s not associated to any domain", service)
|
|
35
37
|
return ""
|
|
36
38
|
|
|
37
39
|
if isinstance(arg, dict):
|
|
38
40
|
return _service(arg["service"])
|
|
39
|
-
|
|
41
|
+
if isinstance(arg, Media):
|
|
40
42
|
return _service(arg.service)
|
|
41
43
|
|
|
42
44
|
return _service(arg)
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def get_title(post:
|
|
47
|
+
def get_title(post: Post) -> str:
|
|
48
|
+
"""From a Post Model return the title"""
|
|
49
|
+
title = post.title
|
|
50
|
+
if title == "":
|
|
51
|
+
title = post.substring
|
|
52
|
+
if title == "":
|
|
53
|
+
title = post.id
|
|
54
|
+
return sanitize_filename(title)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_title_json(post: dict) -> str:
|
|
46
58
|
"""Extract title from a post(dict)"""
|
|
47
59
|
title = post["title"]
|
|
48
60
|
if title == "":
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
title = post["substring"]
|
|
61
|
+
title = post["substring"]
|
|
62
|
+
if title == "":
|
|
63
|
+
title = post["id"]
|
|
53
64
|
return sanitize_filename(title)
|
|
54
65
|
|
|
55
66
|
|
|
@@ -60,7 +71,7 @@ def get_date(post: dict) -> str:
|
|
|
60
71
|
elif "added" in post:
|
|
61
72
|
date = post["added"][0:10]
|
|
62
73
|
else:
|
|
63
|
-
logging.error(
|
|
74
|
+
logging.error("Could not extract date from %s", post["id"])
|
|
64
75
|
date = "NA"
|
|
65
76
|
return date
|
|
66
77
|
|
|
@@ -81,13 +92,14 @@ def get_part(post: dict, url: str) -> int:
|
|
|
81
92
|
part += 1
|
|
82
93
|
|
|
83
94
|
logging.error(
|
|
84
|
-
|
|
95
|
+
"Could not extract part number for post id %s with url %s", post["id"], url
|
|
85
96
|
)
|
|
86
97
|
return -1
|
|
87
98
|
|
|
88
99
|
|
|
89
100
|
def get_filename(post: dict, url: str) -> str:
|
|
90
|
-
|
|
101
|
+
"""Get filename from pst dict and url"""
|
|
102
|
+
title = get_title_json(post)
|
|
91
103
|
date = get_date(post)
|
|
92
104
|
part = get_part(post, url)
|
|
93
105
|
file_title = f"{date}_{title}".replace("'", " ").replace('"', "")
|
|
@@ -95,45 +107,19 @@ def get_filename(post: dict, url: str) -> str:
|
|
|
95
107
|
return filename
|
|
96
108
|
|
|
97
109
|
|
|
98
|
-
def
|
|
99
|
-
|
|
110
|
+
def get_filename_fuse(post: Post) -> str:
|
|
111
|
+
"""Get filename for fuse output from Post Model
|
|
112
|
+
Fuse output has 'X' as part number"""
|
|
100
113
|
title = get_title(post)
|
|
101
|
-
date =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return Video(
|
|
108
|
-
post_id=post["id"],
|
|
109
|
-
creator_id=post["user"],
|
|
110
|
-
service=post["service"],
|
|
111
|
-
relative_path=filename,
|
|
112
|
-
url=url,
|
|
113
|
-
domain=get_domain(post),
|
|
114
|
-
part=part,
|
|
115
|
-
published=date,
|
|
116
|
-
title=title,
|
|
117
|
-
status=VideoStatus.NOT_DOWNLOADED,
|
|
118
|
-
fail_count=0,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def convert_posts_to_videos(posts: list[dict], discover: bool = False) -> list[Video]:
|
|
123
|
-
videos = []
|
|
124
|
-
for post in posts:
|
|
125
|
-
urls = extract_video_urls(post)
|
|
126
|
-
if not discover:
|
|
127
|
-
for url in urls:
|
|
128
|
-
videos.append(convert_post_to_video(post, url))
|
|
129
|
-
else:
|
|
130
|
-
if len(urls) == 0:
|
|
131
|
-
continue
|
|
132
|
-
videos.append(convert_post_to_video(post, urls[0], discover=discover))
|
|
133
|
-
return videos
|
|
114
|
+
date = post.published[0:10]
|
|
115
|
+
part = "X"
|
|
116
|
+
file_title = f"{date}_{title}".replace("'", " ").replace('"', "")
|
|
117
|
+
filename = f"{file_title}_p{part}.mp4"
|
|
118
|
+
return filename
|
|
134
119
|
|
|
135
120
|
|
|
136
121
|
def extract_video_urls(post: dict) -> list:
|
|
122
|
+
"""Extract all videos urls from a dict post"""
|
|
137
123
|
video_extensions = (".mp4", ".webm", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".m4v")
|
|
138
124
|
urls = set()
|
|
139
125
|
|
|
@@ -176,6 +162,7 @@ def filter_posts_with_videos_from_json(path: str) -> list:
|
|
|
176
162
|
|
|
177
163
|
|
|
178
164
|
def valid_service(service: str) -> bool:
|
|
165
|
+
"""Check if a service is valid (within list of DOMAIN services)"""
|
|
179
166
|
if service in COOMER_PAYSITES:
|
|
180
167
|
return True
|
|
181
168
|
if service in KEMONO_PAYSITES:
|
|
@@ -183,31 +170,44 @@ def valid_service(service: str) -> bool:
|
|
|
183
170
|
return False
|
|
184
171
|
|
|
185
172
|
|
|
173
|
+
def _default_creator(_id: str, service: str, domain: str):
|
|
174
|
+
return Creator(
|
|
175
|
+
id=_id,
|
|
176
|
+
service=service,
|
|
177
|
+
domain=domain,
|
|
178
|
+
name="",
|
|
179
|
+
indexed="",
|
|
180
|
+
updated="",
|
|
181
|
+
favorited=1,
|
|
182
|
+
status=CreatorStatus.NA,
|
|
183
|
+
max_date="",
|
|
184
|
+
max_posts=1,
|
|
185
|
+
max_size=1,
|
|
186
|
+
min_date="",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
186
190
|
def get_creator_from_line(line: str) -> Creator | None:
|
|
187
191
|
"""
|
|
188
192
|
Convert a line into a Creator model
|
|
189
193
|
arg: line -> 'service/creator'
|
|
190
194
|
This is the format of creators.txt
|
|
191
195
|
"""
|
|
196
|
+
|
|
192
197
|
parts = line.split("/")
|
|
193
198
|
if valid_service(parts[0].strip()):
|
|
194
|
-
return
|
|
195
|
-
|
|
196
|
-
service=parts[0].strip(),
|
|
197
|
-
domain=get_domain(parts[0].strip()),
|
|
198
|
-
status=None,
|
|
199
|
-
)
|
|
200
|
-
elif valid_service(parts[1].strip()):
|
|
201
|
-
return Creator(
|
|
202
|
-
creator_id=parts[0].strip(),
|
|
203
|
-
service=parts[1].strip(),
|
|
204
|
-
domain=get_domain(parts[1].strip()),
|
|
205
|
-
status=None,
|
|
199
|
+
return _default_creator(
|
|
200
|
+
parts[1].strip(), parts[0].strip(), get_domain(parts[0].strip())
|
|
206
201
|
)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
if valid_service(parts[1].strip()):
|
|
203
|
+
return _default_creator(
|
|
204
|
+
parts[0].strip(), parts[1].strip(), get_domain(parts[1].strip())
|
|
210
205
|
)
|
|
206
|
+
|
|
207
|
+
UI.error(
|
|
208
|
+
f"Creator file not valid: {line} can not be interpreted."
|
|
209
|
+
f" Format is: 'service/creator_id'"
|
|
210
|
+
)
|
|
211
211
|
return None
|
|
212
212
|
|
|
213
213
|
|
|
@@ -228,7 +228,8 @@ def get_creators() -> list[Creator]:
|
|
|
228
228
|
|
|
229
229
|
|
|
230
230
|
def get_creators_from_posts(posts: list[dict]) -> list[Creator]:
|
|
231
|
-
|
|
231
|
+
"""Extract a list of Creators model form a list of dict posts"""
|
|
232
|
+
creators = []
|
|
232
233
|
seen = set()
|
|
233
234
|
|
|
234
235
|
for post in posts:
|
|
@@ -237,24 +238,20 @@ def get_creators_from_posts(posts: list[dict]) -> list[Creator]:
|
|
|
237
238
|
continue
|
|
238
239
|
|
|
239
240
|
seen.add(key)
|
|
240
|
-
creators.append(
|
|
241
|
-
Creator(
|
|
242
|
-
creator_id=post["user"],
|
|
243
|
-
service=post["service"],
|
|
244
|
-
domain="coomer",
|
|
245
|
-
status="to_be_treated",
|
|
246
|
-
)
|
|
247
|
-
)
|
|
241
|
+
creators.append(_default_creator(post["user"], post["service"], "coomer"))
|
|
248
242
|
return creators
|
|
249
243
|
|
|
250
244
|
|
|
251
245
|
def parse_creator_input(value: str) -> tuple[str | None, str]:
|
|
246
|
+
"""Parse user input in cli to extract creator id & service"""
|
|
252
247
|
value = value.strip()
|
|
253
248
|
|
|
254
249
|
# url
|
|
255
250
|
if "://" in value:
|
|
256
251
|
parts = value.replace("https://", "").strip().split("/")
|
|
257
|
-
logging.info(
|
|
252
|
+
logging.info(
|
|
253
|
+
"From %s extracte service %s and creator %s", value, parts[1], parts[3]
|
|
254
|
+
)
|
|
258
255
|
return parts[1], parts[3] # service, creator_id
|
|
259
256
|
|
|
260
257
|
# creators.txt format
|
|
@@ -262,16 +259,21 @@ def parse_creator_input(value: str) -> tuple[str | None, str]:
|
|
|
262
259
|
c = get_creator_from_line(value)
|
|
263
260
|
if c is not None:
|
|
264
261
|
logging.info(
|
|
265
|
-
|
|
262
|
+
"From %s extracte service %s and creator %s",
|
|
263
|
+
value,
|
|
264
|
+
c.service,
|
|
265
|
+
c.id,
|
|
266
266
|
)
|
|
267
|
-
return c.service, c.
|
|
267
|
+
return c.service, c.id
|
|
268
268
|
|
|
269
|
-
logging.info(
|
|
269
|
+
logging.info("From %s extracted service None and creator %s", value, value)
|
|
270
270
|
return None, value
|
|
271
271
|
|
|
272
272
|
|
|
273
273
|
def append_creator(creator: Creator):
|
|
274
|
-
|
|
274
|
+
"""Append a creator to the creators.txt file
|
|
275
|
+
Creators.txt hold all creators used in refresh command"""
|
|
276
|
+
line = f"{creator.service}/{creator.id}"
|
|
275
277
|
lines = load_txt(Config.CREATORS_FILE)
|
|
276
278
|
|
|
277
279
|
if line in lines:
|
rcdl/gui/__init__.py
ADDED
|
File without changes
|
rcdl/gui/__main__.py
ADDED
rcdl/gui/db_viewer.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# gui/db_viewer.py
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
import sqlite3
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from rcdl.core.config import Config
|
|
9
|
+
|
|
10
|
+
TABLES = ["medias", "posts", "fuses"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_table_columns(table_name):
|
|
14
|
+
conn = sqlite3.connect(Config.DB_PATH)
|
|
15
|
+
cur = conn.cursor()
|
|
16
|
+
cur.execute(f"PRAGMA table_info({table_name})")
|
|
17
|
+
columns = [info[1] for info in cur.fetchall()]
|
|
18
|
+
conn.close()
|
|
19
|
+
return columns
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_table_data(table_name, sort_by=None, ascending=True):
|
|
23
|
+
conn = sqlite3.connect(Config.DB_PATH)
|
|
24
|
+
df = pd.read_sql_query(f"SELECT * FROM {table_name}", conn)
|
|
25
|
+
conn.close()
|
|
26
|
+
if sort_by and sort_by in df.columns:
|
|
27
|
+
df = df.sort_values(by=sort_by, ascending=ascending)
|
|
28
|
+
return df
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_db_viewer():
|
|
32
|
+
st.set_page_config(page_title="DB Viewer", layout="wide")
|
|
33
|
+
st.title("Database Viewer")
|
|
34
|
+
|
|
35
|
+
table_name = st.selectbox("Select Table", TABLES)
|
|
36
|
+
|
|
37
|
+
# Load data
|
|
38
|
+
df = get_table_data(table_name, sort_by=None, ascending=True)
|
|
39
|
+
|
|
40
|
+
st.write(f"Showing `{table_name}` table ({len(df)} rows)")
|
|
41
|
+
st.dataframe(df, width="stretch")
|
rcdl/gui/gui.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# gui/gui.py
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
from rcdl.gui.db_viewer import run_db_viewer
|
|
6
|
+
from rcdl.gui.video_manager import video_manager
|
|
7
|
+
|
|
8
|
+
st.markdown(
|
|
9
|
+
"""
|
|
10
|
+
<style>
|
|
11
|
+
/* Remove top padding */
|
|
12
|
+
.block-container {
|
|
13
|
+
padding-top: 1rem !important;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Optional: remove Streamlit header */
|
|
17
|
+
header[data-testid="stHeader"] {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Optional: remove footer */
|
|
22
|
+
footer {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
""",
|
|
27
|
+
unsafe_allow_html=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_gui():
|
|
32
|
+
"""
|
|
33
|
+
Launches the Streamlit GUI.
|
|
34
|
+
This function can be called from a CLI command.
|
|
35
|
+
"""
|
|
36
|
+
# Streamlit code
|
|
37
|
+
st.set_page_config(page_title="RCDL", layout="wide")
|
|
38
|
+
|
|
39
|
+
# Sidebar navigation
|
|
40
|
+
page = st.sidebar.radio("Go to", ["Home", "Manage Videos", "View DB"])
|
|
41
|
+
|
|
42
|
+
if page == "Home":
|
|
43
|
+
st.header("Home Page")
|
|
44
|
+
st.write("Develloped by - ritonun -")
|
|
45
|
+
|
|
46
|
+
elif page == "Manage Videos":
|
|
47
|
+
video_manager()
|
|
48
|
+
|
|
49
|
+
elif page == "View DB":
|
|
50
|
+
run_db_viewer()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
run_gui()
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# gui/video_manager.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import streamlit as st
|
|
6
|
+
|
|
7
|
+
from rcdl.core.config import Config
|
|
8
|
+
from rcdl.core.models import Status, Media
|
|
9
|
+
from rcdl.core.db import DB
|
|
10
|
+
from rcdl.utils import format_seconds
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
previous_statuses = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def set_status(media: Media, status: Status):
|
|
17
|
+
key = media.post_id + media.url
|
|
18
|
+
previous_statuses[key] = media.status
|
|
19
|
+
media.status = status
|
|
20
|
+
with DB() as db:
|
|
21
|
+
db.update_media(media)
|
|
22
|
+
print(f"Set {media.post_id} to {status.value}")
|
|
23
|
+
|
|
24
|
+
for m in st.session_state.medias:
|
|
25
|
+
if m.post_id == media.post_id and m.url == media.url:
|
|
26
|
+
m.status = status
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def video_manager():
|
|
31
|
+
st.title("Video Manager")
|
|
32
|
+
|
|
33
|
+
# Filter & Sorting UI
|
|
34
|
+
with st.expander("Filters & Sorting", expanded=True):
|
|
35
|
+
col1, col2, col3 = st.columns(3)
|
|
36
|
+
with col1:
|
|
37
|
+
sort_by = st.selectbox(
|
|
38
|
+
"Sort By",
|
|
39
|
+
options=["file_size", "service", "duration", "file_path"],
|
|
40
|
+
index=0,
|
|
41
|
+
)
|
|
42
|
+
with col2:
|
|
43
|
+
ascending = st.radio(
|
|
44
|
+
"Order",
|
|
45
|
+
options=[True, False],
|
|
46
|
+
format_func=lambda x: "Ascending" if x else "Descending",
|
|
47
|
+
horizontal=True,
|
|
48
|
+
)
|
|
49
|
+
with col3:
|
|
50
|
+
creator_filter = st.text_input(
|
|
51
|
+
"Creator ID(user)", placeholder="Leave empty for all"
|
|
52
|
+
)
|
|
53
|
+
status_filter = st.multiselect(
|
|
54
|
+
"Status",
|
|
55
|
+
options=list(Status),
|
|
56
|
+
default=[Status.DOWNLOADED, Status.OPTIMIZED],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
reload = st.button("Apply")
|
|
60
|
+
|
|
61
|
+
# load db
|
|
62
|
+
if reload or "medias" not in st.session_state:
|
|
63
|
+
with DB() as db:
|
|
64
|
+
medias = db.query_medias_by_status_sorted(
|
|
65
|
+
status_filter,
|
|
66
|
+
sort_by=sort_by,
|
|
67
|
+
ascending=ascending,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# check if in a fuse group
|
|
71
|
+
|
|
72
|
+
# creator filter
|
|
73
|
+
if creator_filter:
|
|
74
|
+
filtered = []
|
|
75
|
+
for m in medias:
|
|
76
|
+
post = db.query_post_by_id(m.post_id)
|
|
77
|
+
if post and post.user == creator_filter:
|
|
78
|
+
filtered.append(m)
|
|
79
|
+
# check i na fuse group
|
|
80
|
+
fm = db.query_fuses_by_id(m.post_id)
|
|
81
|
+
if fm is None:
|
|
82
|
+
filtered.append(m)
|
|
83
|
+
medias = filtered
|
|
84
|
+
|
|
85
|
+
st.session_state.medias = medias
|
|
86
|
+
st.session_state.media_index = 0
|
|
87
|
+
|
|
88
|
+
medias = st.session_state.medias
|
|
89
|
+
if not medias:
|
|
90
|
+
st.info("No media found")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# session state
|
|
94
|
+
if "media_index" not in st.session_state:
|
|
95
|
+
st.session_state.media_index = 0
|
|
96
|
+
|
|
97
|
+
idx = st.session_state.media_index
|
|
98
|
+
media = medias[idx]
|
|
99
|
+
|
|
100
|
+
# media info
|
|
101
|
+
st.subheader(f"Media {idx + 1} / {len(medias)}")
|
|
102
|
+
|
|
103
|
+
with DB() as db:
|
|
104
|
+
post = db.query_post_by_id(media.post_id)
|
|
105
|
+
if post is None:
|
|
106
|
+
st.info("No matching post found")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
col_video, col_info = st.columns([1, 2])
|
|
110
|
+
with col_info:
|
|
111
|
+
col1, col2 = st.columns(2)
|
|
112
|
+
with col1:
|
|
113
|
+
st.write("**Post ID:**", media.post_id)
|
|
114
|
+
st.write("**Service:**", media.service)
|
|
115
|
+
st.write("**User:**", post.user)
|
|
116
|
+
st.write("**Duration:**", format_seconds(media.duration))
|
|
117
|
+
st.write("**Sequence:**", media.sequence)
|
|
118
|
+
st.write("**Size:**", round(media.file_size / (1024 * 1024), 1), "MB")
|
|
119
|
+
st.write("**Status:**", media.status)
|
|
120
|
+
key = media.post_id + media.url
|
|
121
|
+
if key in previous_statuses:
|
|
122
|
+
st.write("**PREV STATUS:**", previous_statuses[key])
|
|
123
|
+
st.write("**Path:**", media.file_path)
|
|
124
|
+
st.write("**Created at**:", media.created_at[0:16])
|
|
125
|
+
|
|
126
|
+
with col2:
|
|
127
|
+
# controls
|
|
128
|
+
c1, c2, c3 = st.columns([1, 1, 2])
|
|
129
|
+
with c1:
|
|
130
|
+
if st.button("⏮ Prev", disabled=idx == 0):
|
|
131
|
+
st.session_state.media_index -= 1
|
|
132
|
+
st.rerun()
|
|
133
|
+
if st.button("⏭ Next", disabled=idx >= len(medias) - 1):
|
|
134
|
+
st.session_state.media_index += 1
|
|
135
|
+
st.rerun()
|
|
136
|
+
with c2:
|
|
137
|
+
if st.button("Remove"):
|
|
138
|
+
set_status(media, Status.TO_BE_DELETED)
|
|
139
|
+
st.rerun()
|
|
140
|
+
if st.button("Revert Status"):
|
|
141
|
+
key = media.post_id + media.url
|
|
142
|
+
if key in previous_statuses:
|
|
143
|
+
set_status(media, previous_statuses[key])
|
|
144
|
+
else:
|
|
145
|
+
print("Not in previous status")
|
|
146
|
+
st.rerun()
|
|
147
|
+
with c3:
|
|
148
|
+
chosen_status = st.selectbox(
|
|
149
|
+
"Set Status",
|
|
150
|
+
options=list(Status),
|
|
151
|
+
index=list(Status).index(media.status)
|
|
152
|
+
if media.status in list(Status)
|
|
153
|
+
else 0,
|
|
154
|
+
)
|
|
155
|
+
if st.button("Apply Status"):
|
|
156
|
+
set_status(media, chosen_status)
|
|
157
|
+
st.rerun()
|
|
158
|
+
|
|
159
|
+
# video player
|
|
160
|
+
full_path = os.path.join(Config.creator_folder(post.user), media.file_path)
|
|
161
|
+
if os.path.exists(full_path):
|
|
162
|
+
with col_video:
|
|
163
|
+
with st.container():
|
|
164
|
+
if media.file_size > 199 * 1024 * 1024: # 199MB
|
|
165
|
+
with open(full_path, "rb") as f:
|
|
166
|
+
st.video(f.read(), autoplay=True, loop=True)
|
|
167
|
+
else:
|
|
168
|
+
st.video(full_path, autoplay=True, loop=True)
|
|
169
|
+
else:
|
|
170
|
+
st.error(f"Video file {full_path} not found on disk")
|
|
File without changes
|