加入音频的人声分类和语音识别
parent
1589fccfd8
commit
06cffe7cfa
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,499 @@
|
|||
|
||||
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
||||
from app.models.models import Meeting, TranscriptSegment, CreateMeetingRequest, UpdateMeetingRequest
|
||||
from app.core.database import get_db_connection
|
||||
from app.core.config import UPLOAD_DIR, AUDIO_DIR, MARKDOWN_DIR, ALLOWED_EXTENSIONS, ALLOWED_IMAGE_EXTENSIONS, MAX_FILE_SIZE, MAX_IMAGE_SIZE
|
||||
from app.services.qiniu_service import qiniu_service
|
||||
from typing import Optional
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/meetings", response_model=list[Meeting])
|
||||
def get_meetings(user_id: Optional[int] = None):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
base_query = '''
|
||||
SELECT
|
||||
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at,
|
||||
m.user_id as creator_id, u.caption as creator_username
|
||||
FROM meetings m
|
||||
JOIN users u ON m.user_id = u.user_id
|
||||
'''
|
||||
|
||||
if user_id:
|
||||
query = f'''
|
||||
{base_query}
|
||||
LEFT JOIN attendees a ON m.meeting_id = a.meeting_id
|
||||
WHERE m.user_id = %s OR a.user_id = %s
|
||||
GROUP BY m.meeting_id
|
||||
ORDER BY m.meeting_time DESC, m.created_at DESC
|
||||
'''
|
||||
cursor.execute(query, (user_id, user_id))
|
||||
else:
|
||||
query = f" {base_query} ORDER BY m.meeting_time DESC, m.created_at DESC"
|
||||
cursor.execute(query)
|
||||
|
||||
meetings = cursor.fetchall()
|
||||
|
||||
meeting_list = []
|
||||
for meeting in meetings:
|
||||
attendees_query = '''
|
||||
SELECT u.user_id, u.caption
|
||||
FROM attendees a
|
||||
JOIN users u ON a.user_id = u.user_id
|
||||
WHERE a.meeting_id = %s
|
||||
'''
|
||||
cursor.execute(attendees_query, (meeting['meeting_id'],))
|
||||
attendees_data = cursor.fetchall()
|
||||
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
|
||||
|
||||
meeting_list.append(Meeting(
|
||||
meeting_id=meeting['meeting_id'],
|
||||
title=meeting['title'],
|
||||
meeting_time=meeting['meeting_time'],
|
||||
summary=meeting['summary'],
|
||||
created_at=meeting['created_at'],
|
||||
attendees=attendees,
|
||||
creator_id=meeting['creator_id'],
|
||||
creator_username=meeting['creator_username']
|
||||
))
|
||||
|
||||
return meeting_list
|
||||
|
||||
@router.get("/meetings/{meeting_id}", response_model=Meeting)
|
||||
def get_meeting_details(meeting_id: int):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = '''
|
||||
SELECT
|
||||
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at,
|
||||
m.user_id as creator_id, u.caption as creator_username,
|
||||
af.file_path as audio_file_path
|
||||
FROM meetings m
|
||||
JOIN users u ON m.user_id = u.user_id
|
||||
LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id
|
||||
WHERE m.meeting_id = %s
|
||||
'''
|
||||
cursor.execute(query, (meeting_id,))
|
||||
meeting = cursor.fetchone()
|
||||
|
||||
if not meeting:
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
attendees_query = '''
|
||||
SELECT u.user_id, u.caption
|
||||
FROM attendees a
|
||||
JOIN users u ON a.user_id = u.user_id
|
||||
WHERE a.meeting_id = %s
|
||||
'''
|
||||
cursor.execute(attendees_query, (meeting['meeting_id'],))
|
||||
attendees_data = cursor.fetchall()
|
||||
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
|
||||
|
||||
meeting_data = Meeting(
|
||||
meeting_id=meeting['meeting_id'],
|
||||
title=meeting['title'],
|
||||
meeting_time=meeting['meeting_time'],
|
||||
summary=meeting['summary'],
|
||||
created_at=meeting['created_at'],
|
||||
attendees=attendees,
|
||||
creator_id=meeting['creator_id'],
|
||||
creator_username=meeting['creator_username']
|
||||
)
|
||||
|
||||
# Add audio file path if exists
|
||||
if meeting['audio_file_path']:
|
||||
meeting_data.audio_file_path = meeting['audio_file_path']
|
||||
|
||||
return meeting_data
|
||||
|
||||
@router.get("/meetings/{meeting_id}/transcript", response_model=list[TranscriptSegment])
|
||||
def get_meeting_transcript(meeting_id: int):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# First check if meeting exists
|
||||
meeting_query = "SELECT meeting_id FROM meetings WHERE meeting_id = %s"
|
||||
cursor.execute(meeting_query, (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# Get transcript segments
|
||||
transcript_query = '''
|
||||
SELECT segment_id, meeting_id, speaker_tag, start_time_ms, end_time_ms, text_content
|
||||
FROM transcript_segments
|
||||
WHERE meeting_id = %s
|
||||
ORDER BY start_time_ms ASC
|
||||
'''
|
||||
cursor.execute(transcript_query, (meeting_id,))
|
||||
segments = cursor.fetchall()
|
||||
|
||||
return [TranscriptSegment(**segment) for segment in segments]
|
||||
|
||||
@router.post("/meetings")
|
||||
def create_meeting(meeting_request: CreateMeetingRequest):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Create meeting
|
||||
meeting_query = '''
|
||||
INSERT INTO meetings (user_id, title, meeting_time, summary)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
'''
|
||||
# Note: You'll need to pass user_id, for now using hardcoded value
|
||||
cursor.execute(meeting_query, (1, meeting_request.title, meeting_request.meeting_time, None))
|
||||
meeting_id = cursor.lastrowid
|
||||
|
||||
# Add attendees
|
||||
for attendee_id in meeting_request.attendee_ids:
|
||||
attendee_query = '''
|
||||
INSERT INTO attendees (meeting_id, user_id)
|
||||
VALUES (%s, %s)
|
||||
ON DUPLICATE KEY UPDATE meeting_id = meeting_id
|
||||
'''
|
||||
cursor.execute(attendee_query, (meeting_id, attendee_id))
|
||||
|
||||
connection.commit()
|
||||
return {"meeting_id": meeting_id, "message": "Meeting created successfully"}
|
||||
|
||||
@router.put("/meetings/{meeting_id}")
|
||||
def update_meeting(meeting_id: int, meeting_request: UpdateMeetingRequest):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Check if meeting exists
|
||||
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# Update meeting
|
||||
update_query = '''
|
||||
UPDATE meetings
|
||||
SET title = %s, meeting_time = %s, summary = %s
|
||||
WHERE meeting_id = %s
|
||||
'''
|
||||
cursor.execute(update_query, (
|
||||
meeting_request.title,
|
||||
meeting_request.meeting_time,
|
||||
meeting_request.summary,
|
||||
meeting_id
|
||||
))
|
||||
|
||||
# Update attendees - remove existing ones and add new ones
|
||||
cursor.execute("DELETE FROM attendees WHERE meeting_id = %s", (meeting_id,))
|
||||
|
||||
for attendee_id in meeting_request.attendee_ids:
|
||||
attendee_query = '''
|
||||
INSERT INTO attendees (meeting_id, user_id)
|
||||
VALUES (%s, %s)
|
||||
'''
|
||||
cursor.execute(attendee_query, (meeting_id, attendee_id))
|
||||
|
||||
connection.commit()
|
||||
return {"message": "Meeting updated successfully"}
|
||||
|
||||
@router.delete("/meetings/{meeting_id}")
|
||||
def delete_meeting(meeting_id: int):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Check if meeting exists
|
||||
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# Delete related records first (foreign key constraints)
|
||||
cursor.execute("DELETE FROM transcript_segments WHERE meeting_id = %s", (meeting_id,))
|
||||
cursor.execute("DELETE FROM audio_files WHERE meeting_id = %s", (meeting_id,))
|
||||
cursor.execute("DELETE FROM attachments WHERE meeting_id = %s", (meeting_id,))
|
||||
cursor.execute("DELETE FROM attendees WHERE meeting_id = %s", (meeting_id,))
|
||||
|
||||
# Delete meeting
|
||||
cursor.execute("DELETE FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
|
||||
connection.commit()
|
||||
return {"message": "Meeting deleted successfully"}
|
||||
|
||||
@router.post("/meetings/{meeting_id}/regenerate-summary")
|
||||
def regenerate_summary(meeting_id: int):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Check if meeting exists
|
||||
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# For now, return a mock summary
|
||||
# In a real implementation, this would call an AI service
|
||||
mock_summary = """# AI 生成摘要
|
||||
|
||||
## 主要议题
|
||||
- 项目进度回顾
|
||||
- 技术方案讨论
|
||||
- 下阶段规划
|
||||
|
||||
## 关键决策
|
||||
- 采用新的技术架构
|
||||
- 调整项目时间节点
|
||||
- 分配任务责任
|
||||
|
||||
## 后续行动
|
||||
- [ ] 完成技术方案文档
|
||||
- [ ] 安排下次会议时间
|
||||
- [ ] 跟进项目进度"""
|
||||
|
||||
# Update meeting summary
|
||||
cursor.execute(
|
||||
"UPDATE meetings SET summary = %s WHERE meeting_id = %s",
|
||||
(mock_summary, meeting_id)
|
||||
)
|
||||
connection.commit()
|
||||
|
||||
return {"summary": mock_summary}
|
||||
|
||||
@router.get("/meetings/{meeting_id}/edit", response_model=Meeting)
|
||||
def get_meeting_for_edit(meeting_id: int):
|
||||
"""Get meeting details with full attendee information for editing"""
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = '''
|
||||
SELECT
|
||||
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at,
|
||||
m.user_id as creator_id, u.caption as creator_username,
|
||||
af.file_path as audio_file_path
|
||||
FROM meetings m
|
||||
JOIN users u ON m.user_id = u.user_id
|
||||
LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id
|
||||
WHERE m.meeting_id = %s
|
||||
'''
|
||||
cursor.execute(query, (meeting_id,))
|
||||
meeting = cursor.fetchone()
|
||||
|
||||
if not meeting:
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# Get attendees with full info for editing
|
||||
attendees_query = '''
|
||||
SELECT u.user_id, u.caption
|
||||
FROM attendees a
|
||||
JOIN users u ON a.user_id = u.user_id
|
||||
WHERE a.meeting_id = %s
|
||||
'''
|
||||
cursor.execute(attendees_query, (meeting['meeting_id'],))
|
||||
attendees_data = cursor.fetchall()
|
||||
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
|
||||
|
||||
meeting_data = Meeting(
|
||||
meeting_id=meeting['meeting_id'],
|
||||
title=meeting['title'],
|
||||
meeting_time=meeting['meeting_time'],
|
||||
summary=meeting['summary'],
|
||||
created_at=meeting['created_at'],
|
||||
attendees=attendees,
|
||||
creator_id=meeting['creator_id'],
|
||||
creator_username=meeting['creator_username']
|
||||
)
|
||||
|
||||
# Add audio file path if exists
|
||||
if meeting['audio_file_path']:
|
||||
meeting_data.audio_file_path = meeting['audio_file_path']
|
||||
|
||||
return meeting_data
|
||||
|
||||
@router.post("/meetings/upload-audio")
|
||||
async def upload_audio(
|
||||
audio_file: UploadFile = File(...),
|
||||
meeting_id: int = Form(...)
|
||||
):
|
||||
# Validate file extension
|
||||
file_extension = os.path.splitext(audio_file.filename)[1].lower()
|
||||
if file_extension not in ALLOWED_EXTENSIONS:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Unsupported file type. Allowed types: {', '.join(ALLOWED_EXTENSIONS)}"
|
||||
)
|
||||
|
||||
# Check file size
|
||||
if audio_file.size > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="File size exceeds 100MB limit"
|
||||
)
|
||||
|
||||
# Check if meeting exists
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# TEMP: Use existing file to test Qiniu upload instead of client file
|
||||
# This bypasses potential client file processing issues
|
||||
existing_file = AUDIO_DIR / "31ce039a-f619-4869-91c8-eab934bbd1d4.m4a"
|
||||
if not existing_file.exists():
|
||||
raise HTTPException(status_code=500, detail="Test file not found")
|
||||
|
||||
temp_path = existing_file
|
||||
print(f"DEBUG: Using existing test file: {temp_path}")
|
||||
print(f"DEBUG: Test file exists: {temp_path.exists()}")
|
||||
print(f"DEBUG: Test file size: {temp_path.stat().st_size}")
|
||||
|
||||
# Upload to Qiniu
|
||||
try:
|
||||
print(f"DEBUG: Attempting to upload audio to Qiniu - meeting_id: {meeting_id}, filename: {audio_file.filename}")
|
||||
print(f"DEBUG: Temp file path: {temp_path}")
|
||||
print(f"DEBUG: Temp file exists: {temp_path.exists()}")
|
||||
|
||||
success, qiniu_url, error_msg = qiniu_service.upload_audio_file(
|
||||
str(temp_path), meeting_id, audio_file.filename
|
||||
)
|
||||
|
||||
print(f"DEBUG: Qiniu upload result - success: {success}, url: {qiniu_url}, error: {error_msg}")
|
||||
|
||||
# TEMP: Don't delete existing test file
|
||||
# if temp_path.exists():
|
||||
# temp_path.unlink()
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to upload to Qiniu: {error_msg}")
|
||||
|
||||
# Save file info to database with Qiniu URL
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# Insert audio file record with Qiniu URL
|
||||
insert_query = '''
|
||||
INSERT INTO audio_files (meeting_id, file_name, file_path, file_size, upload_time)
|
||||
VALUES (%s, %s, %s, %s, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
file_name = VALUES(file_name),
|
||||
file_path = VALUES(file_path),
|
||||
file_size = VALUES(file_size),
|
||||
upload_time = VALUES(upload_time)
|
||||
'''
|
||||
cursor.execute(insert_query, (meeting_id, audio_file.filename, qiniu_url, audio_file.size))
|
||||
connection.commit()
|
||||
|
||||
return {
|
||||
"message": "Audio file uploaded successfully to Qiniu",
|
||||
"file_name": audio_file.filename,
|
||||
"file_path": qiniu_url,
|
||||
"qiniu_url": qiniu_url
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Exception in audio upload: {str(e)}")
|
||||
print(f"DEBUG: Exception type: {type(e)}")
|
||||
import traceback
|
||||
print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||
# TEMP: Don't delete existing test file in case of error
|
||||
# if temp_path.exists():
|
||||
# temp_path.unlink()
|
||||
raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
|
||||
|
||||
@router.get("/meetings/{meeting_id}/audio")
|
||||
def get_audio_file(meeting_id: int):
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
query = '''
|
||||
SELECT file_name, file_path, file_size, upload_time
|
||||
FROM audio_files
|
||||
WHERE meeting_id = %s
|
||||
'''
|
||||
cursor.execute(query, (meeting_id,))
|
||||
audio_file = cursor.fetchone()
|
||||
|
||||
if not audio_file:
|
||||
raise HTTPException(status_code=404, detail="Audio file not found for this meeting")
|
||||
|
||||
return {
|
||||
"file_name": audio_file['file_name'],
|
||||
"file_path": audio_file['file_path'],
|
||||
"file_size": audio_file['file_size'],
|
||||
"upload_time": audio_file['upload_time']
|
||||
}
|
||||
|
||||
@router.post("/meetings/{meeting_id}/upload-image")
|
||||
async def upload_image(
|
||||
meeting_id: int,
|
||||
image_file: UploadFile = File(...)
|
||||
):
|
||||
# Validate file extension
|
||||
file_extension = os.path.splitext(image_file.filename)[1].lower()
|
||||
if file_extension not in ALLOWED_IMAGE_EXTENSIONS:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Unsupported image type. Allowed types: {', '.join(ALLOWED_IMAGE_EXTENSIONS)}"
|
||||
)
|
||||
|
||||
# Check file size
|
||||
if image_file.size > MAX_IMAGE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Image size exceeds 10MB limit"
|
||||
)
|
||||
|
||||
# Check if meeting exists
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# Create temporary file for upload
|
||||
temp_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
temp_path = MARKDOWN_DIR / temp_filename
|
||||
|
||||
# Save file temporarily
|
||||
# Save file temporarily
|
||||
try:
|
||||
contents = await image_file.read()
|
||||
with open(temp_path, "wb") as buffer:
|
||||
buffer.write(contents)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to save temporary image: {str(e)}")
|
||||
|
||||
# Upload to Qiniu
|
||||
try:
|
||||
print(f"DEBUG: Attempting to upload image to Qiniu - meeting_id: {meeting_id}, filename: {image_file.filename}")
|
||||
print(f"DEBUG: Temp file path: {temp_path}")
|
||||
print(f"DEBUG: Temp file exists: {temp_path.exists()}")
|
||||
|
||||
success, qiniu_url, error_msg = qiniu_service.upload_markdown_image(
|
||||
str(temp_path), meeting_id, image_file.filename
|
||||
)
|
||||
|
||||
print(f"DEBUG: Qiniu upload result - success: {success}, url: {qiniu_url}, error: {error_msg}")
|
||||
|
||||
# Clean up temporary file
|
||||
if temp_path.exists():
|
||||
temp_path.unlink()
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to upload image to Qiniu: {error_msg}")
|
||||
|
||||
return {
|
||||
"message": "Image uploaded successfully to Qiniu",
|
||||
"file_name": image_file.filename,
|
||||
"file_path": qiniu_url,
|
||||
"url": qiniu_url,
|
||||
"qiniu_url": qiniu_url
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Exception in image upload: {str(e)}")
|
||||
print(f"DEBUG: Exception type: {type(e)}")
|
||||
import traceback
|
||||
print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||
# Clean up temporary file in case of error
|
||||
if temp_path.exists():
|
||||
temp_path.unlink()
|
||||
raise HTTPException(status_code=500, detail=f"Image upload failed: {str(e)}")
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
||||
from app.models.models import Meeting, TranscriptSegment, CreateMeetingRequest, UpdateMeetingRequest
|
||||
from app.core.database import get_db_connection
|
||||
from app.core.config import UPLOAD_DIR, AUDIO_DIR, MARKDOWN_DIR, ALLOWED_EXTENSIONS, ALLOWED_IMAGE_EXTENSIONS, MAX_FILE_SIZE, MAX_IMAGE_SIZE
|
||||
from app.core.config import BASE_DIR, UPLOAD_DIR, AUDIO_DIR, MARKDOWN_DIR, ALLOWED_EXTENSIONS, ALLOWED_IMAGE_EXTENSIONS, MAX_FILE_SIZE, MAX_IMAGE_SIZE
|
||||
from app.services.qiniu_service import qiniu_service
|
||||
from typing import Optional
|
||||
import os
|
||||
import uuid
|
||||
|
|
@ -144,8 +145,7 @@ def create_meeting(meeting_request: CreateMeetingRequest):
|
|||
INSERT INTO meetings (user_id, title, meeting_time, summary)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
'''
|
||||
# Note: You'll need to pass user_id, for now using hardcoded value
|
||||
cursor.execute(meeting_query, (1, meeting_request.title, meeting_request.meeting_time, None))
|
||||
cursor.execute(meeting_query, (meeting_request.user_id, meeting_request.title, meeting_request.meeting_time, None))
|
||||
meeting_id = cursor.lastrowid
|
||||
|
||||
# Add attendees
|
||||
|
|
@ -326,16 +326,18 @@ async def upload_audio(
|
|||
detail="File size exceeds 100MB limit"
|
||||
)
|
||||
|
||||
# Create meeting-specific directory
|
||||
meeting_dir = AUDIO_DIR / str(meeting_id)
|
||||
meeting_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Generate unique filename
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
file_path = AUDIO_DIR / unique_filename
|
||||
|
||||
# Store only relative path for database (audio/filename)
|
||||
relative_path = f"audio/{unique_filename}"
|
||||
absolute_path = meeting_dir / unique_filename
|
||||
relative_path = absolute_path.relative_to(BASE_DIR)
|
||||
|
||||
# Save file
|
||||
try:
|
||||
with open(file_path, "wb") as buffer:
|
||||
with open(absolute_path, "wb") as buffer:
|
||||
shutil.copyfileobj(audio_file.file, buffer)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to save file: {str(e)}")
|
||||
|
|
@ -348,7 +350,7 @@ async def upload_audio(
|
|||
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
|
||||
if not cursor.fetchone():
|
||||
# Clean up uploaded file if meeting doesn't exist
|
||||
os.remove(file_path)
|
||||
os.remove(absolute_path)
|
||||
raise HTTPException(status_code=404, detail="Meeting not found")
|
||||
|
||||
# Insert audio file record
|
||||
|
|
@ -361,13 +363,13 @@ async def upload_audio(
|
|||
file_size = VALUES(file_size),
|
||||
upload_time = VALUES(upload_time)
|
||||
'''
|
||||
cursor.execute(insert_query, (meeting_id, audio_file.filename, relative_path, audio_file.size))
|
||||
cursor.execute(insert_query, (meeting_id, audio_file.filename, '/'+str(relative_path), audio_file.size))
|
||||
connection.commit()
|
||||
|
||||
return {
|
||||
"message": "Audio file uploaded successfully",
|
||||
"file_name": audio_file.filename,
|
||||
"file_path": relative_path
|
||||
"file_path": '/'+str(relative_path)
|
||||
}
|
||||
|
||||
@router.get("/meetings/{meeting_id}/audio")
|
||||
|
|
@ -426,14 +428,12 @@ async def upload_image(
|
|||
|
||||
# Generate unique filename
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
file_path = meeting_dir / unique_filename
|
||||
|
||||
# Store relative path for URL access
|
||||
relative_path = f"markdown/{meeting_id}/{unique_filename}"
|
||||
absolute_path = meeting_dir / unique_filename
|
||||
relative_path = absolute_path.relative_to(BASE_DIR)
|
||||
|
||||
# Save file
|
||||
try:
|
||||
with open(file_path, "wb") as buffer:
|
||||
with open(absolute_path, "wb") as buffer:
|
||||
shutil.copyfileobj(image_file.file, buffer)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to save image: {str(e)}")
|
||||
|
|
@ -441,6 +441,5 @@ async def upload_image(
|
|||
return {
|
||||
"message": "Image uploaded successfully",
|
||||
"file_name": image_file.filename,
|
||||
"file_path": relative_path,
|
||||
"url": f"/uploads/{relative_path}"
|
||||
"file_path": '/'+ str(relative_path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,13 @@ API_CONFIG = {
|
|||
'host': os.getenv('API_HOST', '0.0.0.0'),
|
||||
'port': int(os.getenv('API_PORT', '8000')),
|
||||
'cors_origins': os.getenv('CORS_ORIGINS', 'http://localhost:5173').split(',')
|
||||
}
|
||||
}
|
||||
|
||||
# 七牛云配置
|
||||
QINIU_ACCESS_KEY = os.getenv('QINIU_ACCESS_KEY', 'A0tp96HCtg-wZCughTgi5vc2pJnw3btClwxRE_e8')
|
||||
QINIU_SECRET_KEY = os.getenv('QINIU_SECRET_KEY', 'Lj-MSHpaVbmzpS86kMIjmwikvYOT9iPBjCk9hm6k')
|
||||
QINIU_BUCKET = os.getenv('QINIU_BUCKET', 'imeeting')
|
||||
QINIU_DOMAIN = os.getenv('QINIU_DOMAIN', 't0vogyxkz.hn-bkt.clouddn.com')
|
||||
|
||||
# Dashscope (Tongyi Qwen) API Key
|
||||
DASHSCOPE_API_KEY = os.getenv('DASHSCOPE_API_KEY', 'sk-c2bf06ea56b4491ea3d1e37fdb472b8f')
|
||||
|
|
@ -47,6 +47,7 @@ class TranscriptSegment(BaseModel):
|
|||
text_content: str
|
||||
|
||||
class CreateMeetingRequest(BaseModel):
|
||||
user_id: int
|
||||
title: str
|
||||
meeting_time: Optional[datetime.datetime]
|
||||
attendee_ids: list[int]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
from http import HTTPStatus
|
||||
import requests
|
||||
import json
|
||||
import dashscope
|
||||
from dashscope.audio.asr import Transcription
|
||||
from app.core.config import DASHSCOPE_API_KEY
|
||||
from app.core.database import get_db_connection
|
||||
|
||||
class AIService:
|
||||
def __init__(self):
|
||||
dashscope.api_key = DASHSCOPE_API_KEY
|
||||
|
||||
def transcribe(self, file_urls: list[str], meeting_id: int):
|
||||
print(f"Starting transcription for meeting_id: {meeting_id}, files: {file_urls}")
|
||||
|
||||
try:
|
||||
task_response = Transcription.async_call(
|
||||
model='paraformer-v2',
|
||||
file_urls=file_urls,
|
||||
language_hints=['zh', 'en'],
|
||||
disfluency_removal_enabled=True,
|
||||
diarization_enabled=True,
|
||||
speaker_count=10
|
||||
)
|
||||
|
||||
transcribe_response = Transcription.wait(task=task_response.output.task_id)
|
||||
|
||||
if transcribe_response.status_code != HTTPStatus.OK:
|
||||
print(f"Transcription failed: {transcribe_response.status_code}, {transcribe_response.message}")
|
||||
return
|
||||
|
||||
print("Transcription task submitted successfully!")
|
||||
if not (transcribe_response.output and transcribe_response.output.get('results')):
|
||||
print("No transcription results found in the response.")
|
||||
return
|
||||
|
||||
transcription_url = transcribe_response.output['results'][0]['transcription_url']
|
||||
print(f"Fetching transcription from URL: {transcription_url}")
|
||||
|
||||
response = requests.get(transcription_url)
|
||||
response.raise_for_status()
|
||||
transcription_data = response.json()
|
||||
|
||||
self._save_segments_to_db(transcription_data, meeting_id)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error fetching transcription from URL: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error decoding JSON from transcription URL: {e}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
|
||||
def _save_segments_to_db(self, data: dict, meeting_id: int):
|
||||
segments_to_insert = []
|
||||
for transcript in data.get('transcripts', []):
|
||||
for sentence in transcript.get('sentences', []):
|
||||
segments_to_insert.append((
|
||||
meeting_id,
|
||||
sentence.get('speaker_id', 'Unknown'),
|
||||
sentence.get('begin_time'),
|
||||
sentence.get('end_time'),
|
||||
sentence.get('text')
|
||||
))
|
||||
|
||||
if not segments_to_insert:
|
||||
print("No segments to save.")
|
||||
return
|
||||
|
||||
try:
|
||||
with get_db_connection() as connection:
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Clear existing segments for this meeting to avoid duplicates
|
||||
delete_query = "DELETE FROM transcript_segments WHERE meeting_id = %s"
|
||||
cursor.execute(delete_query, (meeting_id,))
|
||||
print(f"Deleted existing segments for meeting_id: {meeting_id}")
|
||||
|
||||
insert_query = '''
|
||||
INSERT INTO transcript_segments (meeting_id, speaker_tag, start_time_ms, end_time_ms, text_content)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
'''
|
||||
cursor.executemany(insert_query, segments_to_insert)
|
||||
connection.commit()
|
||||
print(f"Successfully saved {len(segments_to_insert)} segments to the database for meeting_id: {meeting_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Database error: {e}")
|
||||
|
||||
# Main method for testing
|
||||
if __name__ == '__main__':
|
||||
# This is an example of how to use the service.
|
||||
# You need to provide a valid meeting_id that exists in your database
|
||||
# and a publicly accessible URL for the audio file.
|
||||
|
||||
# Example usage:
|
||||
# 1. Make sure you have a meeting with meeting_id = 1 in your database.
|
||||
# 2. Make sure the audio file URL is correct and accessible.
|
||||
|
||||
test_meeting_id = 37
|
||||
# Please replace with your own publicly accessible audio file URL
|
||||
test_file_urls = ['http://t0vogyxkz.hn-bkt.clouddn.com/record/meeting_records_2.mp3']
|
||||
|
||||
print("--- Running AI Service Test ---")
|
||||
ai_service = AIService()
|
||||
ai_service.transcribe(file_urls=test_file_urls, meeting_id=test_meeting_id)
|
||||
print("--- AI Service Test Finished ---")
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
from qiniu import Auth, put_file_v2, etag, BucketManager
|
||||
import qiniu.config
|
||||
import os
|
||||
import uuid
|
||||
from typing import Optional, Tuple
|
||||
from app.core.config import QINIU_ACCESS_KEY, QINIU_SECRET_KEY, QINIU_BUCKET, QINIU_DOMAIN
|
||||
|
||||
|
||||
class QiniuService:
|
||||
def __init__(self):
|
||||
self.access_key = QINIU_ACCESS_KEY
|
||||
self.secret_key = QINIU_SECRET_KEY
|
||||
self.bucket_name = QINIU_BUCKET
|
||||
self.domain = QINIU_DOMAIN
|
||||
|
||||
if not self.access_key or not self.secret_key:
|
||||
print("ERROR: 七牛云Access Key或Secret Key为空!")
|
||||
if not self.bucket_name:
|
||||
print("ERROR: 七牛云Bucket名称为空!")
|
||||
if not self.domain:
|
||||
print("ERROR: 七牛云Domain为空!")
|
||||
|
||||
self.q = Auth(self.access_key, self.secret_key)
|
||||
|
||||
|
||||
def upload_audio_file(self, file_path: str, meeting_id: int, original_filename: str) -> Tuple[bool, str, Optional[str]]:
|
||||
"""
|
||||
Upload audio file to Qiniu cloud storage
|
||||
|
||||
Args:
|
||||
file_path: Local file path
|
||||
meeting_id: Meeting ID for directory structure
|
||||
original_filename: Original filename
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, url: str, error_message: Optional[str])
|
||||
"""
|
||||
try:
|
||||
# Validate file exists
|
||||
if not os.path.exists(file_path):
|
||||
return False, "", f"File not found: {file_path}"
|
||||
|
||||
file_extension = os.path.splitext(original_filename)[1]
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
key = f"record/{meeting_id}/{unique_filename}"
|
||||
|
||||
# Generate upload token
|
||||
token = self.q.upload_token(self.bucket_name, key, 3600)
|
||||
print(f"DEBUG: 生成音频上传token成功, key: {key}")
|
||||
print(f"DEBUG: token前50位: {token[:50] if token else 'None'}")
|
||||
|
||||
# Upload file with retry mechanism
|
||||
ret, info = put_file_v2(token, key, file_path)
|
||||
|
||||
print(f"DEBUG: Qiniu upload response - ret: {ret}, info: {info}")
|
||||
print(f"DEBUG: Qiniu upload info details - status_code: {info.status_code}, text_body: {info.text_body}")
|
||||
|
||||
# Check if upload was successful
|
||||
# For put_file_v2, we need to check the info.status_code
|
||||
if info.status_code == 200 and ret and 'key' in ret and ret['key'] == key:
|
||||
url = f"http://{self.domain}/{key}"
|
||||
return True, url, None
|
||||
else:
|
||||
# Extract error information properly
|
||||
error_msg = f"Upload failed: status_code={info.status_code}"
|
||||
if hasattr(info, 'text_body') and info.text_body:
|
||||
error_msg += f", response={info.text_body}"
|
||||
return False, "", error_msg
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False, "", f"Upload error: {str(e)}"
|
||||
|
||||
def upload_markdown_image(self, file_path: str, meeting_id: int, original_filename: str) -> Tuple[bool, str, Optional[str]]:
|
||||
"""
|
||||
Upload markdown image to Qiniu cloud storage
|
||||
|
||||
Args:
|
||||
file_path: Local file path
|
||||
meeting_id: Meeting ID for directory structure
|
||||
original_filename: Original filename
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, url: str, error_message: Optional[str])
|
||||
"""
|
||||
try:
|
||||
# Validate file exists
|
||||
if not os.path.exists(file_path):
|
||||
return False, "", f"File not found: {file_path}"
|
||||
|
||||
file_extension = os.path.splitext(original_filename)[1]
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
key = f"markdown/{meeting_id}/{unique_filename}"
|
||||
|
||||
# Generate upload token
|
||||
token = self.q.upload_token(self.bucket_name, key, 3600)
|
||||
|
||||
# Upload file with retry mechanism
|
||||
ret, info = put_file_v2(token, key, file_path)
|
||||
|
||||
print(f"DEBUG: Qiniu image upload response - ret: {ret}, info: {info}")
|
||||
print(f"DEBUG: Qiniu image upload info details - status_code: {info.status_code}, text_body: {info.text_body}")
|
||||
|
||||
# Check if upload was successful
|
||||
# For put_file_v2, we need to check the info.status_code
|
||||
if info.status_code == 200 and ret and 'key' in ret and ret['key'] == key:
|
||||
url = f"http://{self.domain}/{key}"
|
||||
return True, url, None
|
||||
else:
|
||||
# Extract error information properly
|
||||
error_msg = f"Upload failed: status_code={info.status_code}"
|
||||
if hasattr(info, 'text_body') and info.text_body:
|
||||
error_msg += f", response={info.text_body}"
|
||||
return False, "", error_msg
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False, "", f"Upload error: {str(e)}"
|
||||
|
||||
def delete_file(self, key: str) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Delete file from Qiniu cloud storage
|
||||
|
||||
Args:
|
||||
key: File key in Qiniu storage
|
||||
|
||||
Returns:
|
||||
Tuple of (success: bool, error_message: Optional[str])
|
||||
"""
|
||||
try:
|
||||
from qiniu import BucketManager
|
||||
bucket = BucketManager(self.q)
|
||||
ret, info = bucket.delete(self.bucket_name, key)
|
||||
|
||||
if ret is None:
|
||||
return True, None
|
||||
else:
|
||||
return False, f"Delete failed: {info}"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Delete error: {str(e)}"
|
||||
|
||||
|
||||
# Global instance
|
||||
qiniu_service = QiniuService()
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,4 +3,5 @@ mysql-connector-python
|
|||
uvicorn[standard]
|
||||
python-multipart
|
||||
pydantic[email]
|
||||
passlib[bcrypt]
|
||||
passlib[bcrypt]
|
||||
qiniu
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
# Add the app directory to the path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'app'))
|
||||
|
||||
# Mock the FastAPI UploadFile
|
||||
class MockUploadFile:
|
||||
def __init__(self, filename, content):
|
||||
self.filename = filename
|
||||
self.content = content
|
||||
self.size = len(content)
|
||||
self._file_pos = 0
|
||||
|
||||
async def read(self, size=-1):
|
||||
if size == -1:
|
||||
result = self.content[self._file_pos:]
|
||||
self._file_pos = len(self.content)
|
||||
else:
|
||||
result = self.content[self._file_pos:self._file_pos + size]
|
||||
self._file_pos += len(result)
|
||||
return result
|
||||
|
||||
def file(self):
|
||||
from io import BytesIO
|
||||
return BytesIO(self.content.encode() if isinstance(self.content, str) else self.content)
|
||||
|
||||
async def test_audio_upload():
|
||||
from app.api.endpoints.meetings import AUDIO_DIR
|
||||
from app.services.qiniu_service import qiniu_service
|
||||
|
||||
# Path to the problematic audio file
|
||||
audio_file_path = "/Users/jiliu/工作/projects/imeeting/backend/uploads/audio/31ce039a-f619-4869-91c8-eab934bbd1d4.m4a"
|
||||
|
||||
# Read the content of the audio file
|
||||
try:
|
||||
with open(audio_file_path, "rb") as f:
|
||||
test_content = f.read()
|
||||
print(f"Successfully read content from {audio_file_path}")
|
||||
except FileNotFoundError:
|
||||
print(f"Error: The file was not found at {audio_file_path}")
|
||||
return
|
||||
|
||||
# Create mock UploadFile with the real audio content
|
||||
mock_file = MockUploadFile("31ce039a-f619-4869-91c8-eab934bbd1d4.m4a", test_content)
|
||||
|
||||
# Create temporary file for upload (simulating the API endpoint)
|
||||
file_extension = ".m4a"
|
||||
from uuid import uuid4
|
||||
temp_filename = f"{uuid4()}{file_extension}"
|
||||
temp_path = AUDIO_DIR / temp_filename
|
||||
|
||||
print(f"Creating temporary file at: {temp_path}")
|
||||
|
||||
# Save file temporarily (simulating the API endpoint)
|
||||
try:
|
||||
# Simulate shutil.copyfileobj(mock_file.file(), open(temp_path, "wb"))
|
||||
with open(temp_path, "wb") as buffer:
|
||||
buffer.write(mock_file.content) # content is already bytes
|
||||
|
||||
print(f"Temporary file created successfully. Exists: {temp_path.exists()}")
|
||||
print(f"Temporary file size: {temp_path.stat().st_size}")
|
||||
except Exception as e:
|
||||
print(f"Failed to save temporary file: {str(e)}")
|
||||
return
|
||||
|
||||
# Test upload to Qiniu (simulating the API endpoint)
|
||||
try:
|
||||
print(f"Attempting to upload audio to Qiniu...")
|
||||
print(f"Temp file path: {temp_path}")
|
||||
print(f"Temp file exists: {temp_path.exists()}")
|
||||
|
||||
success, qiniu_url, error_msg = qiniu_service.upload_audio_file(
|
||||
str(temp_path), 11, "test-audio.mp3"
|
||||
)
|
||||
|
||||
print(f"Qiniu upload result - success: {success}")
|
||||
print(f"Qiniu upload result - url: {qiniu_url}")
|
||||
print(f"Qiniu upload result - error: {error_msg}")
|
||||
|
||||
# Clean up temporary file
|
||||
if temp_path.exists():
|
||||
temp_path.unlink()
|
||||
print("Temporary file cleaned up")
|
||||
|
||||
if success:
|
||||
print("Upload successful!")
|
||||
print(f"URL: {qiniu_url}")
|
||||
else:
|
||||
print(f"Upload failed: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Exception in audio upload: {str(e)}")
|
||||
import traceback
|
||||
print(f"Traceback: {traceback.format_exc()}")
|
||||
# Clean up temporary file in case of error
|
||||
if temp_path.exists():
|
||||
temp_path.unlink()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_audio_upload())
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
from qiniu import Auth, put_file_v2, BucketManager
|
||||
|
||||
# Add app path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'app'))
|
||||
|
||||
from app.core.config import QINIU_ACCESS_KEY, QINIU_SECRET_KEY, QINIU_BUCKET, QINIU_DOMAIN
|
||||
|
||||
def test_qiniu_connection():
|
||||
print("=== 七牛云连接测试 ===")
|
||||
print(f"Access Key: {QINIU_ACCESS_KEY[:10]}...")
|
||||
print(f"Secret Key: {QINIU_SECRET_KEY[:10]}...")
|
||||
print(f"Bucket: {QINIU_BUCKET}")
|
||||
print(f"Domain: {QINIU_DOMAIN}")
|
||||
|
||||
# 创建认证对象
|
||||
q = Auth(QINIU_ACCESS_KEY, QINIU_SECRET_KEY)
|
||||
|
||||
# 测试1: 生成上传token
|
||||
try:
|
||||
key = "test/connection-test.txt"
|
||||
token = q.upload_token(QINIU_BUCKET, key, 3600)
|
||||
print(f"✓ Token生成成功: {token[:50]}...")
|
||||
except Exception as e:
|
||||
print(f"✗ Token生成失败: {e}")
|
||||
return False
|
||||
|
||||
# 测试2: 列举存储空间 (测试认证是否正确)
|
||||
try:
|
||||
bucket_manager = BucketManager(q)
|
||||
ret, eof, info = bucket_manager.list(QINIU_BUCKET, limit=100)
|
||||
print(f"✓ Bucket访问成功, status_code: {info.status_code}")
|
||||
if ret:
|
||||
print(f" 存储空间中有文件: {len(ret.get('items', []))} 个")
|
||||
except Exception as e:
|
||||
print(f"✗ Bucket访问失败: {e}")
|
||||
return False
|
||||
|
||||
# 测试3: 上传一个小文件
|
||||
test_file = "/Users/jiliu/工作/projects/imeeting/backend/uploads/result.json"
|
||||
if os.path.exists(test_file):
|
||||
try:
|
||||
key = "test/result1.json"
|
||||
token = q.upload_token(QINIU_BUCKET, key, 3600)
|
||||
ret, info = put_file_v2(token, key, test_file, version='v2')
|
||||
|
||||
print(f"上传结果:")
|
||||
print(f" ret: {ret}")
|
||||
print(f" status_code: {info.status_code}")
|
||||
print(f" text_body: {info.text_body}")
|
||||
print(f" url: {info.url}")
|
||||
print(f" req_id: {info.req_id}")
|
||||
print(f" x_log: {info.x_log}")
|
||||
|
||||
if info.status_code == 200:
|
||||
print("✓ 文件上传成功")
|
||||
url = f"http://{QINIU_DOMAIN}/{key}"
|
||||
print(f" 访问URL: {url}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 文件上传失败: {info.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 文件上传异常: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
else:
|
||||
print(f"✗ 测试文件不存在: {test_file}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_qiniu_connection()
|
||||
if success:
|
||||
print("\n🎉 七牛云连接测试成功!")
|
||||
else:
|
||||
print("\n❌ 七牛云连接测试失败!")
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa
|
||||
from qiniu import Auth, put_data ,put_file_v2, etag
|
||||
import qiniu.config
|
||||
#需要填写你的 Access Key 和 Secret Key
|
||||
access_key = 'A0tp96HCtg-wZCughTgi5vc2pJnw3btClwxRE_e8'
|
||||
secret_key = 'Lj-MSHpaVbmzpS86kMIjmwikvYOT9iPBjCk9hm6k'
|
||||
#构建鉴权对象
|
||||
q = Auth(access_key, secret_key)
|
||||
#要上传的空间
|
||||
bucket_name = 'imeeting'
|
||||
#上传后保存的文件名
|
||||
key = 'test/result.json'
|
||||
#生成上传 Token,可以指定过期时间等
|
||||
token = q.upload_token(bucket_name, key, 3600)
|
||||
#要上传文件的本地路径
|
||||
localfile = './uploads/result.json'
|
||||
ret, info = put_file_v2(token, key, localfile, version='v2')
|
||||
print(info)
|
||||
assert ret['key'] == key
|
||||
assert ret['hash'] == etag(localfile)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
|||
fastapi
|
||||
mysql-connector-python
|
||||
uvicorn[standard]
|
||||
python-multipart
|
||||
pydantic[email]
|
||||
passlib[bcrypt]
|
||||
qiniu
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in New Issue