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.
Files changed (26) hide show
  1. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/PKG-INFO +1 -1
  2. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/__init__.py +1 -1
  3. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/facebook.py +22 -24
  4. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/facebook_manager.py +79 -5
  5. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/PKG-INFO +1 -1
  6. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/setup.py +1 -1
  7. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/LICENSE +0 -0
  8. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/README.md +0 -0
  9. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/config.py +0 -0
  10. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/crud.py +0 -0
  11. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/dashboard.py +0 -0
  12. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/db.py +0 -0
  13. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/export.py +0 -0
  14. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/file_compressor.py +0 -0
  15. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/file_manager.py +0 -0
  16. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/file_sharing.py +0 -0
  17. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/main.py +0 -0
  18. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/utils.py +0 -0
  19. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/web.py +0 -0
  20. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager/youtube.py +0 -0
  21. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/SOURCES.txt +0 -0
  22. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/dependency_links.txt +0 -0
  23. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/entry_points.txt +0 -0
  24. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/requires.txt +0 -0
  25. {lecture_manager-2.3.2 → lecture_manager-2.3.3}/lecture_manager.egg-info/top_level.txt +0 -0
  26. {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.2
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
@@ -6,4 +6,4 @@ YouTube Lecture Manager package.
6
6
 
7
7
  from .facebook_manager import add_facebook_entry, get_facebook_entry_by_id, list_facebook_entries, delete_facebook_entry
8
8
 
9
- __version__ = "2.3.2"
9
+ __version__ = "2.3.3"
@@ -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
- # Determine cookie option
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
- # ---- Determine filename ----
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
- # ---- Download ----
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
- # Find the downloaded file
134
- # yt-dlp may add extra extensions; we'll glob for the base name
135
- downloaded_files = [f for f in os.listdir(DOWNLOAD_DIR) if f.startswith(filename_base)]
136
- if not downloaded_files:
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
- temp_path = os.path.join(DOWNLOAD_DIR, downloaded_files[0])
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
- # Move to final location
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 new Facebook entry into the database.
203
- Returns the inserted ID or None on error.
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
- # Try by facebook_id, then id, then file_hash
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.2
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.2", # Hardcoded (no import from package)
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