Eluza133 commited on
Commit
d9f6023
·
verified ·
1 Parent(s): 813cb9b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -121
app.py CHANGED
@@ -27,16 +27,16 @@ def load_data():
27
  data = json.load(file)
28
  if not isinstance(data, dict):
29
  logging.warning("Данные не в формате dict, инициализация пустой базы")
30
- return {'videos': [], 'users': {}}
31
- if 'videos' not in data:
32
- data['videos'] = []
33
  if 'users' not in data:
34
  data['users'] = {}
35
  logging.info("Данные успешно загружены")
36
  return data
37
  except Exception as e:
38
  logging.error(f"Ошибка загрузки данных: {e}")
39
- return {'videos': [], 'users': {}}
40
 
41
  def save_data(data):
42
  try:
@@ -76,7 +76,9 @@ def download_db_from_hf():
76
  logging.info("База данных скачана с Hugging Face")
77
  except Exception as e:
78
  logging.error(f"Ошибка скачивания базы: {e}")
79
- raise
 
 
80
 
81
  def periodic_backup():
82
  while True:
@@ -116,7 +118,7 @@ def register():
116
  button { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; width: 100%; }
117
  .flash { color: red; margin-bottom: 10px; }
118
  @media (max-width: 600px) {
119
- .container { padding: 15px; }
120
  input, button { font-size: 14px; padding: 8px; }
121
  }
122
  </style>
@@ -155,7 +157,7 @@ def login():
155
  session['username'] = username
156
  session.permanent = True
157
  logging.info(f"Успешный вход: {username}")
158
- return redirect(url_for('video_feed'))
159
  flash('Неверный логин или пароль!')
160
  return redirect(url_for('login'))
161
 
@@ -174,7 +176,7 @@ def login():
174
  button { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; width: 100%; }
175
  .flash { color: red; margin-bottom: 10px; }
176
  @media (max-width: 600px) {
177
- .container { padding: 15px; }
178
  input, button { font-size: 14px; padding: 8px; }
179
  }
180
  </style>
@@ -206,13 +208,13 @@ def login():
206
  def logout():
207
  session.pop('username', None)
208
  logging.info("Пользователь вышел из системы")
209
- return redirect(url_for('video_feed'))
210
 
211
- # Главная страница - лента видео
212
  @app.route('/')
213
- def video_feed():
214
  data = load_data()
215
- videos = data['videos']
216
  is_authenticated = 'username' in session
217
  username = session.get('username', None)
218
  html = '''
@@ -221,28 +223,30 @@ def video_feed():
221
  <head>
222
  <meta charset="UTF-8">
223
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
224
- <title>Видеохостинг</title>
225
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
226
  <style>
227
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
228
  .container { max-width: 1200px; margin: 0 auto; padding-left: 220px; transition: padding-left 0.3s; }
229
  .sidebar { position: fixed; left: -220px; top: 0; width: 200px; height: 100%; background: #fff; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); transition: left 0.3s; z-index: 1000; }
230
  .sidebar.active { left: 0; }
231
- .video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
232
- .video-item { background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); text-decoration: none; color: #2d3748; position: relative; }
233
- .video-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
234
  .auth-links, .upload-btn, .logout-btn, .profile-link { display: block; margin: 10px 0; padding: 10px; background: #3b82f6; color: white; text-align: center; text-decoration: none; border-radius: 8px; }
235
  .logout-btn { background: #ef4444; }
236
  .profile-link { background: #10b981; }
237
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
 
238
  @media (max-width: 768px) {
239
- .container { padding-left: 20px; }
240
- .sidebar { left: -220px; }
241
  .sidebar.active { left: 0; }
242
  .menu-btn { display: block; }
243
- .video-grid { grid-template-columns: 1fr; }
244
- .video-item { padding: 10px; }
245
- .video-preview { height: 150px; }
 
246
  }
247
  </style>
248
  </head>
@@ -260,16 +264,21 @@ def video_feed():
260
  {% endif %}
261
  </div>
262
  <div class="container">
263
- <h1>Лента видео</h1>
264
- <div class="video-grid">
265
- {% for video in videos %}
266
- <a href="{{ url_for('video_page', video_id=video['id']) }}" class="video-item">
267
- <video class="video-preview" preload="metadata" muted>
268
- <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/videos/{{ video['filename'] }}" type="video/mp4">
269
- </video>
270
- <h2>{{ video['title'] }}</h2>
271
- <p>{{ video['description'] }}</p>
272
- <p>Загрузил: {{ video['uploader'] }} | {{ video['upload_date'] }}</p>
 
 
 
 
 
273
  </a>
274
  {% endfor %}
275
  </div>
@@ -279,56 +288,80 @@ def video_feed():
279
  document.getElementById('sidebar').classList.toggle('active');
280
  }
281
  document.addEventListener('DOMContentLoaded', function() {
282
- const videos = document.querySelectorAll('.video-preview');
283
  videos.forEach(video => {
284
- video.addEventListener('loadedmetadata', function() {
285
- const duration = video.duration;
286
- const randomTime = Math.random() * duration;
287
- video.currentTime = randomTime;
288
- });
 
 
289
  });
290
  });
291
  </script>
292
  </body>
293
  </html>
294
  '''
