233 lines
8.1 KiB
Python
233 lines
8.1 KiB
Python
"""Archive API endpoints with SQLite backend."""
|
|
|
|
import os
|
|
from flask import Blueprint, request, Response, send_file
|
|
from utils import make_response, make_error_response
|
|
from models import ArchiveItem
|
|
|
|
archive_bp = Blueprint('archive', __name__, url_prefix='/api')
|
|
|
|
|
|
@archive_bp.route('/archive', methods=['GET'])
|
|
def get_archive():
|
|
"""Get archived videos with pagination and filtering."""
|
|
from app import archive_db
|
|
try:
|
|
page = int(request.args.get('page', 1))
|
|
limit = int(request.args.get('limit', 24))
|
|
search = request.args.get('search')
|
|
category = request.args.get('category')
|
|
start_date = request.args.get('startDate')
|
|
end_date = request.args.get('endDate')
|
|
|
|
videos, total = archive_db.get_videos(
|
|
page=page, limit=limit, search=search,
|
|
category=category, start_date=start_date, end_date=end_date
|
|
)
|
|
|
|
items = []
|
|
for v in videos:
|
|
items.append({
|
|
"videoId": v.video_id,
|
|
"title": v.title,
|
|
"url": v.url,
|
|
"description": v.description,
|
|
"thumbnail": v.thumbnail,
|
|
"channel": v.channel,
|
|
"views": v.views,
|
|
"duration": v.duration,
|
|
"category": v.category,
|
|
"downloadPath": v.download_path,
|
|
"networkSharePath": v.network_share_path,
|
|
"fileSize": v.file_size,
|
|
"downloadDate": v.download_date.isoformat() if v.download_date else "",
|
|
"type": v.item_type,
|
|
})
|
|
|
|
return make_response({
|
|
"archive": items,
|
|
"items": items,
|
|
"total": total,
|
|
"page": page,
|
|
"hasMore": page * limit < total,
|
|
})
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to get archive: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/<video_id>', methods=['GET'])
|
|
def get_archive_item(video_id):
|
|
"""Get a single archive item."""
|
|
from app import archive_db
|
|
try:
|
|
video = archive_db.get_video(video_id)
|
|
if not video:
|
|
return make_error_response(f"Video {video_id} not found in archive", 404)
|
|
|
|
return make_response({
|
|
"videoId": video.video_id,
|
|
"title": video.title,
|
|
"url": video.url,
|
|
"description": video.description,
|
|
"thumbnail": video.thumbnail,
|
|
"channel": video.channel,
|
|
"views": video.views,
|
|
"duration": video.duration,
|
|
"category": video.category,
|
|
"downloadPath": video.download_path,
|
|
"networkSharePath": video.network_share_path,
|
|
"fileSize": video.file_size,
|
|
"downloadDate": video.download_date.isoformat() if video.download_date else "",
|
|
"type": video.item_type,
|
|
})
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to get archive item: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/<video_id>/stream', methods=['GET'])
|
|
def stream_video(video_id):
|
|
"""Stream a downloaded video file."""
|
|
from app import archive_db
|
|
try:
|
|
video = archive_db.get_video(video_id)
|
|
if not video or not video.download_path:
|
|
return make_error_response(f"Video {video_id} not found or has no file", 404)
|
|
|
|
if not os.path.exists(video.download_path):
|
|
return make_error_response(f"Video file not found: {video.download_path}", 404)
|
|
|
|
return send_file(
|
|
video.download_path,
|
|
mimetype='video/mp4',
|
|
as_attachment=False,
|
|
conditional=True,
|
|
)
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to stream video: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/<video_id>', methods=['DELETE'])
|
|
def remove_from_archive(video_id):
|
|
"""Remove a video from the archive."""
|
|
from app import archive_db
|
|
try:
|
|
removed = archive_db.delete_video(video_id)
|
|
if not removed:
|
|
return make_error_response(f"Video {video_id} not found in archive", 404)
|
|
return make_response({"message": f"Video {video_id} removed from archive"})
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to remove from archive: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive', methods=['DELETE'])
|
|
def clear_archive():
|
|
"""Clear the entire archive."""
|
|
from app import archive_db
|
|
try:
|
|
count = archive_db.clear_archive()
|
|
return make_response({
|
|
"message": "Archive cleared",
|
|
"count": count
|
|
})
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to clear archive: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/stats', methods=['GET'])
|
|
def get_archive_stats():
|
|
"""Get archive statistics."""
|
|
from app import archive_db
|
|
try:
|
|
stats = archive_db.get_stats()
|
|
return make_response(stats)
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to get archive stats: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/categories', methods=['GET'])
|
|
def get_archive_categories():
|
|
"""Get unique categories from the archive."""
|
|
from app import archive_db
|
|
try:
|
|
categories = archive_db.get_categories()
|
|
# Flatten SQLAlchemy row tuples to plain strings
|
|
flat = [c[0] if isinstance(c, (tuple, list)) else c for c in categories]
|
|
return make_response(flat)
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to get categories: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/export', methods=['GET'])
|
|
def export_archive():
|
|
"""Export archive data as JSON or CSV."""
|
|
from app import archive_db
|
|
try:
|
|
fmt = request.args.get('format', 'json')
|
|
if fmt not in ('json', 'csv'):
|
|
return make_error_response("Format must be 'json' or 'csv'", 400)
|
|
|
|
data = archive_db.export_archive(fmt)
|
|
|
|
if fmt == 'json':
|
|
mimetype = 'application/json'
|
|
filename = 'archive.json'
|
|
else:
|
|
mimetype = 'text/csv'
|
|
filename = 'archive.csv'
|
|
|
|
return Response(
|
|
data,
|
|
mimetype=mimetype,
|
|
headers={"Content-Disposition": f"attachment; filename={filename}"}
|
|
)
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to export archive: {str(e)}", 500)
|
|
|
|
|
|
@archive_bp.route('/archive/import', methods=['POST'])
|
|
def import_archive():
|
|
"""Import archive data from a JSON file."""
|
|
from app import archive_db
|
|
try:
|
|
if 'file' not in request.files:
|
|
return make_error_response("No file provided", 400)
|
|
|
|
file = request.files['file']
|
|
if not file.filename.endswith('.json'):
|
|
return make_error_response("Only JSON files are supported", 400)
|
|
|
|
import json
|
|
data = json.loads(file.read())
|
|
items = data.get('items', data if isinstance(data, list) else [])
|
|
|
|
imported = 0
|
|
for item in items:
|
|
video_id = item.get('videoId', item.get('video_id', ''))
|
|
if not video_id:
|
|
continue
|
|
|
|
archive_item = ArchiveItem(
|
|
video_id=video_id,
|
|
title=item.get('title', 'Unknown'),
|
|
url=item.get('url', ''),
|
|
description=item.get('description', ''),
|
|
thumbnail=item.get('thumbnail', ''),
|
|
channel=item.get('channel', ''),
|
|
views=item.get('views', 0) or 0,
|
|
duration=item.get('duration', ''),
|
|
category=item.get('category', ''),
|
|
download_path=item.get('downloadPath', item.get('download_path', '')),
|
|
network_share_path=item.get('networkSharePath', item.get('network_share_path')),
|
|
file_size=item.get('fileSize', item.get('file_size', 0)) or 0,
|
|
download_date=item.get('downloadDate', item.get('download_date', '')),
|
|
item_type=item.get('type', 'video'),
|
|
)
|
|
archive_db.add_video(archive_item)
|
|
imported += 1
|
|
|
|
return make_response({
|
|
"success": True,
|
|
"imported": imported
|
|
})
|
|
except Exception as e:
|
|
return make_error_response(f"Failed to import archive: {str(e)}", 500) |