yolo数据集标记
This commit is contained in:
@@ -0,0 +1,541 @@
|
||||
import json
|
||||
import logging
|
||||
import sqlite3
|
||||
import time
|
||||
import uuid
|
||||
import config
|
||||
import file_storage
|
||||
from bbox_writer import extract_labels
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect(config.DATABASE_FILE)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
def _add_column_if_not_exists(cursor, table_name, column_name, column_type):
|
||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
||||
columns = [row['name'] for row in cursor.fetchall()]
|
||||
if column_name not in columns:
|
||||
cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}")
|
||||
print(f"Added column '{column_name}' to table '{table_name}'.")
|
||||
|
||||
|
||||
def migrate_db():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
_add_column_if_not_exists(cursor, 'datasets', 'eval_percent', 'REAL')
|
||||
_add_column_if_not_exists(cursor, 'datasets', 'test_percent', 'REAL')
|
||||
_add_column_if_not_exists(cursor, 'models', 'label_filename', 'TEXT')
|
||||
_add_column_if_not_exists(cursor, 'models', 'model_type', 'TEXT')
|
||||
_add_column_if_not_exists(cursor, 'videos', 'last_pre_annotation_info', 'TEXT')
|
||||
_add_column_if_not_exists(cursor, 'video_frames', 'tags', 'TEXT')
|
||||
_add_column_if_not_exists(cursor, 'video_frames', 'suggested_bboxes_text', 'TEXT')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS videos (
|
||||
video_uuid TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL UNIQUE,
|
||||
video_filename TEXT,
|
||||
file_size INTEGER,
|
||||
create_time_ms INTEGER,
|
||||
status TEXT,
|
||||
status_message TEXT,
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
fps REAL,
|
||||
frame_count INTEGER,
|
||||
extracted_frame_count INTEGER DEFAULT 0,
|
||||
included_frame_count INTEGER DEFAULT 0,
|
||||
labeled_frame_count INTEGER DEFAULT 0,
|
||||
last_pre_annotation_info TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS video_frames (
|
||||
frame_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
video_uuid TEXT,
|
||||
frame_number INTEGER,
|
||||
bboxes_text TEXT,
|
||||
suggested_bboxes_text TEXT,
|
||||
tags TEXT,
|
||||
include_frame_in_dataset INTEGER,
|
||||
FOREIGN KEY (video_uuid) REFERENCES videos (video_uuid) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS datasets (
|
||||
dataset_uuid TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL UNIQUE,
|
||||
video_uuids TEXT,
|
||||
create_time_ms INTEGER,
|
||||
status TEXT,
|
||||
status_message TEXT,
|
||||
zip_path TEXT,
|
||||
sorted_label_list TEXT,
|
||||
eval_percent REAL,
|
||||
test_percent REAL
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS models (
|
||||
model_uuid TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL UNIQUE,
|
||||
create_time_ms INTEGER,
|
||||
label_filename TEXT,
|
||||
model_type TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS annotation_tasks (
|
||||
task_uuid TEXT PRIMARY KEY,
|
||||
video_uuid TEXT NOT NULL,
|
||||
assigned_to TEXT NOT NULL,
|
||||
description TEXT,
|
||||
start_frame INTEGER NOT NULL,
|
||||
end_frame INTEGER NOT NULL,
|
||||
status TEXT,
|
||||
create_time_ms INTEGER,
|
||||
FOREIGN KEY (video_uuid) REFERENCES videos (video_uuid) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS class_labels (
|
||||
label_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label_name TEXT NOT NULL UNIQUE,
|
||||
create_time_ms INTEGER
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS class_tags (
|
||||
tag_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tag_name TEXT NOT NULL UNIQUE,
|
||||
create_time_ms INTEGER
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS frame_labels (
|
||||
frame_id INTEGER NOT NULL,
|
||||
label_name TEXT NOT NULL,
|
||||
FOREIGN KEY (frame_id) REFERENCES video_frames (frame_id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_frame_labels_name ON frame_labels (label_name)')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def create_video_entry(description, video_filename, file_size, create_time_ms):
|
||||
conn = get_db_connection()
|
||||
video_uuid = str(uuid.uuid4().hex)
|
||||
conn.execute(
|
||||
'INSERT INTO videos (video_uuid, description, video_filename, file_size, create_time_ms, status) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
(video_uuid, description, video_filename, file_size, create_time_ms, 'UPLOADING')
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return video_uuid
|
||||
|
||||
|
||||
def get_ready_videos_with_labels():
|
||||
conn = get_db_connection()
|
||||
videos = conn.execute(
|
||||
'SELECT * FROM videos WHERE status = "READY" AND labeled_frame_count > 0 ORDER BY create_time_ms DESC').fetchall()
|
||||
conn.close()
|
||||
return [dict(row) for row in videos]
|
||||
|
||||
|
||||
def get_all_video_list():
|
||||
conn = get_db_connection()
|
||||
videos = conn.execute('SELECT * FROM videos ORDER BY create_time_ms DESC').fetchall()
|
||||
conn.close()
|
||||
return [dict(row) for row in videos]
|
||||
|
||||
|
||||
def get_video_entity(video_uuid):
|
||||
conn = get_db_connection()
|
||||
video = conn.execute('SELECT * FROM videos WHERE video_uuid = ?', (video_uuid,)).fetchone()
|
||||
conn.close()
|
||||
return dict(video) if video else None
|
||||
|
||||
|
||||
def update_video_status(video_uuid, status, message=""):
|
||||
conn = get_db_connection()
|
||||
conn.execute('UPDATE videos SET status = ?, status_message = ? WHERE video_uuid = ?', (status, message, video_uuid))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_pre_annotation_info(video_uuid, model_uuid, model_desc):
|
||||
conn = get_db_connection()
|
||||
info = {
|
||||
"model_uuid": model_uuid,
|
||||
"model_desc": model_desc,
|
||||
"time_ms": int(time.time() * 1000)
|
||||
}
|
||||
conn.execute('UPDATE videos SET last_pre_annotation_info = ? WHERE video_uuid = ?', (json.dumps(info), video_uuid))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_video_after_extraction_start(video_uuid, width, height, fps, frame_count):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
'UPDATE videos SET width=?, height=?, fps=?, frame_count=?, included_frame_count=?, status=? WHERE video_uuid=?',
|
||||
(width, height, fps, frame_count, frame_count, 'EXTRACTING', video_uuid)
|
||||
)
|
||||
frames_to_insert = [(video_uuid, i, '', '', '', 1) for i in range(frame_count)]
|
||||
cursor.executemany(
|
||||
'INSERT INTO video_frames (video_uuid, frame_number, bboxes_text, suggested_bboxes_text, tags, include_frame_in_dataset) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
frames_to_insert
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_extracted_frame_count(video_uuid, count):
|
||||
conn = get_db_connection()
|
||||
conn.execute('UPDATE videos SET extracted_frame_count = ? WHERE video_uuid = ?', (count, video_uuid))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def delete_video(video_uuid):
|
||||
conn = get_db_connection()
|
||||
conn.execute('DELETE FROM videos WHERE video_uuid = ?', (video_uuid,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_video_frames(video_uuid):
|
||||
conn = get_db_connection()
|
||||
frames = conn.execute('SELECT * FROM video_frames WHERE video_uuid = ? ORDER BY frame_number ASC',
|
||||
(video_uuid,)).fetchall()
|
||||
conn.close()
|
||||
return [dict(row) for row in frames]
|
||||
|
||||
|
||||
def save_frame_bboxes(video_uuid, frame_number, bboxes_text):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
frame = cursor.execute(
|
||||
'SELECT frame_id FROM video_frames WHERE video_uuid = ? AND frame_number = ?',
|
||||
(video_uuid, frame_number)
|
||||
).fetchone()
|
||||
|
||||
if not frame:
|
||||
conn.close()
|
||||
logging.error(f"无法为 video {video_uuid}, frame {frame_number} 找到 frame_id。")
|
||||
return
|
||||
|
||||
frame_id = frame['frame_id']
|
||||
cursor.execute(
|
||||
'UPDATE video_frames SET bboxes_text = ?, suggested_bboxes_text = ? WHERE frame_id = ?',
|
||||
(bboxes_text, '', frame_id)
|
||||
)
|
||||
cursor.execute('DELETE FROM frame_labels WHERE frame_id = ?', (frame_id,))
|
||||
unique_labels_in_frame = set(extract_labels(bboxes_text))
|
||||
if unique_labels_in_frame:
|
||||
labels_to_insert = [(frame_id, label) for label in unique_labels_in_frame]
|
||||
cursor.executemany(
|
||||
'INSERT INTO frame_labels (frame_id, label_name) VALUES (?, ?)',
|
||||
labels_to_insert
|
||||
)
|
||||
new_labeled_count = cursor.execute(
|
||||
"SELECT COUNT(*) FROM video_frames WHERE video_uuid = ? AND ((bboxes_text IS NOT NULL AND bboxes_text != '') OR (tags IS NOT NULL AND tags != '[]' AND tags != ''))",
|
||||
(video_uuid,)
|
||||
).fetchone()[0]
|
||||
cursor.execute(
|
||||
'UPDATE videos SET labeled_frame_count = ? WHERE video_uuid = ?',
|
||||
(new_labeled_count, video_uuid)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def save_frame_suggestions(video_uuid, frame_number, suggested_bboxes_text):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
'UPDATE video_frames SET suggested_bboxes_text = ? WHERE video_uuid = ? AND frame_number = ?',
|
||||
(suggested_bboxes_text, video_uuid, frame_number)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def save_frame_tags(video_uuid, frame_number, tags_json_string):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
'UPDATE video_frames SET tags = ? WHERE video_uuid = ? AND frame_number = ?',
|
||||
(tags_json_string, video_uuid, frame_number)
|
||||
)
|
||||
new_labeled_count = cursor.execute(
|
||||
"SELECT COUNT(*) FROM video_frames WHERE video_uuid = ? AND ((bboxes_text IS NOT NULL AND bboxes_text != '') OR (tags IS NOT NULL AND tags != '[]' AND tags != ''))",
|
||||
(video_uuid,)
|
||||
).fetchone()[0]
|
||||
cursor.execute(
|
||||
'UPDATE videos SET labeled_frame_count = ? WHERE video_uuid = ?',
|
||||
(new_labeled_count, video_uuid)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def add_frames_from_upload(video_uuid, frame_files):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
video_data = cursor.execute('SELECT frame_count FROM videos WHERE video_uuid = ?', (video_uuid,)).fetchone()
|
||||
if not video_data:
|
||||
raise ValueError("Video UUID not found in database.")
|
||||
start_frame_number = video_data['frame_count']
|
||||
frames_to_insert = []
|
||||
for i, file_storage_obj in enumerate(frame_files):
|
||||
new_frame_number = start_frame_number + i
|
||||
image_bytes = file_storage_obj.read()
|
||||
file_storage.save_frame_image(video_uuid, new_frame_number, image_bytes)
|
||||
frames_to_insert.append((video_uuid, new_frame_number, '', '', '', 1))
|
||||
if frames_to_insert:
|
||||
cursor.executemany(
|
||||
'INSERT INTO video_frames (video_uuid, frame_number, bboxes_text, suggested_bboxes_text, tags, include_frame_in_dataset) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
frames_to_insert
|
||||
)
|
||||
new_total_frames = start_frame_number + len(frames_to_insert)
|
||||
cursor.execute(
|
||||
'UPDATE videos SET frame_count = ?, extracted_frame_count = ?, included_frame_count = ? WHERE video_uuid = ?',
|
||||
(new_total_frames, new_total_frames, new_total_frames, video_uuid)
|
||||
)
|
||||
conn.commit()
|
||||
return len(frames_to_insert)
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
raise e
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def create_annotation_task(video_uuid, assigned_to, description, start_frame, end_frame):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
existing_tasks = cursor.execute(
|
||||
'SELECT start_frame, end_frame FROM annotation_tasks WHERE video_uuid = ?', (video_uuid,)
|
||||
).fetchall()
|
||||
for task in existing_tasks:
|
||||
if start_frame <= task['end_frame'] and end_frame >= task['start_frame']:
|
||||
conn.close()
|
||||
raise ValueError(f"Frame range overlaps with an existing task ({task['start_frame']}-{task['end_frame']}).")
|
||||
task_uuid = str(uuid.uuid4().hex)
|
||||
create_time_ms = int(time.time() * 1000)
|
||||
cursor.execute(
|
||||
'''INSERT INTO annotation_tasks
|
||||
(task_uuid, video_uuid, assigned_to, description, start_frame, end_frame, status, create_time_ms)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
|
||||
(task_uuid, video_uuid, assigned_to, description, start_frame, end_frame, 'PENDING', create_time_ms)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return task_uuid
|
||||
|
||||
|
||||
def get_tasks_for_video(video_uuid):
|
||||
conn = get_db_connection()
|
||||
tasks = conn.execute('SELECT * FROM annotation_tasks WHERE video_uuid = ? ORDER BY create_time_ms DESC',
|
||||
(video_uuid,)).fetchall()
|
||||
conn.close()
|
||||
return [dict(row) for row in tasks]
|
||||
|
||||
|
||||
def get_task_entity(task_uuid):
|
||||
conn = get_db_connection()
|
||||
task = conn.execute('SELECT * FROM annotation_tasks WHERE task_uuid = ?', (task_uuid,)).fetchone()
|
||||
conn.close()
|
||||
return dict(task) if task else None
|
||||
|
||||
|
||||
def delete_task(task_uuid):
|
||||
conn = get_db_connection()
|
||||
conn.execute('DELETE FROM annotation_tasks WHERE task_uuid = ?', (task_uuid,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_task_status(task_uuid, status):
|
||||
conn = get_db_connection()
|
||||
conn.execute('UPDATE annotation_tasks SET status = ? WHERE task_uuid = ?', (status, task_uuid))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def add_class_label(label_name):
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
create_time_ms = int(time.time() * 1000)
|
||||
conn.execute('INSERT INTO class_labels (label_name, create_time_ms) VALUES (?, ?)',
|
||||
(label_name, create_time_ms))
|
||||
conn.commit()
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_all_class_labels():
|
||||
conn = get_db_connection()
|
||||
labels = conn.execute('SELECT label_name FROM class_labels ORDER BY label_name ASC').fetchall()
|
||||
conn.close()
|
||||
return [row['label_name'] for row in labels]
|
||||
|
||||
|
||||
def delete_class_label(label_name):
|
||||
conn = get_db_connection()
|
||||
conn.execute('DELETE FROM class_labels WHERE label_name = ?', (label_name,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_all_frames_with_class(class_name):
|
||||
|
||||
conn = get_db_connection()
|
||||
query = """
|
||||
SELECT T1.*, T2.width, T2.height
|
||||
FROM video_frames AS T1
|
||||
INNER JOIN videos AS T2 ON T1.video_uuid = T2.video_uuid
|
||||
INNER JOIN frame_labels AS T3 ON T1.frame_id = T3.frame_id
|
||||
WHERE T3.label_name = ? \
|
||||
"""
|
||||
frames = conn.execute(query, (class_name,)).fetchall()
|
||||
conn.close()
|
||||
|
||||
return [dict(row) for row in frames]
|
||||
|
||||
|
||||
def add_class_tag(tag_name):
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
create_time_ms = int(time.time() * 1000)
|
||||
conn.execute('INSERT INTO class_tags (tag_name, create_time_ms) VALUES (?, ?)', (tag_name, create_time_ms))
|
||||
conn.commit()
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_all_class_tags():
|
||||
conn = get_db_connection()
|
||||
tags = conn.execute('SELECT tag_name FROM class_tags ORDER BY tag_name ASC').fetchall()
|
||||
conn.close()
|
||||
return [row['tag_name'] for row in tags]
|
||||
|
||||
|
||||
def delete_class_tag(tag_name):
|
||||
conn = get_db_connection()
|
||||
conn.execute('DELETE FROM class_tags WHERE tag_name = ?', (tag_name,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def create_dataset_entry(description, video_uuids, create_time_ms, eval_percent, test_percent):
|
||||
conn = get_db_connection()
|
||||
dataset_uuid = str(uuid.uuid4().hex)
|
||||
conn.execute(
|
||||
'INSERT INTO datasets (dataset_uuid, description, video_uuids, create_time_ms, status, eval_percent, test_percent) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
(dataset_uuid, description, json.dumps(video_uuids), create_time_ms, 'PENDING', eval_percent, test_percent)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return dataset_uuid
|
||||
|
||||
|
||||
def update_dataset_status(dataset_uuid, status, message="", zip_path="", sorted_label_list=None):
|
||||
conn = get_db_connection()
|
||||
if sorted_label_list is not None:
|
||||
conn.execute(
|
||||
'UPDATE datasets SET status=?, status_message=?, zip_path=?, sorted_label_list=? WHERE dataset_uuid=?',
|
||||
(status, message, zip_path, json.dumps(sorted_label_list), dataset_uuid)
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
'UPDATE datasets SET status=?, status_message=?, zip_path=? WHERE dataset_uuid=?',
|
||||
(status, message, zip_path, dataset_uuid)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_dataset_list():
|
||||
conn = get_db_connection()
|
||||
datasets = conn.execute('SELECT * FROM datasets ORDER BY create_time_ms DESC').fetchall()
|
||||
conn.close()
|
||||
return [dict(row) for row in datasets]
|
||||
|
||||
|
||||
def get_dataset_entity(dataset_uuid):
|
||||
conn = get_db_connection()
|
||||
dataset = conn.execute('SELECT * FROM datasets WHERE dataset_uuid = ?', (dataset_uuid,)).fetchone()
|
||||
conn.close()
|
||||
return dict(dataset) if dataset else None
|
||||
|
||||
|
||||
def delete_dataset(dataset_uuid):
|
||||
conn = get_db_connection()
|
||||
conn.execute('DELETE FROM datasets WHERE dataset_uuid = ?', (dataset_uuid,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def import_model_metadata(description, label_filename, model_type, create_time_ms):
|
||||
conn = get_db_connection()
|
||||
model_uuid = str(uuid.uuid4().hex)
|
||||
conn.execute(
|
||||
'INSERT INTO models (model_uuid, description, label_filename, model_type, create_time_ms) VALUES (?, ?, ?, ?, ?)',
|
||||
(model_uuid, description, label_filename, model_type, create_time_ms)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return model_uuid
|
||||
|
||||
|
||||
def get_model_list():
|
||||
conn = get_db_connection()
|
||||
models = conn.execute('SELECT * FROM models ORDER BY create_time_ms DESC').fetchall()
|
||||
conn.close()
|
||||
return [dict(row) for row in models]
|
||||
|
||||
|
||||
def get_model_entity(model_uuid):
|
||||
conn = get_db_connection()
|
||||
model = conn.execute('SELECT * FROM models WHERE model_uuid = ?', (model_uuid,)).fetchone()
|
||||
conn.close()
|
||||
return dict(model) if model else None
|
||||
|
||||
|
||||
def delete_model(model_uuid):
|
||||
conn = get_db_connection()
|
||||
conn.execute('DELETE FROM models WHERE model_uuid = ?', (model_uuid,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_frame_numbers_for_video(video_uuid):
|
||||
conn = get_db_connection()
|
||||
frames = conn.execute('SELECT frame_number FROM video_frames WHERE video_uuid = ? ORDER BY frame_number ASC',
|
||||
(video_uuid,)).fetchall()
|
||||
conn.close()
|
||||
return [row['frame_number'] for row in frames]
|
||||
Reference in New Issue
Block a user