Eluza133 commited on
Commit
a561ca9
·
verified ·
1 Parent(s): 9fde009

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -108
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:
@@ -155,7 +155,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
 
@@ -206,13 +206,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 +221,29 @@ 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 +261,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 +285,77 @@ 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 +373,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 +414,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 +425,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,22 +448,23 @@ 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>
@@ -429,28 +479,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 +514,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 +598,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; }
@@ -567,11 +631,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:
 
155
  session['username'] = username
156
  session.permanent = True
157
  logging.info(f"Успешный вход: {username}")
158
+ return redirect(url_for('feed'))
159
  flash('Неверный логин или пароль!')
160
  return redirect(url_for('login'))
161
 
 
206
  def logout():
207
  session.pop('username', None)
208
  logging.info("Пользователь вышел из системы")
209
+ return redirect(url_for('feed'))
210
 
211
+ # Главная страница - лента публикаций
212
  @app.route('/')
213
+ def feed():
214
  data = load_data()
215
+ posts = data['posts']
216
  is_authenticated = 'username' in session
217
  username = session.get('username', None)
218
  html = '''
 
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
+ .post-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
232
+ .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; }
233
+ .post-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
+ .stats { font-size: 0.9em; color: #666; }
239
  @media (max-width: 768px) {
240
  .container { padding-left: 20px; }
241
  .sidebar { left: -220px; }
242
  .sidebar.active { left: 0; }
243
  .menu-btn { display: block; }
244
+ .post-grid { grid-template-columns: 1fr; }
245
+ .post-item { padding: 10px; }
246
+ .post-preview { height: 150px; }
247
  }
248
  </style>
249
  </head>
 
261
  {% endif %}
262
  </div>
263
  <div class="container">
264
+ <h1>Лента публикаций</h1>
265
+ <div class="post-grid">
266
+ {% for post in posts %}
267
+ <a href="{{ url_for('post_page', post_id=post['id']) }}" class="post-item">
268
+ {% if post['type'] == 'video' %}
269
+ <video class="post-preview" preload="metadata" muted>
270
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
271
+ </video>
272
+ {% else %}
273
+ <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
274
+ {% endif %}
275
+ <h2>{{ post['title'] }}</h2>
276
+ <p>{{ post['description'] }}</p>
277
+ <p>Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}</p>
278
+ <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
279
  </a>
280
  {% endfor %}
281
  </div>
 
285
  document.getElementById('sidebar').classList.toggle('active');
286
  }
287
  document.addEventListener('DOMContentLoaded', function() {
288
+ const videos = document.querySelectorAll('.post-preview');
289
  videos.forEach(video => {
290
+ if (video.tagName === 'VIDEO') {
291
+ video.addEventListener('loadedmetadata', function() {
292
+ const duration = video.duration;
293
+ const randomTime = Math.random() * duration;
294
+ video.currentTime = randomTime;
295
+ });
296
+ }
297
  });
298
  });
299
  </script>
300
  </body>
301
  </html>
302
  '''
303
+ return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID)
304
 
305
+ # Страница отдельной публикации
306
+ @app.route('/post/<post_id>', methods=['GET', 'POST'])
307
+ def post_page(post_id):
308
  data = load_data()
309
+ post = next((p for p in data['posts'] if p['id'] == post_id), None)
310
+ if not post:
311
+ return "Публикация не найдена", 404
312
 
313
  is_authenticated = 'username' in session
314
  username = session.get('username', None)