295
- return render_template_string(html, videos=videos, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID)
296
 
297
- # Страница отдельного видео
298
- @app.route('/video/<video_id>')
299
- def video_page(video_id):
300
  data = load_data()
301
- video = next((v for v in data['videos'] if v['id'] == video_id), None)
302
- if not video:
303
- return "Видео не найдено", 404
304
 
305
  is_authenticated = 'username' in session
306
  username = session.get('username', None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  html = '''
308
  <!DOCTYPE html>
309
  <html lang="ru">
310
  <head>
311
  <meta charset="UTF-8">
312
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
313
- <title>{{ video['title'] }}</title>
314
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
315
  <style>
316
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
317
  .sidebar { position: fixed; left: -220px; top: 0; width: 200px; height: 100%; background: #fff; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); transition: left 0.3s; z-index: 1000; }
318
  .sidebar.active { left: 0; }
319
  .container { max-width: 800px; margin: 0 auto; padding-left: 220px; background: #fff; padding: 20px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); transition: padding-left 0.3s; }
320
- video { width: 100%; border-radius: 8px; }
321
- .back-btn, .profile-link, .logout-btn { display: inline-block; margin-top: 10px; padding: 10px; background: #3b82f6; color: white; text-decoration: none; border-radius: 8px; }
322
  .profile-link { background: #10b981; }
323
  .logout-btn { background: #ef4444; }
 
324
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
 
 
325
  @media (max-width: 768px) {
326
- .container { padding-left: 20px; padding: 15px; }
327
- .sidebar { left: -220px; }
328
  .sidebar.active { left: 0; }
329
  .menu-btn { display: block; }
330
- video { height: auto; }
331
- .back-btn { width: 100%; text-align: center; }
 
332
  }
333
  </style>
334
  </head>
@@ -346,15 +379,37 @@ def video_page(video_id):
346
  {% endif %}
347
  </div>
348
  <div class="container">
349
- <h1>{{ video['title'] }}</h1>
350
- <video controls>
351
- <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/videos/{{ video['filename'] }}" type="video/mp4">
352
- </video>
353
- <p>{{ video['description'] }}</p>
354
- <p>Загрузил: {{ video['uploader'] }} | {{ video['upload_date'] }}</p>
355
- <a href="{{ url_for('video_feed') }}" class="back-btn">Назад к ленте</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  {% if not is_authenticated %}
357
- <p><a href="{{ url_for('login') }}">Войдите</a>, чтобы загрузить свои видео.</p>
358
  {% endif %}
359
  </div>
360
  <script>
@@ -365,7 +420,7 @@ def video_page(video_id):
365
  </body>
366
  </html>
367
  '''
368
- return render_template_string(html, video=video, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username)
369
 
370
  # Страница профиля
371
  @app.route('/profile', methods=['GET', 'POST'])
@@ -376,14 +431,14 @@ def profile():
376
 
377
  data = load_data()
378
  username = session['username']
379
- user_videos = [v for v in data['videos'] if v['uploader'] == username]
380
 
381
  if request.method == 'POST':
382
- video_id = request.form.get('video_id')
383
- if video_id:
384
- data['videos'] = [v for v in data['videos'] if v['id'] != video_id or v['uploader'] != username]
385
  save_data(data)
386
- logging.info(f"Видео {video_id} удалено пользователем {username}")
387
  return redirect(url_for('profile'))
388
 
389
  html = '''
@@ -399,23 +454,25 @@ def profile():
399
  .sidebar { position: fixed; left: -220px; top: 0; width: 200px; height: 100%; background: #fff; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); transition: left 0.3s; z-index: 1000; }
400
  .sidebar.active { left: 0; }
401
  .container { max-width: 800px; margin: 0 auto; padding-left: 220px; transition: padding-left 0.3s; }
402
- .video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
403
- .video-item { background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
404
- .video-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
405
  .upload-btn, .delete-btn, .profile-link, .logout-btn { display: block; margin: 10px 0; padding: 10px; background: #3b82f6; color: white; text-decoration: none; border-radius: 8px; }
406
  .delete-btn { background: #ef4444; border: none; cursor: pointer; }
407
  .profile-link { background: #10b981; }
408
  .logout-btn { background: #ef4444; }
409
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
 
410
  @media (max-width: 768px) {
411
- .container { padding-left: 20px; }
412
- .sidebar { left: -220px; }
413
  .sidebar.active { left: 0; }
414
  .menu-btn { display: block; }
415
- .video-grid { grid-template-columns: 1fr; }
416
- .video-item { padding: 10px; }
417
- .video-preview { height: 150px; }
418
- .upload-btn, .delete-btn { font-size: 14px; padding: 8px; }
 
419
  }
420
  </style>
421
  </head>
@@ -429,28 +486,33 @@ def profile():
429
  </div>
430
  <div class="container">
431
  <h1>Профиль: {{ username }}</h1>
432
- <a href="{{ url_for('upload_video') }}" class="upload-btn">Добавить видео</a>
433
- <h2>Ваши видео</h2>
434
- <div class="video-grid">
435
- {% if user_videos %}
436
- {% for video in user_videos %}
437
- <div class="video-item">
438
- <a href="{{ url_for('video_page', video_id=video['id']) }}">
439
- <video class="video-preview" preload="metadata" muted>
440
- <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/videos/{{ video['filename'] }}" type="video/mp4">
441
- </video>
442
- <h3>{{ video['title'] }}</h3>
 
 
 
 
443
  </a>
444
- <p>{{ video['description'] }}</p>
445
- <p>{{ video['upload_date'] }}</p>
 
446
  <form method="POST">
447
- <input type="hidden" name="video_id" value="{{ video['id'] }}">
448
  <button type="submit" class="delete-btn">Удалить</button>
449
  </form>
450
  </div>
451
  {% endfor %}
452
  {% else %}
453
- <p>Вы пока не загрузили ни одного видео.</p>
454
  {% endif %}
455
  </div>
456
  </div>
@@ -459,68 +521,77 @@ def profile():
459
  document.getElementById('sidebar').classList.toggle('active');
460
  }
461
  document.addEventListener('DOMContentLoaded', function() {
462
- const videos = document.querySelectorAll('.video-preview');
463
  videos.forEach(video => {
464
- video.addEventListener('loadedmetadata', function() {
465
- const duration = video.duration;
466
- const randomTime = Math.random() * duration;
467
- video.currentTime = randomTime;
468
- });
 
 
469
  });
470
  });
471
  </script>
472
  </body>
473
  </html>
474
  '''
475
- return render_template_string(html, username=username, user_videos=user_videos, repo_id=REPO_ID)
476
 
477
- # Страница загрузки видео
478
  @app.route('/upload', methods=['GET', 'POST'])
479
- def upload_video():
480
  if 'username' not in session:
481
- flash('Войдите, чтобы загрузить видео!')
482
  return redirect(url_for('login'))
483
 
484
  if request.method == 'POST':
485
  title = request.form.get('title')
486
  description = request.form.get('description')
487
- video_file = request.files.get('video')
488
  uploader = session['username']
489
 
490
- if not title or not video_file:
491
- return "Укажите название и выберите видео", 400
492
 
493
- filename = secure_filename(video_file.filename)
494
  temp_path = os.path.join('uploads', filename)
495
  os.makedirs('uploads', exist_ok=True)
496
- video_file.save(temp_path)
 
 
 
497
 
498
  # Загрузка на Hugging Face
499
  api = HfApi()
500
  api.upload_file(
501
  path_or_fileobj=temp_path,
502
- path_in_repo=f"videos/{filename}",
503
  repo_id=REPO_ID,
504
  repo_type="dataset",
505
  token=HF_TOKEN_WRITE,
506
- commit_message=f"Добавлено видео: {title} пользователем {uploader}"
507
  )
508
 
509
- # Обновление базы данных с уникальным ID
510
  data = load_data()
511
- video_id = f"video_{len(data['videos']) + 1}"
512
- while any(v['id'] == video_id for v in data['videos']):
513
- video_id = f"video_{len(data['videos']) + 1}_{int(time.time())}"
514
- data['videos'].append({
515
- 'id': video_id,
516
  'title': title,
517
  'description': description,
518
  'filename': filename,
 
519
  'uploader': uploader,
520
- 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
 
 
 
521
  })
522
  save_data(data)
523
- logging.info(f"Видео {title} загружено пользователем {uploader} с ID {video_id}")
524
 
525
  if os.path.exists(temp_path):
526
  os.remove(temp_path)
@@ -534,7 +605,7 @@ def upload_video():
534
  <head>
535
  <meta charset="UTF-8">
536
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
537
- <title>Загрузка видео</title>
538
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
539
  <style>
540
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
@@ -550,11 +621,14 @@ def upload_video():
550
  .logout-btn { background: #ef4444; }
551
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
552
  @media (max-width: 768px) {
553
- .container { padding-left: 20px; padding: 15px; }
554
- .sidebar { left: -220px; }
555
  .sidebar.active { left: 0; }
556
  .menu-btn { display: block; }
557
- input, textarea, button { font-size: 14px; padding: 8px; }
 
 
 
558
  }
559
  </style>
560
  </head>
@@ -567,11 +641,11 @@ def upload_video():
567
  <a href="{{ url_for('logout') }}" class="logout-btn">Выйти</a>
568
  </div>
569
  <div class="container">
570
- <h1>Загрузить видео</h1>
571
  <form id="upload-form" enctype="multipart/form-data">
572
- <input type="text" name="title" placeholder="Название видео" required>
573
  <textarea name="description" placeholder="Описание" rows="4"></textarea>
574
- <input type="file" name="video" accept="video/*" required>
575
  <button type="submit">Загрузить</button>
576
  </form>
577
  <div id="progress-container">
 
27
  data = json.load(file)
28
  if not isinstance(data, dict):
29
  logging.warning("Данные не в формате dict, инициализация пустой базы")
30
+ return {'posts': [], 'users': {}}
31
+ if 'posts' not in data:
32
+ data['posts'] = []
33
  if 'users' not in data:
34
  data['users'] = {}
35
  logging.info("Данные успешно загружены")
36
  return data
37
  except Exception as e:
38
  logging.error(f"Ошибка загрузки данных: {e}")
39
+ return {'posts': [], 'users': {}}
40
 
41
  def save_data(data):
42
  try:
 
76
  logging.info("База данных скачана с Hugging Face")
77
  except Exception as e:
78
  logging.error(f"Ошибка скачивания базы: {e}")
79
+ if not os.path.exists(DATA_FILE):
80
+ with open(DATA_FILE, 'w', encoding='utf-8') as f:
81
+ json.dump({'posts': [], 'users': {}}, f)
82
 
83
  def periodic_backup():
84
  while True:
 
118
  button { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; width: 100%; }
119
  .flash { color: red; margin-bottom: 10px; }
120
  @media (max-width: 600px) {
121
+ .container { padding: 15px; max-width: 100%; }
122
  input, button { font-size: 14px; padding: 8px; }
123
  }
124
  </style>
 
157
  session['username'] = username
158
  session.permanent = True
159
  logging.info(f"Успешный вход: {username}")
160
+ return redirect(url_for('feed'))
161
  flash('Неверный логин или пароль!')
162
  return redirect(url_for('login'))
163
 
 
176
  button { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 8px; cursor: pointer; width: 100%; }
177
  .flash { color: red; margin-bottom: 10px; }
178
  @media (max-width: 600px) {
179
+ .container { padding: 15px; max-width: 100%; }
180
  input, button { font-size: 14px; padding: 8px; }
181
  }
182
  </style>
 
208
  def logout():
209
  session.pop('username', None)
210
  logging.info("Пользователь вышел из системы")
211
+ return redirect(url_for('feed'))
212
 
213
+ # Главная страница - лента публикаций
214
  @app.route('/')
215
+ def feed():
216
  data = load_data()
217
+ posts = data.get('posts', [])
218
  is_authenticated = 'username' in session
219
  username = session.get('username', None)
220
  html = '''
 
223
  <head>
224
  <meta charset="UTF-8">
225
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
226
+ <title>Хостинг контента</title>
227
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
228
  <style>
229
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
230
  .container { max-width: 1200px; margin: 0 auto; padding-left: 220px; transition: padding-left 0.3s; }
231
  .sidebar { position: fixed; left: -220px; top: 0; width: 200px; height: 100%; background: #fff; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); transition: left 0.3s; z-index: 1000; }
232
  .sidebar.active { left: 0; }
233
+ .post-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
234
+ .post-item { background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); text-decoration: none; color: #2d3748; }
235
+ .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
236
  .auth-links, .upload-btn, .logout-btn, .profile-link { display: block; margin: 10px 0; padding: 10px; background: #3b82f6; color: white; text-align: center; text-decoration: none; border-radius: 8px; }
237
  .logout-btn { background: #ef4444; }
238
  .profile-link { background: #10b981; }
239
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
240
+ .stats { font-size: 0.9em; color: #666; }
241
  @media (max-width: 768px) {
242
+ .container { padding-left: 20px; padding: 10px; }
243
+ .sidebar { left: -220px; width: 150px; padding: 15px; }
244
  .sidebar.active { left: 0; }
245
  .menu-btn { display: block; }
246
+ .post-grid { grid-template-columns: 1fr; gap: 15px; }
247
+ .post-item { padding: 10px; }
248
+ .post-preview { height: 150px; }
249
+ .auth-links, .upload-btn, .logout-btn, .profile-link { font-size: 14px; padding: 8px; }
250
  }
251
  </style>
252
  </head>
 
264
  {% endif %}
265
  </div>
266
  <div class="container">
267
+ <h1>Лента публикаций</h1>
268
+ <div class="post-grid">
269
+ {% for post in posts %}
270
+ <a href="{{ url_for('post_page', post_id=post['id']) }}" class="post-item">
271
+ {% if post['type'] == 'video' %}
272
+ <video class="post-preview" preload="metadata" muted>
273
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
274
+ </video>
275
+ {% else %}
276
+ <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
277
+ {% endif %}
278
+ <h2>{{ post['title'] }}</h2>
279
+ <p>{{ post['description'] }}</p>
280
+ <p>Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}</p>
281
+ <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
282
  </a>
283
  {% endfor %}
284
  </div>
 
288
  document.getElementById('sidebar').classList.toggle('active');
289
  }
290
  document.addEventListener('DOMContentLoaded', function() {
291
+ const videos = document.querySelectorAll('.post-preview');
292
  videos.forEach(video => {
293
+ if (video.tagName === 'VIDEO') {
294
+ video.addEventListener('loadedmetadata', function() {
295
+ const duration = video.duration;
296
+ const randomTime = Math.random() * duration;
297
+ video.currentTime = randomTime;
298
+ });
299
+ }
300
  });
301
  });
302
  </script>
303
  </body>
304
  </html>
305
  '''
306
+ return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID)
307
 
308
+ # Страница отдельной публикации
309
+ @app.route('/post/<post_id>', methods=['GET', 'POST'])
310
+ def post_page(post_id):
311
  data = load_data()
312
+ post = next((p for p in data['posts'] if p['id'] == post_id), None)
313
+ if not post:
314
+ return "Публикация не найдена", 404
315
 
316
  is_authenticated = 'username' in session
317
  username = session.get('username', None)
318
+
319
+ # Увеличение количества просмотров
320
+ post['views'] = post.get('views', 0) + 1
321
+ save_data(data)
322
+
323
+ if request.method == 'POST' and is_authenticated:
324
+ if 'like' in request.form:
325
+ if username not in post.get('likes', []):
326
+ post['likes'] = post.get('likes', []) + [username]
327
+ else:
328
+ post['likes'] = [user for user in post.get('likes', []) if user != username]
329
+ save_data(data)
330
+ elif 'comment' in request.form:
331
+ comment_text = request.form.get('comment')
332
+ if comment_text:
333
+ post['comments'] = post.get('comments', []) + [{'user': username, 'text': comment_text, 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]
334
+ save_data(data)
335
+
336
  html = '''
337
  <!DOCTYPE html>
338
  <html lang="ru">
339
  <head>
340
  <meta charset="UTF-8">
341
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
342
+ <title>{{ post['title'] }}</title>
343
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
344
  <style>
345
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
346
  .sidebar { position: fixed; left: -220px; top: 0; width: 200px; height: 100%; background: #fff; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); transition: left 0.3s; z-index: 1000; }
347
  .sidebar.active { left: 0; }
348
  .container { max-width: 800px; margin: 0 auto; padding-left: 220px; background: #fff; padding: 20px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); transition: padding-left 0.3s; }
349
+ video, img { width: 100%; border-radius: 8px; max-height: 400px; object-fit: cover; }
350
+ .back-btn, .profile-link, .logout-btn, .like-btn { display: inline-block; margin: 10px 0; padding: 10px; background: #3b82f6; color: white; text-decoration: none; border-radius: 8px; border: none; cursor: pointer; }
351
  .profile-link { background: #10b981; }
352
  .logout-btn { background: #ef4444; }
353
+ .like-btn.liked { background: #ef4444; }
354
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
355
+ textarea { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #e2e8f0; border-radius: 8px; }
356
+ .comment { border-top: 1px solid #e2e8f0; padding: 10px 0; }
357
  @media (max-width: 768px) {
358
+ .container { padding-left: 20px; padding: 15px; max-width: 100%; }
359
+ .sidebar { left: -220px; width: 150px; padding: 15px; }
360
  .sidebar.active { left: 0; }
361
  .menu-btn { display: block; }
362
+ video, img { max-height: 250px; }
363
+ .back-btn, .like-btn, textarea, button { width: 100%; font-size: 14px; padding: 8px; margin: 5px 0; }
364
+ h1 { font-size: 1.2em; }
365
  }
366
  </style>
367
  </head>
 
379
  {% endif %}
380
  </div>
381
  <div class="container">
382
+ <h1>{{ post['title'] }}</h1>
383
+ {% if post['type'] == 'video' %}
384
+ <video controls>
385
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
386
+ </video>
387
+ {% else %}
388
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
389
+ {% endif %}
390
+ <p>{{ post['description'] }}</p>
391
+ <p>Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}</p>
392
+ <p>Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
393
+ {% if is_authenticated %}
394
+ <form method="POST">
395
+ <button type="submit" name="like" class="like-btn {% if username in post['likes'] %}liked{% endif %}">
396
+ {% if username in post['likes'] %}Убрать лайк{% else %}Лайк{% endif %}
397
+ </button>
398
+ </form>
399
+ <form method="POST">
400
+ <textarea name="comment" placeholder="Оставьте комментарий" rows="3"></textarea>
401
+ <button type="submit">Отправить</button>
402
+ </form>
403
+ {% endif %}
404
+ <h3>Комментарии</h3>
405
+ {% for comment in post.get('comments', []) %}
406
+ <div class="comment">
407
+ <p><strong>{{ comment['user'] }}</strong> ({{ comment['date'] }}): {{ comment['text'] }}</p>
408
+ </div>
409
+ {% endfor %}
410
+ <a href="{{ url_for('feed') }}" class="back-btn">Назад к ленте</a>
411
  {% if not is_authenticated %}
412
+ <p><a href="{{ url_for('login') }}">Войдите</a>, чтобы ставить лайки и комментировать.</p>
413
  {% endif %}
414
  </div>
415
  <script>
 
420
  </body>
421
  </html>
422
  '''
423
+ return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username)
424
 
425
  # Страница профиля
426
  @app.route('/profile', methods=['GET', 'POST'])
 
431
 
432
  data = load_data()
433
  username = session['username']
434
+ user_posts = [p for p in data['posts'] if p['uploader'] == username]
435
 
436
  if request.method == 'POST':
437
+ post_id = request.form.get('post_id')
438
+ if post_id:
439
+ data['posts'] = [p for p in data['posts'] if p['id'] != post_id or p['uploader'] != username]
440
  save_data(data)
441
+ logging.info(f"Публикация {post_id} удалена пользователем {username}")
442
  return redirect(url_for('profile'))
443
 
444
  html = '''
 
454
  .sidebar { position: fixed; left: -220px; top: 0; width: 200px; height: 100%; background: #fff; padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); transition: left 0.3s; z-index: 1000; }
455
  .sidebar.active { left: 0; }
456
  .container { max-width: 800px; margin: 0 auto; padding-left: 220px; transition: padding-left 0.3s; }
457
+ .post-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
458
+ .post-item { background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
459
+ .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
460
  .upload-btn, .delete-btn, .profile-link, .logout-btn { display: block; margin: 10px 0; padding: 10px; background: #3b82f6; color: white; text-decoration: none; border-radius: 8px; }
461
  .delete-btn { background: #ef4444; border: none; cursor: pointer; }
462
  .profile-link { background: #10b981; }
463
  .logout-btn { background: #ef4444; }
464
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
465
+ .stats { font-size: 0.9em; color: #666; }
466
  @media (max-width: 768px) {
467
+ .container { padding-left: 20px; padding: 15px; max-width: 100%; }
468
+ .sidebar { left: -220px; width: 150px; padding: 15px; }
469
  .sidebar.active { left: 0; }
470
  .menu-btn { display: block; }
471
+ .post-grid { grid-template-columns: 1fr; gap: 15px; }
472
+ .post-item { padding: 10px; }
473
+ .post-preview { height: 150px; }
474
+ .upload-btn, .delete-btn, .profile-link, .logout-btn { font-size: 14px; padding: 8px; }
475
+ h1, h2 { font-size: 1.2em; }
476
  }
477
  </style>
478
  </head>
 
486
  </div>
487
  <div class="container">
488
  <h1>Профиль: {{ username }}</h1>
489
+ <a href="{{ url_for('upload') }}" class="upload-btn">Добавить публикацию</a>
490
+ <h2>Ваши публикации</h2>
491
+ <div class="post-grid">
492
+ {% if user_posts %}
493
+ {% for post in user_posts %}
494
+ <div class="post-item">
495
+ <a href="{{ url_for('post_page', post_id=post['id']) }}">
496
+ {% if post['type'] == 'video' %}
497
+ <video class="post-preview" preload="metadata" muted>
498
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
499
+ </video>
500
+ {% else %}
501
+ <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
502
+ {% endif %}
503
+ <h3>{{ post['title'] }}</h3>
504
  </a>
505
+ <p>{{ post['description'] }}</p>
506
+ <p>{{ post['upload_date'] }}</p>
507
+ <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
508
  <form method="POST">
509
+ <input type="hidden" name="post_id" value="{{ post['id'] }}">
510
  <button type="submit" class="delete-btn">Удалить</button>
511
  </form>
512
  </div>
513
  {% endfor %}
514
  {% else %}
515
+ <p>Вы пока не загрузили ни одной публикации.</p>
516
  {% endif %}
517
  </div>
518
  </div>
 
521
  document.getElementById('sidebar').classList.toggle('active');
522
  }
523
  document.addEventListener('DOMContentLoaded', function() {
524
+ const videos = document.querySelectorAll('.post-preview');
525
  videos.forEach(video => {
526
+ if (video.tagName === 'VIDEO') {
527
+ video.addEventListener('loadedmetadata', function() {
528
+ const duration = video.duration;
529
+ const randomTime = Math.random() * duration;
530
+ video.currentTime = randomTime;
531
+ });
532
+ }
533
  });
534
  });
535
  </script>
536
  </body>
537
  </html>
538
  '''
539
+ return render_template_string(html, username=username, user_posts=user_posts, repo_id=REPO_ID)
540
 
541
+ # Страница загрузки контента
542
  @app.route('/upload', methods=['GET', 'POST'])
543
+ def upload():
544
  if 'username' not in session:
545
+ flash('Войдите, чтобы загрузить контент!')
546
  return redirect(url_for('login'))
547
 
548
  if request.method == 'POST':
549
  title = request.form.get('title')
550
  description = request.form.get('description')
551
+ file = request.files.get('file')
552
  uploader = session['username']
553
 
554
+ if not title or not file:
555
+ return "Укажите название и выберите файл", 400
556
 
557
+ filename = secure_filename(file.filename)
558
  temp_path = os.path.join('uploads', filename)
559
  os.makedirs('uploads', exist_ok=True)
560
+ file.save(temp_path)
561
+
562
+ # Определение типа файла
563
+ file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
564
 
565
  # Загрузка на Hugging Face
566
  api = HfApi()
567
  api.upload_file(
568
  path_or_fileobj=temp_path,
569
+ path_in_repo=f"{file_type}s/{filename}",
570
  repo_id=REPO_ID,
571
  repo_type="dataset",
572
  token=HF_TOKEN_WRITE,
573
+ commit_message=f"Добавлена публикация: {title} пользователем {uploader}"
574
  )
575
 
576
+ # Обновление базы данных
577
  data = load_data()
578
+ post_id = f"post_{len(data['posts']) + 1}"
579
+ while any(p['id'] == post_id for p in data['posts']):
580
+ post_id = f"post_{len(data['posts']) + 1}_{int(time.time())}"
581
+ data['posts'].append({
582
+ 'id': post_id,
583
  'title': title,
584
  'description': description,
585
  'filename': filename,
586
+ 'type': file_type,
587
  'uploader': uploader,
588
+ 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
589
+ 'views': 0,
590
+ 'likes': [],
591
+ 'comments': []
592
  })
593
  save_data(data)
594
+ logging.info(f"Публикация {title} ({file_type}) загружена пользователем {uploader} с ID {post_id}")
595
 
596
  if os.path.exists(temp_path):
597
  os.remove(temp_path)
 
605
  <head>
606
  <meta charset="UTF-8">
607
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
608
+ <title>Загрузка контента</title>
609
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
610
  <style>
611
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
 
621
  .logout-btn { background: #ef4444; }
622
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
623
  @media (max-width: 768px) {
624
+ .container { padding-left: 20px; padding: 15px; max-width: 100%; }
625
+ .sidebar { left: -220px; width: 150px; padding: 15px; }
626
  .sidebar.active { left: 0; }
627
  .menu-btn { display: block; }
628
+ input, textarea, button { font-size: 14px; padding: 8px; margin: 5px 0; }
629
+ h1 { font-size: 1.2em; }
630
+ #progress-container { height: 15px; }
631
+ #progress-bar { height: 15px; }
632
  }
633
  </style>
634
  </head>
 
641
  <a href="{{ url_for('logout') }}" class="logout-btn">Выйти</a>
642
  </div>
643
  <div class="container">
644
+ <h1>Загрузить контент</h1>
645
  <form id="upload-form" enctype="multipart/form-data">
646
+ <input type="text" name="title" placeholder="Название" required>
647
  <textarea name="description" placeholder="Описание" rows="4"></textarea>
648
+ <input type="file" name="file" accept="video/*,image/*" required>
649
  <button type="submit">Загрузить</button>
650
  </form>
651
  <div id="progress-container">