lecture-manager 2.3.2__tar.gz → 2.3.3__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.
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/PKG-INFO +1 -1
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/__init__.py +1 -1
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/facebook.py +22 -24
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/facebook_manager.py +79 -5
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/PKG-INFO +1 -1
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/setup.py +1 -1
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/LICENSE +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/README.md +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/config.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/crud.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/dashboard.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/db.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/export.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/file_compressor.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/file_manager.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/file_sharing.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/main.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/utils.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/web.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/youtube.py +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/SOURCES.txt +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/dependency_links.txt +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/entry_points.txt +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/requires.txt +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/top_level.txt +0 -0
- {lecture_manager-2.3.2 → lecture_manager-2.3.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lecture-manager
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Unified media manager for YouTube lectures and Facebook content with terminal and web interface
|
|
5
5
|
Home-page: https://github.com/blee-design/lecture-manager
|
|
6
6
|
Author: Udaya Raj Joshi
|
|
@@ -42,6 +42,7 @@ def _is_video_link(url):
|
|
|
42
42
|
'/watch',
|
|
43
43
|
'/reel/',
|
|
44
44
|
'/share/r/',
|
|
45
|
+
'/share/v/', # <-- Add this line
|
|
45
46
|
'/videos/',
|
|
46
47
|
'/video/',
|
|
47
48
|
]
|
|
@@ -51,25 +52,14 @@ def _is_video_link(url):
|
|
|
51
52
|
def _download_video(url, custom_name=None):
|
|
52
53
|
from .facebook_manager import add_facebook_entry
|
|
53
54
|
|
|
54
|
-
"""
|
|
55
|
-
Download a single Facebook video/Reel.
|
|
56
|
-
After download, organise into ROOT_DIR/facebook/videos/ and add to DB.
|
|
57
|
-
"""
|
|
58
|
-
# Ensure cookies.txt is fresh
|
|
59
55
|
_ensure_cookie_file()
|
|
56
|
+
cookie_opt = {'cookiefile': 'cookies.txt'} if os.path.exists('cookies.txt') else {'cookiesfrombrowser': ('edge',)}
|
|
60
57
|
|
|
61
|
-
#
|
|
62
|
-
if os.path.exists('cookies.txt'):
|
|
63
|
-
cookie_opt = {'cookiefile': 'cookies.txt'}
|
|
64
|
-
else:
|
|
65
|
-
cookie_opt = {'cookiesfrombrowser': ('edge',)}
|
|
66
|
-
|
|
67
|
-
# ---- Try to get metadata ----
|
|
58
|
+
# Fetch metadata
|
|
68
59
|
title = None
|
|
69
60
|
uploader = None
|
|
70
61
|
description = None
|
|
71
62
|
facebook_id = None
|
|
72
|
-
|
|
73
63
|
ydl_opts_info = {
|
|
74
64
|
'quiet': True,
|
|
75
65
|
'no_warnings': True,
|
|
@@ -77,7 +67,6 @@ def _download_video(url, custom_name=None):
|
|
|
77
67
|
'ignoreerrors': True,
|
|
78
68
|
**cookie_opt
|
|
79
69
|
}
|
|
80
|
-
|
|
81
70
|
try:
|
|
82
71
|
with yt_dlp.YoutubeDL(ydl_opts_info) as ydl:
|
|
83
72
|
info = ydl.extract_info(url, download=False)
|
|
@@ -87,12 +76,11 @@ def _download_video(url, custom_name=None):
|
|
|
87
76
|
description = info.get('description')
|
|
88
77
|
if description:
|
|
89
78
|
description = description.split('\n')[0].strip()
|
|
90
|
-
# Try to get a stable ID
|
|
91
79
|
facebook_id = info.get('id') or _extract_facebook_id(url)
|
|
92
80
|
except Exception as e:
|
|
93
81
|
print_colored(f"[!] Could not fetch metadata: {e}", COLORS.YELLOW)
|
|
94
82
|
|
|
95
|
-
#
|
|
83
|
+
# Determine filename
|
|
96
84
|
if custom_name:
|
|
97
85
|
filename_base = custom_name
|
|
98
86
|
elif title and title.lower() != 'video':
|
|
@@ -112,7 +100,7 @@ def _download_video(url, custom_name=None):
|
|
|
112
100
|
|
|
113
101
|
print_colored(f"[i] Saving as: {filename_base}", COLORS.GREEN)
|
|
114
102
|
|
|
115
|
-
#
|
|
103
|
+
# Download
|
|
116
104
|
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
|
|
117
105
|
temp_filename = f"{filename_base}.%(ext)s"
|
|
118
106
|
temp_path_pattern = os.path.join(DOWNLOAD_DIR, temp_filename)
|
|
@@ -130,13 +118,21 @@ def _download_video(url, custom_name=None):
|
|
|
130
118
|
with yt_dlp.YoutubeDL(ydl_opts_download) as ydl:
|
|
131
119
|
print_colored(f"[⏳] Downloading Facebook video...", COLORS.BLUE)
|
|
132
120
|
ydl.download([url])
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
121
|
+
|
|
122
|
+
# ---- Improved file detection ----
|
|
123
|
+
import glob
|
|
124
|
+
pattern = os.path.join(DOWNLOAD_DIR, f"{filename_base}.*")
|
|
125
|
+
matches = glob.glob(pattern)
|
|
126
|
+
if not matches:
|
|
127
|
+
all_files = os.listdir(DOWNLOAD_DIR)
|
|
128
|
+
matches = [os.path.join(DOWNLOAD_DIR, f) for f in all_files
|
|
129
|
+
if filename_base in f and f.lower().endswith(('.mp4', '.mkv', '.webm', '.avi', '.mov'))]
|
|
130
|
+
if not matches:
|
|
137
131
|
print_colored("[!] Could not locate downloaded file.", COLORS.RED)
|
|
138
132
|
return
|
|
139
|
-
|
|
133
|
+
if len(matches) > 1:
|
|
134
|
+
matches.sort(key=lambda p: os.path.getsize(p), reverse=True)
|
|
135
|
+
temp_path = matches[0]
|
|
140
136
|
print_colored(f"[✓] Downloaded to {temp_path}", COLORS.GREEN)
|
|
141
137
|
|
|
142
138
|
# ---- Compute MD5 and move to final directory ----
|
|
@@ -148,11 +144,13 @@ def _download_video(url, custom_name=None):
|
|
|
148
144
|
os.makedirs(FACEBOOK_VIDEO_DIR, exist_ok=True)
|
|
149
145
|
final_path = os.path.join(FACEBOOK_VIDEO_DIR, new_filename)
|
|
150
146
|
|
|
151
|
-
#
|
|
147
|
+
# If final file already exists, remove it (replace)
|
|
148
|
+
if os.path.exists(final_path):
|
|
149
|
+
os.remove(final_path)
|
|
152
150
|
shutil.move(temp_path, final_path)
|
|
153
151
|
print_colored(f"[✓] File stored at: {final_path}", COLORS.BLUE)
|
|
154
152
|
|
|
155
|
-
# ---- Insert into database ----
|
|
153
|
+
# ---- Insert into database (upsert) ----
|
|
156
154
|
if not facebook_id:
|
|
157
155
|
facebook_id = _extract_facebook_id(url)
|
|
158
156
|
entry_id = add_facebook_entry(
|
|
@@ -14,6 +14,7 @@ from .db import get_connection
|
|
|
14
14
|
from .file_sharing import _load_share_db, _save_share_db, SHARE_DIR
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
FACEBOOK_VIDEO_DIR = os.path.join(ROOT_DIR, 'facebook', 'videos')
|
|
18
19
|
FACEBOOK_PHOTO_DIR = os.path.join(ROOT_DIR, 'facebook', 'photos')
|
|
19
20
|
TABLE_NAME = 'facebook_entries'
|
|
@@ -168,12 +169,21 @@ def _extract_facebook_id(url):
|
|
|
168
169
|
# fallback: use the last part of the URL
|
|
169
170
|
return url.rstrip('/').split('/')[-1]
|
|
170
171
|
|
|
172
|
+
def _extract_facebook_id_from_url_or_id(identifier):
|
|
173
|
+
"""If identifier is a URL, extract the Facebook ID; otherwise return as-is."""
|
|
174
|
+
if isinstance(identifier, str) and identifier.startswith('http'):
|
|
175
|
+
from .facebook import _extract_facebook_id
|
|
176
|
+
return _extract_facebook_id(identifier)
|
|
177
|
+
return identifier
|
|
178
|
+
|
|
171
179
|
def add_facebook_lecture(url_or_id):
|
|
172
180
|
"""
|
|
173
181
|
Handle adding a Facebook video or photo.
|
|
174
182
|
Detects type, downloads, organises, and stores in DB.
|
|
175
183
|
"""
|
|
176
184
|
from .facebook import _is_video_link, _download_video, _download_single_photo
|
|
185
|
+
from .youtube import _ensure_cookie_file
|
|
186
|
+
import yt_dlp
|
|
177
187
|
|
|
178
188
|
print("\n" + "═" * 50)
|
|
179
189
|
print_colored(" ADD FACEBOOK CONTENT", COLORS.CYAN, bold=True)
|
|
@@ -183,6 +193,54 @@ def add_facebook_lecture(url_or_id):
|
|
|
183
193
|
print_colored("[!] Please provide a full Facebook URL.", COLORS.RED)
|
|
184
194
|
return
|
|
185
195
|
|
|
196
|
+
# ---- Resolve to canonical ID using yt-dlp ----
|
|
197
|
+
# ---- Resolve to canonical ID using yt-dlp ----
|
|
198
|
+
facebook_id = None
|
|
199
|
+
try:
|
|
200
|
+
_ensure_cookie_file()
|
|
201
|
+
ydl_opts = {
|
|
202
|
+
'quiet': True,
|
|
203
|
+
'extract_flat': False, # get full info, not just metadata
|
|
204
|
+
'ignoreerrors': True,
|
|
205
|
+
'cookiefile': 'cookies.txt' if os.path.exists('cookies.txt') else None,
|
|
206
|
+
}
|
|
207
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
208
|
+
info = ydl.extract_info(url_or_id, download=False)
|
|
209
|
+
if info and isinstance(info, dict):
|
|
210
|
+
# Try multiple possible fields
|
|
211
|
+
facebook_id = info.get('id') or info.get('display_id') or info.get('webpage_url_basename')
|
|
212
|
+
if not facebook_id:
|
|
213
|
+
# Some Facebook videos have ID in 'url' or 'original_url'
|
|
214
|
+
if info.get('url'):
|
|
215
|
+
import re
|
|
216
|
+
match = re.search(r'facebook\.com/watch/?\?v=(\d+)', info['url'])
|
|
217
|
+
if match:
|
|
218
|
+
facebook_id = match.group(1)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
print_colored(f"[!] Could not resolve URL: {e}", COLORS.YELLOW)
|
|
221
|
+
|
|
222
|
+
# ---- Fallback to old extraction method ----
|
|
223
|
+
if not facebook_id:
|
|
224
|
+
from .facebook import _extract_facebook_id
|
|
225
|
+
facebook_id = _extract_facebook_id(url_or_id)
|
|
226
|
+
|
|
227
|
+
if not facebook_id:
|
|
228
|
+
print_colored("[!] Could not extract a valid Facebook ID from the URL.", COLORS.RED)
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# ---- Check if this Facebook ID already exists ----
|
|
232
|
+
existing = get_facebook_entry_by_id(facebook_id)
|
|
233
|
+
if existing:
|
|
234
|
+
print_colored(f"[!] Facebook ID {facebook_id} already exists in the database.", COLORS.YELLOW)
|
|
235
|
+
print_colored(f" Title: {existing.get('title', 'Unknown')}", COLORS.BLUE)
|
|
236
|
+
print_colored(f" Type: {existing.get('type', 'Unknown')}", COLORS.BLUE)
|
|
237
|
+
print_colored(f" ID: {existing.get('id', 'Unknown')}", COLORS.BLUE)
|
|
238
|
+
overwrite = input(color_text("Download and overwrite? (y/n): ", COLORS.MAGENTA)).strip().lower()
|
|
239
|
+
if overwrite != 'y':
|
|
240
|
+
print_colored("Cancelled.", COLORS.YELLOW)
|
|
241
|
+
return
|
|
242
|
+
print_colored("[i] Overwriting existing entry...", COLORS.BLUE)
|
|
243
|
+
|
|
186
244
|
custom_name = input(color_text("Enter a custom name (or press Enter to auto-detect): ", COLORS.MAGENTA)).strip()
|
|
187
245
|
if not custom_name:
|
|
188
246
|
custom_name = None
|
|
@@ -199,8 +257,9 @@ def add_facebook_lecture(url_or_id):
|
|
|
199
257
|
|
|
200
258
|
def add_facebook_entry(facebook_id, entry_type, title, uploader, url, file_hash=None, original_filename=None, notes=None):
|
|
201
259
|
"""
|
|
202
|
-
Insert a
|
|
203
|
-
|
|
260
|
+
Insert or update a Facebook entry in the database.
|
|
261
|
+
If facebook_id already exists, update the record with new values.
|
|
262
|
+
Returns the inserted/updated ID or None on error.
|
|
204
263
|
"""
|
|
205
264
|
conn = get_connection()
|
|
206
265
|
cursor = conn.cursor()
|
|
@@ -209,6 +268,15 @@ def add_facebook_entry(facebook_id, entry_type, title, uploader, url, file_hash=
|
|
|
209
268
|
INSERT INTO {TABLE_NAME}
|
|
210
269
|
(facebook_id, type, title, uploader, url, file_hash, original_filename, notes)
|
|
211
270
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
271
|
+
ON DUPLICATE KEY UPDATE
|
|
272
|
+
type = VALUES(type),
|
|
273
|
+
title = VALUES(title),
|
|
274
|
+
uploader = VALUES(uploader),
|
|
275
|
+
url = VALUES(url),
|
|
276
|
+
file_hash = VALUES(file_hash),
|
|
277
|
+
original_filename = VALUES(original_filename),
|
|
278
|
+
notes = VALUES(notes),
|
|
279
|
+
download_date = NOW()
|
|
212
280
|
""", (facebook_id, entry_type, title, uploader, url, file_hash, original_filename, notes))
|
|
213
281
|
conn.commit()
|
|
214
282
|
inserted_id = cursor.lastrowid
|
|
@@ -216,7 +284,7 @@ def add_facebook_entry(facebook_id, entry_type, title, uploader, url, file_hash=
|
|
|
216
284
|
conn.close()
|
|
217
285
|
return inserted_id
|
|
218
286
|
except Exception as e:
|
|
219
|
-
print_colored(f"[!] Failed to insert Facebook entry: {e}", COLORS.RED)
|
|
287
|
+
print_colored(f"[!] Failed to insert/update Facebook entry: {e}", COLORS.RED)
|
|
220
288
|
cursor.close()
|
|
221
289
|
conn.close()
|
|
222
290
|
return None
|
|
@@ -228,10 +296,10 @@ def get_facebook_entry_by_id(identifier):
|
|
|
228
296
|
"""
|
|
229
297
|
conn = get_connection()
|
|
230
298
|
cursor = conn.cursor(dictionary=True)
|
|
231
|
-
#
|
|
299
|
+
# Use CAST to avoid implicit integer conversion
|
|
232
300
|
sql = f"""
|
|
233
301
|
SELECT * FROM {TABLE_NAME}
|
|
234
|
-
WHERE facebook_id = %s OR id = %s OR file_hash = %s
|
|
302
|
+
WHERE facebook_id = %s OR CAST(id AS CHAR) = %s OR file_hash = %s
|
|
235
303
|
"""
|
|
236
304
|
cursor.execute(sql, (identifier, identifier, identifier))
|
|
237
305
|
row = cursor.fetchone()
|
|
@@ -356,6 +424,7 @@ def export_facebook_csv():
|
|
|
356
424
|
def export_facebook_json():
|
|
357
425
|
"""Export all Facebook entries to a JSON file."""
|
|
358
426
|
import json
|
|
427
|
+
from datetime import datetime
|
|
359
428
|
conn = get_connection()
|
|
360
429
|
cursor = conn.cursor(dictionary=True)
|
|
361
430
|
cursor.execute("SELECT * FROM facebook_entries ORDER BY download_date DESC")
|
|
@@ -370,6 +439,11 @@ def export_facebook_json():
|
|
|
370
439
|
filename = "facebook_export.json"
|
|
371
440
|
if not filename.endswith('.json'):
|
|
372
441
|
filename += '.json'
|
|
442
|
+
# Convert datetime objects to strings
|
|
443
|
+
for row in rows:
|
|
444
|
+
for key, value in row.items():
|
|
445
|
+
if isinstance(value, datetime):
|
|
446
|
+
row[key] = value.isoformat()
|
|
373
447
|
try:
|
|
374
448
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
375
449
|
json.dump(rows, f, indent=2, ensure_ascii=False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lecture-manager
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Unified media manager for YouTube lectures and Facebook content with terminal and web interface
|
|
5
5
|
Home-page: https://github.com/blee-design/lecture-manager
|
|
6
6
|
Author: Udaya Raj Joshi
|
|
@@ -36,7 +36,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
36
36
|
|
|
37
37
|
setup(
|
|
38
38
|
name="lecture-manager",
|
|
39
|
-
version="2.3.
|
|
39
|
+
version = "2.3.3", # Hardcoded (no import from package)
|
|
40
40
|
description="Unified media manager for YouTube lectures and Facebook content with terminal and web interface",
|
|
41
41
|
long_description=long_description,
|
|
42
42
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|