315
+
316
+ # Увеличение количества просмотров
317
+ post['views'] = post.get('views', 0) + 1
318
+ save_data(data)
319
+
320
+ if request.method == 'POST' and is_authenticated:
321
+ if 'like' in request.form:
322
+ if username not in post.get('likes', []):
323
+ post['likes'] = post.get('likes', []) + [username]
324
+ save_data(data)
325
+ elif 'comment' in request.form:
326
+ comment_text = request.form.get('comment')
327
+ if comment_text:
328
+ post['comments'] = post.get('comments', []) + [{'user': username, 'text': comment_text, 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]
329
+ save_data(data)
330
+
331
  html = '''
332
  <!DOCTYPE html>
333
  <html lang="ru">
334
  <head>
335
  <meta charset="UTF-8">
336
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
337
+ <title>{{ post['title'] }}</title>
338
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
339
  <style>
340
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
341
  .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; }
342
  .sidebar.active { left: 0; }
343
  .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; }
344
+ video, img { width: 100%; border-radius: 8px; }
345
+ .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; }
346
  .profile-link { background: #10b981; }
347
  .logout-btn { background: #ef4444; }
348
+ .like-btn.liked { background: #ef4444; }
349
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
350
+ textarea { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #e2e8f0; border-radius: 8px; }
351
+ .comment { border-top: 1px solid #e2e8f0; padding: 10px 0; }
352
  @media (max-width: 768px) {
353
  .container { padding-left: 20px; padding: 15px; }
354
  .sidebar { left: -220px; }
355
  .sidebar.active { left: 0; }
356
  .menu-btn { display: block; }
357
+ video, img { height: auto; }
358
+ .back-btn, .like-btn { width: 100%; text-align: center; }
359
  }
360
  </style>
361
  </head>
 
373
  {% endif %}
374
  </div>
375
  <div class="container">
376
+ <h1>{{ post['title'] }}</h1>
377
+ {% if post['type'] == 'video' %}
378
+ <video controls>
379
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
380
+ </video>
381
+ {% else %}
382
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
383
+ {% endif %}
384
+ <p>{{ post['description'] }}</p>
385
+ <p>Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}</p>
386
+ <p>Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
387
+ {% if is_authenticated %}
388
+ <form method="POST">
389
+ <button type="submit" name="like" class="like-btn {% if username in post['likes'] %}liked{% endif %}">
390
+ {% if username in post['likes'] %}Убрать лайк{% else %}Лайк{% endif %}
391
+ </button>
392
+ </form>
393
+ <form method="POST">
394
+ <textarea name="comment" placeholder="Оставьте комментарий" rows="3"></textarea>
395
+ <button type="submit">Отправить</button>
396
+ </form>
397
+ {% endif %}
398
+ <h3>Комментарии</h3>
399
+ {% for comment in post.get('comments', []) %}
400
+ <div class="comment">
401
+ <p><strong>{{ comment['user'] }}</strong> ({{ comment['date'] }}): {{ comment['text'] }}</p>
402
+ </div>
403
+ {% endfor %}
404
+ <a href="{{ url_for('feed') }}" class="back-btn">Назад к ленте</a>
405
  {% if not is_authenticated %}
406
+ <p><a href="{{ url_for('login') }}">Войдите</a>, чтобы ставить лайки и комментировать.</p>
407
  {% endif %}
408
  </div>
409
  <script>
 
414
  </body>
415
  </html>
416
  '''
417
+ return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username)
418
 
419
  # Страница профиля
420
  @app.route('/profile', methods=['GET', 'POST'])
 
425
 
426
  data = load_data()
427
  username = session['username']
428
+ user_posts = [p for p in data['posts'] if p['uploader'] == username]
429
 
430
  if request.method == 'POST':
431
+ post_id = request.form.get('post_id')
432
+ if post_id:
433
+ data['posts'] = [p for p in data['posts'] if p['id'] != post_id or p['uploader'] != username]
434
  save_data(data)
435
+ logging.info(f"Публикация {post_id} удалена пользователем {username}")
436
  return redirect(url_for('profile'))
437
 
438
  html = '''
 
448
  .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; }
449
  .sidebar.active { left: 0; }
450
  .container { max-width: 800px; margin: 0 auto; padding-left: 220px; transition: padding-left 0.3s; }
451
+ .post-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
452
+ .post-item { background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
453
+ .post-preview { width: 100%; border-radius: 8px; height: 200px; object-fit: cover; }
454
  .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; }
455
  .delete-btn { background: #ef4444; border: none; cursor: pointer; }
456
  .profile-link { background: #10b981; }
457
  .logout-btn { background: #ef4444; }
458
  .menu-btn { display: none; font-size: 24px; background: none; border: none; cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; }
459
+ .stats { font-size: 0.9em; color: #666; }
460
  @media (max-width: 768px) {
461
  .container { padding-left: 20px; }
462
  .sidebar { left: -220px; }
463
  .sidebar.active { left: 0; }
464
  .menu-btn { display: block; }
465
+ .post-grid { grid-template-columns: 1fr; }
466
+ .post-item { padding: 10px; }
467
+ .post-preview { height: 150px; }
468
  .upload-btn, .delete-btn { font-size: 14px; padding: 8px; }
469
  }
470
  </style>
 
479
  </div>
480
  <div class="container">
481
  <h1>Профиль: {{ username }}</h1>
482
+ <a href="{{ url_for('upload') }}" class="upload-btn">Добавить публикацию</a>
483
+ <h2>Ваши публикации</h2>
484
+ <div class="post-grid">
485
+ {% if user_posts %}
486
+ {% for post in user_posts %}
487
+ <div class="post-item">
488
+ <a href="{{ url_for('post_page', post_id=post['id']) }}">
489
+ {% if post['type'] == 'video' %}
490
+ <video class="post-preview" preload="metadata" muted>
491
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
492
+ </video>
493
+ {% else %}
494
+ <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
495
+ {% endif %}
496
+ <h3>{{ post['title'] }}</h3>
497
  </a>
498
+ <p>{{ post['description'] }}</p>
499
+ <p>{{ post['upload_date'] }}</p>
500
+ <p class="stats">Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}</p>
501
  <form method="POST">
502
+ <input type="hidden" name="post_id" value="{{ post['id'] }}">
503
  <button type="submit" class="delete-btn">Удалить</button>
504
  </form>
505
  </div>
506
  {% endfor %}
507
  {% else %}
508
+ <p>Вы пока не загрузили ни одной публикации.</p>
509
  {% endif %}
510
  </div>
511
  </div>
 
514
  document.getElementById('sidebar').classList.toggle('active');
515
  }
516
  document.addEventListener('DOMContentLoaded', function() {
517
+ const videos = document.querySelectorAll('.post-preview');
518
  videos.forEach(video => {
519
+ if (video.tagName === 'VIDEO') {
520
+ video.addEventListener('loadedmetadata', function() {
521
+ const duration = video.duration;
522
+ const randomTime = Math.random() * duration;
523
+ video.currentTime = randomTime;
524
+ });
525
+ }
526
  });
527
  });
528
  </script>
529
  </body>
530
  </html>
531
  '''
532
+ return render_template_string(html, username=username, user_posts=user_posts, repo_id=REPO_ID)
533
 
534
+ # Страница загрузки контента
535
  @app.route('/upload', methods=['GET', 'POST'])
536
+ def upload():
537
  if 'username' not in session:
538
+ flash('Войдите, чтобы загрузить контент!')
539
  return redirect(url_for('login'))
540
 
541
  if request.method == 'POST':
542
  title = request.form.get('title')
543
  description = request.form.get('description')
544
+ file = request.files.get('file')
545
  uploader = session['username']
546
 
547
+ if not title or not file:
548
+ return "Укажите название и выберите файл", 400
549
 
550
+ filename = secure_filename(file.filename)
551
  temp_path = os.path.join('uploads', filename)
552
  os.makedirs('uploads', exist_ok=True)
553
+ file.save(temp_path)
554
+
555
+ # Определение типа файла
556
+ file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
557
 
558
  # Загрузка на Hugging Face
559
  api = HfApi()
560
  api.upload_file(
561
  path_or_fileobj=temp_path,
562
+ path_in_repo=f"{file_type}s/{filename}",
563
  repo_id=REPO_ID,
564
  repo_type="dataset",
565
  token=HF_TOKEN_WRITE,
566
+ commit_message=f"Добавлена публикация: {title} пользователем {uploader}"
567
  )
568
 
569
+ # Обновление базы данных
570
  data = load_data()
571
+ post_id = f"post_{len(data['posts']) + 1}"
572
+ while any(p['id'] == post_id for p in data['posts']):
573
+ post_id = f"post_{len(data['posts']) + 1}_{int(time.time())}"
574
+ data['posts'].append({
575
+ 'id': post_id,
576
  'title': title,
577
  'description': description,
578
  'filename': filename,
579
+ 'type': file_type,
580
  'uploader': uploader,
581
+ 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
582
+ 'views': 0,
583
+ 'likes': [],
584
+ 'comments': []
585
  })
586
  save_data(data)
587
+ logging.info(f"Публикация {title} ({file_type}) загружена пользователем {uploader} с ID {post_id}")
588
 
589
  if os.path.exists(temp_path):
590
  os.remove(temp_path)
 
598
  <head>
599
  <meta charset="UTF-8">
600
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
601
+ <title>Загрузка контента</title>
602
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
603
  <style>
604
  body { font-family: 'Poppins', sans-serif; background: #f0f2f5; padding: 20px; margin: 0; }
 
631
  <a href="{{ url_for('logout') }}" class="logout-btn">Выйти</a>
632
  </div>
633
  <div class="container">
634
+ <h1>Загрузить контент</h1>
635
  <form id="upload-form" enctype="multipart/form-data">
636
+ <input type="text" name="title" placeholder="Название" required>
637
  <textarea name="description" placeholder="Описание" rows="4"></textarea>
638
+ <input type="file" name="file" accept="video/*,image/*" required>
639
  <button type="submit">Загрузить</button>
640
  </form>
641
  <div id="progress-container">